Decode a Heat Map

32

8

Heatmaps

Consider a rectangular room, on whose ceiling we have a thermal camera pointing downward. In the room, there are some number of heat sources of intensity 1-9, the background temperature being 0. The heat dissipates from each source, dropping by one unit per (non-diagonal) step. For example, the 20x10 room

...........1........
....................
...8................
..5...............2.
....................
.1..................
................1...
.................65.
....................
............2.......

contains 9 heat sources, and the temperature gradient shown by the thermal camera is

34565432100100000000
45676543210000000000
56787654321000000110
45676543210000001221
34565432100000012321
23454321000000123432
12343210000001234543
01232100000012345654
00121000000011234543
00010000000121123432

In graphical form this might look like:

heatmap of 9 sources

From the gradient, we can infer the positions and intensities of some heat sources, but not all. For example, all 9s can always be inferred, since they have the maximal temperature, and so can the 8 in this case, since it produces a local maximum in the gradient. The 2 near the right border can also be inferred, even though it is not at a local maximum, since it does not have another 2 as a neighbor. The 5s, on the other hand, are not inferred, since their heat might as well be produced by the more intense sources near them. The 0s are known to contain no heat sources, but all the other tiles may potentially contain one. Let's denote the uncertain tiles by hyphens -, certain heat sources by the corresponding digits, and certain empty space by periods .:

---------..1........
----------..........
---8-------......--.
----------......--2-
---------......-----
--------......------
-------......-------
.-----......-----6--
..---.......--------
...-.......-2-------

Your task shall be to produce this inferred pattern from the temperature gradient.

Rules

You are given the input as a string delimited by either newlines or vertical pipes |, whichever is more convenient, and the output shall be of the same form. There may be a trailing delimiter in the input and/or output, but no preceding one. The size of the input may vary, but its width and height are always at least 4. Both functions and full programs are acceptable. The lowest byte count wins, and standard loopholes are forbidden.

Additional Test Cases

Input:

898778765432100
787667654321100
677656543211210
678765432112321
567654321123210

which looks like this in graphical form:

test case 1

Output:

-9---8-------..
-------------..
--------------.
--8---------3--
-----------3--.

Input:

7898
8787
7676
6565

Output:

--9-
8---
----
----

Input:

00001
00000
00000
10000

Output:

....1
.....
.....
1....

Zgarb

Posted 2015-02-18T15:56:28.133

Reputation: 39 083

1Do you mind if I add 2 heatmap graphics to your question if you think they add value? They are just a 2 minute experiment. – Logic Knight – 2015-02-18T16:32:28.787

@CarpetPython Sure, go ahead. They look very nice to me. You can also add a "Courtesy of CarpetPython" to give yourself the credit. ;) – Zgarb – 2015-02-18T16:34:42.100

2Done. No credit required, but I thought it would be rude not to ask before editing. – Logic Knight – 2015-02-18T16:40:51.010

Why not allow the input as a 2-dimensional array instead of a string? – feersum – 2015-02-18T18:32:13.703

@feersum generally input methods are consistent. – Optimizer – 2015-02-18T18:37:10.563

@feersum As Optimizer said, I wanted the input and output to have the same format. In particular, I had to use only single digits, so string was a natural choice. Also, they look nicer than arrays. :P – Zgarb – 2015-02-19T12:17:46.673

@Zgarb I meant a 2-dimensional character array. – feersum – 2015-02-19T20:27:26.890

@feersum Well, I simply wanted parsing (or not) the input string to be an integral part of the challenge. – Zgarb – 2015-02-20T09:24:07.260

Answers

10

CJam, 73 69 62 55 bytes

UPDATE : New algorithm. Shorter and more scope for improvement

qN/5ff*{{[{_@_@<{I'0t}*\}*]W%}%z}4fI{):X-'-X~X'.??}f%N*

How it works

The logic is similar to the algorithm below, but here I am not checking for all 4 neighbors in a single iteration. Instead, I use a smaller approach to iterate through all rows and columns in both directions. Here are the steps involved:

  • Convert each character into sets of 5. The first 4 will be modified to tell if they are bigger than the adjacent cell in the row while iterating. Last one is for comparison purposes.
  • Iterate on each row and reduce on each row. While reducing, I have two 5 character string. I know what kind of iteration is [0 for rows normal, 1 columns reversed, 2 for rows reversed and 3 for columns normal] I update the ith character in the first 5 character string and make it 0 if it is smaller than the second.
  • After all 4 iterations, if all 5 characters are same and non-zero, that is the local maxima. I map through all 5 characters strings and convert them to either single digit, . or -.

Here is an example run on a small input:

7898
8787
7676
6565

After first step:

["77777" "88888" "99999" "88888"
 "88888" "77777" "88888" "77777"
 "77777" "66666" "77777" "66666"
 "66666" "55555" "66666" "55555"]

After second step:

["00777" "08888" "99999" "88088"
 "88888" "07007" "88808" "77007"
 "77707" "06006" "77707" "66006"
 "66606" "05005" "66606" "55005"]

After last mapping to single character, final output:

--9-
8---
----
----

Code Explanation:

qN/5ff*                         "Split the input on new line and convert each character";
                                "to string of 5 of those characters.";
{{[{             }*]W%}%z}4fI   "This code block runs 4 times. In each iteration, it";
                                "maps over each row/column and then for each of them,";
                                "It reduce over all elements of the row/column";
                                "Using combination of W% and z ensures that both rows and";
                                "columns are covered and in both directions while reducing";
    _@_@                        "Take a copy of last two elements while reducing over";
        <                       "If the last element is bigger than second last:";
         {I'0t}*\               "Convert the Ith character of the 5 char string of"
                                "second last element to 0";
                                "We don't have to compare Ith character of last two 5 char";
                                "string as the smaller one will be having more leading";
                                "0 anyways. This saves 4 bytes while comparing elements";
{):X-'-X~X'.??}f%N*             "This part of code converts the 5 char back to single char";
 ):X                            "Remove the last character and store in X. This last char";
                                "was not touched in the prev. loop, so is the original char";
    -                           "Subtract X from remaining 4 char. If string is not empty";
                                "then it means that it was not all same characters";
                                "In other words, this character was smaller then neighbors";
     '-      ?                  "If non-empty, then replace with - else ...";
       X~X'.?                   "if int(X) is zero, put . else put X";
               f%N*             "The mapping code block was run for each row and then";
                                "The rows are joined by newline.";

Try it here


Older approach

qN/~_,):L0s*]0s*:Q_,{QI=:A[W1LL~)]If+Qf=$W=<'-A?A~\'.?I\t}fIL/W<Wf<N*

How it works

The logic is simple, iterate through the grid and see if the current value is greater or equal to the remaining four neighbors - up, down, left and right. Then transform the current value based on the above rule and if the value is equal to 0, make it "." .

Code Explanation

qN/~_,):L0s*]0s*:Q         "This part of code pads the grid with 0s";
qN/~                       "Read the input, split on new lines and unwrap the arrays";
    _,):L                  "Copy the last row, taken length, increment and store in L";
         0s*               "Get L length 0 string";
            ]0s*           "Wrap everything in an array and join the rows by 0";
                :Q         "Store this final single string in Q";

_,{        ...      }fI    "Copy Q and take length. For I in 0..length, execute block";
   QI=:A                   "Get the I'th element from Q and store in A";
   [WiLL~)]If+             "This creates indexes of all 4 neighboring cells to the Ith cell";
              Qf=          "Get all 4 values on the above 4 indexes";
                 $W=       "Sort and get the maximum value";
<'-A?                      "If the current value is not the largest, convert it to -";
     A~\'.?                "If current value is 0, convert it to .";
           I\t             "Update the current value back in the string";
{ ... }fIL/                "After the loop, split the resulting string into chunks of L";
           W<Wf<           "Remove last row and last column";
                N*         "Join by new line and auto print";

Try it online here

Optimizer

Posted 2015-02-18T15:56:28.133

Reputation: 25 836

5I must say, I seldom hear "too long" when describing CJam code. – Alex A. – 2015-02-18T20:05:48.757

6

JavaScript (ES6) 99

F=h=>[...h].map((c,i)=>[o=~h.search('\n'),-o,1,-1].some(d=>h[d+i]>c)&c>0?'-':c=='0'?'.':c).join('')

Test In Firefox/FireBug console

console.log(F('\
34565432100100000000\n\
45676543210000000000\n\
56787654321000000110\n\
45676543210000001221\n\
34565432100000012321\n\
23454321000000123432\n\
12343210000001234543\n\
01232100000012345654\n\
00121000000011234543\n\
00010000000121123432\n'),'\n\n',
F('\
898778765432100\n\
787667654321100\n\
677656543211210\n\
678765432112321\n\
567654321123210\n'), '\n\n',
F('7898\n8787\n7676\n6565\n'))

Output

---------..1........
----------..........
---8-------......--.
----------......--2-
---------......-----
--------......------
-------......-------
.-----......-----6--
..---.......--------
...-.......-2-------


-9---8-------..
-------------..
--------------.
--8---------3--
-----------3--.


--9-
8---
----
----

edc65

Posted 2015-02-18T15:56:28.133

Reputation: 31 086

4

Python 2: 154 byte

b=input()
l=b.index('\n')+1
print''.join(('\n.'+('-'+v)[all([v>=b[j]for j in i-l,i-1,i+l,i+1if 0<=j<len(b)])])[('\n0'+v).index(v)]for i,v in enumerate(b))

Input has to be of the form "00001\n00000\n00000\n10000".

Converting a string to a 2D-matrix is quite lengthy in Python. So I keep the original string format. I enumerate over the input, i is the index, v is the char (Finally enumerate saved bytes in a golf solution!!). For each pair (i,v) I compute the correct char of output, and join them. How do I choose the correct output char? If v == '\n', the output char is \n, it v == '0', than the output char is '.'. Otherwise I test the 4 neighbors of v, which are b[i-b.index('\n')-1] (above), b[i-1] (left, b[i+1] (right) and b[i+b.index('\n')+1] (under), if they are <= v and choose the char '-' or v. Here I'm comparing chars not the numbers, but it works quite fine, because the ascii values are in the correct order. Also there are no problems, if b[i-1] or b[i+1] equal '\n', because ord('\n') = 10.

Pyth: 61 58

JhxQbVQK@QN~k@++b\.?\-f&&gT0<TlQ<K@QT[tNhN-NJ+NJ)Kx+b\0K)k

More or less a translation of the Python script. Quite ugly ;-)

Try it online: Pyth Compiler/Executor Same input format as the Python solution.

JhxQb      Q = input()
  xQb      Q.index('\n')
 h         +1
J          store in J

VQK@QN~k.....)k   k is initialized as empty string
VQ           )    for N in [0, 1, 2, ..., len(Q)-1]:
  K@QN                K = Q[n]
      ~k              k += ... (a char, computed in the next paragraph)
             )    end for
              k   print k

@...x+b\0K   ... is a char of len 3 (is constructed below)
     +b\0    the string "\n0"
    x    K   find Q[d] in this string and return index, if not found -1
@...         lookup in string at the computed position (this is done mod 3 automatically!)

++b\.?\-f&&gT0<TlQ<K@QT[tNhN-NJ+NJ)K   not to the string
                       [tNhN-NJ+NJ)    the list [d-1, d+1, d-J, d+j]
        f                              filter the list for indices T which
           gT0                            T >= 0
          &                               and
              <TlQ                        T < len(Q)
         &                                and
                  <K@QT                   Q[d] < Q[T]
     ?\-                           K   use "-" if len(filter) > 0 else Q[d]
                                       this creates the third char
++b\.                                  "\n" + "." + third char

Jakube

Posted 2015-02-18T15:56:28.133

Reputation: 21 462

4

Perl, 77, 75, 72 70

Standard 2d regex matching tricks.

#!perl -p0
/
/;$x="(.{@-})?";y/0/./while s/$.$x\K$"|$"(?=$x$.)/-/s||($"=$.++)<9

Example:

$ perl heat.pl <in.txt
---------..1........
----------..........
---8-------......--.
----------......--2-
---------......-----
--------......------
-------......-------
.-----......-----6--
..---.......--------
...-.......-2-------

Try it here

nutki

Posted 2015-02-18T15:56:28.133

Reputation: 3 634

3

Java, 307, 304, 303, 299 298

This is for sure a "perfect" challenge for some Java codegolf :)

class M{public static void main(String[]a){int c=a[0].indexOf('|'),i=c,d,v;char[]r=a[0].replace("|","").toCharArray(),m=new char[(v=r.length+c)+c];for(;i<v;){m[i]=r[i++-c];}for(i=c;i<v;i++){a[0]=i%c<1?"\n":"";d=m[i];System.out.print(a[0]+(d<49?'.':m[i-c]>d|m[i+c]>d|m[i-1]>d|m[i+1]>d?'-':m[i]));}}}

Input (pipe '|' method):

34565432100100000000|45676543210000000000|56787654321000000110|45676543210000001221|34565432100000012321|23454321000000123432|12343210000001234543|01232100000012345654|00121000000011234543|00010000000121123432

Output:

---------..1........
----------..........
---8-------......--.
----------......--2-
---------......-----
--------......------
-------......-------
.-----......-----6--
..---.......--------
...-.......-2-------

Rolf ツ

Posted 2015-02-18T15:56:28.133

Reputation: 711

1This can be 288 if you remove the space in char[]r=a[0].replace("|", <--here"").toCharArray(). – bcsb1001 – 2015-02-19T20:10:59.680

1Did not spot that one, thanks! Well this makes 298 – Rolf ツ – 2015-02-19T20:49:05.070

2

APL, 92

('.-',⎕D)[1+(M≠0)+M{(1+⍺)×0≠⍺∧M[J/⍨Z∊⍨J←⍵∘+¨(⌽¨,+)(-,+)⊂0 1]∧.≤⍺}¨Z←⍳⍴M←↑{×⍴⍵:(⊂⍎¨⍵),∇⍞⋄⍬}⍞]

Example:

       ('.-',⎕D)[1+(M≠0)+M{(1+⍺)×0≠⍺∧M[J/⍨Z∊⍨J←⍵∘+¨(⌽¨,+)(-,+)⊂0 1]∧.≤⍺}¨Z←⍳⍴M←↑{×⍴⍵:(⊂⍎¨⍵),∇⍞⋄⍬}⍞]
34565432100100000000
45676543210000000000
56787654321000000110
45676543210000001221
34565432100000012321
23454321000000123432
12343210000001234543
01232100000012345654
00121000000011234543
00010000000121123432

---------..1........
----------..........
---8-------......--.
----------......--2-
---------......-----
--------......------
-------......-------
.-----......-----6--
..---.......--------
...-.......-2-------

marinus

Posted 2015-02-18T15:56:28.133

Reputation: 30 224

The longest APL program I've ever seen. You might want to note that this is not standard APL as it uses dfns. – FUZxxl – 2015-02-19T10:26:57.337

2

Ruby 140

f=->s{
r=s.dup
l=s.index(?\n)+1
(0...s.size).map{|i|
s[i]<?0||r[i]=r[i]<?1??.:[i-1,i+1,i-l,i+l].map{|n|n<0??0:s[n]||?0}.max>r[i]??-:s[i]}
r}

Nothing special; just iterate through the map and compare the current value with the value of the four neighbors.

Run it online with tests: http://ideone.com/AQkOSY

Cristian Lupascu

Posted 2015-02-18T15:56:28.133

Reputation: 8 369

1

Perl - 226

sub f{for(split'
',$_[0]){chomp;push@r,r($_);}for(t(@r)){push@y,r($_)=~s/0/./gr}$,=$/;say t(@y);}sub r{$_[0]=~s/(?<=(.))?(.)(?=(.))?/$1<=$2&&$3<=$2?$2:$2eq'0'?0:"-"/ger;}sub t{@q=();for(@_){for(split//){$q[$i++].=$_;}$i=0;}@q}

You can try it on ideone. If anyone is interested in an explanation, let me know.

hmatt1

Posted 2015-02-18T15:56:28.133

Reputation: 3 356

I think you have 226 chars, not 227. – Cristian Lupascu – 2015-02-20T21:44:17.013

@w0lf you're right, the newline got counted for 2 since I'm on Windows. – hmatt1 – 2015-02-20T21:54:48.980

1

R, 223

About the best I can come up with at the moment. Dealing with the string is quite expensive. I think there is room for improvement, but can't see it at the moment

s=strsplit;a=c(m<-do.call(rbind,s(s(scan(w="c"),'|',T)[[1]],'')));w=(d<-dim(m))[1];n=c(-1,1,-w,w);cat(t(array(sapply(seq(a),function(x)if(a[x]>0)if(any(a[(n+x)[which(n+x>0)]]>a[x]))'-'else a[x]else'.'),d)),fill=d[2],sep='')

Test Result

> s=strsplit;a=c(m<-do.call(rbind,s(s(scan(w="c"),'|',T)[[1]],'')));w=(d<-dim(m))[1];n=c(-1,1,-w,w);cat(t(array(sapply(seq(a),function(x)if(a[x]>0)if(any(a[(n+x)[which(n+x>0)]]>a[x]))'-'else a[x]else'.'),d)),fill=d[2],sep='')
1: 898778765432100|787667654321100|677656543211210|678765432112321|567654321123210
2: 
Read 1 item
-9---8-------..
-------------..
--------------.
--8---------3--
-----------3--.
> s=strsplit;a=c(m<-do.call(rbind,s(s(scan(w="c"),'|',T)[[1]],'')));w=(d<-dim(m))[1];n=c(-1,1,-w,w);cat(t(array(sapply(seq(a),function(x)if(a[x]>0)if(any(a[(n+x)[which(n+x>0)]]>a[x]))'-'else a[x]else'.'),d)),fill=d[2],sep='')
1: 34565432100100000000|45676543210000000000|56787654321000000110|45676543210000001221|34565432100000012321|23454321000000123432|12343210000001234543|01232100000012345654|00121000000011234543|00010000000121123432
2: 
Read 1 item
---------..1........
----------..........
---8-------......--.
----------......--2-
---------......-----
--------......------
-------......-------
.-----......-----6--
..---.......--------
...-.......-2-------
> 

MickyT

Posted 2015-02-18T15:56:28.133

Reputation: 11 735

1

J - 69 bytes

[:u:45+[:(+2 0 3{~"#1+*)@((]*]=(0,(,-)1 0,:0 1)>./@:|.])-0=])"."0;._2

Examples:

   ([:u:45+[:(+2 0 3{~"#1+*)@((]*]=(0,(,-)1 0,:0 1)>./@:|.])-0=])"."0;._2) (0 : 0)
34565432100100000000
45676543210000000000
56787654321000000110
45676543210000001221
34565432100000012321
23454321000000123432
12343210000001234543
01232100000012345654
00121000000011234543
00010000000121123432
)
---------..1........
----------..........
---8-------......--.
----------......--2-
---------......-----
--------......------
-------......-------
.-----......-----6--
..---.......--------
...-.......-2-------
   ([:u:45+[:(+2 0 3{~"#1+*)@((]*]=(0,(,-)1 0,:0 1)>./@:|.])-0=])"."0;._2) (0 : 0)
898778765432100
787667654321100
677656543211210
678765432112321
567654321123210
)
-9---8-------..
-------------..
--------------.
--8---------3--
-----------3--.

PS: the (0 : 0) is the standard J way of specifying strings. You might as well use | delimited strings (with a trailing |).

jpjacobs

Posted 2015-02-18T15:56:28.133

Reputation: 3 440

1

Excel VBA - 426

It'll be a rare occasion that VBA wins any code golf games, but as it's what I use the most, it's fun to play around with it. The first line is an edge case that made this longer than it seems it should have to be.

Sub m(a)
    b = InStr(a, "|")
    For i = 1 To Len(a)
        t = Mid(a, i, 1)
        Select Case t
            Case "|"
                r = r & "|"
            Case 0
                r = r & "."
            Case Else
                On Error Resume Next
                x = Mid(a, i - 1, 1)
                y = Mid(a, i + 1, 1)
                Z = Mid(a, i + b, 1)
                If i < b Then
                    If t < x Or t < y Or t < Z Then
                        r = r & "-"
                    Else
                        r = r & t
                    End If
                Else
                    If t < x Or t < y Or t < Z Or t < Mid(a, i - b, 1) Then
                        r = r & "-"
                    Else
                        r = r & t
                    End If
                End If
        End Select
    Next
    MsgBox r
End Sub

The count doesn't include initial line whitespace.

I played around with the idea of sending the input to a sheet and working from there, but I think looping the passed string character-by-character saves code.

Call from Immediate Window:

m "34565432100100000000|45676543210000000000|56787654321000000110|45676543210000001221|34565432100000012321|23454321000000123432|12343210000001234543|01232100000012345654|00121000000011234543|00010000000121123432"

Output (in a Window):

---------..1........|----------..........|---8-------......--.|----------......--2-|---------......-----|--------......------|-------......-------|.-----......-----6--|..---.......--------|...-.......-2-------

phrebh

Posted 2015-02-18T15:56:28.133

Reputation: 41

1

Haskell - 193

z='0'
r=repeat z
g s=zipWith3(\u t d->zip3(zip(z:t)u)t$zip(tail t++[z])d)(r:s)s$tail s++[r]
f=unlines.map(map(\((l,u),t,(r,d))->case()of _|t==z->'.'|maximum[u,l,t,r,d]==t->t|0<1->'-')).g.lines

f is a funtion that takes a string in the form 0001\n0000\n0000\n1000 and returns the required string.

g is a function that takes a list of lists of chars and returns a list of lists of ((left, up), this, (right, down)).

Jmac

Posted 2015-02-18T15:56:28.133

Reputation: 111