Draw my Contours

25

5

Given a rectangular matrix of elevations, draw its contours.

Task

Two elements x and y are on the same contour level if floor(x/10) == floor(y/10). For example, 52 and 58 are on the same contour level, but 58 and 64 are not.

The act of drawing contours is defined as follows: For every element e, replace it with a two-char string chosen as follows:

  • the first character is " " if the element below e is on the same contour level as e or if there is no element to the below e and "_" otherwise
  • the second character is " " if the element to the right of e is on the same contour level as e or there is no element to the right of e and "|" otherwise

The elements within rows are joined together, then the rows are joined together with newlines.

Example

Let's say the input is [[5,20],[3,6]], visualized as

5 20
3 6

We first look at 5. Since 3 is on the same contour level as 5, the first character is " ". Since 20 is not on the same contour level as 5, the second character is "|".

Now we look at 20. Since 6 is not on the same contour level as 20, the first character is "_". Since there is no element to the right of 20, the second character is " ".

Now we look at 3. Since there is no element below 3, the first character is " ". Since 6 is on the same contour level as 3, the second character is " ".

Now we look at 6. Since there is no element below 6, the first character is " ". Since there is no element to the right of 6, the second character is " ".

Based on these two-char strings, we make replacements to get [[" |","_ "],[" "," "]]. Joining these together, we get an output of

 |_ 
    

Rules

  • The input matrix will always be rectangular and composed of positive integers.
  • Trailing spaces or newlines may be in any amount (including 0) and do not have to be consistent in any way.
  • You do not have to follow the same algorithm as long as you produce the same results.
  • Your program or function may output a newline-separated string, list of strings, or equivalent.
  • This is , so shortest code in bytes wins.

Test Cases

input
output

[[1,5,8,9],[3,11,13,8],[7,14,10,9],[4,8,7,6]]
  _ _  
 |   | 
 |_ _| 

[[0,10,20,30,40,50,60,70,80,90],[0,0,10,10,20,20,30,30,40,40],[0,0,0,10,10,10,20,20,20,30],[0,0,0,0,10,10,10,10,20,20],[0,0,0,0,0,10,10,10,10,10],[0,0,0,0,0,0,10,10,10,10],[0,0,0,0,0,0,0,10,10,10],[0,0,0,0,0,0,0,0,10,10],[0,0,0,0,0,0,0,0,0,10],[0,0,0,0,0,0,0,0,0,0]]
 |_|_|_|_|_|_|_|_|_
   |_  |_ _|_ _|_ _
     |_    |_ _  |_
       |_      |_ _
         |_        
           |_      
             |_    
               |_  
                 |_


[[5,5,5,5,5,5,5,5,5,5,5],[5,10,10,10,10,10,10,10,10,10,5],[5,10,15,15,15,15,15,15,15,10,5],[5,10,15,20,20,20,20,20,15,10,5],[5,10,15,20,25,25,25,20,15,10,5],[5,10,15,20,25,30,25,20,15,10,5],[5,10,15,20,25,25,25,20,15,10,5],[5,10,15,20,20,20,20,20,15,10,5],[5,10,15,15,15,15,15,15,15,10,5],[5,10,10,10,10,10,10,10,10,10,5],[5,5,5,5,5,5,5,5,5,5,5]]
  _ _ _ _ _ _ _ _ _  
 |                 | 
 |    _ _ _ _ _    | 
 |   |         |   | 
 |   |    _    |   | 
 |   |   |_|   |   | 
 |   |         |   | 
 |   |_ _ _ _ _|   | 
 |                 | 
 |_ _ _ _ _ _ _ _ _| 

[[35,32,29,26,25,25,25,26,29,32,35],[32,28,25,22,20,20,20,22,25,28,32],[29,25,21,18,15,15,15,18,21,25,29],[26,22,18,14,11,10,11,14,18,22,26],[25,20,15,11,7,5,7,11,15,20,25],[25,20,15,10,5,0,5,10,15,20,25],[25,20,15,11,7,5,7,11,15,20,25],[26,22,18,14,11,10,11,14,18,22,26],[29,25,21,18,15,15,15,18,21,25,29],[32,28,25,22,20,20,20,22,25,28,32],[35,32,29,26,25,25,25,26,29,32,35]]
  _|             |_  
_|    _ _ _ _ _    |_
    _|         |_    
   |    _ _ _    |   
   |   |     |   |   
   |   |     |   |   
   |   |_ _ _|   |   
   |_           _|   
_    |_ _ _ _ _|    _
 |_               _| 
   |             |  

fireflame241

Posted 2017-09-14T23:24:09.057

Reputation: 7 021

1I don't even begin to read this before realizing how cool this will be – Christopher – 2017-09-14T23:37:21.833

Answers

6

Perl 6, 135 bytes (131 chars)

{my$n="_";sub w{$^a.chop-$^b.chop??$n!!" "};my&q={|.[1..*],.tail};(($_ «[&w]».&q) ZZ~{$n="|";$_ «[&w]».map(*.&q)}()).map:{say |$_}}

Try it online!

Slightly ungolfed:

{
    my $n = "_";
    sub w { $^a.chop - $^b.chop ?? $n !! " "};
    my &q = {|.[1..*],.tail};
    (
        ($_ «[&w]».&q)
        ZZ~
        {$n="|";$_ «[&w]».map(*.&q)}()
    ).map:{say |$_}
}

Explanation: First, we define a variable $n (line 2) and a function w (line 3). This function returns a space if its two arguments are at the same "elevation", and the contents of variable $n otherwise. Instead of dividing by 10 and flooring, we abuse the fact that ints are Cool (can be treated like strings) and use chop to remove the last character (=digit). Then we calmly subtract them, forcing them into numbers again :—).

After that (line 4) we make up a function q that takes a list and returns that list with first element removed and the last element duplicated.

On the next 3 lines, we're going to make 2 more matrices from the input matrix: the first one has the first row missing and the last row duplicated (that's just .&q — using .&, you can call a function on anything as if it was a method — the thing in front of the dot is then the first argument), the other one has the first column missing and the last column duplicated (that's .map(*.&q)).

First (line 4) we take the original matrix $_, "overlay" it with the "shifted rows" matrix and use the function w as a binary operator (that's that [&w]) on the matching elements. That one puts a _ wherever the matching elements are on the different elevations, and a otherwise. So we get ½ of the result (only the "first characters").

At line 6, we do the same thing, but first we change $n to |, and we now "overlay" the original matrix with the matrix with shifted columns. Result has a | at different and at the same elevations. These are the "second characters".

Now we just combine them together. We zip the arrays with a zip with a concat (yeah...), which results in a matrix of the original shape whose each element is the 2 matching elements of the "half-solutions" concatenated. Finally, we just map over this matrix (which is really a list of lists). Each of these lists is flattened and then say-ed (printed with a newline). Since say can take any number of arguments and it prints them all without separators, making the newline only at the end, we get the desired picture on stdout. (And the block returns a list of Trues (each say returns one True), but who cares.)

Ramillies

Posted 2017-09-14T23:24:09.057

Reputation: 1 923

+1 for but who cares – HyperNeutrino – 2017-09-15T02:20:24.830

5

Jelly,  25 23  22 bytes

-1 byte thanks to miles (I vectorises)

:⁵I;€0ao⁶
Zç”_Zż"ç”|$Y

A full program printing the result. As a monadic link it takes a list of lists of numbers, the elevations, and returns a list lists, however these "rows" consist of lists of two character "pairs" - if this is fine then 1 byte may be saved by removing Y.

Try it online!

How?

:⁵I;€0ao⁶ - Link 1, assignCharacters (row-wise): list of lists of numbers; character, c
 ⁵        - literal 10
:         - integer division (vectorises)
  I       - incremental differences (vectorises) (zero if the same else non-zero)
     0    - literal 0
   ;€     - concatenate for €ach (rightmost edge of a row has no contour mark)
      a   - logical and (vectorises) with c (replace non-zeros with the contour character)
        ⁶ - literal space character
       o  - logical or (vectorises) (replace the zeros with spaces)

Zç”_Zż"ç”|$Y - Main link: list of lists of numbers, contours
Z            - transpose the input (get the columns)
  ”_         - literal underscore character, '_'
 ç           - call the last link (1) as a dyad with '_'
    Z        - transpose the result
          $  - last two links as a monad:
        ”|   -   literal pipe character, '|'
       ç     -   call the last link (1) as a dyad with '|'
      "      - zip with the dyadic operation:
     ż       -   zip (interleave the column-wise characters with the row-wise ones)
           Y - join with newlines
             - implicit print

Jonathan Allan

Posted 2017-09-14T23:24:09.057

Reputation: 67 804

grr 3 bytes. +1 but I will try to outgolf you ;) – HyperNeutrino – 2017-09-15T02:32:49.247

Independent solution - just saw yours is very similar! saves you one straight off... – Jonathan Allan – 2017-09-15T02:33:34.163

You can save a byte using each on the join in the helper :⁵I;€0ao⁶ instead of in the main link Zç”_Zż"ç”|$Y – miles – 2017-09-15T10:14:15.663

@miles Oh wow, that works? Thanks! I imagined I would not vectorise like that. – Jonathan Allan – 2017-09-15T10:19:43.567

Yeah I vectorizes at depth 1, and both a and o vectorize at depth 0 – miles – 2017-09-15T10:25:02.847

3

Jelly, 24 bytes

:⁵IṠ;€0
ZÇZị⁾_ +³Ç¤ị⁾| ¤

Try it online!

Explanation

:⁵IṠ;€0           Helper Link; get contour data
:                 Floor division by
 ⁵                10
  I               Compute increments
   Ṡ              Sign; ±1 for different values and 0 for same values
    ;             Append
      0           Zero
     €            To each row
ZÇZị⁾_ +³Ç¤ị⁾| ¤  Main Link
Z                 Zip the input (for vertical contours _)
 Ç                Get the contour data
  Z               Zip the data (because it's zipped from the first Z)
   ị              Index into the string
    ⁾_            "_ "
       +          Add (vectorizing twice) to
        ³ ¤    ¤  Nilad starting from (input)
         Ç        Get contour data (horizontal contours |)
           ị      Index into the string
            ⁾|    "| "

-2 bytes thanks to Jonathan Allan

HyperNeutrino

Posted 2017-09-14T23:24:09.057

Reputation: 26 575

Your solution can actually do away with the Y - it will return a list of lists of characters, which I believe is OK (while mine has pairs inside the "rows"). – Jonathan Allan – 2017-09-15T02:36:42.857

@JonathanAllan oh yeah true... thanks! – HyperNeutrino – 2017-09-15T02:38:12.110

3

Python 2, 199 186 157 155 bytes

lambda a:(lambda x:'\n'.join(''.join('_ '[x==z]+'| '[x==y]for x,y,z in zip(r,r[1:]+r[-1:],q))for r,q in zip(x,x[1:]+x[-1:])))([[v/10for v in r]for r in a])

Try it online!

Chas Brown

Posted 2017-09-14T23:24:09.057

Reputation: 8 959

2

Python 2, 226 bytes

l=[[j/10for j in i]for i in input()]
for i,j in enumerate(l[:-1]):print''.join('_ '[h==l[i+1][g]]+'| '[h==j[g+1]]for g,h in enumerate(j[:-1]))+'_ '[j[-1]==l[i+1][-1]]
print''.join(' '+'| '[i==j]for i,j in zip(l[-1],l[-1][1:]))

Try it online!

Whew, that was a doozy to work the logic out. I now see Hyper Neutrino ninja'd me with a shorter answer but I spent too much work on this not to post it. :P

Also, can I just say, this is an awesome way to make ASCII-art. Excuse me while I make a boatload more of these.

totallyhuman

Posted 2017-09-14T23:24:09.057

Reputation: 15 378

ockquote>

ninja'd: dude it's been 45 minutes

– HyperNeutrino – 2017-09-15T00:31:22.040

Yeah, I didn't look at the answers... :P – totallyhuman – 2017-09-15T00:31:51.993

You can save 4 bytes by defining a variable for enumerate instead of using the full name twice. – Jonathan Frech – 2017-09-15T01:19:46.640

218 bytes by removing the first enumerate (obs, i had to remove some inputs to be able to link it here) – Felipe Nardi Batista – 2017-09-15T13:01:06.217

2

J, 58 bytes

f=.{~0==/@]
[:(,/"2)2 2((' _'f{."1),' |'f{.);.3 1:+<.@%&10

Try it online!

An anonymous function which takes a matrix and outputs the contours.

Lotta room for improvement here. I didn't have time to try all the test cases so let me know if there are any issues. Will try to golf more and explain later.

(Quick) Explanation

Helper function: indexes into a length 2 string based on whether the first element of a 2 length array is equal to the second. If it is equal, it indexes into the zeroth element, if it is unequal, it indexes into the first. A 1 length array always indexes into the zeroth element of the string.

f=.{~0==/@]

Main function

[:(,/"2)2 2((' _'f{."1),' |'f{.);.3 1:+<.@%&10

1:+<.@%&10 floors each element divided by 10 and adds 1 (so we will never get 0 -- this is important for the helper function).

2 2((' _'f{."1),' |'f{.);.3 cuts the matrix into 2 x 2 segments if it can (otherwise it'll give a 2 x 1, 1 x 2, or 1 x 1 segment near the edges) and applies the function that uses f to compare the top left element to the top right and the top left element to the bottom left.

(,/"2) flattens the result into the desired shape. I really feel like I should be able to avoid having to use this (and a lot of other things, but I digress).

cole

Posted 2017-09-14T23:24:09.057

Reputation: 3 526

2

J, 46 45 bytes

' _|'({~];"1@(,&.>-@|:)&(0,~}.~:}:)|:)<.@%&10

Try it online!

miles

Posted 2017-09-14T23:24:09.057

Reputation: 15 654

2

JavaScript (ES6), 120 118 bytes

a=>a.map(b=>b.map(c=>c/10|0)).map((b,i,a)=>b.map((c,j)=>((a[i+1]||0)[j]-c?'_':' ')+(b[j+1]-c?'|':' ')).join``).join`\n`

Where \n represents the literal newline character. Edit: Saved 2 bytes thanks to @Bálint.

f=
a=>a.map(b=>b.map(c=>c/10|0)).map((b,i,a)=>b.map((c,j)=>((a[i+1]||0)[j]-c?'_':' ')+(b[j+1]-c?'|':' ')).join``).join`
`
;[
[[5,20],[3,6]]
,
[[1,5,8,9],[3,11,13,8],[7,14,10,9],[4,8,7,6]]
,
[[0,10,20,30,40,50,60,70,80,90],[0,0,10,10,20,20,30,30,40,40],[0,0,0,10,10,10,20,20,20,30],[0,0,0,0,10,10,10,10,20,20],[0,0,0,0,0,10,10,10,10,10],[0,0,0,0,0,0,10,10,10,10],[0,0,0,0,0,0,0,10,10,10],[0,0,0,0,0,0,0,0,10,10],[0,0,0,0,0,0,0,0,0,10],[0,0,0,0,0,0,0,0,0,0]]
,
[[5,5,5,5,5,5,5,5,5,5,5],[5,10,10,10,10,10,10,10,10,10,5],[5,10,15,15,15,15,15,15,15,10,5],[5,10,15,20,20,20,20,20,15,10,5],[5,10,15,20,25,25,25,20,15,10,5],[5,10,15,20,25,30,25,20,15,10,5],[5,10,15,20,25,25,25,20,15,10,5],[5,10,15,20,20,20,20,20,15,10,5],[5,10,15,15,15,15,15,15,15,10,5],[5,10,10,10,10,10,10,10,10,10,5],[5,5,5,5,5,5,5,5,5,5,5]]
,
[[35,32,29,26,25,25,25,26,29,32,35],[32,28,25,22,20,20,20,22,25,28,32],[29,25,21,18,15,15,15,18,21,25,29],[26,22,18,14,11,10,11,14,18,22,26],[25,20,15,11,7,5,7,11,15,20,25],[25,20,15,10,5,0,5,10,15,20,25],[25,20,15,11,7,5,7,11,15,20,25],[26,22,18,14,11,10,11,14,18,22,26],[29,25,21,18,15,15,15,18,21,25,29],[32,28,25,22,20,20,20,22,25,28,32],[35,32,29,26,25,25,25,26,29,32,35]]
].forEach(a=>document.write(['<pre>','</pre>'].join(f(a))));

Neil

Posted 2017-09-14T23:24:09.057

Reputation: 95 035

You can turn the (a[i] || [])[j] constructs to (a[i] || 0)[j] – Bálint – 2017-09-15T22:33:35.730

Also, in the last join\\n`` you could remove the \n part and replace it with an actual newline – Bálint – 2017-09-15T22:36:21.670

114 bytes: a=>a.map(b=>b.map(c=>c/10|0)).map((b,i,a)=>b.map((c,j)=>" _"[(a[i+1]||0)[j]-c&1]+" |"[b[j+1]-c&1]).join\`).join`<new line here>`` – Bálint – 2017-09-15T22:52:23.257

@Bálint Bah, I keep forgetting to do that \n part; I test in a REPL so literal newlines would get in the way. – Neil – 2017-09-15T23:00:26.363

@Bálint But your last suggestion fails for the original example, which I've added to the list of outputs. – Neil – 2017-09-16T00:07:08.143

1

Proton, 202 bytes

R=(L=len)+range
k=[map((//)&10,r)for r:eval(input())]
d=(x,y,X,Y)=>X>=L(k)or Y>=L(k[X])or k[x][y]==k[X][Y]
print('\n'.join(map(''.join,[['_ '[d(x,y,x+1,y)]+'| '[d(x,y,x,y+1)]for y:R(k[x])]for x:R(k)])))

Try it online!

-2 bytes thanks to Jonathan Frech
-15 bytes by switching to Proton instead of Python 2

HyperNeutrino

Posted 2017-09-14T23:24:09.057

Reputation: 26 575

You can save two bytes by replacing len with Land defining L=len;. – Jonathan Frech – 2017-09-15T01:15:33.300

1

Java 8, 200 170 169 bytes

a->{String r="";for(int l=a.length,i=0,j;i<l;i++,r+="\n")for(j=0;j<l;r+=(i>l-2||a[i][j]/10==a[i+1][j]/10?" ":"_")+(j++>l-2||a[i][j-1]/10==a[i][j]/10?" ":"|"));return r;}

Explanation:

Try it here.

Note that integer division in Java automatically floors.

a->{                   // Method with 2D int-array as parameter and String return-type
  String r="";         //  Result-String
  for(int l=a.length,  //  Length of the input array
      i=0,j;           //  Index integers
      i<l;i++,         //  Loop (1) over the rows of the input array
          r+="\n")     //  and append a new-line to the result after every iteration
    for(j=0;j<l;       //   Inner loop (2) over the columns of a row
      r+=              //    Append the String with:
         (i>l-2        //      If it's the last row,
         ||a[i][j]/10==a[i+1][j]/10?
                       //      or the current and next rows are equal floored/10:
          " "          //       Use a space
         :             //      Else:
          "_")         //       Use a "_"
        +              //     Plus
         (j++>l-2      //      If it's the last column in the row,
         ||a[i][j-1]/10==a[i][j]/10?
                       //      or the current and next columns are equal floored/10:
          " "          //       Use a space
         :             //      Else:
          "|")         //       Use "|"
    );                 //   End of column loop (2)
                       //  End of row-loop (1) (implicit / single-line body)
  return r;            //  Return the result-String
}                      // End of method

Kevin Cruijssen

Posted 2017-09-14T23:24:09.057

Reputation: 67 575

1

R, 159 bytes

f=function(m){M=m%/%10;a=cbind(0,t(apply(M,1,diff)));b=rbind(apply(M,2,diff),0);a[!!a]="|";b[!!b]="_";M[]=gsub("0"," ",paste0(a,b));write(t(M),"",ncol(m),,"")}

With newlines and indentations:

f=function(m){
    M=m%/%10
    a=cbind(0,t(apply(M,1,diff))) #row-wise difference
    b=rbind(apply(M,2,diff),0) #column-wise difference
    a[!!a]="|"
    b[!!b]="_"
    M[]=gsub("0"," ",paste0(a,b)) # M[] is a trick to force the result to have the same structure as M
    write(t(M),"",ncol(m),,"")
    }

Does the integer division of the matrix, measures the row-wise and column-wise differences, and when not null replace by |and _ respectively, then pastes both (painless, thanks to R's vectorization) and outputs.

Test cases:

> m=matrix(c(0,10,20,30,40,50,60,70,80,90,0,0,10,10,20,20,30,30,40,40,0,0,0,10,10,10,20,20,20,30,0,0,0,0,10,10,10,10,20,20,0,0,0,0,0,10,10,10,10,10,0,0,0,0,0,0,10,10,10,10,0,0,0,0,0,0,0,10,10,10,0,0,0,0,0,0,0,0,10,10,0,0,0,0,0,0,0,0,0,10,0,0,0,0,0,0,0,0,0,0),byrow=T,ncol=10)
> f(m)
  |_|_|_|_|_|_|_|_|_
    |_  |_ _|_ _|_ _
      |_    |_ _  |_
        |_      |_ _
          |_        
            |_      
              |_    
                |_  
                  |_

> m=matrix(c(5,5,5,5,5,5,5,5,5,5,5,5,10,10,10,10,10,10,10,10,10,5,5,10,15,15,15,15,15,15,15,10,5,5,10,15,20,20,20,20,20,15,10,5,5,10,15,20,25,25,25,20,15,10,5,5,10,15,20,25,30,25,20,15,10,5,5,10,15,20,25,25,25,20,15,10,5,5,10,15,20,20,20,20,20,15,10,5,5,10,15,15,15,15,15,15,15,10,5,5,10,10,10,10,10,10,10,10,10,5,5,5,5,5,5,5,5,5,5,5,5),byrow=T,ncol=11)
> f(m)
   _ _ _ _ _ _ _ _ _  
  |                 | 
  |    _ _ _ _ _    | 
  |   |         |   | 
  |   |    _    |   | 
  |   |   |_|   |   | 
  |   |         |   | 
  |   |_ _ _ _ _|   | 
  |                 | 
  |_ _ _ _ _ _ _ _ _| 

plannapus

Posted 2017-09-14T23:24:09.057

Reputation: 8 610

0

Perl 5, 130 126 bytes

124 bytes of code + 2 for -ap flags

push@a,[map 0|$_/10,@F]}{map{say map{($a[$r+1][$c]-$_&&$r<$#a?'_':$").($a[$r][++$c]-$_&&$c<@{$a[0]}?'|':$")}@$_;$c=0;$r++}@a

Try it online!

Input format is 2-D grid of space separated numbers.

Explanation

This is from a previous iteration of the code.

push@a,[map 0|$_/10,@F]     # read the input, divide it by 10, and store it in a 2-D array
}{                          # end the implicit while loop and start the final block
map{                        # repeat this for each line
  $_=($a[$r+1][$c]-$_&&$r<$#a?'_':$")       # set appropriate characters to output based
     .($a[$r][++$c]-$_&&$c<@{$a[0]}?'|':$") # on the given rules
  for@$_;                                   # repeat for each number on the line
  $c=0;$r++;                         # setup row and column counters for next iteration
  say@$_                             # output this line
}@a

Xcali

Posted 2017-09-14T23:24:09.057

Reputation: 7 671