Volumes of ASCII boxes

40

5

Introduction

In this challenge, you are given as input the ASCII representation of the net (unfolded surface) of a rectangular cuboid (3D box). The format is this:

....+--+.......
....|##|.......
....|##|.......
....|##|.......
+---+--+---+--+
|###|##|###|##|
+---+--+---+--+
....|##|.......
....|##|.......
....|##|.......
....+--+.......

Each face of the cuboid is a rectangle of #s surrounded by +-|-characters. The outside of the net is filled with .s. The net will always have the same orientation: there's a middle face surrounded by its four neighboring faces, and the counterpart of the middle face is at the right border of the input. The input is padded with .s to a rectangular shape and will not contain extra rows or columns of .s.

The task

Your task is to take as input a diagram as above, and compute the volume of the cuboid that it represents, which is just the product of its height, width and depth. You can take the input as a newline-delimited string or an array of strings.

The length of each edge is the distance between the +-characters at its two ends. For example, the horizontal edge +--+ has length 3, and the vertical edge

+
|
|
|
+

has length 4. The minimum length of an edge is 1. The example cuboid above has volume 2*3*4 = 24.

Rules and scoring

You can write a full program or a function, and the lowest byte count wins.

Test cases

.++..
+++++
+++++
.++..
1

...++....
...||....
...||....
+--++--++
+--++--++
...||....
...||....
...++....
3

..+-+....
..|#|....
+-+-+-+-+
|#|#|#|#|
|#|#|#|#|
+-+-+-+-+
..|#|....
..+-+....
12

.+---+.....
++---++---+
||###||###|
||###||###|
||###||###|
++---++---+
.+---+.....
16

....++.....
....||.....
....||.....
....||.....
+---++---++
|###||###||
|###||###||
|###||###||
+---++---++
....||.....
....||.....
....||.....
....++.....
16

...+--+......
...|##|......
...|##|......
+--+--+--+--+
|##|##|##|##|
+--+--+--+--+
...|##|......
...|##|......
...+--+......
18

....+--+.......
....|##|.......
....|##|.......
....|##|.......
+---+--+---+--+
|###|##|###|##|
+---+--+---+--+
....|##|.......
....|##|.......
....|##|.......
....+--+.......
24

....+-----+..........
....|#####|..........
....|#####|..........
....|#####|..........
+---+-----+---+-----+
|###|#####|###|#####|
|###|#####|###|#####|
|###|#####|###|#####|
|###|#####|###|#####|
+---+-----+---+-----+
....|#####|..........
....|#####|..........
....|#####|..........
....+-----+..........
120

Zgarb

Posted 2016-12-12T07:27:59.387

Reputation: 39 083

13I really like this challenge. Because the input has so much redundant structure, there's lots of choices for how to recover the dimensions. – xnor – 2016-12-12T22:14:59.317

Answers

25

Retina, 29 28 bytes

T`.p`xy`\G\..+¶
xy

¶\|
$`
y

Try it online!

There's a lot of ways to approach this in Retina, depending on which area you want to multiply with which side, so I'm not sure how optimal this is, but it's actually already a lot shorter than I thought it would be.

I've currently got two other solutions at the same byte count which seem a bit more golfable than the above approach:

\G\..+¶

¶\|
$'¶
G`\.
T`.|+

¶\||\+¶\.\D+
$'¶
G`\.
T`.|+

Although in these I could save a byte each if I assume that the input ends with a trailing linefeed, but I'd rather not have to rely on that.

And another one, still at 28 bytes (this one actually multiplies three sides instead of multiplying one area by a side):

\G\.
x
-(?<=^.+)
$`
¶\|
$`
x

Explanation

The main idea is to multiply the area of the face on top by the length of the vertical side that touches the length border of the input.

I'll use the following input as an example (it has side lengths 2, 3 and 4, so an area of 24):

...+---+.......
...|###|.......
...|###|.......
+--+---+--+---+
|##|###|##|###|
+--+---+--+---+
...|###|.......
...|###|.......
...+---+.......

Stage 1: Transliterate

T`.p`xy`\G\..+¶

The regex \G\..+¶ matches a line which starts with . and is immediately adjacent to the previous line. So this matches all the lines that contain the top face. The stage itself turns . into x and all other characters (any of |+-#) into y. This gives us the following result:

xxxyyyyyxxxxxxx
xxxyyyyyxxxxxxx
xxxyyyyyxxxxxxx
+--+---+--+---+
|##|###|##|###|
+--+---+--+---+
...|###|.......
...|###|.......
...+---+.......

This has one more column of y than we need to represent the area of the top face. We fix this with the next stage.

Stage 2: Replace

xy

So we match a y that is preceded by an x (which is exactly one of them per line) and remove them both from the string. We get this:

xxyyyyxxxxxxx
xxyyyyxxxxxxx
xxyyyyxxxxxxx
+--+---+--+---+
|##|###|##|###|
+--+---+--+---+
...|###|.......
...|###|.......
...+---+.......

So now we've got the area of the top face represented by the number of ys.

Stage 3: Replace

¶\|
$`

Our goal here is to multiply this area A by the missing side length, which is the number of | at the beginning of a line plus 1. However, it's actually easier to multiply by a number n+1 because we've already got one copy of A in the string. If we replace n things with A, we end up with n+1 copies of A. This makes things a lot easier for us.

So we simply replace any | immediately after a linefeed with everything in front of the match. This mangles the string quite a lot and makes it a fair bit larger than we need, but the number of ys ends up being the result we're looking for:

xxyyyyxxxxxxx
xxyyyyxxxxxxx
xxyyyyxxxxxxx
+--+---+--+---+xxyyyyxxxxxxx
xxyyyyxxxxxxx
xxyyyyxxxxxxx
+--+---+--+---+##|###|##|###|
+--+---+--+---+
...|###|.......
...|###|.......
...+---+.......

Stage 4: Match

y

All that's left is to count the number of ys, which is printed as a decimal number at the end.

Martin Ender

Posted 2016-12-12T07:27:59.387

Reputation: 184 808

15

Python 2, 57 bytes

lambda l:l[0].find('+')*~l[0].count('-')*~`l`.count("'|")

A function that takes in a list of strings.

Determines the 3 dimensions separately:

l[0].find('+')
The index of the first + in the first row.

-~l[0].count('-')
The number of - signs in the first row.

~`l`.count("'|")
The number of rows starting with | symbol, via the string representation of the list having a quote symbol before it.


62 bytes:

def f(l):a=l[0].find('+');print(len(l[0])/2-a)*(len(l)-a+~a)*a

A function that takes in a list of strings and prints the result.

Finds one dimension a as the index of + in the first row. The other two dimensions are inferred from it and the width and height of the input rectangle.

A 63-byte alternative, finding the dimensions separately:

lambda l:l[0].find('+')*~l[0].count('-')*~zip(*l)[0].count('|')

xnor

Posted 2016-12-12T07:27:59.387

Reputation: 115 687

11

Bash + coreutils, 83, 77 bytes

EDITS:

  • Saved 6 bytes, by using "Here String" and optimizing regexp a bit

Golfed

bc<<<`sed -rn '1{s/(.+)[^\.]*\1/(0\1)*(0/
s/\./+1/gp;a)*(-1
}
/^[+|]/a+1
'`\)

Explained

Transform with sed:

....+--+....... => (0+1+1+1+1)*(0+1+1+1 )*(-2 +1
. =>()
. =>()
. =>()
. =>()
+ => +1
| => +1
+ => +1
. =>()
. =>()
. =>()
. =>()

Get rid of newlines using backticks, append )

=> (0+1+1+1+1)*(0+1+1+1 )*(-2 +1 +1 +1 +1)

Feed resulting expression to bc

=> 24

Test

./box <<EOF
.++..
+++++
+++++
.++..
EOF

1

./box <<EOF
...++....
...||....
...||....
+--++--++
+--++--++
...||....
...||....
...++....
EOF

3

./box <<EOF
....+--+.......
....|##|.......
....|##|.......
....|##|.......
+---+--+---+--+
|###|##|###|##|
+---+--+---+--+
....|##|.......
....|##|.......
....|##|.......
....+--+.......
EOF

24

Try it online ! (uses bash arithmetic expansion, instead of bc, as the latter is not available)

zeppelin

Posted 2016-12-12T07:27:59.387

Reputation: 7 884

10

Snails, 19 bytes

AM
=~d^.+\+.+l.+^.2

Try it online.

The idea is that we start somewhere on the rightmost edge in the net, and then travel to somewhere in the bottommost face. The length of the edge and the area of the face are multiplied by the mechanism of counting all matching paths.

AM   ,, A -> count all matching paths
     ,, M -> first char matched is the one in the current direction
     ,,      from the starting location, rather than directly on it
=~          ,, check that we are on the right edge of the grid
d ^.+ \+    ,, go down, matching one or more non-'.' characters, then a '+'
.+          ,, go down one or more times
l .+        ,, go left one or more times
^. 2        ,, match two further characters which aren't '.' to the left

feersum

Posted 2016-12-12T07:27:59.387

Reputation: 29 566

4

JavaScript (ES6), 67 91

s=>(a=~-s.search`
`/2-(b=s.indexOf`+`))*b*(s.split`
`.length-1-2*b)

Test

F=
s=>(a=~-s.search`
`/2-(b=s.indexOf`+`))*b*(s.split`
`.length-1-2*b)

out=x=>O.textContent+=x+'\n\n'

;`.++..
+++++
+++++
.++..
1

...++....
...||....
...||....
+--++--++
+--++--++
...||....
...||....
...++....
3

..+-+....
..|#|....
+-+-+-+-+
|#|#|#|#|
|#|#|#|#|
+-+-+-+-+
..|#|....
..+-+....
12

.+---+.....
++---++---+
||###||###|
||###||###|
||###||###|
++---++---+
.+---+.....
16

....++.....
....||.....
....||.....
....||.....
+---++---++
|###||###||
|###||###||
|###||###||
+---++---++
....||.....
....||.....
....||.....
....++.....
16

...+--+......
...|##|......
...|##|......
+--+--+--+--+
|##|##|##|##|
+--+--+--+--+
...|##|......
...|##|......
...+--+......
18

....+--+.......
....|##|.......
....|##|.......
....|##|.......
+---+--+---+--+
|###|##|###|##|
+---+--+---+--+
....|##|.......
....|##|.......
....|##|.......
....+--+.......
24

....+-----+..........
....|#####|..........
....|#####|..........
....|#####|..........
+---+-----+---+-----+
|###|#####|###|#####|
|###|#####|###|#####|
|###|#####|###|#####|
|###|#####|###|#####|
+---+-----+---+-----+
....|#####|..........
....|#####|..........
....|#####|..........
....+-----+..........
120`
.split('\n\n').forEach(t=>{
  t=t.split('\n')
  k=+t.pop()
  t=t.join('\n')
  v=F(t)
  out(v+' '+k +' '+(v==k?'OK':'KO')+'\n'+t)
})
<pre id=O></pre>

edc65

Posted 2016-12-12T07:27:59.387

Reputation: 31 086

3

Ruby, 44

Works on a similar principle to other answers: find the first + to find the depth, find the next . after the + to find the width, and count the number of | at end of line and add 1 to find height.

->s{(s=~/\+/)*($'=~/\./)*s.split("|
").size}

ungolfed in test program

f=->s{(s=~/\+/)*    # index of first match of /\+/ in s
($'=~/\./)*         # $' is a special variable, contains string to right of last match. index of /\./ in $' 
s.split("|
").size}            # split the string at |\n to form an array and count the members

puts f[".++..
+++++
+++++
.++.."]

puts f["...++....
...||....
...||....
+--++--++
+--++--++
...||....
...||....
...++...."]

#etc.

Level River St

Posted 2016-12-12T07:27:59.387

Reputation: 22 049

3

05AB1E, 21 bytes

Let W and H be respectively the width and the height of the input - not the box. Then, the box dimensions A, B and C follow these rules:

W = 2(A+C)+1
H = B+2C+1

The following figure shows what A, B and C are, in terms of edge names:

....AAAA.......
....|##|.......
....|##|.......
....|##|.......
B---+--CCCCC--+
B###|##|###|##|
B---+--+---+--+
....|##|.......
....|##|.......
....|##|.......
....+--+.......

Hence the above formulas. This program computes A, deduces the values of B and C and finally computes their product.

S'.ÊO<D¹g<;-(D·|g-()P

S'.Ê                  From each character of the first line, yield 0 if it is '.' or 1 otherwise. The result is stored in an array
    O<D               A = sum(array) - 1
       ¹g<;-(D        C = (W-1)/2 - A
              ·|g-(   B = H-1-2*C
                   )  Yield [A,C,B]
                    P Take the product and implicitly display it

Try it online!

Former version - Different approach - 26 bytes

|vyS'.Ê})¬O<sø¬O<s€O¬Ê1k)P

|                          Take the input as an array of lines (strings)
 vy                        For each line
   S'.Ê                    For each character in the line, yield 0 if it is '.' or 1 otherwise
       }                   End For
        )                  Wrap the results as an array
         ¬O<               A = sum(first_line) - 1
            sø             Transpose the box pattern
              ¬O<          B = sum(first_line) - 1 ; since the pattern is transposed, it corresponds to the first column
                 s€O       Sum every line from the transposed pattern
                    ¬Ê1k   C = index of the first line that has a different sum from the first line
                        )  Yield [A, B, C]
                         P Take the product A*B*C and implicitly display it

Osable

Posted 2016-12-12T07:27:59.387

Reputation: 1 321

2

Haskell, 64 56 bytes

f(x:r)=sum$fst(span(>'+')x)>>[1|'|':_<-"|":r,'-'<-'-':x]

Try it online!

Explanation

The input is expected to be a list of strings for each line, so in f the parameter x is the first line and r a list of the remaining lines.

  1. fst(span(>'+')x) returns the .-prefix of the first line as a string, so length(fst(span(>'+')x)) is the first dimension d1.
  2. A list comprehension can act as filter, e.g. ['-' | '-' <- x] returns a string of all - in the first line, so 1 + length['-' | '-' <- x] yields the second dimension d2.
  3. Analogously the number of | in the first row can be counted, so 1 + length['|' | '|':_ <- r] is the third dimension d3.

The list comprehensions of 2. and 3. can be shortened to 1+sum[1|'-'<-x] and 1+sum[1|'|':_<-r] by building a list of ones for each occurrence of '-' or '|' and then taking the sum. We can further put the outer 1+ into the list comprehension by appending - to x and "|" to r to yield sum[1|'-'<-'-':x] and sum[1|'|':_<-"|":r]. Now we can combine both list comprehensions by putting both predicates in the same comprehension: sum[1|'|':_<-"|":r,'-'<-'-':x] Conveniently this computes exactly the product of the two dimensions because for lists F and G the following list comprehension is the Cartesian product F x G =[(a,b)|a<-F,b<-G].

Finally, instead of multiplying 1. with the combination of 2. and 3. we can make use of the >> operator on lists: F>>G repeats G length F times and concatenates the result. So fst(span(>'+')x)>>[1|'|':_<-"|":r,'-'<-'-':x] repeats the list of d2*d3 ones d1 times, yielding a list of d1*d2*d3 ones which are then summed up to get the volume.

Laikoni

Posted 2016-12-12T07:27:59.387

Reputation: 23 676

You can take the input as a list of strings, removing the need for lines. – Zgarb – 2016-12-13T10:10:13.683

@Zgarb Thanks, this saves some bytes. – Laikoni – 2016-12-13T11:05:57.413

2

Befunge 93, 56 bytes

~2%#^_1+
  @.*+<
`"z"~<|:`~0+
5*\`#^_\1>*\~7
%2~\<\+1_^#

Try it Online!

Explanation:

The volume of the box can be calculated by multiplying the number of .s on the first line before any other characters, by the number of + and -s on the first line - 1, and the number of lines that start with a | + 1.

~2%#^_1+         Uses the ASCII value % 2 of a character to count the .s

%2~\<\+1_^#      IP wraps around to the bottom. Counts the non . chars
                 Works because ("+" % 2) == ("-" % 2) == 1

5*\`#^_\1>*\~7   Multiplies the previous 2 results and cycles through
                 characters until it hits a newline or EOF

`"z"~<|:`~0+     Adds 1 to the 3rd dimension if the following char is a "|"
                 Also checks for EOF; If there is more input, goes back to
                 previous line. Otherwise, goes to the last line

  @.*+<          Adds 1 to the 3rd dimension, multiplies it to the rest,
                 prints the volume, and ends the program

I had to move the IP up lines instead of down in order to use the vertical if in the 3rd line. If the IP was going down lines, the vertical if would force the top of the stack to be 1 when hitting the following horizontal if, sending it in the wrong direction.

MildlyMilquetoast

Posted 2016-12-12T07:27:59.387

Reputation: 2 907

1

Java 8, 185 129 bytes

thanks to Zgarb for -56 bytes

golfed:

int g(String[]i){int h=0;for(String k:i){if(k.charAt(0)=='.')h++;else break;}return((i[0].length()-2*h-1)/2)*(i.length-2*h-1)*h;}

ungolfed:

int g(String[] i) {
    int h = 0;
    for (String k : i) {
        if (k.charAt(0) == '.') h++;
        else break;
    }
    return ((i[0].length()-2*h-1)/2)*(i.length-2*h-1)*h;
}

Explanation

a*b*h = ((length_of_line-2*h-1)/2)*(number_of_lines-2*h-1)*h

where a and b are the dimensions of the base and h is the height. You can find h by counting the first h lines where you begin with a ..

Bobas_Pett

Posted 2016-12-12T07:27:59.387

Reputation: 965

You can take the input as an array or strings, so no need for manually splitting it. – Zgarb – 2016-12-13T10:11:12.957

oops thx, fixing it... – Bobas_Pett – 2016-12-13T10:19:57.097

1

Java, 112 bytes

int v(String[]s){int a=s[0].lastIndexOf('+')-s[0].indexOf('+'),b=s[0].length()/2-a;return a*b*(s.length-2*b-1);}

Expanded:

int v(String[] s)
{
  // length of the edge in the first line
  int a = s[0].lastIndexOf('+') - s[0].indexOf('+');
  // length of the second edge
  // int b = s[0].length() - 2 * a - 1; <-- multiplied by 2
  int b = s[0].length()/2 - a; // <-- hack, length is always odd
  // length of the third edge in ()
  // volume
  return a * b * (s.length - 2 * b - 1);
} // end method v

Andrey

Posted 2016-12-12T07:27:59.387

Reputation: 121

1

Powershell, 68 67 bytes

($c="$args"|% i*f +)*($args[0].Length/2-.5-$c)*($args.Count-1-2*$c)

Note: "$args"|% i*f + is shortcut for "$args".indexOf('+')

Explanation

Good explanation took from the Osable's answer:

Let W and H be respectively the width and the height of the input - not the box. Then, the box dimensions A, B and C follow these rules:

W = 2(A+C)+1
H = B+2C+1

The following figure shows what A, B and C are, in terms of edge names:

CCCCAAAA.......
....|##|.......
....|##|.......
....|##|.......
B---+--+---+--+
B###|##|###|##|
B---+--+---+--+
....|##|.......
....|##|.......
....|##|.......
....+--+.......

And C is position of the first + in the first line of the input.

Test script:

$f = {

($c="$args"|% i*f +)*($args[0].Length/2-.5-$c)*($args.Count-1-2*$c)

}

@(

,(1, ".++..",
     "+++++",
     "+++++",
     ".++..")

,(3,"...++....",
    "...||....",
    "...||....",
    "+--++--++",
    "+--++--++",
    "...||....",
    "...||....",
    "...++....")

,(12,"..+-+....",
     "..|#|....",
     "+-+-+-+-+",
     "|#|#|#|#|",
     "|#|#|#|#|",
     "+-+-+-+-+",
     "..|#|....",
     "..+-+....")

,(16,".+---+.....",
     "++---++---+",
     "||###||###|",
     "||###||###|",
     "||###||###|",
     "++---++---+",
     ".+---+.....")

,(16,"....++.....",
     "....||.....",
     "....||.....",
     "....||.....",
     "+---++---++",
     "|###||###||",
     "|###||###||",
     "|###||###||",
     "+---++---++",
     "....||.....",
     "....||.....",
     "....||.....",
     "....++.....")

,(18,"...+--+......",
     "...|##|......",
     "...|##|......",
     "+--+--+--+--+",
     "|##|##|##|##|",
     "+--+--+--+--+",
     "...|##|......",
     "...|##|......",
     "...+--+......")


,(24,"....+--+.......",
     "....|##|.......",
     "....|##|.......",
     "....|##|.......",
     "+---+--+---+--+",
     "|###|##|###|##|",
     "+---+--+---+--+",
     "....|##|.......",
     "....|##|.......",
     "....|##|.......",
     "....+--+.......")

,(120,"....+-----+..........",
      "....|#####|..........",
      "....|#####|..........",
      "....|#####|..........",
      "+---+-----+---+-----+",
      "|###|#####|###|#####|",
      "|###|#####|###|#####|",
      "|###|#####|###|#####|",
      "|###|#####|###|#####|",
      "+---+-----+---+-----+",
      "....|#####|..........",
      "....|#####|..........",
      "....|#####|..........",
      "....+-----+..........")

) | % {
    $expected,$s = $_
    $result = &$f @s
    "$($result-eq$expected): $result"
}

Output:

True: 1
True: 3
True: 12
True: 16
True: 16
True: 18
True: 24
True: 120

mazzy

Posted 2016-12-12T07:27:59.387

Reputation: 4 832

0

Wolfram Language (Mathematica), 64 bytes

(2(x=#@"
")-(y=#@"|")-9)((9-5x+y)^2-9#@".")/54&@*CharacterCounts

Try it online!

Uses the number of ., |, and \n characters in the input to solve for the volume. It looks stupid because there's an actual new line in place of \n.

If A, B, and C are the sides, then . = 2C(A+2C), | = 5B+4C-9, and \n = B+2C, so we can solve for the volume ABC in terms of these three character counts.

Misha Lavrov

Posted 2016-12-12T07:27:59.387

Reputation: 4 846