# Hotel room numbering

Given an input of a "hotel" in ASCII art with a single room marked, output the room number according to certain rules.

Here's an example of an ASCII hotel:

               ##
##
#####          ##
#####  ######  ##
#####  ######  ##
#####  ######  ##


Here are a few more things about the ASCII hotels:

• Each "building" is represented by a rectangle of # characters, where each # represents a "room".

• The above hotel consists of three buildings. Each building is separated by two columns of spaces, and the lowest "floor" will always be on the last line.

• Each building will always have anywhere from 1-9 "floors" (rows) and 1-9 "rooms" on each floor. There will also always be 1-9 buildings.

• Rooms are numbered as follows: [building #][floor #][room on floor #]. For example, let's mark a few rooms in the above drawing:

               ##
##
#####          ##
#####  ####$# ## ##%## ###### ## ##### ###### #@  The room marked with the % is room 123 (building 1, floor 2, 3rd room on floor). Similarly, the room marked with the $ is room 235, and the @ is room 312.

• Buildings, floors, and "nth room on floor"s are always 1-indexed.

The input will consist of an ASCII hotel with a single room replaced with an asterisk (*). This is the room for which you must output the room number. The input must be taken as a single string, but you may use commas as line separators instead of newlines (in case your language of choice cannot take multiline input or if it's shorter to take single-line input). You may optionally require a trailing comma/newline. You may also pad lines with trailing spaces to make the input a complete rectangle.

Since this is , the shortest code in bytes wins.

Test cases (contained within a single code block to conserve vertical space):

Input:
*

Output: 111

Input:
#  #  *  #  #

Output: 311

Input:
#####
#####
####*
#####
#####

Output: 135

Input:
#####
#####           ######
#####           ######
#  #  #  #####  #  #  #  ######  *

Output: 911

Input:
#
#  #
#  #  ##
#  #  ##  ###
#  #  ##  ###  #####
#  #  ##  ###  ##*##  ########
#  #  ##  ###  #####  ########

Output: 523

Input:
#
*
#
#
#
#
#
#
#########  #  #

Output: 281

Input:
########*
#########
#########
#########
#########
#########
#########
#########
#  #  #  #  #  #  #  #  #########

Output: 999

• Will there ever be a completely empty input – Downgoat Jan 3 '16 at 5:16
• @Doᴡɴɢᴏᴀᴛ There will always be an asterisk somewhere, so no. – Doorknob Jan 3 '16 at 5:17
• – agtoever Jan 3 '16 at 6:53
• Seriously: it might be usefull to add that you buildings are always rectangular in shape (right?) – agtoever Jan 3 '16 at 6:57
• @agtoever No, it must be a single string, as stated in the question. – Doorknob Jan 3 '16 at 17:11

## CJam, 34 31 bytes

qN/W%zSf-La%{_{s'*&}#_)@@=}3*;\


This requires the input to be padded to a rectangle with spaces.

Try it online! Alternatively, run all test cases.

### Explanation

qN/      e# Read input and split into lines.
W%z      e# Rotate 90° counter-clockwise.
Sf-      e# Remove all spaces from the rows.
La%      e# Split into buildings. We've now got a 3D array of rooms, where the first
e# dimension is the building, the second the room number and the third is the
e# the floor number.
{        e# Run this block three times. At each stage it will find the index of the "*"
e# along the current dimension and leave the element at that index on the stack
e# for the next round...
_      e#   Duplicate the current array.
{      e#   Find the index of the first element where this block yields something
e#   truthy...
s    e#     Flatten into a single string.
'*&  e#     Set intersection with "*".
}#
_)     e#   Duplicate the index and increment it, because the results should be 1-based.
@@=    e#   Pull up the array and the other copy of the index and select the
e#   corresponding element.
}3*
;\       e# We've now got the building, room and floor index on the stack, as well as the
e# "*" character itself. We discard the character and swap the room and the floor
e# floor number. When the three indices are printed back-to-back at the end of
e# the program, that will yield the desired result.


# Pyth, 34 bytes

LxKh/#\*b\*jkhM[//<hJ_.zyJd2xJKycK


Demonstration

This uses a golfing trick I've never used before: Assigning to a variable (K) inside a function (y) to save a partial result from that function.

### Explanation:

LxKh/#\*b\*jkhM[//<hJ_.zyJd2xJKycK
L                                     Define y(b):      (b is a list of strigs)
/#\*b                             Filter b for strings containing '*'
h                                  Take the first such string
K                                   Store it in K
x       \*                           And return the index of '*' in that string.
.z              Take the input as a list of strings
_                Reverse it (bottom to top)
J                 Store in J
h                  Take the bottommost row
yJ            Find y(J). This is the index in whichever
row of J has the * of the *. Also store
that row in K.
<                   Slice J up to that index.
/        d           Count the number of spaces
/          2          Divide by 2. This is the building number.
xJK       Take the index in J of K. This is the floor.
cK    Chop K on whitespace.
y      Find the index in whatever element of K has
the * of the *. This is the room number.
This also overwrites K, but we don't care.
[                      Gather the above into a list.
hM                       Convert 0-indexing to 1-indexing.
jk                         Concatenate. Print implicitly.


# JavaScript (ES6), 142 136 bytes

h=>h.split
.reverse(r=0).map((t,i,l)=>r?0:(f=i+1,b=1,l[o=0].slice(0,r=t.indexOf*+1).replace(/  /g,(_,s)=>o=++b&&s+2),r-=o))&&[b]+f+r


6 bytes saved thanks to @nderscore!

## Explanation

h=>
h.split
// get each line of the input string
.reverse(                  // reverse the lines to make getting the ground floor easy
r=0)                     // initialise r to 0
.map((t,i,l)=>             // for each line of the reversed input string
r?0:(                    // if the marked room has not been found yet:
f=i+1,                 // f = floor number
b=1,                   // b = building number, default to 1
l[o=0].slice(0,        // get the substring of 0 to the marked room, default o to 0
r=t.indexOf*+1)    // r = absolute index of room + 1 (or 0 if not found)
.replace(/  /g,(_,s)=> // count the spaces between buildings
o=++b&&s+2),         // increment b, o = index of marked room's building
r-=o                   // make r relative to the room's building
)
)
&&[b]+f+r                  // output the result ([b] casts b to a string)


## Test

var solution = h=>h.split
.reverse(r=0).map((t,i,l)=>r?0:(f=i+1,b=1,l[o=0].slice(0,r=t.indexOf*+1).replace(/  /g,(_,s)=>o=++b&&s+2),r-=o))&&[b]+f+r
<textarea id="input" rows="8" cols="60">#
#  #
#  #  ##
#  #  ##  ###
#  #  ##  ###  #####
#  #  ##  ###  ##*##  ########
#  #  ##  ###  #####  ########</textarea><br />
<button onclick="result.textContent=solution(input.value)">Go</button>
<pre id="result"></pre>

• Some byte savings (-6): .map((t,i,l)=> , slice(0,r=t.indexOf*+1) , o=++b&&s+2 , [b]+f+r – nderscore Jan 3 '16 at 8:40
• @nderscore Thanks, I really like the [b]+f+r tip! – user81655 Jan 3 '16 at 14:49

# awk, 70

!i{i=index($0,"*")}i{$0=substr($0,0,i);f++}END{print NF f length($NF)}


Example:

Input:
#
#  #
#  #  ##
#  #  ##  ###
#  #  ##  ###  #####
#  #  ##  ###  ##*##  ########
#  #  ##  ###  #####  ########

While no * was found, do nothing.
#
#  #
#  #  ##
#  #  ##  ###
#  #  ##  ###  #####
A * is found in column 14. From now on, truncate and increment the floor counter.
#  #  ##  ###  ##*    f=1
#  #  ##  ###  ###    f=2
Awk automatically splits $0 into space separated fields, counted by builtin NR. In the end, NR and f hold hotel and floor number. The room number is the length of the last hotel.  # C, 131130119 113 bytes b,f,i,j=111;main(c){for(;c=~getchar();)c&32?f+=10,b=i=0:++i<j?c%3?f=j,j=i:c&2?b+=50-b%50:++b:0;printf("%d",b+f);}  Takes input on stdin; input must not have a terminating newline. Assumes 2's complement. Ungolfed: // Declare variables (default type is int) and initialize, by default to 0: b, // Building number (multiplied by 100, 0-based) + room number (0-based) f, // Floor (111-based, multiplied by 10) i, // Current column of input character within line (1-based) j = 111; // Column of asterisk character once found (1-based), 111 before then main (c) // Declare main function and variable c to hold input character { for (; // Loop on input c = ~getchar(); // Read a character into c, bitwise inverted to break // EOF (numeric value -1). This means that following // operations (on the ASCII value of the input) are // also inverted. ) c & 32 ? // Newline? f += 10, b = i = 0 : // Increment floor; reset building, room, column ++i < j ? // Increment column; before asterisk, or asterisk not yet found? c % 3 ? // Asterisk character? f = j, j = i : // Reset floor and record column c & 2 ? // Space character? b += 50 - b % 50 : // Increment building and reset room ++b : // Otherwise, # character; increment room 0; // After asterisk; do nothing printf("%d", b + f); // Write out results }  • I'm sure that trick must be used a lot in golfing, but I've never seen a program before which uses stdio, yet breaks if it's included! – Dave Jan 4 '16 at 21:25 • @Dave an empty pair of parens is just a waste of a potential expression, and headers are a luxury :) Also stole one or two of your ideas to golf it down. – ecatmur Jan 4 '16 at 22:19 • Nice. I like the building increment - saves another 3 bytes. I've nicked that idea back and managed to match your new bytecount, but I can't improve on it any further. – Dave Jan 5 '16 at 1:02 # Stackgoat, 73 bytes [non-competing] Stackgoat is a stack-based language that has nothing to do with goats. y'#ZGDYZG'*iVXsV@"\\*"ZGN2/1+y'#ZG' ZG'q:Nq'*i-yXsq'*i@"[#*]+"M0M1-@'*i1+  It's a fairly new language so let me know if they're any problems with it. I gave myself quite a headache figuring this out so this is about as much as I'm golfing this. ### Explanation This program has 3 parts for each 3 digits of the room number y'#ZG // Remove all # from input D // Duplicate YZG // Remove all spaces '*i // Index of * V // Reverse stack Xs // Split on spaces V@ // Unreverse, item at *'s index "\\*"ZG // Remove all *s N // Get length 2/1+ // Divide by 2, add 1 y'#ZG // Remove all # ' ZG // Remove all spaces 'q: // Store in q N // Get length q'*i // *'s index in q - // Subtracted from length yXs // Split on newlines q'*i // Get index of * in q @ // Get indexed-th line "[#*]+"M // Match all buildings 0M // Get *'s building no. 1- // Subtract one @ // nth building at right line '*i // *'s index 1+ // Added to one  • Relevant – Alex A. Jan 3 '16 at 6:40 • This appears to use two new features implemented after the challenge was posted, so the answer is noncompeting. – Doorknob Jan 3 '16 at 6:43 • Out of curiosity, @Doorknob -- where is that rule defined? I tried searching and looking in all the logical places, but I couldn't find anything on it... – Alex Jan 8 '16 at 12:41 • – Martin Ender Jan 8 '16 at 12:52 # Ruby, 103 ->n{r=x=b=0 n.lines{|s|(t=s=~/\*/)&&(x=t;r=($.reverse+' ')=~/ /)
r+=10;b=s[0..x].count" "}
b*50+r+101}


Ungolfed in test program

g=->n{
r=x=b=0
n.lines{|s|                  #for each line in n
(t=s=~/\*/)&&              #if the line contains an asterisk
(x=t                     #record its position in x. $ is a special variable containing the part of the string to the left of the last match made. r=($.reverse+' ')=~/ /) #reverse $ and search for the index of the first space to find room number (before the search a space is appended in case it is 1st building.) r+=10 #increment r by 10 for the floor number (obviously this will have been reset to the row ith the asterisk by the previous line) b=s[0..x].count" "} #count the number of spaces left of x in the current row to find building number (loop will exit with calc from bottom row, which is the correct one.) b*50+r+101} #multiply number of spaces by 50 to get 1st digit, add r for 2nd and 3rd digit. Then add 101 to correct 1st and 3rd digits from 0-indexed to 1-indexed. puts g[" *"] puts g[" # # * # #"] puts g[" ##### ##### ####* ##### #####"] puts g[" ##### ##### ###### ##### ###### # # # ##### # # # ###### *"] puts g[" # # # # # ## # # ## ### # # ## ### ##### # # ## ### ##*## ######## # # ## ### ##### ########"] puts g[" # * # # # # # # ######### # #"] puts g[" ########* ######### ######### ######### ######### ######### ######### ######### # # # # # # # # #########"]  • I really like the clever use of $ in this one. – Doorknob Jan 3 '16 at 19:57

# JavaScript (ES6), 121

x=>x.split
.reverse().map((r,f,h,a=r.indexOf*)=>x=~a?(h=h[0].slice(0,a).split  ).length+[f+1]+-~h.pop().length:x)|x


Less golfed and explained

H=x=>x.split\n // split in lines
.reverse() // reverse, so we can scan bottom up
.map( (r,f,h) => // exectute for each line
// r is the current row
// f in the row index, so that f+1 is the floor number
// h is the reversed array, h[0] is the bottom floor
~(a=r.indexOf*) // a is the position of '*' in the line, if found - else 0
&& ( // if a >= 0
h = h[0]        // bottom floor line
.slice(0,a) // ... truncated at position of '*'
.split  , // ... and splitted at '  ', as an array
x = h.length  // the array len is the building number
+ [f+1]   // floor number, using [] to force string concatenation
+ -~ h.pop().length // the length of the last array element is the number
// of chars in the block before '*'
// increment by 1 to get the room number
)
)
&& x // return the found value


TEST

H=x=>x.split
.reverse().map((r,f,h,a=r.indexOf*)=>x=~a?(h=h[0].slice(0,a).split  ).length+[f+1]+-~h.pop().length:x)|x

// test
console.log=x=>O.textContent+=x+'\n';

;[['*',111],['#  #  *  #  #',311],
[#####
#####
####*
#####
#####,135],
[         #####
#####           ######
#####           ######
#  #  #  #####  #  #  #  ######  *,911],
[#
#  #
#  #  ##
#  #  ##  ###
#  #  ##  ###  #####
#  #  ##  ###  ##*##  ########
#  #  ##  ###  #####  ########,523],
[           #
*
#
#
#
#
#
#
#########  #  #,281],
[                        ########*
#########
#########
#########
#########
#########
#########
#########
#  #  #  #  #  #  #  #  #########,999]]
.forEach(t=>{
var i=t[0],k=t[1],r=H(i)
console.log(i+'\n' + (k!=r?'Error '+r+' expected '+k:'Ok '+r)+'\n')
})
<pre id=O></pre>

## Python 2.7, 153 168 characters

I liked this challenge! If a Python list is ok as input (see testsuite for examples), this solution works.

Edit 2016-01-05: added one line (10 characters) to split the string in multiline.

Short explanation:

• t is the row in which the room is located (counted from the top-row as array index = 0);
• i is the index of the room in its row;
• the building is calculated as the number of double white spaces in the bottom row until i;
• floor is the number of row minus t;
• room is the index of the first double whitespace in the reversed string from i until the beginning of the floor with the room, appended by a whitespace to cover for the case that the room is in the first building.

Code:

def r(l):
l=h.split(",")
a,w,s="*","  ",str
t=l.index(filter(lambda c:a in c,l)[0])
i=l[t].find(a)
return s(l[-1][:i].count(w)+1)+s(len(l)-t)+s((l[t][i::-1]+w).find(w))


Testsuite:

cases = [
(["*"], 111),
(["#  #  *  #  #"], 311),
(["#####","#####","####*","#####","#####"], 135),
(["         #####","         #####           ######","         #####           ######","#  #  #  #####  #  #  #  ######  *"], 911),
(["#","#  #","#  #  ##","#  #  ##  ###","#  #  ##  ###  #####","#  #  ##  ###  ##*##  ########","#  #  ##  ###  #####  ########"], 523),
(["           #","           *","           #","           #","           #","           #","           #","           #","#########  #  #"], 281),
(["                        ########*","                        #########","                        #########","                        #########","                        #########","                        #########","                        #########","                        #########","#  #  #  #  #  #  #  #  #########"], 999)
]

for idx,(hotel,roomnr) in enumerate(cases):
output=r(hotel)
if str(output)==str(roomnr):
result="SUCCESS"
else:
result="FAILURE!!!"
print "Case {} gives output: {}. Correct output is: {}. Result: {}".format(idx,output,roomnr,result)


# Snails, 32 bytes

A
\*{l\#,|r,9d.,{|=~r,9{|l.,\ \#

• Never seen this one before; impressive. Alas, 1 byte longer than CJam... – Doorknob Jan 9 '16 at 1:35
• – Martin Ender Jan 14 '16 at 9:31

# C, 142138 137 bytes

#include <stdio.h>
f,b,x,p=110;main(c){while(~(c=getchar()))c<11?f+=c,b=x=0:x++<p?++b,c&2?c&8?f=p,p=x:0:(b+=50-b%50):0;printf("%d",b+f);}


(123 119 118 bytes + 19 for #include line)

I stole the value merging idea from ecatmur, but I've merged them in quite a different way (saves 8 bytes in the end). This also makes the same assumption that EOF == -1.

The input is taken from stdin, and must not have whitespace or newlines after the last building on the last line, so an example input would be:

printf "##\n##       #\n##  ##*  #\n##  ###  #" | ./hotel
# or for better visualisation:
printf "##\n##       #\n##  ##*  #\n##  ###  #" | tee /dev/fd/2 | ./hotel;echo ""


Breakdown:

// Globals initialise to 0
f,     // floor number * 10 + shift
b,     // building number * 100 + room number
x,     // current column
p=110; // will store column of * (must start >= 11*9-2, and 110 will be used later)
main(c){
while(~(c=getchar()))              // For each character until EOF
c<11                           //  Is \n? (10)
?f+=c,                     //   Add 10 to floor number
b=x=0                     //   Reset building, room, column
:x++<p                     //  Else, is column <= *?
?++b,                //   Add to room number
c&2                 //   Is # or *?
?c&8             //   If *:
?f=p,p=x:0   //    Set floor to 110, set p to column
:(b+=50-b%50)    //   If ' ': go to next building
:0;
printf("%d",b+f);                  // Result is building+room+floor+shift
}

• Great idea to reuse the initial column constant, and to use the character values as constants. – ecatmur Jan 4 '16 at 22:34
• You may be able to save a byte by changing &&(f=p,p=x) to ?f=p,p=x:0 - the second operand of a ternary conditional has implicit parentheses. – ecatmur Jan 5 '16 at 1:16
• @ecatmur good point; thanks! – Dave Jan 5 '16 at 1:29

l=length
f h|b<-snd$break(elem '*')$lines h,q<-fst(span(<'*')$b!!0)++"*"=l(last$words$q)+10*l b+l(words$take(l q)$last b)*100  Usage example: f "# # * # #" -> 311. How it works: b<-snd$break(elem '*')$lines h -- split the input into a list of lines -- and assign b to the lines starting with -- the one that includes * up to the end, -- i.e. drop leading lines without the * q<-fst(span(<'*')$b!!0)++"*"        -- assign q to the line with the *, but strip
-- off all chars after the *

l(last$words$q)                     -- the room on floor number is the length of
-- the last word of q
10*l b                              -- the floor number is 10 times the length of b
l(words$take(l q)$last b)*100       -- the hotel number is 100 times the number of
-- words in the last line cut down to the
-- length of q

-- add for final room number


## Lua, 165 Bytes

l={}i=1while(l[i-1]~="")do l[i]=io.read()o=l[i]:find"%*"x=o or x y=o and i or y i=i+1 end print(#l[i-2]:sub(1,x):gsub("%S+%s*","#")*100+(i-y-1)*10+#l[y]:match"#-%*")


Ungolfed

l={}
i=1
while(l[i-1]~="")do
o=l[i]:find"%*" --find "*", and record:
x=o or x        --position and
y=o and i or y  --current floor
i=i+1
end
print(#l[i-2]:sub(1,x):gsub("%S+%s*","#")*100 --[[Take last string of list, and then
take the substring up until the
asterisk. Substitute any substrings
that include nonspace characters
(%S+) followed by a minimum of 0 space
characters (%s*) with one character
(in this code snippet I chose # for no
particular reason.) Then take the length
of this string, with the # operator.
The %S+%s* regex and gsub do the bulk
of the magic.
]]
+(i-y-1)*10                             --[[Total number of lines minus '*' floor
minus one.
]]
+#l[y]:match"#-%*")                     --[[Find the substring on the asterisk floor
with '#' symbols preceding an asterisk.
]]


## CoffeeScript, 110 bytes & JavaScript, 121 bytes

(s)->s.split('\n').reverse().map((f,g)->f.split('  ').map((h,i)->r=h.indexOf('*');s=''+i+g+r if r>-1));111+1*s


(s)->
s.split '\n'
.reverse()
.map (f,fi)->
f.split('  ')
.map (h,hi)->
ri = h.indexOf('*')
s = ''+hi+fi+ri if ri>-1
111+1*s


Basically same thing in Javascript:

(s)=>{s.split('\n').reverse().map((f,g)=>f.split('  ').map((h,i)=>{r=h.indexOf('*');r>-1?s=''+i+g+r:''}));return 111+1*s}


## Java, 231 Bytes

String a(String a){String[]b=a.split("\n");int i=0,c=b.length,m;String x,k=" ";for(;i<c;i++){x=b[c-1].substring(0,b[i].indexOf("*")+1);m=x.length();a=m>0?""+(m-x.replace(k+k,k).length()+1)+(c-i)+(m-x.lastIndexOf(k)-1):a;}return a;}


De-Golfed

 String a(String a) {
String[] b = a.split("\n");                                // Split the input into floor lines
int i = 0, c = b.length, m;                                // i=floor line counter c= number of floor lines
String x, k = " ";
for (; i < c; i++) {                                       // Loop through floor lines
x = b[c - 1].substring(0, b[i].indexOf("*") + 1);       // x = part of bottom floor line up to '*' position in current line (Empty string when no '*')
m = x.length();                                         // m = length of floor line part
a = m > 0 ? "" + (m - x.replace(k + k, k).length() + 1) // if m>0 ('*' is on this line) set a=building no+floor no+room no.   building no calculated by replacing double space in x with single space and compare length to x (+1)
+ (c - i)                                         // floor number is total floor lines (c) - floor line loop counter (i)
+ (m - x.lastIndexOf(k) - 1) : a;                 // room number is m ('*' position in x) - position of last space in x (-1)
}
return a;                                                  // return the result at the end.
}


# Powershell, 154 bytes

param($s)filter s{$s|sls $_ -a|% M*|% Le*}(($s-split'
')[-1]|% s*g 0('(?m)^.*\*'|s)|sls '^|  '-a|% M*).Count,('(?ms)(?<=\*.*)$'|s).Count,('#*\*'|s)-join''  Less golfed test script: $f = {

param($s) filter s{$s|sls $_ -AllMatches|% Matches|% Length } # select an array of lengths of all matches of the string$s by pattern $_$hpos='(?m)^.*\*'|s                 # horizontal position of the room in the source string
$basement=($s-split"n")[-1]        # basement floor string

$building=($basement|% substring 0 $hpos|sls '^| ' -AllMatches|% Matches).Count # truncate the basement to the position of the room # and count all double spaces or a 'start of string'$floor=('(?ms)(?<=\*.*)$'|s).Count # count all 'end of line' after the room$room='#*\*'|s                      # count all #, preceding the room, and room itself

$building,$floor,$room-join'' } @( ,(@" * "@, 111) ,(@" # # * # # "@,311) ,(@" ##### ##### ####* ##### ##### "@, 135) ,(@" ##### ##### ###### ##### ###### # # # ##### # # # ###### * "@, 911) ,(@" # # # # # ## # # ## ### # # ## ### ##### # # ## ### ##*## ######## # # ## ### ##### ######## "@, 523) ,(@" # * # # # # # # ######### # # "@, 281) ,(@" ########* ######### ######### ######### ######### ######### ######### ######### # # # # # # # # ######### "@, 999) ) | % {$n,$expected =$_
$result = &$f $n "$($result-eq$expected): $result" }  Output: True: 111 True: 311 True: 135 True: 911 True: 523 True: 281 True: 999  # 05AB1E, 34 bytes |€SζJðмõ¡εεR'*k>]DOZ©k>;ò®«sĀ€ƶ˜à«  Zip with string-lists is currently bugged. €SζJ could have been just ζ in the old Python legacy version of 05AB1E, but for some reason doesn't work in the Elixir rewrite version anymore. Explanation: | # Take the input split by newlines # i.e. " ###\n# ###\n# ### ##\n# ##* ##" # → [" ###","# ###","# ### ##","# ##* ##"] €S # Convert each to a list of characters ζ # Zip, swapping rows and column J # Join them together to a string again # → [" ###"," "," ","####","####","###*"," "," "," ##"," ##"] ðм # Remove all spaces # → ["###","","","####","####","###*","","","##","##"] õ¡ # Split on empty strings # → [["###"],[],["####","####","###*"],[],["##","##"]] ε # Map each building to: ε # Map each column of the building to: R # Reverse the column '*k '# Get the 0-indexed index of "*" > # Increase it by 1 to make it 1-indexed ] # Close both maps # → [[0],[],[0,0,1],[],[0,0]] D # Duplicate the resulting list O # Sum each building # → [0,0,1,0,0] Z # Get the max (without popping) # → 1 © # Store this max in the register (without popping) k> # Get the index (+ 1) of this max in the sum-list # → 3 ; # Halve it # → 1.5 ò # Round it up to the nearest integer (bankers rounding) # → 2 ® # Retrieve the value from the register again « # Merge the two digits together # → 21 s # Swap so the duplicate list is at the top again Ā # Trutify (0 remains 0, every other integer becomes 1) # → [[0],[],[0,0,1],[],[0,0]] € # For each building: ƶ # Multiply the integer with the 1-indexed index # → [[0],[],[0,0,3],[],[0,0]] ˜ # Flatten the list # → [0,0,0,3,0,0] à # Pop the list, and get the max # → 3 « # Merge it with the other two digits (and output implicitly) # → 213  # Dart, 165 bytes F(List s)=>s.indexWhere((a)=>a.contains('*'));f(String s){var b=s.split('\n'),v=F(b),c=b[v].split(' '),w=F(c),x=c[w].indexOf('*')+1;return '${w+1}${b.length-v}$x';}


Try it online!