Loops and Loops and Loops

16

1

The Challenge

Create a function that, when given an input of ASCII art (directing a path that may eventually loop), outputs the length of the loop (if there is one) and the length of the "tail" leading into the loop in one of the forms below.


Input

Your input must be passed to a function. Below is an example of a simple input.

# --> # --> #
      ^     |
      |     |
      |     v
      # <-- #

You could visualize the above blocks like this

The "tail" is one item, while the loop is four long.

A more difficult one:

            # --> # --> #
            ^           |
            |           |
            |           v
      # --> # <-- #     # --> #
      ^           ^           |
      |           |           |
      |           |           v
# --> #           # <-- # <-- #

Output

You must output through STDOUT or your language's closest alternative.

Your two output integers should be the length of the tail and the length of the loop. This output can be in two forms.

  1. a space-delimited string: "2 10"
  2. an array of integers: [2, 10]

Rules

  • Every block, or #, will only have a single path away from itself.

  • Every arrow is two line segments and one head.

  • The starting block will always be in the leftmost column.

  • The input will never be just a loop.


Example

# --> # --> # --> #
^     ^           |
|     |           |
|     |           v
#     # <-- # <-- #

This one has a tail length of 2 and a loop length of 6. Below, the tail and loop are separated.

Tail

# -->
^
|
|
#

Loop

# --> # --> #
^           |
|           |
|           v
# <-- # <-- #

The correct outputs are [2, 6] and "2 6".

If the input is only a tail, the loop length is zero.

# --> # --> # --> #
                  |
                  |
                  v
        <-- # <-- #

The correct outputs for the above input are [6, 0] and "6 0"

Zach Gates

Posted 2015-09-17T20:57:03.220

Reputation: 6 152

@orlp I think you're confusing input and output. – Sanchises – 2015-09-17T20:59:20.363

1Can the input have extra disconnected pieces of path? – xnor – 2015-09-17T21:00:18.393

I think the intro is confusing. It makes me think the problem will be about program analysis, whereas it is about path-finding in ASCII art. – xnor – 2015-09-17T21:01:12.013

I've removed the intro. It was a bit confusing/misleading. @xnor – Zach Gates – 2015-09-17T21:03:42.150

3

Related: Where is the arrow pointing?

– mınxomaτ – 2015-09-17T21:12:07.087

I've added that piece of information under the new "Rules" section. @Zgarb – Zach Gates – 2015-09-17T21:12:39.210

This could be nice to solve in JavaScript (browser console). But there is not way to input a multiline string from STDIN or closest alternative (prompt). Why not a function call with the string as argument? – edc65 – 2015-09-17T23:26:18.897

Good point. I hadn't thought of this. Just edited the question. @edc65 – Zach Gates – 2015-09-17T23:27:14.343

I understand the input is a single multiline string. Can we assume all lines are of equal length (padded with spaces)? – Level River St – 2015-09-25T23:31:29.697

Yes. All lines are padded so that the input is rectangular. @steveverrill – Zach Gates – 2015-09-26T14:52:09.250

Answers

11

JavaScript (ES6), 221 229

A function with the input as a parameter, output as a string via popup window (alert).

Scan repeatedly the input:
at each step

  • remove the end of the tail
  • count the remaining '#'

When there is not more tail to remove, the number of the steps so far is the size of the tail and the number of remaining '# is the size of the loop.

All the newlines inside backticks are significant and counted

Test running the snippet below with Firefox (not Chrome, as it does not support ...)

F=s=>{s=`


${s}


`.split`
`.map(r=>[...r]);for(t=0,f=1;f;)s.map((r,y)=>r.map((c,x)=>c=='#'&&((r[x+2]+r[x-2]+s[y-1][x]+s[y+1][x]).match`[v<>^]`?++l:t+=(f=r[x-4]=r[x+4]=s[y-3][x]=s[y+3][x]=r[x]=1))),f=l=0);alert(t+' '+l)}

// Less golfed
U=s=>{
  s=`\n\n\n${s}\n\n\n`.split`\n`.map(r=>[...r])
  t=0
  do {
    f=l=0
    s.forEach((r,y) => {
      r.forEach((c,x) => {
        if (c == '#')
        {
          if (!(r[x+2] == '<' || r[x-2] == '>' || s[y-1][x] == 'v' || s[y+1][x] == '^'))
            t+=(f=r[x-4]=r[x+4]=s[y-3][x]=s[y+3][x]=r[x]=1)
          else
            ++l
        }
      })
    })
  } while(f)
  alert(t+' '+l)
}  

//Test

// Redefine console.log
alert=(...x)=>O.innerHTML+=x+'\n'

test=[`
# --> # --> #
      ^     |
      |     |
      |     v
      # <-- #`
,`
            # --> # --> #
            ^           |
            |           |
            |           v
      # --> # <-- #     # --> #
      ^           ^           |
      |           |           |
      |           |           v
# --> #           # <-- # <-- #`
,`
# --> # --> # --> #
^     ^           |
|     |           |
|     |           v
#     # <-- # <-- #`      
]

test.forEach(t=>(alert(t),F(t)))
<pre id=O></pre>

edc65

Posted 2015-09-17T20:57:03.220

Reputation: 31 086

... is the spread operator right? You might want to name it this way since it exists in other languages (like groovy) by other syntaxes (*:list for groovy). Nice solution anyway ! – Aaron – 2015-09-18T12:31:04.843

1+1 I was thinking 'There must be a smart way to do this', came up with this solution, only to scroll down to this answer. – Sanchises – 2015-09-28T09:23:02.993

8

Ruby, 287 278 bytes

->i{n={}
g=->x{n[x]||=[0,p]}
t=y=0
i.lines{|l|x=0
l.chars{|c|x+=1
'><'[c]&&(r=c.ord-61;s,d=[y,x-4*r],[y,x+2*r])
'^v'[c]&&(r=c<?_?1:-1;s,d=[y+r*3,x],[y-r,x])
s&&(g[s][1]=g[d])[0]+=1}
y+=1}
c,*_,s=n.values.sort_by{|v|v[0]}
l=n.size
s[0]>1?((t+=1;c=c[1])while c!=s):t=l-=1
[t,l-t]}

Try it here.

This builds a hash (dictionary) of nodes. For each node, the number of incoming connections and the (possibly null) next node are stored.

Finally:

  • If there is no node with 2 incoming connections (meaning no loop), return 0 for the tail and the number of existing nodes for the loop.
  • Otherwise, start iterating from the node with 0 incoming connections (start) via next->...->next until the node with 2 incoming connections (loop start) is reached. Return the appropriate counts.

The readable version of the code is available here.

Cristian Lupascu

Posted 2015-09-17T20:57:03.220

Reputation: 8 369

2

Ruby, 276

->s{a=k=w=s.index(r='
')*2+2
s=r*w+s+r*w
(s.size).times{|i|s[i,2]=='
#'&&(s[3+j=i+1]+s[j+w]+s[j-w]).strip.size<2&&(a=[j]
d=0
loop{("|-|-"[d])+?#=~/#{s[k=j+[-w,3,w,-3][d]]}/?(a.include?(k)&&break;a<<(j=k);d-=1):d=(d+1)%4}
)}
u=a.size
v=a.index(k)
t=(u-v)/4*2
print u/2-t," ",t}

Level River St

Posted 2015-09-17T20:57:03.220

Reputation: 22 049