Canoagem extrema em águas bravas

28

Você está remando em uma canoa em um rio de águas claras razoavelmente rápido. De repente, seus remos explodem e você se encontra em uma situação perigosa que desce rapidamente um rio sem remos. Felizmente, você ainda tem suas habilidades de programação, por isso decide criar um programa na lateral da sua canoa para ajudá-lo a sobreviver às corredeiras. No entanto, como não há muita área de superfície na lateral da canoa para escrever seu programa, você deve torná-lo o mais curto possível.

O rio pode ser representado como uma grade de 8 por 16. Iremos rotular as colunas com os números 0para 7e as linhas com os números 0para 15.

        y
--------15
--------14
--------13
--------12
--------11
--------10
--------9
--------8
--------7
--------6
--------5
--------4
--------3
--------2
--------1
--------0
01234567
x

Acima: Um rio completamente calmo e comum, sem obstruções. Naturalmente, este não é o rio em que você está.

Você começa na coordenada (4, 0) e daí sobe incontrolavelmente no rio (ou seja, o vetor (0,1)) até atingir uma rocha (representada por um onesses exemplos). Quando você bate em uma pedra, você tem 55% de chance de passar da pedra para a esquerda (ou seja, o vetor (-1,1)) e 45% de chance de passar da pedra para a direita (ou seja, o vetor (1,1)). Se a canoa estiver nas colunas da extrema esquerda ou direita, ela sempre se moverá em direção ao centro. Se não houver pedras, ela se moverá para cima.

        y
----x---15
----xo--14
-o--x---13
----x---12
---ox---11
---x----10
---xo---9
---ox---8
----xo--7
-----x--6
----ox--5
-o--x---4
----x---3
----xo--2
----x---1
----x---0
01234567

Acima: Uma possível rota que a canoa pode seguir, representada usando o personagem x

Dado o mapa do rio, escreva um programa que mostre a probabilidade de a canoa terminar em uma determinada coluna.

Aceite a entrada no método que for mais conveniente para o seu programa (por exemplo, STDIN, argumento de linha de comando raw_input(), leitura de um arquivo, etc.). A primeira parte da entrada é um único inteiro de 0 a 7, representando a coluna para a qual o programa encontrará a probabilidade. A seguir, é apresentada uma lista de tuplas na forma que x,yrepresenta a posição das pedras.

Um exemplo:

Entrada:

4 4,1 5,5 3,5

Isso indicaria um rio com rochas nas posições (4,1), (5,5) e (3,5), e pedia a probabilidade da canoa terminar na 4ª coluna.

Saída:

0.495

Observe que neste exemplo, as posições das rochas eram simétricas, permitindo que o problema fosse resolvido com uma distribuição binomial. Nem sempre será esse o caso!

Além disso, o rio será sempre atravessável. Ou seja, nunca haverá duas rochas posicionadas adjacentes uma à outra horizontalmente. Veja o comentário de Glenn para um exemplo de caso impossível.

Este é o código de golfe, portanto, o menor número de caracteres vence. Sinta-se à vontade para fazer perguntas nos comentários, se a especificação não estiver clara.

absinto
fonte
8
A parte irônica é que o programa não ajuda ninguém a sobreviver às corredeiras. Ele dizer-lhes quão provável é que eles vão sobreviver embora.
o absinto
1
O que acontece quando duas ou mais rochas estão lado a lado seguidas? por exemplo, se o mapa for "0 1,0 1,1", a canoa colidirá com a rocha em 1,1. (a) a condição não é permitida ou (b) a probabilidade de concluir o curso é 0.
Glenn Randers-Pehrson
1
Ah ok. Desculpe, eu perdi essa parte.
Maçaneta
3
Considerações finais: "Talvez construir uma canoa programável não fosse a melhor solução para o problema do uso de remos explosivos".
Kai #
2
Quero ver como é a aparência de uma raquete.
Robbie Wxyz

Respostas:

4

GolfScript, 105 caracteres

~](\2/:A;8,{4=}%15,{:B;{20*}%A{~B={[\\,.1,*\[2$=..20/9*:C-\~)C]+(1,\+1,6*2$8>+]zip{{+}*}%.}*;}/}/=20-15?*

Uma versão do GolfScript que ficou muito mais longa do que o pretendido - mas cada tentativa com uma abordagem diferente foi ainda mais longa. A entrada deve ser fornecida no STDIN.

Exemplo:

> 4 4,1 5,5 3,5
99/200

Código anotado:

# Evaluate the input
#  - stack contains the first number (i.e. column)
#  - variable A contains the rock coordinates (pairs of X y)
#    where X is an array of length x (the coordinate itself)
~](\2/:A;

# Initial probabilities
#  - create array [0 0 0 0 1 0 0 0] of initial probabilities
8,{4=}%

# Loop over rows 
15,{:B;           # for B = 0..14
  {20*}%          #   multiply each probability by 20
  A{              #   for each rock 
    ~B={          #     if rock is in current row then
                  #       (prepare an array of vectors [v0 vD vL vR] 
                  #       where v0 is the current prob. before rocks,
                  #       vD is the change due to rocks,
                  #       vL is a correction term for shifting out to the left
                  #       and vR the same for the right side)
      [\\         #       move v0 inside the array
      ,           #       get x coordinate of the rock
      .1,*        #       get [0 0 ... 0] with x terms
      \[2$=       #       get x-th item of v0
      ..20/9*:C-  #       build array [0.55P -P 0.45P]
      \~)C]+      #       and append to [0 0 ... 0]
      (1,\+       #       drop the leftmost item of vD and prepend [0] again
                  #       which gives vL
      1,6*2$8>+   #       calculate vR using the 8th item of vD
      ]           #       
      zip{{+}*}%  #       sum the columns of this list of vectors
      .           #       dummy dup for end-if ;
    }*;           #     end if
  }/              #   end for
}/                # end for

# take the n-th column and scale with 20^-15
=
20-15?*
Howard
fonte
11

Ruby, 204 191 172 caracteres

c,*r=gets.split
o=[0]*8
s=->x,y,p{y>14?o[x]+=p :(r.index("#{x},#{y+=1}")?(x<1?s[x+1,y,p]:(x>6?s[x-1,y,p]:(s[x-1,y,p*0.55]+s[x+1,y,p*0.45]))):s[x,y,p])}
s[4,0,1]
p o[c.to_i]

Simula recursivamente todos os resultados possíveis, mantendo o controle da probabilidade de cada resultado individual e, em seguida, adiciona essa probabilidade a um contador cumulativo quando y == 15.

Truques extravagantes:

  • c,*r=gets.split- o operador "splat" ( *) pega todos os elementos restantes gets.splite os cola no rarray

  • next {something} if {condition}: essencialmente equivalente a

    if {condition}
        {something}
        return
    end
    

    "Descoberto" evoluindo de if condition; something; return; endpara return something if conditionpara break something if condition, e então imaginei que tentaria um "operador de loop" mais curto para ver se funcionaria (o que funcionou, é claro).

  • Agradeço a @ MartinBüttner por sugerir o uso de operadores ternários encadeados (que acabaram se tornando a enorme terceira linha no código golfado acima) e eliminando o ponto acima (que salvou 19 (!) Caracteres).

    Entretanto, usei um truque chique: percebi que s[foo],s[bar]não funciona no Ruby para duas chamadas de método em uma instrução. Então, no começo eu mudei para (_=s[foo],s[bar])(uma variável dummy), mas então eu percebi que eu poderia apenas adicionar e jogue fora os valores de retorno: s[foo]+s[bar]. Isso só funciona porque as chamadas para sapenas "retornam" outras chamadas para sou para um número ( o[x]+=p), portanto, não preciso me preocupar em procurar nil.

  • Outras várias otimizações: em pvez de putsimprimir números, em <1vez de ==0(já que a canoa nunca sai do rio) e comparações semelhantes em outros lugares, [0]*8para probabilidades iniciais, pois os números de Ruby são sempre "passados ​​por valor"

Ungolfed:

column, *rocks = gets.chomp.split
outcomes = Array.new(8, 0)
simulate = -> x, y, probability {
    if y == 15
        outcomes[x] += probability
    elsif rocks.index("#{x},#{y + 1}")
        case x
        when 0 then simulate[x + 1, y + 1, probability]
        when 7 then simulate[x - 1, y + 1, probability]
        else
            simulate[x - 1, y + 1, probability * 0.55]
            simulate[x + 1, y + 1, probability * 0.45]
        end
    else
        simulate[x, y + 1, probability]
    end
}
simulate[4, 0, 1.0]
p outcomes
puts outcomes[column.to_i]
Maçaneta da porta
fonte
Ainda não seria mais curto coletar tudo isso next X if Yem operadores ternários aninhados? Boa descoberta, no entanto, você pode querer adicioná-lo às dicas de Ruby!
Martin Ender
@ MartinBüttner Sim, na verdade, são impressionantes 19 caracteres mais curtos! Obrigado, embora não têm o efeito infeliz lado de um ridiculamente longa linha: P
Doorknob
5

C # 418 364bytes

Programa C # completo esperando entrada do STDIN. Funciona lendo as rochas em uma matriz de todos os locais do rio, criando efetivamente um mapa e, em seguida, realiza apenas 16 iterações de probabilidades de movimentação em torno de uma matriz decimal de 8 larguras antes de gerar o resultado.

using C=System.Console;class P{static void Main(){var D=C.ReadLine().Split();int i=0,j=D.Length;var R=new int[8,16];var p=new decimal[8];for(p[4]=1;--j>0;)R[D[j][0]-48,int.Parse(D[j].Substring(2))]=1;for(;i<16;i++){var n=new decimal[j=8];for(;j-->0;)if(R[j,i]>0){n[j<1?1:j-1]+=p[j]*0.55M;n[j>6?6:j+1]+=p[j]*0.45M;}else n[j]+=p[j];p=n;}C.WriteLine(p[D[0][0]-48]);}}

Código formatado:

using C=System.Console;

class P
{
    static void Main()
    {
        var D=C.ReadLine().Split();
        int i=0,j=D.Length;
        var R=new int[8,16];
        var p=new decimal[8];

        for(p[4]=1;--j>0;) // read rocks into map (R)
            R[D[j][0]-48,int.Parse(D[j].Substring(2))]=1;

        for(;i<16;i++) // move up the river
        {
            var n=new decimal[j=8];
            for(;j-->0;)
                if(R[j,i]>0)
                { // we hit a rock!
                    n[j<1?1:j-1]+=p[j]*0.55M;
                    n[j>6?6:j+1]+=p[j]*0.45M;
                }
                else
                    n[j]+=p[j];
            p=n; // replace probability array
        }

        C.WriteLine(p[D[0][0]-48]); // output result
    }
}
VisualMelon
fonte
+1 para usar o operador "vai para" ( for(;j-->0;)). Você pode se livrar de alguns caracteres substituindo o último C.WriteLinepor C.Write. Além disso, se você usar em floatvez de, decimalpoderá salvar mais alguns bytes.
Christoph Böhmwalder
A prática padrão do @HackerCow;) tem que tirar o máximo proveito dos seus loops! Estou usando decimalporque floatnão será preciso, mas o decimal deve ser útil para esses problemas, mas provavelmente poderia sair como ele diz. Vou colocar na C.Writese eu conseguir golfe mais esta questão como é provavelmente mais perto da especificação do que C.WriteLinecomo eu não acho que 4 bytes garante uma edição para este programa tamanho;)
VisualMelon
2

Haskell, 256 bytes

import Data.List
m=map;v=reverse
a p n x=take n x++(x!!n+p:drop(n+1)x)
l=abs.pred
o[_,n]s=n#(s!!n)$s
n#p=a(11*p/20)(l n).a(9*p/20)(7-(l$7-n)).a(-p)n
b=0:0:0:0:1:b
k(c:w)=(foldl1(.)$m o$v$sort$m(v.read.('[':).(++"]"))w)b!!read c
main=getLine>>=print.k.words

Aqui está uma versão muito não-gasta, juntamente com alguns truques que foram usados:

import Data.List

-- Types to represent the distribution for the canoe's location
type Prob = Double
type Distribution = [Prob]

-- Just for clarity..
type Index = Int

-- An Action describes some change to the probability distribution
-- which represents the canoe's location.
type Action = Distribution -> Distribution

-- Helper to add k to the nth element of x, since we don't have mutable lists.
add :: Index -> Prob -> Action
add n k x = take n x ++ [p] ++ drop (n + 1) x
    where p = k + x!!n  

-- A trick for going finding the index to the left of n,
-- taking the boundary condition into account.
leftFrom n = abs (n - 1)

-- A trick for getting the other boundary condition cheaply.
rightFrom = mirror . leftFrom . mirror
    where mirror = (7 -)

-- Make the action corresponding to a rock at index n.
doRock :: Index -> Action
doRock n p = (goLeft . goRight . dontGoForward) p
    where goLeft  =  (leftFrom n) `add` (p_n * 11/20)
          goRight = (rightFrom n) `add` (p_n * 9/20)
          dontGoForward =  (at n) `add` (-p_n)
          p_n = p!!n
          at = id

initialProb = [0,0,0,0,1,0,0,0]

-- Parse a pair "3,2" ==> (3,2)
readPair :: String -> (Index,Index)
readPair xy = read $ "(" ++ xy ++ ")"

-- Coordinate swap for the sorting trick described below.
swap (x,y) = (y,x)

-- Put it all together and let it rip!
main = do
    input <- getLine
    let (idx : pairs) = words input
    let coords = reverse . sort $ map (swap . readPair) pairs
    let rockActions = map (doRock . snd) coords
    let finalProb = (foldl1 (.) rockActions) initialProb
    print $ (finalProb !! read idx)

O último truque que usei foi observar que você pode agir como se as rochas em uma única linha fossem realmente separadas por uma quantidade infinitesimal. Em outras palavras, você pode aplicar o transformador de distribuição de probabilidade para cada rocha na mesma linha seqüencialmente e na ordem que desejar, em vez de aplicá-las todas simultaneamente. Isso só funciona porque o problema não permite duas rochas horizontalmente adjacentes.

Portanto, o programa transforma a localização de cada rocha em um transformador de distribuição de probabilidade, ordenado pela coordenada y da rocha. Os transformadores são então encadeados em ordem e aplicados à distribuição de probabilidade inicial. E é isso!

Matt Noonan
fonte
2

Perl 169 bytes

Lê de STDIN.

$_=<>;s/ (.),(\d+)/$s{$1,$2}=1/eg;/./;$x{4}=1.0;for$y(1..15){for$x(0..7){if($s{$x,$y}){$x{$x-1}+=$x{$x}*($x%7?.55:1);$x{$x+1}+=$x{$x}*($x%7?.45:1);$x{$x}=0}}}print$x{$&}

Bem direto, usa implicitamente as colunas -1 e 8 para suavizar casos de borda. As probabilidades podem ser propagadas com segurança para cada próximo nível, porque não existem pedras adjacentes, portanto, uma única corrida é suficiente.

Thaylon
fonte
2

PHP, 358

Usar a inteligência para determinar os caminhos possíveis e suas probabilidades é difícil e provavelmente exigiria mais código do que simplesmente simular 1.000.000 de acidentes com canoas. Oh, a humanidade!

define('MX',7);
define('MY',16);
define('IT',1000000);
error_reporting(0);

function roll(){return rand()%100 > 44;}

function drift($rocks,$print=false) {
    for($px=4,$py=0;$py<MY;$py++) {
        if(isset($rocks[$px][$py])){
            if(roll()) $px--;
            else $px++;
        }
        else if($px==0) $px++;
        else if($px==MX) $px--;
        if($print) {
            for($i=0;$i<MX;$i++){
                if($i==$px) echo 'x';
                else if(isset($rocks[$i][$py])) echo 'o';
                else echo '-';
            }
            echo " $py\n";
        }
    }
    return $px;
}

$input = $argv[1];
$tmp = explode(' ',$input);
$end_target = array_shift($tmp);
$rocks = array();
array_map(function($a) use(&$rocks) {
    list($x,$y) = explode(',',$a);
    $rocks[$x][$y]=1;
}, $tmp);

$results=array();
for($i=0;$i<IT;$i++) {
    $results[drift($rocks)]++;
}

drift($rocks, true); // print an example run

foreach($results as $id=>$result) {
    printf("%d %0.2f\n", $id, $result/IT*100);
}

Exemplo:

php river.php "4 4,1 5,5 3,3 6,2 9,4 12,3 13,5"
----x-- 0
---xo-- 1
---x--o 2
--xo--- 3
--x---- 4
--x--o- 5
--x---- 6
--x---- 7
--x---- 8
--x---- 9
--x---- 10
--x---- 11
--x---- 12
--x---- 13
--x---- 14
--x---- 15
4 49.53
2 30.18
6 20.29

Golfe:

<? function d($r){for($x=4,$y=0;$y<16;$y++){if(isset($r[$x][$y])){if(rand()%100>44)$x--;else $x++;}elseif($x==0)$x++;elseif($x==7)$x--;}return $x;}$t=explode(' ',$argv[1]);$e=array_shift($t);$r=array();array_map(function($a)use(&$r){list($x,$y)=explode(',',$a);$r[$x][$y]=1;},$t);$c=0;for($i=0;$i<1000000;$i++){if(d($r)==$e)$c++;}printf("%.4f", $c/1000000);

Esta versão não produz uma impressão bonita e gera a probabilidade de flutuação do desembarque da canoa na posição especificada.

# php river_golf.php "4 4,1 5,5 3,3 6,2 9,4 12,3 13,5"
0.4952
Sammitch
fonte
Eu acho que o formato de entrada aqui é um pouco fora, por exemplo river.php deve dar 0.561375 em "5 4,4 1,5 5,3 3,6 2,9 4,12 3,13"
Matt Noonan
@MattNoonan, foi um dia difícil ontem. Eu deveria ser capaz de corrigir isso ...
Sammitch
2

PHP, 274

Não consigo ler / escrever o GolfScript para salvar minha vida, mas olhar para a submissão de @ Howard me indicou uma direção melhor do que simplesmente simular 1 milhão de acidentes de canoa.

Começando com uma variedade de probabilidades para posições iniciais, podemos simplesmente dividir esses números cada vez que uma pedra é encontrada.

function psplit($i){ return array(.55*$i,.45*$i); }
function pt($a) {
    foreach($a as $p) {
        printf("%1.4f ", $p);
    }
    echo "\n";
}

$input = $argv[1];
$tmp = explode(' ',$input);
$end_target = array_shift($tmp);
$rocks = array();
array_map(function($a) use(&$rocks) {
    list($x,$y) = explode(',',$a);
    $rocks[$x][$y]=1;
}, $tmp);

$state = array(0,0,0,0,1,0,0,0);
pt($state);
for($y=1;$y<16;$y++){
    for($x=0;$x<8;$x++){
        if(isset($rocks[$x][$y])){
            echo('   o   ');
            list($l,$r)=psplit($state[$x]);
            $state[$x]=0;
            $state[$x-1]+=$l;
            $state[$x+1]+=$r;
        } else { echo '   -   '; }
    }
    echo "\n";
    pt($state);
}

Saída de exemplo:

# php river2.php "4 4,1 5,5 3,3 6,2 9,4 12,3 13,5"
0.0000 0.0000 0.0000 0.0000 1.0000 0.0000 0.0000 0.0000
   -      -      -      -      o      -      -      -
0.0000 0.0000 0.0000 0.5500 0.0000 0.4500 0.0000 0.0000
   -      -      -      -      -      -      o      -
0.0000 0.0000 0.0000 0.5500 0.0000 0.4500 0.0000 0.0000
   -      -      -      o      -      -      -      -
0.0000 0.0000 0.3025 0.0000 0.2475 0.4500 0.0000 0.0000
   -      -      -      -      -      -      -      -
0.0000 0.0000 0.3025 0.0000 0.2475 0.4500 0.0000 0.0000
   -      -      -      -      -      o      -      -
0.0000 0.0000 0.3025 0.0000 0.4950 0.0000 0.2025 0.0000
   -      -      -      -      -      -      -      -
0.0000 0.0000 0.3025 0.0000 0.4950 0.0000 0.2025 0.0000
   -      -      -      -      -      -      -      -
0.0000 0.0000 0.3025 0.0000 0.4950 0.0000 0.2025 0.0000
   -      -      -      -      -      -      -      -
0.0000 0.0000 0.3025 0.0000 0.4950 0.0000 0.2025 0.0000
   -      -      -      -      -      -      -      -
0.0000 0.0000 0.3025 0.0000 0.4950 0.0000 0.2025 0.0000
   -      -      -      -      -      -      -      -
0.0000 0.0000 0.3025 0.0000 0.4950 0.0000 0.2025 0.0000
   -      -      -      -      -      -      -      -
0.0000 0.0000 0.3025 0.0000 0.4950 0.0000 0.2025 0.0000
   -      -      -      -      -      -      -      -
0.0000 0.0000 0.3025 0.0000 0.4950 0.0000 0.2025 0.0000
   -      -      -      -      -      -      -      -
0.0000 0.0000 0.3025 0.0000 0.4950 0.0000 0.2025 0.0000
   -      -      -      -      -      -      -      -
0.0000 0.0000 0.3025 0.0000 0.4950 0.0000 0.2025 0.0000
   -      -      -      -      -      -      -      -
0.0000 0.0000 0.3025 0.0000 0.4950 0.0000 0.2025 0.0000

Golfe:

<? $t=explode(' ',$argv[1]);$e=array_shift($t);$r=array();foreach($t as $n){list($j,$k)=explode(',',$n);$r[$j][$k]=1;}$s=array(0,0,0,0,1,0,0,0);for($y=1;$y<16;$y++){for($x=0;$x<8;$x++){if(isset($r[$x][$y])){$s[$x-1]+=$s[$x]*.55;$s[$x+1]+=$s[$x]*.45;$s[$x]=0;}}}echo $s[$e];

Exemplo de execução:

# php river2_golf.php "4 4,1 5,5 3,3 6,2 9,4 12,3 13,5"
0.495
Sammitch
fonte
1

Haskell, 237

Só espero que a canoa venha com o ghc instalado ...

O truque da lista infinita é roubado de Matt Noonan, parabéns a ele!

import Data.List
r=reverse
(a:b:x)%0=0:a+b:x
x%7=r(r x%0)
x%n=take(n-1)x++(x!!(n-1)+x!!n*0.55:0:x!!(n+1)+x!!n*0.45:drop(n+2)x)
q=0:0:0:0:1:q
u(w:x)=(foldl(%)q.map last.sort.map(r.read.('[':).(++"]"))$x)!!read w
main=interact$show.u.words

Espero ter acertado a lógica, mas o exemplo de Matt "5 4,4 1,5 5,3 3,6 2,9 4,12 3,13"cede 0.5613750000000001e o exemplo de OP "4 4,1 5,5 3,5"cede0.49500000000000005 , o que parece estar correto, exceto por alguns erros de ponto flutuante.

Aqui está em ação:

>>> echo 5 4,4 1,5 5,3 3,6 2,9 4,12 3,13 | codegolf
0.5613750000000001
Flonk
fonte