27
\$\begingroup\$

In this challenge you will take a an ascii art image of a highway like so

 |   |   |  |  |  |
 |       |  |     |
 |   |   |  |  |  |
 |       |  |     |
 |   |   |  |  |  |
 |       |  |     |
 |   |   |  |  |  |

And you need to put rain across its lanes like so

 |\\\|\\\|  |\\|\\|
 |\\\\\\\|  |\\\\\|
 |\\\|\\\|  |\\|\\|
 |\\\\\\\|  |\\\\\|
 |\\\|\\\|  |\\|\\|
 |\\\\\\\|  |\\\\\|
 |\\\|\\\|  |\\|\\|

A highway will be defined as an ascii art string containing only the characters |, and newlines. All rows of the image will have exactly the same number of characters. Every column will be one of the following

  • Empty space, all spaces

  • A line, all |

  • A lane divider, alternating | and space

Furthermore there will always be a line before and after every lane divider. So

|    |   | |   |
|          |   |
|    |   | |   |

would be valid but

|     |     |
|  |  |      
|     |     |

would not be since there is no line after the last lane divider.

And a lane divider will never be exactly next to a line. So

||   |
|    |
||   |

Is invalid but

|   ||  ||
|     | ||
|   ||  ||
|     | ||
|   ||  ||

is fine.

Additionally a highway must be at least two rows high and one column wide.

Task

Your job is to write a program or function that takes a valid highway (as a string or list of lines) as input and outputs the highway with rain across its lanes.

To put rain across a highway's lanes you must replace all space characters that appear between two consecutive lines, where those lines have at least one lane divider between them, with \s. And leave all other characters the same.

So in the following diagram:

A   B   C   1   2  D
|   |   |       |  |
|   |   |   |      |
|   |   |       |  |
|   |   |   |      |

No rain should be put between A and B or B and C since there are no lane dividers between them, but rain should be put between C and D since lane dividers 1 and 2 are between them.

You may output an additional trailing newline, or add/remove trailing spaces from all lines equally if you wish.

This is so answers will be scored in bytes with fewer bytes being better.

Test cases

Simple case

 |   |   |  |  |  |
 |       |  |     |
 |   |   |  |  |  |
 |       |  |     |
 |   |   |  |  |  |
 |       |  |     |
 |   |   |  |  |  |

 |\\\|\\\|  |\\|\\|
 |\\\\\\\|  |\\\\\|
 |\\\|\\\|  |\\|\\|
 |\\\\\\\|  |\\\\\|
 |\\\|\\\|  |\\|\\|
 |\\\\\\\|  |\\\\\|
 |\\\|\\\|  |\\|\\|

Extra line

|    |   | |   |
|          |   |
|    |   | |   |

|\\\\|\\\|\|   |
|\\\\\\\\\\|   |
|\\\\|\\\|\|   |

Unsynced lane dividers

   |    |      |
   |        |  |
   |    |      |
   |        |  |
   |    |      |

   |\\\\|\\\\\\|
   |\\\\\\\\|\\|
   |\\\\|\\\\\\|
   |\\\\\\\\|\\|
   |\\\\|\\\\\\|

Empty road

|   |   |       |  |
|   |   |   |      |
|   |   |       |  |
|   |   |   |      |

|   |   |\\\\\\\|\\|
|   |   |\\\|\\\\\\|
|   |   |\\\\\\\|\\|
|   |   |\\\|\\\\\\|

False roads (suggested by Arnauld)

|   | | |   |
|   |   |   |
|   | | |   |
|   |   |   |

|   |\|\|   |
|   |\\\|   |
|   |\|\|   |
|   |\\\|   |

Hard lane divider

|  |  |  |  |
|     |     |
|  |  |  |  |
|     |     |

|\\|\\|\\|\\|
|\\\\\|\\\\\|
|\\|\\|\\|\\|
|\\\\\|\\\\\|

Adjacent lane dividers

|   ||  ||
|     | ||
|   ||  ||
|     | ||
|   ||  ||

|\\\||\\||
|\\\\\|\||
|\\\||\\||
|\\\\\|\||
|\\\||\\||

No lanes

|    |     |  |
|    |     |  |
|    |     |  |

|    |     |  |
|    |     |  |
|    |     |  |

One line

       |
       |
       |

       |
       |
       |

No lines

    
    
    

    
    
    
\$\endgroup\$
11
  • \$\begingroup\$ Do we need to handle lanes of arbitrary width, or is it okay to assume a single lane is never more than 25 (or some other number) spaces wide? \$\endgroup\$
    – Grimmy
    Mar 10, 2020 at 16:46
  • \$\begingroup\$ @Grimmy You should not assume that. But I do understand that some languages have limits on how much memory they can index, so it would probably be Ok to assume that you can index the entire string. \$\endgroup\$
    – Wheat Wizard
    Mar 10, 2020 at 16:56
  • \$\begingroup\$ Are we allowed to accept and/or output line lists? \$\endgroup\$ Mar 10, 2020 at 21:33
  • \$\begingroup\$ @JonathanAllan It seems like that is what many people are doing. I assume it is some sort of default or something. \$\endgroup\$
    – Wheat Wizard
    Mar 10, 2020 at 21:49
  • \$\begingroup\$ I'm not sure it is, a list of characters may replace a string by default though. Maybe just specify that it is allowed :) \$\endgroup\$ Mar 10, 2020 at 22:00

14 Answers 14

8
\$\begingroup\$

Jelly,  18  16 bytes

ZḢ”\E?€ṣ”|»Ṁ$€K»

A monadic Link accepting a list of lines, each of which is a list of characters, which yields another, similar list.

Try it online! Or see the test-suite.

How?

ZḢ”\E?€ṣ”|»Ṁ$€K» - Link: list of lists of characters (i.e. rows of text)
Z                - transpose - i.e. columns (each being: all spaces; all pipes; or a mix) 
      €          - for each (column):
     ?           -   if...
    E            -   ...condition: all equal? - i.e. if not dashed
 Ḣ               -   ...then: head - i.e. the unique character
  ”\             -   ...else: literal '\' character
        ”|       - literal '|' character
       ṣ         - split at - i.e. highway chunks now contain spaces and '\' characters
             €   - for each (chunk):
            $    -   last two links as a monad:
           Ṁ     -     maximum - N.B. '\' > ' '
          »      -     maximum (vectorises across the chunk)
                 -   - i.e highway chunks become all '\', others remain as is
              K  - join with space characters
               » - maximum (vectorises across the rows)
                 -   - N.B. '|' > ' ' so the solid lines are not replaced by spaces; and
                 -          '|' > '\' so the dashed lines are not replaced with rain.
\$\endgroup\$
6
\$\begingroup\$

JavaScript (ES6),  118 ... 105  103 bytes

I/O format: matrix of characters.

a=>a.map(r=>r.map((c,i)=>(a[k=0][i]+a[1][i]<'||'?r:r=a.some(r=>r[a[k^=1].indexOf(c,i+1)]<c))/c?'\\':c))

Try it online!

How?

To detect lines, we test whether the first and the second row both contain a | at a given position \$i\$:

a[0][i] + a[1][i] < '||'  // true if it's not a line

When a line is detected:

  • We know for sure that the character \$c\$ at the same position in the current row is a |, because lines are guaranteed to go all the way from top to bottom. So we can now use \$c\$ instead of '|'.

  • We update the flag \$r\$ which is set to \$1\$ when we're located on a road with a lane divider.

We update \$r\$ by testing if, for some \$n\$, the next | (starting from \$i+1\$) in either the first or the second row (depending on the parity of \$n\$) is facing a space in the \$n\$-th row:

r = a.some(r => r[a[k ^= 1].indexOf(c, i + 1)] < c  // starting with k = 0

Finally, we must replace \$c\$ with a \ if \$c\$ is a space and \$r=1\$. Because a space is the only road character that is coerced to \$0\$, we can simply do:

r / c

The only truthy value is given by 1/' ' (\$+\infty\$). Something like 1/'|' would result in NaN, and so does 0/' ' (\$0/0\$).

\$\endgroup\$
5
\$\begingroup\$

05AB1E, 20 bytes

₆F€SøíJ„ \S„\|N_èì`:

Try it online!

₆F                    # repeat the following 36 times:
  €SøíJ               #  rotate 90 degrees
       „ \S           #  character array [' ', '\']
           „\|        #  string "\|"
              N       #  0-based iteration count
               _      #  logical not: 0 => 1, anything else => 0
                è     #  use that to index in the string "\|"
                 ì    #  prepend (first loop: ["| ", "|\"], then ["\ ", "\\"])
                  `:  #  recursive replace ("| " => "|\" or "\ " => "\\")
\$\endgroup\$
2
  • 2
    \$\begingroup\$ What's going on here? \$\endgroup\$ Mar 10, 2020 at 22:23
  • \$\begingroup\$ @JonathanAllan Nice catch. Looks like a bug with legacy 05AB1E's ø, it doesn't work on strings that are exactly one character long. The actual test case has 4 spaces per row and works fine. \$\endgroup\$
    – Grimmy
    Mar 11, 2020 at 9:27
5
\$\begingroup\$

Perl 5 -p, 52 51 bytes

/
/;s/(\S(.{@{-}})|\\)\K | (?=(?2)\S|\\)/\\/s&&redo

Replace space character with a backslash if there is a non-space character above or below it or a backslash on the right or the left of it. And redo while no more space can be replaced.

One byte saved diverting recursive regex thanks to Grimmy.

Try it online!

\$\endgroup\$
1
  • \$\begingroup\$ indeed diverting recursive regex \$\endgroup\$ Mar 11, 2020 at 16:17
5
\$\begingroup\$

Python 3, 198 196 186 159 bytes

def g(x):
 for _ in x[0]:x=[''.join([q,'\\'][q<'!'and(x[a-1][b]>'['or'\\'in r[b-1]+r[-~b%len(r)])]for(b,q)in enumerate(x[a]))for(a,r)in enumerate(x)]
 return x

Try it online!

Explanation

This implements a simple automaton. At every step a space is replaced with an \ iff one of the following is true

  • There is a | or \ above it (indexing wraps around)

  • There is \ to the left or right of it.

This means that rain starts at the gaps in the lane dividers and spreads until it hits |s.

\$\endgroup\$
5
  • 2
    \$\begingroup\$ Usually it's better to wait a bit longer before posting your own answer, in case someone else was working on a Python answer. And you can save 2 bytes by replacing the (b+1) with -~b. \$\endgroup\$ Mar 10, 2020 at 15:38
  • 1
    \$\begingroup\$ @KevinCruijssen I waited an hour and then started writing this answer. I think it is better to get the ball rolling in this case. \$\endgroup\$
    – Wheat Wizard
    Mar 10, 2020 at 15:39
  • 2
    \$\begingroup\$ Uhh.. you can do that replacement for the exact reason you stated. ;) - and ~ have higher operator precedence than the %, so it will execute those first, saving 2 bytes on the parenthesis of (b+1). Relevant general tip and apparently the same tip is on the Python tip page as well. \$\endgroup\$ Mar 10, 2020 at 15:43
  • 1
    \$\begingroup\$ 155 bytes by going to Python v3.8 and using x:=, so you can transform it into a lambda. \$\endgroup\$ Mar 10, 2020 at 17:20
  • 2
    \$\begingroup\$ Might want to mention that it wraps around the bottom to save confusion on how the `\`s propagate upwards \$\endgroup\$
    – Jo King
    Mar 11, 2020 at 0:31
4
\$\begingroup\$

Python 2, 112 109 107 bytes

lambda r:reduce(lambda r,c:[''.join(l[::-1]).replace(c+' ',c+'\\')for l in zip(*r)],r'|\|\\\\:'*len(`r`),r)

Try it online!

Python 3.8 (pre-release), 109 bytes

Thanks to Kevin Cruijssen for this version!

lambda r:[(r:=[''.join(l[::-1]).replace(c+' ',c+'\\')for l in zip(*r)])for c in r'|\|\\\\:'*len(repr(r))][-1]

Try it online!

Port of my 05AB1E answer.

\$\endgroup\$
3
  • \$\begingroup\$ 96 bytes by creating a lambda in Python 3.8 \$\endgroup\$ Mar 10, 2020 at 17:11
  • 1
    \$\begingroup\$ @KevinCruijssen your golf is based on the old, invalid version of my answer (can't hardcode 255 since lanes could be wider than that). See the current version. \$\endgroup\$
    – Grimmy
    Mar 10, 2020 at 17:12
  • 1
    \$\begingroup\$ Ah, hadn't noticed you updated it. And I'm afraid I'm not too skilled in Python, so not sure if that 109-byter can be shorter. Just remembered the new r:= variable assign from a recent other Python 3.8 answer and thought it might save some bytes by transforming it into a lambda instead of def-function. But I see you've already transformed it into a lambda to fix the incorrect assumption of no more than 255 iterations. \$\endgroup\$ Mar 10, 2020 at 17:17
4
\$\begingroup\$

Jelly, 25 bytes

n⁶Zṣ1€oFẸḤƊ$€j1€W$Zị“|\ ”

Try it online!

oFẸḤƊ$€ is "borrowed" from @Riolku lol go upvote him

Explanation

n⁶Zṣ1€oFẸḤƊ$€j1€W$Zị“|\ ”  Main Link
n⁶                         vectorized inequality to space; "|" becomes 1, " " becomes 0
  Z                        transpose into a list of columns
   ṣ1€                     split by [1, 1, ..., 1] (length of original; number of rows)
            €              for each block (space between two lines)
      o                    vectorized OR with
       FẸḤ                 (flatten) (any) (double): 0 if there are only spaces; 2 if there are lane dividers
             j             join on
              1€W$         [1, 1, ..., 1] wrapped ([[1, 1, ..., 1]])
                  Z        zip back into original orientation
                   ị“|\ ”  index into "|\ "; 0s are spaces, 1s are lines, and 2s are the replaced spaces which become \
\$\endgroup\$
4
\$\begingroup\$

Jelly, 25 bytes

Zn⁶ṣoFẸḤƊ$€jW}
ç1€ị“|\ ”Z

Try it online!

I/O is a list of strings

Explanation

Zn⁶ṣoFẸḤƊ$€jW}   Primary Link (dyad); takes the input on the left and [1, 1, ..., 1] on the right
Z                zip the input into a list of columns
 n⁶              vectorized inequality to space; "|" becomes 1, " " becomes 0
   ṣ             split (on [1, 1, ..., 1])
          €      for each block (between lines)
    o            vectorized or with
     FẸḤ         (flatten) (any) (double): 0 if there are only spaces; 2 if there are lane dividers
           jW}   join on ([1, 1, ..., 1] wrapped) (2,2-chain using })
ç1€ị“|\ ”Z       Main Link
ç                call the primary link with (default left argument),
 1€              [1, 1, ..., 1]
   ị“|\ ”        index into "|\ "
         Z       zip back into original orientation
\$\endgroup\$
4
\$\begingroup\$

Haskell, 151 bytes

a#b=a!!mod b(length a)
z=zip[0..]
s%_=[[(['\\'|b<'!'&&(s#(x-1)#y>'['||elem '\\'[a#(y-1),a#(y+1)])]++[b])!!0|(y,b)<-z a]|(x,a)<-z s]
g x=foldl(%)x(x!!0)

Try it online!

Explanation

This implements the same algorithm as my python answer. It implements a simple automaton. At every step a space is replaced with an \ iff at least one of the following is true

  • There is a | or \ above it (indexing wraps around)

  • There is \ to the left or right of it.

This means that rain starts at the gaps in the lane dividers and spreads until it hits |s.

\$\endgroup\$
3
\$\begingroup\$

Retina 0.8.2, 79 bytes

+m`(?<=(.)+) ((?<=(?(1).)^(?<-1>.)+\S.*¶.+|\\ )|(?=\\|.*¶(?<-1>.)+(?(1)$)\S))
\

Try it online! Same flood fill approach that @WheatWizard uses in his answers but using .NET balancing groups to detect vertical matches. Would save two bytes if the wind was blowing the other direction. Explanation:

+m`

Repeatedly replace and turn on line detection.

(?<=(.)+) 

Replace a space but count the number of preceding characters on that line.

((?<=...|...)|(?=...|...)))
\

Replace with a backslash if one of the four conditions is true.

The four conditions are:

(?(1).)^(?<-1>.)+\S.*¶.+

This space is below rain or a lane divider. The (?(1).) can only succeed if the characters are in the same column.

\\ 

This space is to the right of existing rain.

\\

This space is to the left of existing rain.

.*¶(?<-1>.)+(?(1)$)\S

This space is above rain or a lane divider. The (?(1)$) can only succeed if the characters are in the same column.

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

Ruby, 144 bytes

->s{s.map{|l|((r=s[0]).size.times.map{|i|r[i]!=s[1][i]?[r.rindex(?|,i-1),r.index(?|,i+1)]:p}-[p]).map{|a,b|a.upto(b){|i|l[i]=?\\if l[i]<?!}};l}}

Try it online!

I thought I would try a different approach to the automaton. This finds lane dividers by checking for which indices the first two rows differ. It then goes through each of these indices and finds the previous and next line. It then fills in all space characters with the rain character.

This takes a list of strings for each row of the road.

Golfy Tricks:

  • Using l[i]<?! instead of l[i]==" ". This checks to see whether the character comes before ! (which only space does of the allowed charactersin this challenge).
  • -[p] to remove all nil elements in an array.
  • map instead of each.
  • size.times instead of chars.each_index
  • Using map with a condition (where failures result in nil and nils are then removed), rather than a select followed by a map.

Explanation:

->s{s.map{|l|((r=s[0]).size.times.map{|i|r[i]!=s[1][i]?[r.rindex(?|,i-1),r.index(?|,i+1)]:p}-[p]).map{|a,b|a.upto(b){|i|l[i]=?\\if l[i]<?!}};l}}

# Go through each character in the first row (saving the row in a variable for later use)
             ((r=s[0]).size.times.map
# Check if it's a line divider
                                         r[i]!=s[1][i] 
# If it is, save the nearest road lines
                                                       [r.rindex(?|,i-1),r.index(?|,i+1)]
# If it isn't save nil
                                                                                          p 
# Remove all the nils
                                                                                            -[p]
# So we have all the indices of the lanes
             ((r=s[0]).size.times.map{|i|r[i]!=s[1][i]?[r.rindex(?|,i-1),r.index(?|,i+1)]:p}-[p]) 
# For each row in the road
->s{s.map{|l| 
# Go through each index within the lane
                                                                                                 .map{|a,b|a.upto(b) 
# And set any empty charaters to rain
                                                                                                                        l[i]=?\\if l[i]<?! 
# And then return the new rainy row
                                                                                                                                             l
\$\endgroup\$
2
\$\begingroup\$

Bash + Core utilities, 154 152 145 142 136 119 bytes

IFS=\

read t
d=`sed s/././g<<<$t`
sed -E ":l;s/([|%]$d|%) /\1%/;tl;s/ ($d[|%]|%)/%\1/;tl;"'y/%@/\\\
/'<<<$t@`tr '
' @`

Try it online!

Input on stdin, and output on stdout.

(The script has spurious output to stderr in one case, but that's considered acceptable.)

Here are the outputs for the test cases:

highway-1-simple
 |\\\|\\\|  |\\|\\|
 |\\\\\\\|  |\\\\\|
 |\\\|\\\|  |\\|\\|
 |\\\\\\\|  |\\\\\|
 |\\\|\\\|  |\\|\\|
 |\\\\\\\|  |\\\\\|
 |\\\|\\\|  |\\|\\|


highway-2-extraline
|\\\\|\\\|\|   |
|\\\\\\\\\\|   |
|\\\\|\\\|\|   |


highway-3-unsyncedlanedividers
   |\\\\|\\\\\\|
   |\\\\\\\\|\\|
   |\\\\|\\\\\\|
   |\\\\\\\\|\\|
   |\\\\|\\\\\\|


highway-4-emptyroad
|   |   |\\\\\\\|\\|
|   |   |\\\|\\\\\\|
|   |   |\\\\\\\|\\|
|   |   |\\\|\\\\\\|


highway-5-falseroads
|   |\|\|   |
|   |\\\|   |
|   |\|\|   |
|   |\\\|   |


highway-6-hardlanedivider
|\\|\\|\\|\\|
|\\\\\|\\\\\|
|\\|\\|\\|\\|
|\\\\\|\\\\\|


highway-7-adjacentlanedividers
|\\\||\\||
|\\\\\|\||
|\\\||\\||
|\\\\\|\||
|\\\||\\||


highway-8-nolanes
|    |     |  |
|    |     |  |
|    |     |  |


highway-9-oneline
       |
       |
       |


highway-A-nolines
\$\endgroup\$
3
  • \$\begingroup\$ tl;tl might be reducible to just tl. More tentatively, maybe some the global flags can be dropped too? What is the purpose of the fold? \$\endgroup\$
    – user41805
    Mar 12, 2020 at 17:47
  • \$\begingroup\$ @user41805 Whoops... I think I have a copy and paste error. (What I wrote works, but wasn't the fully golfed version.) Let me check it out. \$\endgroup\$ Mar 12, 2020 at 17:58
  • \$\begingroup\$ @user41805 Yes, that was not the version that I had intended to post. It should be fixed now. Thanks for pointing it out. \$\endgroup\$ Mar 12, 2020 at 18:08
2
\$\begingroup\$

Husk, 21 20 15 bytes

-5 bytes thanks to Zgarb!

Tṁ?mσ' '\IV≠ġ▼T

Try it online! Takes input as a list of rows. Works by transposing rows and columns, splitting the columns into groups based on whether they have any spaces, adding rain to the column groups with lane divisions, recombining the column groups, and transposing back.

\$\endgroup\$
3
  • \$\begingroup\$ ġo¬€' can be ġΛI since only whitespace is falsy and all columns have the same height. \$\endgroup\$
    – Zgarb
    Oct 16, 2020 at 19:24
  • \$\begingroup\$ Actually ġ▼ is even shorter. \$\endgroup\$
    – Zgarb
    Oct 16, 2020 at 19:25
  • \$\begingroup\$ And I think V#≠ can be just V≠ since the lane dividers will be the only different columns in each group. \$\endgroup\$
    – Zgarb
    Oct 16, 2020 at 19:32
1
\$\begingroup\$

Charcoal, 33 bytes

WS⊞υ⪫⪪ι ψP⪫υ¶F⊟υ¿⊖№KD²↓|KK«¤\↓¤\↗

Try it online! Link is to verbose version of code. Expects rectangular input with newline terminator. Explanation:

WS⊞υ⪫⪪ι ψ

Read the input but change spaces into nulls.

P⪫υ¶

Copy the input to the output; nulls turn into fillable spaces.

F⊟υ

Loop over the width of the input.

¿⊖№KD²↓|

Is this a lane divider? It isn't if it doesn't alternate between |s and empty cells.

KK

If it's not a divider then print the current character thus moving the cursor one step right. Simply moving right doesn't help because this needs to be a two-character Move command to avoid confusion with printing a lambda in a rightwards direction, and negating the condition doesn't help because when there are no lanes then Charcoal prints nulls instead of spaces for some inexplicable reason.

«¤\↓¤\↗

Fill the area with rain. Both the current cell and the cell below need to be checked as we don't know which one is empty. The cursor is then moved to the right of its original position.

\$\endgroup\$

Your Answer

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

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