30
\$\begingroup\$

You are paddling a canoe down a fairly fast whitewater river. Suddenly, your paddles explode, and you find yourself in a dangerous situation hurtling down a river rapid without any paddles. Luckily, you still have your programming skills, so you decide to carve a program on the side of your canoe to help you survive the rapids. However, there is not much surface area on the side of the canoe to write your program with, so you must make the program as short as possible.

The river can be represented as an 8 by 16 grid. We will label the columns with the numbers 0 to 7 and the rows with the numbers 0 to 15.

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

Above: A completely calm, ordinary river with no obstructions. Naturally, this is not the river you are on.

You begin at the coordinate (4, 0) and from there move uncontrollably up the river (i.e. the vector (0,1)) until you hit a rock (represented by an o in these examples). When you hit a rock, you will have a 55% chance of moving past the rock to the left (i.e. the vector (-1,1)) and a 45% chance of moving past the rock to the right (i.e. the vector (1,1)). If the canoe is on the far left or right columns, it will always move towards the centre. If there are no rocks, it will move straight upwards.

        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

Above: A possible route the canoe might take, represented using the character x

Given the map of the river, write a program that will output the probability of the canoe finishing at a given column.

Accept input in whichever method is convenient for your program (e.g. STDIN, command line argument, raw_input(), reading from a file, etc). The first part of the input is a single integer from 0 to 7, representing the column the program will find the probability for. Following that is a list of tuples in the form x,y representing the position of the stones.

An example:

Input:

4 4,1 5,5 3,5

This would indicate a river with rocks at the positions (4,1), (5,5), and (3,5), and asks for the probability of the canoe ending at the 4th column.

Output:

0.495

Note that in this example, the positions of the rocks were symmetrical, allowing the problem to be solved with a binomial distribution. This won't always be the case!

Also, the river will always be crossable. That is, there will never be two rocks that are positioned adjacent to each other horizontally. See Glenn's comment for an example of an impossible case.

This is code golf, so lowest number of characters wins. Feel free to ask questions in the comments if the specification isn't clear.

\$\endgroup\$
8
  • 8
    \$\begingroup\$ The ironic part is that the program doesn't actually help anyone survive the rapids. It does tell them how likely it is they'll survive though.​​​​​​​​​​​​​​​ \$\endgroup\$
    – absinthe
    Aug 17, 2014 at 23:21
  • 1
    \$\begingroup\$ What happens when two or more rocks are side-by-side in a row? e.g., if the map is "0 1,0 1,1" the canoe would crash into the rock at 1,1. (a) condition is disallowed, or (b) probability of completing the course is 0. \$\endgroup\$ Aug 17, 2014 at 23:24
  • 1
    \$\begingroup\$ Ah, ok. Sorry, I missed that part. \$\endgroup\$
    – Doorknob
    Aug 18, 2014 at 0:08
  • 3
    \$\begingroup\$ Final thoughts: "Perhaps building a programmable canoe was not the best solution to the problem of using explosive paddles." \$\endgroup\$
    – Kai
    Aug 18, 2014 at 14:15
  • 2
    \$\begingroup\$ I want to see what it looks like when a paddle explodes. \$\endgroup\$
    – Robin
    Aug 21, 2014 at 0:12

8 Answers 8

11
\$\begingroup\$

Ruby, 204 191 172 characters

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]

It recursively simulates all possibly outcomes while keeping track of each individual outcome's probability, then it adds that probability to a cumulative counter when y == 15.

Fancy tricks:

  • c,*r=gets.split - the "splat" operator (*) takes all the remaining elements of gets.split and sticks them in the r array

  • next {something} if {condition}: essentially equivalent to

    if {condition}
        {something}
        return
    end
    

    "Discovered" by evolving from if condition; something; return; end to return something if condition to break something if condition, and then I figured I would try a shorter "loop operator" to see if it would work (which it did, of course).

  • Thanks to @MartinBüttner for suggesting to use chained ternary operators (which ended up becoming the enormous third line in the golfed code above) and eliminating the above point (which saved 19 (!) characters).

    I did use a somewhat fancy trick with those, though: I realized that s[foo],s[bar] doesn't work in Ruby for two method calls in one statement. So at first I changed it to (_=s[foo],s[bar]) (a dummy variable), but then I realized I could just add and throw away the return values: s[foo]+s[bar]. This only works because calls to s will only ever "return" other calls to s or a number (o[x]+=p), so I don't have to worry about checking for nil.

  • Other various optimizations: p instead of puts for printing numbers, <1 instead of ==0 (since the canoe never leaves the river) and similar comparisons elsewhere, [0]*8 for initial probabilities as Ruby's numbers are always "pass by value"

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]
\$\endgroup\$
2
  • \$\begingroup\$ Wouldn't it still be shorter to collect all of those next X if Y into nested ternary operators? Nice find though, you might want to add it to the Ruby tips! \$\endgroup\$ Aug 18, 2014 at 9:14
  • \$\begingroup\$ @MartinBüttner Yep, that's actually a whopping 19 characters shorter! Thanks, although it does have the unfortunate side effect of a ridiculously long line :P \$\endgroup\$
    – Doorknob
    Aug 18, 2014 at 22:58
5
\$\begingroup\$

C# 418 364bytes

Complete C# program expecting input from STDIN. Works by reading the rocks into an array of all locations in the river, effectively creating a map, and then it just performs 16 iterations of moving probabilities around an 8-width decimal array before outputting the result.

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

Formatted code:

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
    }
}
\$\endgroup\$
2
  • \$\begingroup\$ +1 for using the "goes to" operator (for(;j-->0;)). You can get rid of a couple of characters though by replacing the last C.WriteLine by C.Write. Also, if you use float instead of decimal you can save a couple more bytes. \$\endgroup\$ Aug 19, 2014 at 10:40
  • \$\begingroup\$ @HackerCow standard practice ;) got to get the most out of your for-loops! I'm using decimal because float won't be precise, but decimal should do for these problems, but could probably get away with it as you say. I'll put in C.Write if I do manage to golf this any further as it's probably closer to the spec than C.WriteLine as I don't think 4 bytes warrants an edit for this size program ;) \$\endgroup\$ Aug 19, 2014 at 11:02
4
\$\begingroup\$

GolfScript, 105 characters

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

A GolfScript version which became much longer than intended - but each attempt with a different approach was even longer. The input must be given on STDIN.

Example:

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

Annotated code:

# 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?*
\$\endgroup\$
0
2
\$\begingroup\$

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

Here is a very ungolfed version along with some tricks that were used:

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)

The last trick I used was to note that you can act as if rocks in a single row are actually separated by some infinitesimal amount. In other words, you can apply the probability distribution transformer for each rock in the same row sequentially and in whatever order you want, rather than applying them all simultaneously. This only works because the problem disallows two horizontally adjacent rocks.

So the program turns each rock's location into a probability distribution transformer, ordered by the rock's y coordinate. The transformers are then just chained in order and applied to the initial probability distribution. And that's that!

\$\endgroup\$
2
\$\begingroup\$

Haskell, 237

I just hope the canoe comes with ghc installed...

The trick with the infinite list is stolen from Matt Noonan, kudos to him!

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

I hope I got the logic right, but Matt's example "5 4,4 1,5 5,3 3,6 2,9 4,12 3,13" yields 0.5613750000000001 and OP's example "4 4,1 5,5 3,5" yields 0.49500000000000005, which seems to be correct apart from some floating point errors.

Here it is in action:

>>> echo 5 4,4 1,5 5,3 3,6 2,9 4,12 3,13 | codegolf
0.5613750000000001
\$\endgroup\$
2
\$\begingroup\$

Perl 169 Bytes

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

Pretty straight forward, implicitly uses columns -1 and 8 to smoothen border cases. Probabilities can safely be propagated to each next level because there arent any adjacent stones, thus a single run suffices.

\$\endgroup\$
2
\$\begingroup\$

PHP, 358

Using brainpower to determine the possible paths and their probabilities is hard, and would probably require more code than simply simulating 1,000,000 canoe accidents. Oh, the humanity!

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

Example:

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

Golfed:

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

This version does not do any pretty printing and outputs the float probability of the canoe landing at the specified position.

# php river_golf.php "4 4,1 5,5 3,3 6,2 9,4 12,3 13,5"
0.4952
\$\endgroup\$
2
  • \$\begingroup\$ I think the input format here is slightly off, e.g. river.php should give 0.561375 on "5 4,4 1,5 5,3 3,6 2,9 4,12 3,13" \$\endgroup\$ Aug 19, 2014 at 1:48
  • \$\begingroup\$ @MattNoonan it was a rough day yesterday. I should be able to fix that... \$\endgroup\$
    – Sammitch
    Aug 19, 2014 at 16:40
2
\$\begingroup\$

PHP, 274

I can't read/write GolfScript to save my life, but glancing over @Howard's submission pointed me in a better direction than simply simulating 1 million canoe accidents.

Starting with an array of probabilities for starting positions we can simply split those numbers each time a rock is encountered.

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

Example Output:

# 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

Golfed:

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

Example run:

# php river2_golf.php "4 4,1 5,5 3,3 6,2 9,4 12,3 13,5"
0.495
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.