Help the architects visualize the skyline

29

3

As part of a city planning project, you've gotten the assignment of creating a program or function that will display the city skyline, given some input from the architects. The project is only in the startup phase, so a very rough sketch is sufficient. The easiest approach is of course to simply draw the skyline in ASCII-art.

All buildings will be by the river, thus they are all aligned. The architects will give the height of each building as input, and your code should display the skyline.

The input from the architects will either be an integer or a half-integer. If the number is an integer, the building will have a flat roof, while a half-integer will result in a pitched roof. A zero will just be flat ground. The walls of a building are 3 characters apart, while a zero will be a single character wide. Adjacent buildings share walls.

For details and clarifications regarding the output, please have a look at the examples below:

N = 3
 ___
|   |
|   |
|___|

N = 3.5
  _      
 / \
|   |
|   |
|___|

N = 6
 ___
|   |
|   |
|   |
|   |
|   |
|___|

n = 0
_

Example input: 3 3.5 0 2

      _
 ___ / \  
|   |   |  ___
|   |   | |   |
|___|___|_|___|

Example input: 0 0 2.5 3 0 4 1

             ___
    _  ___  |   |
   / \|   | |   |
  |   |   | |   |___
__|___|___|_|___|___|

Louisville, 0 2 1 3.5 0 4 2 4 2 4 6 1 6 0 5 1

                                    ___     ___
                                   |   |   |   |  ___
           _    ___     ___     ___|   |   |   | |   |
          / \  |   |   |   |   |   |   |   |   | |   |
  ___    |   | |   |___|   |___|   |   |   |   | |   |
 |   |___|   | |   |   |   |   |   |   |___|   | |   |___
_|___|___|___|_|___|___|___|___|___|___|___|___|_|___|___|

The ASCII-characters used are: newline, space, and /\_| (code points 10, 32, 47, 92, 95, 124).

Rules:

  • It's optional to make a program that only take integers as input, by multiplying all numbers by two. So, instead of taking 3 3.5 2, your program may take 6 7 4. If the second input format is chosen, an input of 6 should result in a 3 story building, 7 should be a 3 story building with pitched roofs etc.
  • The output should be exactly as described above, but trailing spaces and newlines are OK.
  • The exact format of the input is optional. Whatever is best in your language.
  • The result must be displayed on the screen, so that the architects can have a look at it.
  • You can assume there will be at least one integer given, and that only valid input will be given.

This is codegolf, so the shortest code in bytes win.

Stewie Griffin

Posted 2015-11-29T12:36:56.227

Reputation: 43 471

1What would a building of height 0.5 look like? – Tom Carpenter – 2015-11-29T20:07:34.583

Haven't thought of it really. The most obvious choice would be just a pitched roof, almost like a hobbit home :-) but you're free to choose, or you can assume the input will never be 0.5... – Stewie Griffin – 2015-11-29T21:44:18.443

1At the moment weird things happen as there are no walls (I assumed 0.5 high didn't exist), so I'll have to work on my answer a bit. – Tom Carpenter – 2015-11-29T21:54:24.070

I just tried your code with height 0.5, and I agree, "weird" is a very descriptive word =P I haven't gone through it in detail, so I'm not sure what is going on... Anyhow, you answer is perfectly valid, you can assume there aren't any 0.5 buildings... – Stewie Griffin – 2015-11-30T12:50:29.067

Answers

5

Python 2, 199 193 188 185 bytes

a=map(int,raw_input().split())
l=max(a)+1|1
while~l:print''.join((x%2*'/  _\\ '[x<l::2]*(x<=l<x+4)or'_ '[x|1!=l>1]*3)[x<1:x+2]+'| '[x<=l>=y]*(x+y>0)for x,y in zip([0]+a,a+[0]))[1:];l-=2

This is a full program that accepts integers as input. Example input.

xsot

Posted 2015-11-29T12:36:56.227

Reputation: 5 069

wonderful! i'ma have to steal some of these tricks for future golfs... – quintopia – 2015-11-30T03:57:25.683

5

MATLAB, 219 209 203 bytes

i=input('');x=1;c=0;m(1:4*numel(i))='_';for a=i;b=fix(a);m(1:b,x)='|';s=95;if a~=b;m(b+2,x+2)=95;s='/ \';end;m(b+1,x+(1:3))=s;x=x+(a>0)*3+1;m(1:b,x)='|';x=x+(a<1&c>0);c=a;end;disp(flipud(m(:,1:x-(a<1))))

This unfortunately doesn't work on Octave. Not entirely sure why, seems to be something to do with the disp/flipud bit that breaks.

Also, there is currently no definition of what a 0.5 height building looks like, nor any mention of them, so in this code I assume that they are disallowed.

The following is the code in a slightly more readable way:

i=input(''); %e.g. [0 0 2 1 3.5 0 4 2 4 2 4 6 1 6 0 5 1 0 0 1 0]
x=1;
c=0;
m(1:4*numel(i))='_';
for a=i;
    b=fix(a);
    m(1:b,x)='|';
    s=95;
    if a~=b;
        m(b+2,x+2)=95;
        s='/ \';
    end;
    m(b+1,x+(1:3))=s;
    x=x+(a>0)*3+1;
    m(1:b,x)='|';
    x=x+(a<1&c>0);
    c=a;
end;
disp(flipud(m(:,1:x-(a<1))))

First we take an input as an array, and do some variable initialisation.

i=input(''); %e.g. [0 0 2 1 3.5 0 4 2 4 2 4 6 1 6 0 5 1]
x=1;
c=0;

Because the zero height buildings are a pain - they basically end up with a width which is dependent on what they are next to (though what is printed doesn't change), we simplify things by drawing enough ground for all of the buildings. We assume each building will be 4 characters wide (because adjacent buildings merge together) - zero height ones aren't, but the excess will be trimmed later.

m(1:4*numel(i))='_';

Now we draw out each building in turn.

for a=i

First we get the integer part of the height as this will determine how many '|' we need.

    b=fix(a);

Now draw in the wall for this building - if there are two adjacent buildings, the wall for this new one will be in the same column as the wall from the last one.

    m(1:b,x)='|';

Check to see if this is a half height building. If it is, then the roof will be different. For the half heights, the roof is going to be / \ whereas full height ones it will be ___ (Matlab will implicitly replicate this from a single underscore, so save a couple of bytes there). There is an extra bit of roof one row higher for the half height buildings, so that is added as well.

    s=95;
    if a~=b;
        m(b+2,x+2)=95;
        s='/ \';
    end;

Draw in the roof

    m(b+1,x+(1:3))=s;

Now move to the start of the next building and draw in the shared wall (if the wall is too short at this point, it will be made larger when the next building is drawn). Note that zero height buildings are 1 wide, normal buildings are 4 wide, so we simplify what would otherwise be an if-else by treating (a>0) as a decimal number not a Boolean.

    x=x+(a>0)*3+1;
    m(1:b,x)='|';

Next comes a bit of hackery to work with zero height buildings. Basically what this says is if this building was zero height, and the one before that wasn't, it means the place of the next building needs incrementing by 1 because a zero height building sandwiched between two other buildings is effectively twice as wide - this accounts for the extra wall which is normally shared with an adjacent building. We also keep track of this building height for doing this check next time.

    x=x+(a<1&c>0);
    c=a;
end;

Once done, flip the building matrix to be the correct way up, and display it. Note that we also trim off any excess ground here as well.

disp(flipud(m(:,1:x-(a<1))))

So, when we run this script, we are asked for our input, for example:

[0 0 2 1 3.5 0 4 2 4 2 4 6 1 6 0 5 1 0 0 1 0]

It then generates the building and displays the result. For the above input, the following is generated:

                                     ___     ___                   
                                    |   |   |   |  ___             
            _    ___     ___     ___|   |   |   | |   |            
           / \  |   |   |   |   |   |   |   |   | |   |            
   ___    |   | |   |___|   |___|   |   |   |   | |   |            
  |   |___|   | |   |   |   |   |   |   |___|   | |   |___    ___  
__|___|___|___|_|___|___|___|___|___|___|___|___|_|___|___|__|___|_

Tom Carpenter

Posted 2015-11-29T12:36:56.227

Reputation: 3 990

Very well done! – Stewie Griffin – 2015-11-29T21:55:41.737

4

Kotlin, 447 442 bytes

val a={s:String->val f=s.split(" ").map{it.toFloat()}.toFloatArray();val m=(f.max()!!+1).toInt();for(d in m downTo 0){var l=0f;for(c in f){val h=c.toInt();print(if(h==d&&d!=0)if(h<l-0.5)"|" else{" "}+if(c>h)"/ \\" else "___" else if(h<d)if(d<l-0.5)"|" else{" "}+if(h==0)" " else if((c+0.5).toInt()==d)" _ " else "   " else{if(h==0)if(l<1)"  " else "| " else "|   "}.replace(' ',if(d==0)'_' else ' '));l=c;};if(d<l-0.5)print("|");println();}}

Ungolfed version:

val ungolfed: (String) -> Unit = {
    s ->

    val floats = s.split(" ").map { it.toFloat() }.toFloatArray()
    val maxH = (floats.max()!! + 1).toInt()

    for (drawHeight in maxH downTo 0) {
        var lastBuildingH = 0f
        for (f in floats) {
            val buildingH = f.toInt()
            if (drawHeight == 0) {
                // Baseline
                if (buildingH == 0)
                    if (lastBuildingH.toInt() == 0) print("__")
                    else print("|_")
                else print("|___")
            } else if (buildingH == drawHeight) {
                // Ceiling
                if (buildingH < lastBuildingH - 0.5) print("|")
                else print(" ")
                if (f > buildingH) print("/ \\")
                else print("___")
            } else if (buildingH < drawHeight) {
                // Above building
                if (drawHeight < lastBuildingH - 0.5) print("|")
                else print(" ")
                if (buildingH == 0) print(" ")
                else {
                    if ((f + 0.5).toInt() == drawHeight) print(" _ ")
                    else print("   ")
                }
            } else {
                if (buildingH == 0) print("| ")
                else print("|   ")
            }
            lastBuildingH = f;
        }
        if (drawHeight < lastBuildingH - 0.5) print("|")
        println()
    }
}

succcubbus

Posted 2015-11-29T12:36:56.227

Reputation: 181

3

Python 2, 357 306 299 294 287 281 276 bytes

def s(l):
 d=len(l)+1;l=[0]+l+[0];h=(max(l)+3)/2;o=''
 for i in range(d*h):
  a=l[i%d+1];c=l[i%d];b=2*(h-1-i/d);o+="|"if(a>b+1)+(c>b+1)else" "*(a+c>0);o+=" _/__  _\\"[a-b+1::3]if b*(1>=abs(a-b))else" "*(1+2*(a>0))
  if b==0:o=o.replace(" ","_")
  if i%d==d-1:print o[:-1];o=''

This uses the "doubled" encoding, to be passed to the function as a list. Edit: Shaved bytes by redoing part of the big conditional as an array selector, and switching to the doubled encoding. Shaved more bytes by rearranging the conditional even more and converting more logic to arithmetic.

EDIT: xsot's is better

Explanation:

d=len(l)+1;l=[0]+l+[0];m=max(l);h=m/2+m%2+1;o=''

d is 1 more than the length of the array, because we're going to add zeros on each end of the list from the second element up to the zero we added on the end. h is the height of the drawing. (We have to divide by 2 in this calculation because we are using the doubled representation, which we use specifically to avoid having to cast floats to ints all over the place. We also add 1 before dividing so odd heights--pointy buildings--get a little more clearance than the regular kind.) o is the output string.

 for i in range(d*h):

A standard trick for collapsing a double for loop into a single for loop. Once we do:

  a=l[i%d+1];c=l[i%d];b=2*(h-1-i/d)

we have now accomplished the same as:

for b in range(2*h-2,-2,-2):
 for j in range(d):
  a=l[j+1];c=l[j]

but in a way which nets us ten bytes saved (including whitespace on the following lines).

  o+="|"if(a>b+1)+(c>b+1)else" "*(a+c>0)

Stick a wall in any time the height of either the current building or the previous building is taller than the current line, as long as there is at least one building boundary here. It's the equivalent of the following conditional:

  o+=("|" if a>b+1 or c>b+1 else " ") if a or c else ""

where b is the current scan height, a is the current building height, and c is the previous building height. The latter part of the conditional prevents putting walls between ground spaces.

  o+=" _/__  _\\"[a-b+1::3]if b*(1>=abs(a-b))else" "*(1+2*(a>0))

This is the part that draws the correct roof, selecting roof parts by comparing the building's height with the current scan height. If a roof doesn't go here, it prints an appropriate number of spaces (3 when it's an actual building, e.g., a>0, otherwise 1). Note that when we're at ground level, it never attempts to draw a roof, which means 0.5 size buildings don't get pointy roofs. Oh well.

  if b==0:o=o.replace(" ","_")

When we're at ground level, we want underscores instead of spaces. We just replace them all in at once here.

  if i%d==d-1:print o[:-1];o=''

Just before we start processing the next line, print the current one and clear the output line. We chop off the last character because it is the "_" corresponding to the ground space we added by appending a zero at the beginning of the function. (We appended that zero so we would not have to add a special case to insert a right wall, if it exists, which would add far more code than we added by adding the 0 and chopping off the "_".)

quintopia

Posted 2015-11-29T12:36:56.227

Reputation: 3 899

Car-golfing. Wow. (Also, +1) – clap – 2015-11-30T01:14:32.520

2

Python 3

725 bytes

608 bytes

Golfed code:

import sys,math;
m,l,w,s,bh,ls,ins,r,a="|   |","___","|"," ",0,[],[],range,sys.argv[1:]
def ru(n):return math.ceil(n)
def bl(h,n):
    if(n>ru(h)):return(s*5,s)[h==0]
    if(h==0):return"_"
    if(n==0):return w+l+w
    if(n<h-1):return m
    return("  _  "," / \ ")[n==ru(h)-1]if(h%1)else(s+l+s,m)[n==h-1]
for arg in a:
    f=ru(float(arg))
    if(bh<f):bh=f
for i in r(bh,-1,-1):
    ln=""
    for bld in a:ln+=bl(float(bld),i)
    ls.append(ln)
for i in r(len(ls[-1])-1):
    if(ls[-1][i]==ls[-1][i+1]==w):ins.append(i-len(ins))
for ln in ls:
    for i in ins:ln=(ln[:i]+ln[i+1:],ln[:i+1]+ln[i+2:])[ln[i]==w]
    print(ln)

Here's the ungolfed code. There are some comments but the basic idea is to create buildings with double walls, so the bottom line looks like:

_|___||___|_|___||___|

Then to get indexes of those double walls and remove those columns so we get:

_|___|___|_|___|___|

Code:

import sys
import numbers
import math

mid="|   |";
l="___";
w="|";
s=" ";

def printList(lst):
    for it in lst:
        print(it);

# h = height of building
# l = line numeber starting at 0
def buildingline(h,n):
    #if (h==0):
    #   return " " if(n>math.ceil(h)) else "   ";
    if(n>math.ceil(h)):
        return s if(h == 0) else s*5;
    if(h==0): return "_";
    if(n==0): return w+l+w;
    if(n<h-1): return mid;
    if(h.is_integer()):
        return mid if(n==h-1) else  s+l+s;
    else:
        return " / \ " if (n==math.ceil(h)-1) else "  _  "; 
# max height
bh=0;

for arg in sys.argv[1:]:
    f = math.ceil(float(arg));
    if(bh<f):bh=f;

# lines for printing
lines = []

for i in range(bh,-1,-1):
    line="";
    for bld in sys.argv[1:]:
        bld=float(bld);
        line += buildingline(bld,i);
        #lh = bld;
    lines.append(line);

#for line in lines:
#   print(line);
#printList(lines);


# column merging
#find indexes for merging (if there are | | next to each other)
indexes = [];
for i in range(len(lines[-1])-1):
    if (lines[-1][i]=='|' and lines[-1][i+1] == '|'):
        indexes.append(i-len(indexes));

#printList(indexes);

#index counter
for line in lines:
    newLine = line;
    for i in indexes:
        if newLine[i] == '|' :
            newLine=newLine[:i+1] + newLine[i+2:];
        else : newLine = newLine[:i] + newLine[i+1:];
    print(newLine);

Time to do some golfing!

Cajova_Houba

Posted 2015-11-29T12:36:56.227

Reputation: 21

You might want to have a look here. I think there's a lot of golfing potential here =) I only know basic Python, so I can't suggest anything specific I'm afraid...

– Stewie Griffin – 2015-11-30T11:40:38.137

It looks to me like you have removed spaces and shortened the variable names, but kept the rest unchanged. You should try to find clever ways to e.g. get rid of some loops, use fewer comparisons etc. Of course, stuff like ru(n):return math.ceil(n) counts as golfing, but still... Please don't take this in a negative way, I'm not a good golfer myself, and sure as hell not a good programmer. I suggest you try to improve it some... It's actually kind of fun once you realize you manage to shorten it. I went from many many to 120 to 55 a few days ago. So it's possible even if you're new at it. – Stewie Griffin – 2015-11-30T13:47:15.507

@StewieGriffin Thank you for that link! I'm really a newbie to code-golfing so it's more about completing the actual task rather than doing code-golfing for me. But it's amazing to discover possibilities of various languages – Cajova_Houba – 2015-11-30T16:29:06.967

FTR: For some of the more complex challenges, such as this one, I would be happy just to finish it myself =) – Stewie Griffin – 2015-12-01T13:11:57.090

2

PHP, 307 297 293 bytes

<?$r=str_pad("",$p=((max($argv)+1)>>1)*$w=4*$argc,str_pad("\n",$w," ",0));for(;++$i<$argc&&$r[$p++]=_;$m=$n)if($n=$argv[$i]){$q=$p+=!$m;eval($x='$r[$q-1]=$r[$q]=$r[$q+1]=_;');for($h=$n>>1;$h--;$q-=$w)$r[$q-2]=$r[$q+2]="|";$n&1?($r[$q-1]="/")&($r[$q-$w]=_)&$r[$q+1]="\\":eval($x);$p+=3;}echo$r;

Takes arguments*2 from command line. save to file, run with php <filename> <parameters>.

breakdown

// initialize result    
$r=str_pad("",              // nested str_pad is 3 bytes shorter than a loop
    $p=                     // cursor=(max height-1)*(max width)=(start of last line)
    ((max($argv)+1)>>1)     // max height-1
    *
    $w=4*$argc              // we need at least 4*($argc-1)-1, +1 for newline
    ,
    // one line
    str_pad("\n",$w," ",0)  // (`str_pad("",$w-1)."\n"` is one byte shorter,
);                          // but requires `$w+1`)

// draw skyline
for(;
    ++$i<$argc              // loop through arguments
    &&$r[$p++]=_                // 0. draw empty ground and go one forward
    ;
    $m=$n                       // 7. remember value
)
    if($n=$argv[$i])            // if there is a house
    {
        $q=                         // 2. copy $p to $q
        $p+=!$m;                    // 1. go one forward if there was no house before this
        // offset all further positions by -2 (overwrite empty ground, share walls)
        eval($x=                    // 3. draw floor
        '$r[$q-1]=$r[$q]=$r[$q+1]=_;'
        );
        for($h=$n>>1;$h--;$q-=$w)   // 4. draw walls
            $r[$q-2]=$r[$q+2]="|";
        $n&1                        // 5. draw roof
            ?($r[$q-1]="/")&($r[$q-$w]=_)&$r[$q+1]="\\"
            :eval($x)               // (eval saved 7 bytes)
        ;                           // (ternary saved 6 bytes over `if`)
        $p+=3;                      // 6. go three forward (5-2)
    }

// output
echo$r;

Titus

Posted 2015-11-29T12:36:56.227

Reputation: 13 814

1

C++, ungolfed

(or maybe ungolfable)

Assuming there are less than 100 elements and each element is less than 100. s is the number of buildings (required in input).

#include <iostream>
using namespace std;
int main()
{
float a[100];
int i,j,s;
cin>>s;
for(i=0;i<s;++i)
 cin>>a[i];
for(i=100;i>=1;--i)
{
for(j=0;j<s;++j)
{
if((a[j]>=i)||(a[j-1]>=i))
 cout<<"|";
else
 cout<<" ";
if(i==1)
 cout<<"___";
else if(a[j]+1==i)
 cout<<"___";
else if(a[j]+1.5==i)
 cout<<" _ ";
else if(a[j]+0.5==i)
 cout<<"/ \\";
else cout<<"   ";
}
if(a[s-1]>=i)
 cout<<"|";
cout<<endl;
}
}

ghosts_in_the_code

Posted 2015-11-29T12:36:56.227

Reputation: 2 907

There are a few errors in the output... The ground is 3 characters wide (it should only be 1), and the last wall is missing. – Stewie Griffin – 2015-11-29T14:49:31.617

@StewieGriffin I was still sorting out the errors when I posted this. 1. I have added the last wall. 2. The ground has to be 3 characters wide, because the slanted roof /_\ is 3 characters wide. – ghosts_in_the_code – 2015-11-29T14:54:01.557

1*The ground between the buildings, not inside. – Stewie Griffin – 2015-11-29T14:56:35.313

If you're still working on it, you might want to wait, but you can get rid of many bytes if you remove the newlines and the indentation. I haven't fixed the ground problem, but this works.346 bytes instead of 401.

– Stewie Griffin – 2015-11-29T15:06:47.887

@StewieGriffin I don't actually intend to submit a golfed answer since it's way too long anyways. I can bet there exist better languages where it gets done in under 100 bytes. So my code is more of a reference solution to others. – ghosts_in_the_code – 2015-11-29T15:56:07.590

@StewieGriffin You are free to edit it, either to remove whitespace or to decrease ground length. (Writing programs isn't exactly my interest.) – ghosts_in_the_code – 2015-11-29T15:57:09.400