Canotaje extremo en aguas bravas

28

Estás remando una canoa por un río de aguas blancas bastante rápido. De repente, tus remos explotan, y te encuentras en una situación peligrosa que se precipita río abajo rápidamente sin remos. Afortunadamente, todavía tiene sus habilidades de programación, por lo que decide tallar un programa en el costado de su canoa para ayudarlo a sobrevivir a los rápidos. Sin embargo, no hay mucha superficie en el costado de la canoa para escribir su programa, por lo que debe hacer que el programa sea lo más corto posible.

El río se puede representar como una cuadrícula de 8 por 16. Vamos a etiquetar las columnas con los números 0a 7y las filas con los números0 a 15.

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

Arriba: un río completamente tranquilo y ordinario sin obstrucciones. Naturalmente, este no es el río en el que estás.

Usted comienza en la coordenada (4, 0) y desde allí se mueve incontrolablemente río arriba (es decir, el vector (0,1) ) hasta que golpea una roca (representada por una oen estos ejemplos). Cuando golpeas una roca, tendrás una probabilidad del 55% de pasar la roca hacia la izquierda (es decir, el vector (-1,1)) y una probabilidad del 45% de pasar la roca hacia la derecha (es decir, el vector(1,1) ). Si la canoa está en las columnas del extremo izquierdo o derecho, siempre se moverá hacia el centro. Si no hay rocas, se moverá hacia arriba.

        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

Arriba: una posible ruta que puede tomar la canoa, representada usando el personaje x

Dado el mapa del río, escriba un programa que genere la probabilidad de que la canoa termine en una columna dada.

Acepte la entrada en cualquier método que sea conveniente para su programa (por ejemplo, STDIN, argumento de línea de comando, raw_input() , lectura de un archivo, etc.). La primera parte de la entrada es un número entero único de 0 a 7, que representa la columna para la que el programa encontrará la probabilidad. A continuación hay una lista de tuplas en la forma que x,yrepresenta la posición de las piedras.

Un ejemplo:

Entrada:

4 4,1 5,5 3,5

Esto indicaría un río con rocas en las posiciones (4,1), (5,5) y (3,5), y solicita la probabilidad de que la canoa termine en la cuarta columna.

Salida:

0.495

Tenga en cuenta que en este ejemplo, las posiciones de las rocas eran simétricas, lo que permitió resolver el problema con una distribución binomial. ¡Este no siempre será el caso!

Además, el río siempre será transitable. Es decir, nunca habrá dos rocas que se coloquen adyacentes horizontalmente. Ver el comentario de Glenn para un ejemplo de un caso imposible.

Este es el código de golf, por lo que gana el menor número de caracteres. Siéntase libre de hacer preguntas en los comentarios si la especificación no es clara.

Ajenjo
fuente
8
La parte irónica es que el programa en realidad no ayuda a nadie a sobrevivir a los rápidos. No les diga qué tan probable es que van a sobrevivir sin embargo.
absenta
1
¿Qué sucede cuando dos o más rocas están una al lado de la otra? por ejemplo, si el mapa es "0 1,0 1,1", la canoa chocaría contra la roca a 1,1. (a) la condición no está permitida, o (b) la probabilidad de completar el curso es 0.
Glenn Randers-Pehrson
1
Ah ok Lo siento, me perdí esa parte.
Pomo de la puerta
3
Reflexiones finales: "Quizás construir una canoa programable no fue la mejor solución al problema del uso de palas explosivas".
Kai
2
Quiero ver cómo se ve cuando explota una pala.
Robbie Wxyz

Respuestas:

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?*

Una versión de GolfScript que se hizo mucho más larga de lo previsto, pero cada intento con un enfoque diferente fue aún más largo. La entrada debe darse en STDIN.

Ejemplo:

> 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
fuente
11

Rubí, 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 los resultados posibles mientras realiza un seguimiento de la probabilidad de cada resultado individual, luego agrega esa probabilidad a un contador acumulativo cuando y == 15.

Trucos de lujo:

  • c,*r=gets.split- el operador "splat" ( *) toma todos los elementos restantes gets.splity los pega en la rmatriz

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

    if {condition}
        {something}
        return
    end
    

    "Descubierto" al evolucionar de if condition; something; return; endun return something if conditiona otro break something if condition, y luego pensé que probaría con un "operador de bucle" más corto para ver si funcionaba (lo cual sí, por supuesto).

  • Gracias a @ MartinBüttner por sugerir utilizar operadores ternarios encadenados (que terminaron convirtiéndose en la tercera línea enorme en el código de golf anterior) y eliminar el punto anterior (que salvó 19 (!) Caracteres).

    Sin embargo, utilicé un truco un tanto elegante con ellos: me di cuenta de que s[foo],s[bar]no funciona en Ruby para dos llamadas a métodos en una sola declaración. Así que al principio me cambió a (_=s[foo],s[bar])(una variable ficticia), pero luego me di cuenta de que sólo podía sumar y tirar a la basura los valores de retorno: s[foo]+s[bar]. Esto solo funciona porque las llamadas a ssolo "devolverán" otras llamadas sao un número ( o[x]+=p), por lo que no tengo que preocuparme por verificar nil.

  • Otras diversas optimizaciones: en plugar de putsimprimir números, en <1lugar de ==0(dado que la canoa nunca abandona el río) y comparaciones similares en otros lugares, [0]*8para probabilidades iniciales, ya que los números de Ruby siempre son "pasados ​​por valor"

Sin golf:

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]
Pomo de la puerta
fuente
¿No sería aún más corto next X if Yreunirlos en operadores ternarios anidados? Buen hallazgo, sin embargo, es posible que desee agregarlo a los consejos Ruby
Martin Ender
@ MartinBüttner Sí, ¡en realidad son 19 caracteres más cortos! Gracias, aunque tiene el desafortunado efecto secundario de una línea ridículamente larga: P
Pomo de la puerta
5

C # 418 364bytes

Complete el programa C # esperando la entrada de STDIN. Funciona leyendo las rocas en una matriz de todas las ubicaciones en el río, creando efectivamente un mapa, y luego solo realiza 16 iteraciones de probabilidades de movimiento alrededor de una matriz decimal de 8 anchos antes de generar el 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 formateado:

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
fuente
+1 por usar el operador "va a" ( for(;j-->0;)). Sin embargo, puedes deshacerte de un par de personajes reemplazando el último C.WriteLinepor C.Write. Además, si usa en floatlugar de decimalpuede guardar un par de bytes más.
Christoph Böhmwalder
¡La práctica estándar de @HackerCow;) tiene que aprovechar al máximo tus for-loops! Estoy usando decimalporque floatno será preciso, pero el decimal debería funcionar para estos problemas, pero probablemente podría salirse con la suya como usted dice. Lo pondré C.Writesi logro jugar más golf, ya que probablemente esté más cerca de la especificación que C.WriteLineporque no creo que 4 bytes justifiquen una edición para este programa de tamaño;)
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

Aquí hay una versión muy poco fiel junto con algunos trucos que se usaron:

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)

El último truco que utilicé fue notar que puedes actuar como si las rocas en una sola fila estuvieran realmente separadas por una cantidad infinitesimal. En otras palabras, puede aplicar el transformador de distribución de probabilidad para cada roca en la misma fila secuencialmente y en el orden que desee, en lugar de aplicarlos todos simultáneamente. Esto solo funciona porque el problema no permite dos rocas adyacentes horizontalmente.

Entonces, el programa convierte la ubicación de cada roca en un transformador de distribución de probabilidad, ordenado por la coordenada y de la roca. Los transformadores se encadenan en orden y se aplican a la distribución de probabilidad inicial. ¡Y eso es eso!

Matt Noonan
fuente
2

Perl 169 Bytes

Lecturas 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{$&}

Bastante sencillo, implícitamente usa las columnas -1 y 8 para suavizar los casos de borde. Las probabilidades se pueden propagar de manera segura a cada nivel siguiente porque no hay piedras adyacentes, por lo tanto, una sola carrera es suficiente.

Thaylon
fuente
2

PHP, 358

Usar la capacidad intelectual para determinar los posibles caminos y sus probabilidades es difícil, y probablemente requeriría más código que simplemente simular 1,000,000 de accidentes en canoas. ¡Oh la humanidad!

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);
}

Ejemplo:

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

Golfizado:

<? 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 versión no hace ninguna impresión bonita y genera la probabilidad de flotación del aterrizaje de la canoa en la posición especificada.

# php river_golf.php "4 4,1 5,5 3,3 6,2 9,4 12,3 13,5"
0.4952
Sammitch
fuente
Creo que el formato de entrada aquí está ligeramente desactivado, por ejemplo, river.php debería dar 0.561375 en "5 4,4 1,5 5,3 3,6 2,9 4,12 3,13"
Matt Noonan
@MattNoonan ayer fue un día difícil. Debería poder arreglar eso ...
Sammitch
2

PHP, 274

No puedo leer / escribir GolfScript para salvar mi vida, pero echar un vistazo a la presentación de @ Howard me indicó una mejor dirección que simplemente simular 1 millón de accidentes en canoa.

Comenzando con una serie de probabilidades para las posiciones iniciales, simplemente podemos dividir esos números cada vez que se encuentra una roca.

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);
}

Salida de ejemplo:

# 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

Golfizado:

<? $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];

Ejemplo de ejecución:

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

Haskell, 237

Solo espero que la canoa venga con ghc instalado ...

El truco con la lista infinita es robado de Matt Noonan, ¡felicitaciones a él!

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 tener la lógica correcta, pero el ejemplo de Matt "5 4,4 1,5 5,3 3,6 2,9 4,12 3,13"rinde 0.5613750000000001y el ejemplo de OP "4 4,1 5,5 3,5"rinde 0.49500000000000005, lo que parece ser correcto aparte de algunos errores de coma flotante.

Aquí está en acción:

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