# 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:

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-------


# 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.

Input:

898778765432100
787667654321100
677656543211210
678765432112321
567654321123210


which looks like this in graphical form:

Output:

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


Input:

7898
8787
7676
6565


Output:

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


Input:

00001
00000
00000
10000


Output:

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

• Do you mind if I add 2 heatmap graphics to your question if you think they add value? They are just a 2 minute experiment. Feb 18, 2015 at 16:32
• @CarpetPython Sure, go ahead. They look very nice to me. You can also add a "Courtesy of CarpetPython" to give yourself the credit. ;) Feb 18, 2015 at 16:34
• Done. No credit required, but I thought it would be rude not to ask before editing. Feb 18, 2015 at 16:40
• Why not allow the input as a 2-dimensional array instead of a string? Feb 18, 2015 at 18:32
• @feersum generally input methods are consistent. Feb 18, 2015 at 18:37

# 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

• I must say, I seldom hear "too long" when describing CJam code. Feb 18, 2015 at 20:05

# 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---
----
----


# 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


# 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

# 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-------

• This can be 288 if you remove the space in char[]r=a[0].replace("|", <--here"").toCharArray(). Feb 19, 2015 at 20:10
• Did not spot that one, thanks! Well this makes 298 Feb 19, 2015 at 20:49

# 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-------

• The longest APL program I've ever seen. You might want to note that this is not standard APL as it uses dfns. Feb 19, 2015 at 10:26

# 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

# 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:
-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:
---------..1........
----------..........
---8-------......--.
----------......--2-
---------......-----
--------......------
-------......-------
.-----......-----6--
..---.......--------
...-.......-2-------
>


## 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 |).

# 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-------


## 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.

• I think you have 226 chars, not 227. Feb 20, 2015 at 21:44
• @w0lf you're right, the newline got counted for 2 since I'm on Windows. Feb 20, 2015 at 21:54

z='0'
g s=zipWith3(\u t d->zip3(zip(z:t)u)t$zip(tail t++[z])d)(r:s)s$tail s++[r]

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)).