22
\$\begingroup\$

Consider a finite, one-dimensional grid where each cell is marked with one of two symbols (I will use the symbols < and >, but you can use other symbols).

When a pinball is placed on one of the cells, it moves according to the following rules:

  • If the pinball is on a cell that is marked with <, the pinball moves one cell left in the next second, and if marked with >, it moves one cell right in the next second.
  • After the pinball has moved, the marker on the cell is inverted (i. e. if cell was marked with <, it becomes >, and vice versa).
  • The pinball stops moving when it leaves the grid.

Challenge

Given the initial markers on the grid and a starting location for the pinball, calculate how many seconds it would take for the pinball to leave the grid.

Input Format

  • You may represent the markers on the initial grid as an array/string containing two distinct values.
  • You may take the starting position as a 0-indexed or 1-indexed value.

Worked example

initial grid = ><<, start = 1
  *    *      *      *    *    *    *   
 ><<   >><   <><   <<<   <<>   <>>   >>>
        1     2     3     4     5     6

Here the * represents the pinball, and the numbers on the bottom represent the time elapsed.

Testcases

><<, 0 -> 3
><<, 1 -> 6
><<, 2 -> 5

<<<<, 0 -> 1
<<<<, 3 -> 4

<><<<>, 1 -> 4
<><<<>, 3 -> 10
<><<<>, 4 -> 8
<><<<>, 5 -> 1

Based on this Codeforces problem (Same problem but you need to solve for all starting locations in linear time)

\$\endgroup\$
1

21 Answers 21

16
\$\begingroup\$

Minecraft Functions, 378 + 141 + 541 = 1060 bytes

Made up of 3 functions in a data pack named a:

a.mcfunction:

scoreboard objectives add a dummy
data modify storage s a set from storage s c[0]
data remove storage s c[0]
execute at @p if data storage s {a:0} run setblock ~ ~ ~ mud
execute at @p if data storage s {a:1} run setblock ~ ~ ~ tnt
execute at @p run tp @p ~1 ~ ~
execute if data storage s c[0] run schedule function a:a 1
execute at @p unless data storage s c[0] run function a:b

b.mcfunction:

summon cat
data modify entity @e[type=cat,limit=1] Pos[0] set from storage s i
execute at @e[type=cat,limit=1] run tp @p ~.5 ~ ~
function a:c

c.mcfunction:

execute at @p if block ~ ~ ~ air run tellraw @p {"score":{"name":"a","objective":"a"}}
execute at @p unless block ~ ~ ~ air run schedule function a:c 1
execute at @p if block ~ ~ ~ mud run data merge storage s {a:0}
execute at @p if block ~ ~ ~ tnt run data merge storage s {a:1}
execute at @p if data storage s {a:0} run setblock ~ ~ ~ tnt
execute at @p if data storage s {a:1} run setblock ~ ~ ~ mud
execute at @p if data storage s {a:0} run tp @p ~-1 ~ ~
execute at @p if data storage s {a:1} run tp @p ~1 ~ ~
scoreboard players add a a 1

The input grid is given as an array c of 0 (left) and 1 (right) in storage s. The starting position at is given as a double i in the same storage. They can be set with these commands:

# Set grid (<><<<>)
data modify storage s c set value [0,1,0,0,0,1]
# Set starting position (position 3)
data modify storage s i set value 3.0d

It's run with /function a:a and outputs the number of seconds to chat. For best results (well, correct results), run in a void world in Spectator mode at x=0.

Here's a slowed down GIF of it in action for test case #7 (RIP the cat).

GIF showing thing

Definitely not anywhere near optimal, but it was a fun challenge!

\$\endgroup\$
8
\$\begingroup\$

K (ngn/k), 25 bytes

Takes markers as 1/-1 for left and right.

{1+.[o;(x;y-x[y]*:-1);0]}

Try it online!

\$\endgroup\$
7
\$\begingroup\$

JavaScript (Node.js), 30 bytes

s=>g=p=>s[p]&&-~g(p-=s[p]*=-1)

Try it online!

-1B from Arnauld

32 if it need to be one char

\$\endgroup\$
1
7
\$\begingroup\$

Excel ms365, 138 129 bytes

Lovely challenge! I decided to make a recursive function that will keep calling itself through LAMBDA():

enter image description here

Formula in A3:

=LET(v,TOCOL(1:1,1)="<",i,SEQUENCE(ROWS(v)),x,LAMBDA(f,g,s,p,IF(OR(i=p),f(f,IF(i=p,1-g,g),s+1,p-2*INDEX(g,p)+1),s)),x(x,v,,A2+1))

The answer is 0-indexed and is made in such a way you can alter the values in the 1st row and the starting position in cell A2.

The actuall LAMBDA() has 4 parameters;

  • f - A call to itself (to make this recursive);
  • g - A grid made up of 0's and 1's based on symbol;
  • s - Seconds elapsed;
  • p - Current position in the grid.
\$\endgroup\$
3
  • 1
    \$\begingroup\$ Nice! Save 8 bytes by replacing not(g) with 1-g and p+if(index(g,p),-1,1)) with p-2*index(g,p)+1). \$\endgroup\$ Mar 6 at 16:31
  • \$\begingroup\$ I think this can be thinned down to 129 bytes. See my Google Sheets answer. \$\endgroup\$ Mar 6 at 17:52
  • \$\begingroup\$ @doubleunary, thanks for the suggestions! Edited my answer accordingly =) \$\endgroup\$
    – JvdV
    Mar 7 at 16:29
5
\$\begingroup\$

05AB1E, 27 24 22 bytes

V[ā<Yå_#DYè©(YǝY®+V¼}¾

Inputs in the order \$startingPosition,gridList\$, where the \$gridList\$ is a list of -1 and 1 for < and > respectively and \$startingPosition\$ is 0-based.

Try it online or verify all test cases.

Explanation:

V         # Store the first (implicit) input-index in variable `Y`
[         # Start an infinite loop:
 ā<       #  Push a list in the range [0,length) (without popping)
          #  (which will use the second implicit input-list in the first iteration)
   Yå_    #  Check whether `Y` is NOT in this list
      #   #  If it isn't (aka it's out of bounds): stop the infinite loop
 D        #  Duplicate the current list
  Yè      #  Pop the copy, and get its 0-based `Y`'th value
    ©     #  Store this value in variable `®` (without popping)
     (    #  Pop and negate it (-1 becomes 1 and vice versa)
      Yǝ  #  Insert it back into the list at 0-based index `Y`
 Y        #  Push 0-based index `Y` again
  ®       #  Push the current value `®` again
   +      #  Add that to `Y`
    V     #  Overwrite the previous `Y` with this new position
 ¼        #  Increase counter variable `¾` by 1 (0 by default)
}¾        # After the infinite loop: push counter variable `¾`
          # (which is output implicitly as result)
\$\endgroup\$
4
\$\begingroup\$

Charcoal, 24 bytes

SJN⁰≔⁰ζWKK«≦⊕ζ✳ι⁻lrι»⎚Iζ

Try it online! Link is to verbose version of code. Takes input as a string of ls and rs. Explanation:

Write the pinball layout to the canvas.

JN⁰

Jump to the initial position of the pinball.

≔⁰ζ

Start with 0 steps.

WKK«

While the pinball is on an l or an r, ...

≦⊕ζ

... increment the number of steps, and...

✳ι⁻lrι

... overwrite the current cell with the letter of lr that it isn't, moving the pinball in the appropriate direction.

»⎚Iζ

Clear the canvas and output the final number of steps.

\$\endgroup\$
1
  • \$\begingroup\$ For comparison, the best I could do with an array manipulation method was 26 bytes and I came up with the ingenious idea of reflecting the canvas rather than the pinball direction but that was still 25 bytes. \$\endgroup\$
    – Neil
    Mar 7 at 0:16
4
\$\begingroup\$

Python 3, 64 60 56 bytes

-8 bytes thanks to Mukundan314.

f=lambda g,p:g[p]and-~f(g[:p]+[-g[p],*g[p+1:],0],p+g[p])

Try it online!

\$\endgroup\$
5
  • \$\begingroup\$ g[:p]+[-g[p]]+g[p+1:] saves 4 bytes \$\endgroup\$ Mar 6 at 5:49
  • \$\begingroup\$ @Mukundan314 ...I don't wanna talk about -1*g[p]. >< Thanks! \$\endgroup\$ Mar 6 at 5:54
  • 1
    \$\begingroup\$ [*g,0][p] instead of 0<=p<len(g) saves 2 bytes \$\endgroup\$ Mar 6 at 6:36
  • \$\begingroup\$ @Mukundan314 Ooh, nice. \$\endgroup\$ Mar 6 at 6:41
  • 1
    \$\begingroup\$ 2 more bytes: f=lambda g,p:g[p]and-~f(g[:p]+[-g[p],*g[p+1:],0],p+g[p]) \$\endgroup\$ Mar 6 at 6:45
4
\$\begingroup\$

Uiua SBCS, 32 bytes

◌◌⍢(⊙⊙(+1)+⟜⊃⊡⍜⊡¯|↧≥0,>,⧻,)⊙⊙0

Try it!

Takes 0-indexed starting index as an integer and markers as a list of -1 for left and 1 for right.

Someone crush this answer, I beg of you. I tried the approach of padding the markers with a 0 at the front and back and taking a fixed point, but the best I could do was tie 32 bytes.

◌◌⍢(⊙⊙(+1)+⟜⊃⊡⍜⊡¯|↧≥0,>,⧻,)⊙⊙0
                           ⊙⊙0  # store the count under inputs
  ⍢(             |        )     # while...
                  ↧≥0,>,⧻,      # index is in-bounds...
              ⍜⊡¯               # negate at index
          +⟜⊃⊡                  # and update index
    ⊙⊙(+1)                      # increment count
◌◌                              # pop markers and index
\$\endgroup\$
0
4
\$\begingroup\$

PARI/GP, 35 bytes

f(s,g)=iferr(1+f(s-=g[s]*=-1,g),e,)

Attempt This Online!

The grid g is represented as a list of 1s and -1s. The starting position s is 1-indexed.

\$\endgroup\$
4
\$\begingroup\$

Google Sheets, 124 bytes

=sort(let(v,tocol(2:2,1),i,sequence(rows(v)),_,lambda(_,g,s,p,if(or(i=p),_(_,if(i=p,-g,g),s+1,p+index(g,p)),s)),_(_,v,,A3)))

Takes an array of -1 and +1 that represent < and >, and the starting position as a 1-indexed positive integer.

pinball.png

Based on JvdV's original Excel answer with some optimizations.

In Google Sheets, array iteration needs to be enabled by wrapping the formula in arrayformula(), sumproduct(), filter(), index(), sortn() or sort(). This formula uses sort() that manipulates data but that does not matter here because the final result is just one value.

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

Perl 5 -plF, 49 bytes

$i=<>;$\++,$i+=1-2*($F[$i]^=1)while$i>=0&&$i<@F}{

Try it online!

Uses 0 for move right and 1 for move left.

\$\endgroup\$
4
  • \$\begingroup\$ Very nice solution and smart to change the input format! Another -1 if you use $_ instead of $i and check !/-/ instead of $i>=0: Try it online! \$\endgroup\$ Mar 7 at 20:22
  • \$\begingroup\$ Was thinking about this again and using -1 for > and 1 for < can save a few more bytes (1-2*(...^=1) => (...*=-1): Try it online! \$\endgroup\$ Mar 8 at 9:20
  • \$\begingroup\$ I had thought about that, but it didn't seem to me to fit the spirit of the challenge as -1 takes two characters. \$\endgroup\$
    – Xcali
    Mar 8 at 14:58
  • \$\begingroup\$ Yeah, fair enough. Looks like a few answers use the same, but the general approach doesn't really change. :) Nice answer nontheless! \$\endgroup\$ Mar 8 at 16:15
2
\$\begingroup\$

Wolfram Language (Mathematica), 69 bytes

1-indexed, markers are {-1,1}

({g,c}={##};t=0;While[0<c<=Tr[1^g],t++;c+=(o=g[[c]]);g[[c-o]]=-o];t)&

Try it online!

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

Ada, 187 bytes

function F(G:String;P:Integer;C:Integer:=0)return Integer is(if P<0 or P>=G'Last then C else F(G(1..P)&(if G(P+1)='>'then'<'else'>')&G(P+2..G'Last),(if G(P+1)='>'then P+1 else P-1),C+1));

F is a recursive expression function that:

  • Defaults C to 0, and uses it to count iterations
  • Returns C if P (the position) is out of bounds
  • Calls itself on an altered G array and an updated position
\$\endgroup\$
1
2
\$\begingroup\$

Ruby, 42 bytes

->s,g{1.step.find{g-=s[g]*=-1;g<0||!s[g]}}

Try it online!

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

Jelly, 16 bytes

NJf©¥¦,®ị+ɗɗ/ƬL’

A monadic Link that accepts a pair, [State, Position] and yields the step count as an integer. State is a list of -1 for left and 1 for right; Position is the 1-indexed index at which the ball starts.

Try it online! Or see the test-suite (transforms from the <> and zero-index test cases).

How?

NJf©¥¦,®ị+ɗɗ/ƬL’ - Link: pair, [State; Position]
             Ƭ   - start with [State; Position]; collect up while distinct, applying:
           ɗ/    -   reduce by last three links as a dyad - f(S=State, P=Position):
     ¦           -     apply to...
    ¥            -     ...indices: last two links as a dyad - f(S, P):
 J               -                   indices of {S} -> [1,2,...,length(S)]
  f              -                   filter keep {P} -> [P] if P is in bounds
                                                         [] otherwise
   ©             -                   (& copy this to the register)
N                -     ...action: negate -> Next_S
          ɗ      -     last three links as a dyad - f(S, P):
       ®         -       recall from the register
        ị        -       {that} index into {S} (vectorises)
         +       -       add {P} -> Next_P = [S[P]+P] if P is in bounds
                                                  [P] otherwise
      ,          -     {Next_S} pair {Next_P}
                         (the next collection all works with Next_P being
                          a singleton list, so no need to extract the value)
              L  - length
               ’ - decrement
\$\endgroup\$
2
\$\begingroup\$

Swift 5.9, 104 88 86 bytes

Shaved off 10 bytes thanks to @RolandSchmitz's suggestion to use the ~= operator and to use [Int] instead of [Bool]; and an additional 6 bytes by shuffling types around and using implicit parameters.

Removed another 2 bytes thanks to another suggestion by @RolandSchmitz to use g.indices instead of 0..<g.count. (This works because the indices property is also a Range when used on an Array — if this wasn't the case, we wouldn't be able to use the pattern matching operator.)

let s={var n=0,g:[Int]=$0,i=$1+0
while g.indices~=i{g[i] *= -1
i-=g[i]
n+=1}
return n}

g is the grid; 1 corresponds to ">", and -1 corresponds to "<". i is the starting position as an Int, 0-indexed.

When f is called:

  1. Three variables are created:

    • n, which is simply a counter

    • g, a mutable copy of the first implicit parameter ($0)

      This is where we put the type signature for $0 -- this allows us to have a type-less closure header, letting us dispense with the header altogether and use implicit closure parameters instead.

    • i, similar in purpose to g (we add 0 to give the type checker a hint)

  2. The closure loops until i ceases to be a valid index of g.

    In Swift, the ~=(lhs:rhs:) operator is used internally for pattern matching (switch statements, if case, and the like). Here, we call it directly to take advantage of the overload defined by Range, which checks if rhs is contained in lhs.

    Inside this loop, we:

    1. flip the sign of g[I]

      The whitespace around the *= operator is required so the compiler sees infix operator *= followed by prefix operator -, instead of the nonexistent infix operator *=-.

    2. decrement i by g[i]

    3. increment n

  3. Once we're out of the loop, n has the number of iterations it took for i to go out of bounds. This is exactly what we want, so we just go ahead and return that.

\$\endgroup\$
5
  • \$\begingroup\$ I don't really know swift but is +0 in i+0 necessary, if so curious to know the reason? Also, are spaces around = in g[i] = !g[i] and before ? required as well? \$\endgroup\$ Mar 6 at 17:22
  • 1
    \$\begingroup\$ @Mukundan314 The +0 is needed to help the compiler with type inference. There needs to be a space in between = and !, due to there being no =! operator; and since Swift requires infix operators to have equal whitespace on both sides (to differentiate them from prefix and postfix operators), the other space there is also needed. The space before ? is needed to make it clear that it's the ternary conditional operator and not an attempt to optional-chain on a non-Optional type. \$\endgroup\$ Mar 6 at 20:07
  • 1
    \$\begingroup\$ Could be improved a bit with an array containing 1 and -1 instead of true and false and with the ~= operator: let s={(g:[Int],i)in var n=0,g=g,i=i+0;while 0..<g.count~=i{g[i] *= -1;i-=g[i];n+=1};return n} \$\endgroup\$ Mar 12 at 21:47
  • \$\begingroup\$ @RolandSchmitz I honestly forgot the ~= operator even existed... thanks for the 10-byte save! \$\endgroup\$ Mar 12 at 22:30
  • 1
    \$\begingroup\$ You can save another 2 bytes by replacing the range with g.indices like that: let s={var n=0,g:[Int]=$0,i=$1+0;while g.indices~=i{g[i] *= -1;i-=g[i];n+=1};return n} \$\endgroup\$ Mar 13 at 20:43
2
\$\begingroup\$

C (gcc), 98 96 59 56 54 bytes

f(g,n,p)int*g;{return-1<p&p<n?f(g,n,p-=g[p]*=-1)+1:0;}

Try it online!

Readable version:

int f(int* g, int n, int p) {
    return -1 < p & p < n ? 
           f(g, n, p -= (g[p] *= -1)) + 1 :
           0;
}
  • g is the grid with -1 corresponding to < and 1 to >.
  • n is the length of the grid.
  • p is the position of the pinball on the grid.

Edits

-1 thanks to @noodle man

-1 by changing the data type of g from char* to int*

-37 thanks to @ceilingcat

-3 thanks to @Roland Schmitz

-2 thanks to @ceilingcat

\$\endgroup\$
3
  • \$\begingroup\$ Welcome to Code Golf Stack Exchange! \$\endgroup\$
    – noodle man
    Mar 10 at 15:46
  • 1
    \$\begingroup\$ Can you add a Try it Online link so others can test out your code? Also, return f(g,n,p)+1 can be return-~f(g,n,p). \$\endgroup\$
    – noodle man
    Mar 10 at 16:24
  • 1
    \$\begingroup\$ Hello Johann, this can be simplified further if you use -1 for < with this: f(g,n,p)int*g;{return-1<p&p<n?f(g,n,p-=(g[p]*=-1))+1:0;} \$\endgroup\$ Mar 13 at 21:11
1
\$\begingroup\$

APL+WIN, 66, 64 bytes

2 bytes saved thanks to Mukundan

Prompts for grid followed by pinball position. ¯1 moves left and 1 moves right. Index origin = 1

g←⎕⋄p←⎕⋄n←0
:while ((p>0)^p≤⍴g)
g[p]←-g[p]⋄p←p-g[p]⋄n←n+1
:end
n

Try it online! Thanks to Dyalog Classic

\$\endgroup\$
2
  • \$\begingroup\$ monadic - (aka negate) instead of ׯ1? \$\endgroup\$ Mar 6 at 18:18
  • \$\begingroup\$ @Mukundan314 Thanks. Edited \$\endgroup\$
    – Graham
    Mar 6 at 19:01
1
\$\begingroup\$

PowerShell Core, 85 bytes

filter f{param($c,$s)&({$c},{2*($s[$_]=!$s[$_])-1+$_|f($c+1)$s})[$_+1-in1..$s.Count]}

Try it online!

Recursive filter
Takes an array of booleans

  • true for <
  • false for >

Returns an integer

Explanations

param($c,$s)                                                    # As it is a filter, we have 3 parameters $_ the current position, $c the score, $s the pinball directions
&({  },{                                 })[$_+1-in1..$s.Count] # If the ball position is outside of the array of directions
   $c                                                           # Return the count
          ($s[$_]=!$s[$_])                                      # Change the direction on the current ball position
        2*                -1+$_|f($c+1)$s                       # Increment the score by 1 and invoke the filter recursively on the new ball position
\$\endgroup\$
1
\$\begingroup\$

Java 8, 70 69 61 bytes

a->s->{int c=0;try{for(;;c++)s-=a[s]*=-1;}finally{return c;}}

-8 bytes thanks to a tip from @Neil of using -1,1 for <,> respectively, instead of 1,0.

Grid input as an array with -1 and 1 for < and > respectively.

Try it online.

Explanation:

a->s->{                 // Method with integer-array & integer parameters and integer
                        // return-type
  int c=0;              //  Counter, starting at 0
  try{for(;             //  Start an infinite loop:
          ;             //    After every iteration:
           c++)         //     Increase the counter by 1
    s-=                 //   Update position `s` based on the current `s`'th value:
       a[s]*=-1;}       //    Invert the current `s`'th value (-1 to 1 and vice versa)
  finally{              //  If an error occurs (ArrayOutOfBoundsException when `s` is
                        //  out of bounds):
    return c;}}         //   Return the counter as result

The try{...}finally{...} is 1 byte shorter than a manual in-bounds check s>=0&s<a.length within the loop.

\$\endgroup\$
3
  • 1
    \$\begingroup\$ Surely you can save 3 bytes by using -1 instead of 0 in the input? \$\endgroup\$
    – Neil
    Mar 8 at 0:19
  • 1
    \$\begingroup\$ Make that 8 bytes. \$\endgroup\$
    – Neil
    Mar 8 at 0:22
  • \$\begingroup\$ @Neil Good call. I completely forgot any inputs are allowed instead of just 0/1 bits. Thanks. \$\endgroup\$ Mar 8 at 7:58
1
\$\begingroup\$

TI-Basic, 40 bytes

Prompt L,P
While P and P≤dim(ʟL
~ʟL(P→ʟL(P
P-Ans→P
C+1→C
End
C

Takes input as a list (with 1 for > and ~1 for <) and a 1-indexed position. ~ represents the negative sign while - represents the subtraction symbol.

\$\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.