Ruby (135 characters)
a=(0..48).map{rand(9)+1}
([0,0,j=8]*3).each{|l|a[j]=[0,1,6,7,8].inject{|s,e|s+a[j+e]+a[j-e]};j+=l+2}
a.each_slice(7){|r|puts"%-3s"*7%r}
Sample output
2 1 6 9 4 5 1
9 34 4 37 2 31 3
7 2 3 1 8 1 7
5 42 4 40 2 47 9
3 9 9 4 9 4 7
3 44 4 41 2 47 4
6 9 1 5 7 6 8
Breakdown
It's not too obvious how this works, so here's a quick breakdown. NOTE: You can probably skip some of these steps and jump to shorter versions more quickly, but I think it's educational enough to see different ways I shaved off characters, especially by spotting patterns in literals to turn 2-digit numbers into 1-digit versions.
Naive version
Unlike the other Ruby solutions that rely on a two-dimensional array, you can (eventually) get a shorter version by starting with a 1-dimensional array and working with offset values, since the patterns repeat.
ary=(0..48).map { rand(9) + 1 }
offsets = [-8,-7,-6,-1,1,6,7,8]
3.times do |i|
[8,10,12].each do |j|
ary[j + 14*i] = ary.values_at(*offsets.map { |e| j+14*i + e }).inject(:+)
end
end
ary.each.with_index do |e,i|
$> << ("%-3s" % e)
$> << ?\n if i % 7==6
end
The key principle here is that we're working at index positions 8, 10, 12, just offset by multiples of 14. Positions 8, 10 and 12 are the centers of the 3x3 grids that we're summing up. In the sample output, 34 is position 8, 42 is position 8 + 14*1, etc. We replace position 8 with 34 by positions offset from position 8 by [-8,-7,-6,-1,1,6,7,8]
— in other words 34 = sum(ary[8-8], ary[8-7], ..., ary[8+8])
. This same principle holds for all the values of [8 + 14*i, 10 + 14*i, 12 + 14*i]
, since the pattern repeats.
Optimising it
First, some quick optimisations:
- Instead of
3.times { ... }
, and calculating j + 14*i
each time, "inline" the positions [8,10,12,22,24,26,36,38,40]
.
- The
offsets
array is used once, so replace the variable with the literal.
- Replace
do ... end
with {...}
and switch around the printing to $> << foo
. (There's a trick here involving puts nil
and () == nil
.)
- Shorter variable names.
The code after this is 177 characters:
a=(0..48).map{rand(9)+1}
[8,10,12,22,24,26,36,38,40].each{|j|a[j]=a.values_at(*[-8,-7,-6,-1,1,6,7,8].map{|e|j+e}).inject(:+)}
a.map.with_index{|e,i|$><<"%-3s"%e<<(?\nif i%7==6)}
For the next reduction, note that the inject
doesn't need the offsets array to be in order. We can either have [-8,-7,-6,-1,1,6,7,8]
or some other ordering, since addition is commutative.
So first pair up the positives and the negatives to get [1,-1,6,-6,7,-7,8,-8]
.
Now you can shorten
[1,-1,6,-6,7,-7,8,-8].map { |e| j+e }.inject(:+)
to
[1,6,7,8].flat_map { |e| [j+e, j-e] }
This results in
a=(0..48).map{rand(9)+1}
[8,10,12,22,24,26,36,38,40].each{|j|a[j]=a.values_at(*[1,6,7,8].flat_map{|e|[j+e,j-e]}).inject(:+)}
a.map.with_index{|e,i|$><<"%-3s"%e<<(?\nif i%7==6)}
which is 176 characters.
Shift by 8 and move to differences
The two-character literal values seem like they can be shortened away, so take [8,10,12,22,24,26,36,38,40]
and shift everything down by 8
, updating j
at the start of the loop. (Note that +=8
avoids needing to update the offset values of 1,6,7,8
.)
a=(0..48).map{rand(9)+1}
[0,2,4,14,16,18,28,30,32].each{|j|j+=8;a[j]=a.values_at(*[1,6,7,8].flat_map{|e|[j+e,j-e]}).inject(:+)}
a.map.with_index{|e,i|$><<"%-3s"%e<<(?\nif i%7==6)}
This is 179, which is bigger, but the j+=8
can actually be removed.
First change
[0,2,4,14,16,18,28,30,32]
to an array of differences:
[2,2,10,2,2,10,2,2]
and cumulatively add these values to an initial j=8
. This will eventually cover the same values. (We could probably skip straight to this instead of shifting by 8 first.)
Note that we'll also add a dummy value of 9999
to the end of the differences array, and add to j
at the end, not the start of the loop. The justification is that 2,2,10,2,2,10,2,2
looks awfully close to being the same 3 numbers repeated 3 times, and by computing j+difference
at the end of the loop, the final value of 9999
won't actually affect the output, since there isn't a a[j]
call where j
is some value over 10000
.
a=(0..48).map{rand(9)+1}
j=8
[2,2,10,2,2,10,2,2,9999].each{|l|a[j]=a.values_at(*[1,6,7,8].flat_map{|e|[j+e,j-e]}).inject(:+);j+=l}
a.map.with_index{|e,i|$><<"%-3s"%e<<(?\nif i%7==6)}
With this differences array, the j+=8
is now just j=8
, of course, since otherwise we'd repeatedly add 8
too many. We've also changed the block variable from j
to l
.
So since the 9999
element has no effect on the output, we can change it to 10
and shorten the array.
a=(0..48).map{rand(9)+1}
j=8
([2,2,10]*3).each{|l|a[j]=a.values_at(*[1,6,7,8].flat_map{|e|[j+e,j-e]}).inject(:+);j+=l}
a.map.with_index{|e,i|$><<"%-3s"%e<<(?\nif i%7==6)}
This is 170 characters.
But now the j=8
looks a bit clunky, and you can save 2 characters by shifting [2,2,10]
down by 2 to conveniently get an 8
you can use for assignment. This also needs j+=l
to become j+=l+2
.
a=(0..48).map{rand(9)+1}
([0,0,j=8]*3).each{|l|a[j]=a.values_at(*[1,6,7,8].flat_map{|e|[j+e,j-e]}).inject(:+);j+=l+2}
a.map.with_index{|e,i|$><<"%-3s"%e<<(?\nif i%7==6)}
This is 169 characters. A round-about way to squeeze 7 characters, but it's neat.
Final tweaks
The values_at
call is actually sort of redundant, and we can inline an Array#[]
call. So
a.values_at(*[1,6,7,8].flat_map{|e|[j+e,j-e]}).inject(:+)
becomes
[1,6,7,8].flat_map{|e|[a[j+e],a[j-e]]}.inject(:+)
You can also spot that flat_map
+ j+e/j-e
+ inject
can be reduced to a more direct summation with an initial 0
in the array.
This leaves you with 152 characters:
a=(0..48).map{rand(9)+1}
([0,0,j=8]*3).each{|l|a[j]=[0,1,6,7,8].inject{|s,e|s+a[j+e]+a[j-e]};j+=l+2}
a.map.with_index{|e,i|$><<"%-3s"%e<<(?\nif i%7==6)}
Finally:
map.with_index
can become each_slice
.
- Change the printing approach.
135:
a=(0..48).map{rand(9)+1}
([0,0,j=8]*3).each{|l|a[j]=[0,1,6,7,8].inject{|s,e|s+a[j+e]+a[j-e]};j+=l+2}
a.each_slice(7){|r|puts"%-3s"*7%r}