# Fill the (possibly leaky) bowl

## Challenge:

Given an ASCII art of a (possibly leaky) bowl consisting of a random distinct non-whitespace and non-~ character, fill it completely with ~ characters. If the bowl is leaky, fill the bottom row below the bowl and a stream of liquid emerging from that, with the intended amount of ~ if the bowl would not have been leaky.

For example:

Regular bowl:

Input Output
#      # #    #   ####  
#~~~~~~# #~~~~#   ####  

Leaky bowl:

Input Output
00     00 00   00   000 0  
00     00 00   00 ~~000~0~~     ~        ~        ~   

If there wouldn't have been a leak, it could have contained eight ~. Instead, the bottom row including leak position is now filled with five ~, and the remaining three ~ are below the leak.
(Imagine the bowl standing on a table, so the five ~ at the bottom row of the bowl are on the table, and the ~ vertically below the leak are dripping off the table.)

## Challenge rules:

• The potential leak is guaranteed to be at the bottom row, and there will never be any gaps at the sides of a bowl.
• The potential leak is guaranteed to be a single character gap.
• The character used for the bowl can be any printable ASCII character, except for the ~ and whitespaces.
• The bowl can be in an irregular shape (see some of the test cases).
• The top ridges of the bowl are guaranteed to be on the same top row, and there will only be two top ridges.
• If the bottom row contains more space characters than inside the leaky bowl (see the third leaky bowl test case below), we still fill the entire bottom row of the output regardless, but there won't be any additional ~ below it.
• For the sake of this challenge, there won't be any smaller inner bowls (e.g. no doughnut-shaped bowls if we'd imagine it as 3D). So every space in the bowl will always flow towards the leak. (See the fourth leaky bowl test case below, which doesn't have #~# # as its second line.)
• There also won't be any enclosed blobs at the sides (or inside) of a bowl, not any 'stalagmites' nor 'stalactites'.
• The bottom of the bowl won't have a path traveling up and back down.
• I/O is flexible. Could be a multi-line string; a list of lines; a character matrix; etc.
• You're allowed to pad the input with trailing spaces to make the input a rectangle.
• You're allowed to have leading/trailing whitespaces and/or newlines in the output, as long as the expected result is somewhere on the screen.

Here some examples of invalid bowls based on the rules. Your program can have unspecified behavior for any of the invalid bowls. If you have a question about a certain bowl-shape, feel free to ask in the comments.

## General rules:

• This is , so the shortest answer in bytes wins.
Don't let code-golf languages discourage you from posting answers with non-codegolfing languages. Try to come up with an as short as possible answer for 'any' programming language.
• Standard rules apply for your answer with default I/O rules, so you are allowed to use STDIN/STDOUT, functions/method with the proper parameters and return-type, full programs. Your call.
• Default Loopholes are forbidden.

## Test cases

Regular bowls:

Inputs Outputs
#      # #    #   ####  
#~~~~~~# #~~~~#   ####  
!!!        !!   !!      !      !!!   !         !!!! 
!!!~~~~~~~~!!   !!~~~~~~!      !!!~~~!         !!!! 
4   44 4 444444
4~~~44~4~444444
  B B   B   B B     BB     B B   B   BBB  
  B~B   B~~~B B~~~~~BB~~~~~B B~~~B   BBB  
sss  sss  s  s     s s  ssssssssss
sss~~sss  s~~s     s~s  ssssssssss

Leaky bowls:

Inputs Outputs
00     00 00   00   000 0  
00     00 00   00 ~~000~0~~     ~        ~        ~   
^^^        ^   ^^      ^     ^^^   ^        ^ ^ 
^^^        ^   ^^      ^     ^^^   ^~~~~~~~~^~^~         ~           ~           ~           ~           ~           ~           ~  
@@@ @@@  @ @  
@@@ @@@~~@~@~~
#   ## # #### #
#   ## # ####~#   ~    ~    ~    ~ 
xx xxx   xx   x xx  
xx xxx   xx   x~xx~~   ~    ~    ~    ~ 
2 22 2
2 22~2
• I'm not totally clear what shapes a bowl might have. For instance, might it have multiple columns going up to the top row so it has multiple compartments? What about protrusions coming out of the sides?
– xnor
Jan 12 at 14:53
• @xnor No to the first question (combination of fifth + seventh rule prevent this). And I'll clarify the second question. Jan 12 at 15:01
• Thanks for the additional info. I'm not really sure what counts as a stalagmites/stalactite from the examples. Is the key thing that they have a mini-cup that could hold water right side up or upside down? What if the protrusion is just a diagonal line? What if it's on the inside of the bowl? I think these types of thing can matter for answers and you're going to need to come up with a comprehensive definition for valid bowls, tricky as that may be. Or, restrict the possibilities.
– xnor
Jan 12 at 15:39
• For the fourth leaky bowl test case, if there was a space below the bowl section in the middle of the bowl, does that count as leaky? Jan 19 at 21:32
• @jeptguy Oh, that's a good one. I assume you mean like this pastebin? I will add a rule to prevent test cases like that. Especially since all three existing answers have a different result for it.. Jan 19 at 21:45

# APL(Dyalog Unicode), 145 bytes SBCS

A dfn taking a character matrix.

{h w←⍴m←' '=⍵
i←{∪⍵,(⌷∘(0@h⊢m)¨n)/n←⍵~⍨,(⌽q)(-q)q∘.+⍵}⍣≡⊂q+⊃⍸m⍷⍨q←0 1
b←h⌷m
×x←⊃⍸b∧i∊⍨(h-1),¨⍳w:'~'@((h,¨⍸b),x,⍨¨h+⍳d)⊢⍵↑⍨h+d←0⌈(≢i)-+/b
'~'@i⊢⍵}


Try it on APLgolf!

Ungolfed:

Fill ← {
Height Width ← ⍴ Mask ← ' '=⍵
⍝ The initial interior piece is identified by the first occurence of <wall><no wall>
⍝ Iteratively expand the interior to the right, left and bottom
Deltas  ← (1 0)(0 ¯1)(0 1)
Volume  ← ≢ Interior ← {∪⍵,(⌷∘Fixed¨n)/n←⍵~⍨,Deltas∘.+⍵}⍣≡ Opening
⍝ There is an hole when air on the bottom is below an interior point
Hole    ← ⊃⍸Bottom∧Interior∊⍨(Height-1),¨⍳Width
⍝ If there is no hole, fill the interior
0=Hole: '~'@Interior⊢⍵
⍝ Otherwise expand the matrix downwards and fill the column below the hole
⍝ and the spaces on the last row
Drip    ← 0⌈Volume-+/Bottom
'~'@((Height,¨⍸Bottom),Hole,⍨¨Height + ⍳Drip)⊢⍵↑⍨Height+Drip
}


Read it on APLgolf! Apparently this doesn't run with the comments on there, so if you want to have to run this version, you'll have to remove the comments.

# Charcoal, 76 bytes

ＷＳ⊞υι≔Ｅυ⭆ι⎇∧∧⁻…ιμ ⁻✂ιμＬι¹ ⁼ λ~λη¿⁼§η±¹⊟υη«υ≔⊟ηζ⟦⭆ζ⎇⁼ ι~ι⟧Ｅ⁻№⪫ηω~№ＫＡ~⭆ζ§ ~⁼~λ


Try it online! Link is to verbose version of code. Takes input as a rectangular list of newline-terminated strings. Explanation:

ＷＳ⊞υι≔


Input the bowl.

Ｅυ⭆ι⎇∧∧⁻…ιμ ⁻✂ιμＬι¹ ⁼ λ~λη


Try to fill the bowl with water.

¿⁼§η±¹⊟υη«


If the bowl isn't leaky, then output the filled bowl. Otherwise:

υ


Output all but the last row of the bowl.

≔⊟ηζ


Get the last row of the leaky bowl.

⟦⭆ζ⎇⁼ ι~ι⟧


Flood it and output the result.

Ｅ⁻№⪫ηω~№ＫＡ~⭆ζ§ ~⁼~λ


Count how much extra water there is and output just the hole of the last row of the leaky bowl for each extra line needed.

# Python 3.8 (pre-release), 256 251 223 bytes

lambda s:[[*map(F,s)],s+[(b:=s.pop()).replace(E,W)]+[E*(h:=F(''.join(map(max,zip(b,s[-1])))).find(W))+W]*(sum(F(x).count(W)for x in s)-b.count(E))][h>-1]
E,W=' ~'
F=lambda s:E*(len(s)-len(s.lstrip()))+s.strip().replace(E,W)


Try it online!

A function taking in a list of strings representing an empty bowl, and output a list of strings representing the filled bowl. The input must be padded with whitespaces so that each line is the same length.

# Explanation

F is a helper functions for filling a line with water between the walls. This is done by stripping all leading and trailing whitespaces, replace the remaining whitespaces with water, then add the leading whitespaces back.

f is the main function, and has the structure: [answer_if_not_leak, answer_if_leak][has_leak]

• h:=F(''.join(map(max,zip(b,s[-1])))).find(W) is the the index of the hole. This is done by first merging the walls of the bottom 2 lines (b and s[-1]), then fill the merged line with water. The only position that has water must be the hole:
L1     |  xxx        xx  |
L2     |    xxxx xxxx    |
Merged |  xxxxxx xxxxxx  |
Filled |  xxxxxx~xxxxxxx |

• If the bowl doesn't leak, there's no hole. So h>-1 indicates that there's a leak.
• If no leak, simply applies F to each line
• If there is a leak, we returns a list consists of:
• Every line except the bottom: s (note that we used b:=s.pop() so the bottom is already removed)
• The bottom, with all whitespaces replaced with water: [(b:=s.pop()).replace(E,W)]
• The rows representing leaking water stream: [E*h+W]*number_of_water_in_the_stream

# Python3, 522 bytes:

from re import*
r=range
g=lambda x:[i for i in r(len(x))if i and i<len(x)-1 and findall('^(?:[^\s]|\s)\s+$',''.join(x[i]))and any(d!=' ' for d in x[i-1][1:])and any(d!=' ' for d in x[i+1][1:])] f=lambda x:'\n'.join(sub('(?<=[^\s])\s+(?=[^\s])',lambda x:'~'*len(x.group()),l)for l in k)if not(j:=g([*zip(*(k:=x.split('\n')))]))else'\n'.join(k[:-1]+[sub(' ','~',k[-1])]+[' '*j[0]+'~'+' '*(len(k[-1])-j[0]+1) for _ in r(sum(len(findall('(?<=[^\s])\s+(?=[^\s])',n)[0])for n in k[:-1])-k[-1].count(' ')+(k[-1].count(' ')==1))])  Try it online! • 499 bytes by removing some spaces and creating a variable for the duplicated '(?<=[^\s])\s+(?=[^\s])' Jan 12 at 20:57 • The +(k[-1].count(' ')==1) can be removed. I made an error in that last leaky test case. Jan 13 at 11:46 # J,(Version 903) 270 bytes fb=:{{((y~:' ')+.(*./\"1 &.|."1 y=' ')+.(*./\"1 y=' '))}'~',:y}} l=:{{'~' e. {."1 (]#~"1' '&~:) |."1|: fb y}} flb=:{{ lr=.{:I.+./"1 '~'= fy=.fb y lb=.(lr{(' '=y)} y,:'~') lr} y r=.('~'=lr{fy){' ~' c=.-/+/"1^:2 '~'=fy ,: lb lb, ((c,#r)$r)
}}
cgb=:{{>(l y){(fb y);flb y}}



NB. FillBowl
fb=:{{((y~:' ')+.(*./\"1 &.|."1 y=' ')+.(*./\"1 y=' '))}'~',:y}}

NB. LeakyBowl?
l=:{{'~' e. {."1 (]#~"1' '&~:) |."1|: fb y}}

NB. Fill Leaky Bowl
flb=:{{
lr=.{:I.+./"1 '~'= fy=.fb y NB. leak row
lb=.(lr{(' '=y)} y,:'~') lr} y
r=.('~'=lr{fy){' ~' NB. dripping row
c=.-/+/"1^:2 '~'=fy ,: lb NB. add c dripping rows
lb, ((c,#r)$r) }} cgb=:{{>(l y){(fb y);flb y}} NB. Code Golf Bowl solution  # J,(Version 806) 320 bytes tio.run currently only has an older version of j without the direct definition syntax: fb=:3 : 0 ((y~:' ')+.(*./\"1 &.|."1 y=' ')+.(*./\"1 y=' '))}'~',:y ) l=:3 : 0 '~' e. {."1 (]#~"1' '&~:) |."1|: fb y ) flb=:3 : 0 lr=.{:I.+./"1 '~'= fy=.fb y lb=.(lr{(' '=y)} y,:'~') lr} y r=.('~'=lr{fy){' ~' c=.-/+/"1^:2 '~'=fy ,: lb lb, ((c,#r)$r)
)
cgb=:3 : 0
>(l y){(fb y);flb y
)

echo b1 ; cgb b1
echo lb1 ; cgb lb1


Try it online!

## Explanation

I'm still relatively new to J and this solution could probably be made shorter again by someone with more experience golfing in J.

We define three helper verbs (J terminology for functions) fb "FillBowl", l "IsBowlLeaky?" and flb "FillLeakyBowl" and then finally a main verb cgb "CodeGolfBowl" to tie them all together.

All three verbs take a rectangular 2 dimensional array of characters as argument y.

### fb "FillBowl"

fb=:{{((y~:' ')+.(*./\"1 &.|."1 y=' ')+.(*./\"1 y=' '))}'~',:y}}


fb creates a boolean mask of not-spaces and spaces from the sides and then uses the mask to index into either water ('~' array) or the original argument. Eg:

   y
#      #
#    #
####
((y~:' ')+.(*./\"1 &.|."1 y=' ')+.(*./\"1 y=' '))
1 0 0 0 0 0 0 1
1 1 0 0 0 0 1 1
1 1 1 1 1 1 1 1



### l "IsBowlLeaky?"

l=:{{'~' e. {."1 (]#~"1' '&~:) |."1|: fb y}}


l fills the bowl ignoring whether it leaks or not and then checks if the bottom non-whitespace row contains any water ('~' chars). If it does then the bowl is leaky. To check for the leak we transpose the array to the right, delete leading spaces and then check if any water '~' is in the first column. Eg:

   y
00     00
00   00
000 0
fb y
00~~~~~00
00~~~00
000~0
(]#~"1 ' '&~:) |."1 |: fb y
0
00
00~
0~~
0~~
~~~
00~
00
0



### flb "FillLeakyBowl"

flb=:{{
lr=.{:I.+./"1 '~'= fy=.fb y
lb=.(lr{(' '=y)} y,:'~') lr} y
r=.('~'=lr{fy){' ~'
c=.-/+/"1^:2 '~'=fy ,: lb
lb, ((c,#r)$r) }}  flb proceeds with the following logic: • Find the leaky row index lr • Create the leakybowl array lb with the leaky row flooded • Create the dripping row r with the drip in the correct location • Count c how many water drops are missing • Append c dripping rows r to the leakybowl array lb ### cgb "CodeGolfBowl" cgb=:{{>(l y){(fb y);flb y}}  Finally our cgb verb combines everything together with the following logic: • Create a boxed array of both the filledbowl and filledleakybowl solutions (fb y);flb y • Check if the bowl is leaky (l y) and use the boolean 0 or 1 as the index into the previous boxed array Eg:  y 00 00 00 00 000 0 (fb y);flb y ┌─────────┬─────────┐ │00~~~~~00│00 00│ │ 00~~~00 │ 00 00 │ │ 000~0 │~~000~0~~│ │ │ ~ │ │ │ ~ │ │ │ ~ │ │ │ ~ │ └─────────┴─────────┘  For more information on this fascinating and fun language the J wiki has a huge amount of information and also links to quite a few free books: • Hi, welcome to CGCC. Would you mind adding a link to an online compiler if possible (e.g. tio.run) with test code as verification? We also have a Tips for golfing in J which might be useful. Jan 29 at 14:06 # Python 3, 387 365 bytes from re import* def l(b): s=b[-1];r=search;l=len;m=r("^( *[^ ]+)(.*?)([^ ]+ *$)",b[-2])
if r("^.{"+str(l(m.group(1)))+"}[^ ]{"+str(l(m.group(2)))+"}",s):b=[r("^ *(?=[^ ])",x).group(0)+x.strip().replace(" ","~")for x in b]
else:b[-1]=s.replace(" ","~");b+=[" "*l(r("^( *[^ ]+)",s).group(0))+"~"]*(sum([x.strip().count(" ")for x in b[:-1]])-s.count(" "))
return b


Try it online!

Takes input as a list of strings and returns a string representation of the bowl.

The first line sets up variables and then checks for bowl leaky-ness. The second line deals with non-leaky bowls, replacing spaces from the whitespace trimmed version of each line with ~. The third line deals with leaky bowls, first filling the bottom row with water, then adding extra lines on the bottom as many times as is necessary.

Just out of interest, this answer, and I would guess most, are able to use bowls made of ~ characters, it is just confusing to look at.

• 365 bytes - the four indentation spaces can be a single space; the if m can be done directly; and you can just return the list of lines b, and move the "\n".join(...) to the footer. :) Jan 21 at 7:33
• Nice catches. I do count the indents as a tab character instead, I just leave it like this for readability generally. Jan 21 at 16:01

# Python 3.8 (pre-release), 315308 303 bytes

e=enumerate
def f(b):
for y,l in e(b):
s=0
for x,c in e(l):
if c==" ":
if s and(L:=b[y])[x:].strip():
if y<len(b)-1:b[y]=L[:x]+"~"+L[x+1:]
else:return[n.replace("~"," ")for n in b[:y]]+[l.replace(" ","~")]+[" "*x+"~"]*max(0,"".join(b).count("~")-l.count(" "))
else:s=1
return b


Try it online!

Takes a list of strings (each representing a line) and outputs a modified version of that list. Input must be padded to a rectangle.

Not the smallest Python solution here, but I wanted to try a more standard iterative approach. If anyone has ideas on how to make it smaller it'd be much appreciated!

# JavaScript (Node.js), 314 bytes

(r,l=r.pop(),w=t=>t.replace(/ /g,'~'),m=r.reduce((a,c)=>a+(s=t=>t.split .length-1)(c.trim()),0))=>((q=l.trim()).search >0|q.length<s([...r].pop().trim())?[r,w(l),[...Array((m-=s(l))<0?0:m)].map(_=>'~'.padStart(l.search\\S +2))]:[r.map(v=>v.trimEnd().replace(/( *\S)( +)/g,(_,x,y)=>x+w(y))),l]).flat().join



Try it online!

Expects input as an array of strings, each representing a line. Outputs a string with the bowl.