Generate a grid of sums
APL, 53 49 43 42 40 39 36
I managed to replicate J's ;.
in APL, and used Gareth's approach, saving 13 chars.
{×5⌷⍵:5⌷⍵⋄+/⍵}¨3,⌿3,/×∘?∘9¨∘.∨⍨9⍴0 1
Sample run:
{×5⌷⍵:5⌷⍵⋄+/⍵}¨3,⌿3,/×∘?∘9¨∘.∨⍨9⍴0 1
9 9 6 1 7 5 6
7 55 5 39 9 54 9
9 8 2 1 8 1 9
2 43 8 41 6 42 5
7 3 4 4 8 3 2
2 29 1 26 2 35 8
6 4 2 3 2 3 7
Explanation:
∘.∨⍨9⍴0 1
generates a bit mask.×∘?∘9¨
multiplies each bit by a random value from 1 to 9 inclusive, generating a masked grid of random numbers.3,⌿3,/
uses what can only be described as hackery to return all 3 by 3 overlapping boxes in the masked array. These are also flattened in the process.{×5⌷⍵:5⌷⍵⋄+/⍵}¨
iterates over the array, assigning each element to⍵
. For each iteration, it takes the fifth (middle, remembering that APL indexing is 1-based), and returns its sign. In this case this is equivalent to testing if the number is greater than 0. If this returns 1 (for true), then return that element. Otherwise, return the sum of the elements in the flattened 3 by 3 box. It uses the:⋄
ternary operator, which is the equivalent of?:
in many languages.
J, 63 61 59 55 52 51 49 47 39 37 characters
3 3(4&{+4{*|+/)@,;._3(**1+?)+./~9$0 9
With thanks to Volatility for his 10 character saving.
Explanation (each step will have different random numbers...):
Generate the mask for generating the random numbers (uses $
:
9 9$9$0 9
0 9 0 9 0 9 0 9 0
0 9 0 9 0 9 0 9 0
0 9 0 9 0 9 0 9 0
0 9 0 9 0 9 0 9 0
0 9 0 9 0 9 0 9 0
0 9 0 9 0 9 0 9 0
0 9 0 9 0 9 0 9 0
0 9 0 9 0 9 0 9 0
0 9 0 9 0 9 0 9 0
Now we have a hook. This is actually a happy accident from when I was whittling down an earlier version. It was meant to be transpose |:
and OR +.
with the original. It made sense since I was using ones and zeroes at the time, but now I have nines and zeroes. It just so happens that it works in the same way with the GCD meaning of +.
. Lucky for me. :-)
(+.|:)9 9$9$0 9
0 9 0 9 0 9 0 9 0
9 9 9 9 9 9 9 9 9
0 9 0 9 0 9 0 9 0
9 9 9 9 9 9 9 9 9
0 9 0 9 0 9 0 9 0
9 9 9 9 9 9 9 9 9
0 9 0 9 0 9 0 9 0
9 9 9 9 9 9 9 9 9
0 9 0 9 0 9 0 9 0
So, now that we have a grid of 9s and 0s we want to generate some random numbers. ?
generates a random number from 0 up to (but not including) a given number. Given a list it will generate one random number in this way for each member of the list. So in this case it will generate a number from 0 to 8 for every 9 in the table and a floating point number from 0 to 1 for each 0.
?(+.|:)9 9$9$0 9
0.832573 7 0.926379 7 0.775468 6 0.535925 3 0.828123
7 0 5 5 4 3 4 5 4
0.0944584 2 0.840913 2 0.990768 1 0.853054 3 0.881741
3 8 7 0 8 3 3 4 8
0.641563 4 0.699892 7 0.498026 1 0.438401 6 0.417791
6 8 7 5 2 3 6 6 3
0.753671 6 0.487016 4 0.886369 7 0.489956 5 0.902991
3 4 7 8 1 4 8 0 8
0.0833539 4 0.311055 4 0.200411 6 0.247177 5 0.0464731
But we want numbers from 1 to 9 not 0 to 8. So we add 1.
(1+?)(+.|:)9 9$9$0 9
1.4139 4 1.7547 7 1.67065 4 1.52987 1 1.96275
2 8 2 4 3 9 6 9 9
1.15202 7 1.11341 5 1.0836 1 1.24713 2 1.13858
9 3 3 2 4 7 3 8 6
1.06383 9 1.67909 4 1.09801 8 1.4805 6 1.0171
9 5 5 5 9 5 9 4 3
1.22819 1 1.85259 4 1.95632 6 1.33034 3 1.39417
4 2 5 1 3 7 2 5 6
1.06572 5 1.9942 5 1.78341 5 1.16516 6 1.37087
This is very nice but we've lost the zeroes that I want, so we'll multiply it by the original mask after turning all the nines into ones. I do this by checking if the value is greater than 1. That gives us: (1&<*1+?)
.
There are a couple of things going on here:
- We've created a fork which allows us to pack a lot of work into very few characters.
- We've bonded (
&
) the 1 to the<
verb.
So all combined the (1&<*1+?)
is generating random numbers, and zeroing all the numbers which where generated by zeroes in the original grid.
(1&<*1+?)(+.|:)9 9$9$0 9
0 3 0 2 0 7 0 1 0
9 5 2 7 7 1 4 5 7
0 6 0 8 0 3 0 1 0
4 8 7 5 9 7 7 9 4
0 9 0 6 0 9 0 9 0
6 1 2 1 4 6 8 9 4
0 3 0 8 0 6 0 6 0
2 5 2 2 2 2 3 9 3
0 9 0 3 0 5 0 3 0
The next bit is the (in my opinion, anyway :-) clever bit.
The cut ;.
verb has a form x u;._3 y
which cuts the input into boxes described by x
, and then applies the verb u
to them. In this case we have 3 3(4&{++/*0=4&{)@,;._3
.
- The
3 3
is describing the boxes we want - 3x3. - The
(4&{++/*0=4&{)@,
is a verb train which describes what we want to do to each box.
To demonstrate the ;.
verb I'll use <
to show each box:
3 3(<);._3(1&<*1+?)(+.|:)9 9$9$0 9
┌─────┬─────┬─────┬─────┬─────┬─────┬─────┐
│0 8 0│8 0 7│0 7 0│7 0 4│0 4 0│4 0 3│0 3 0│
│9 1 3│1 3 2│3 2 3│2 3 8│3 8 5│8 5 5│5 5 9│
│0 6 0│6 0 1│0 1 0│1 0 2│0 2 0│2 0 4│0 4 0│
├─────┼─────┼─────┼─────┼─────┼─────┼─────┤
│9 1 3│1 3 2│3 2 3│2 3 8│3 8 5│8 5 5│5 5 9│
│0 6 0│6 0 1│0 1 0│1 0 2│0 2 0│2 0 4│0 4 0│
│7 1 6│1 6 7│6 7 1│7 1 2│1 2 1│2 1 6│1 6 1│
├─────┼─────┼─────┼─────┼─────┼─────┼─────┤
│0 6 0│6 0 1│0 1 0│1 0 2│0 2 0│2 0 4│0 4 0│
│7 1 6│1 6 7│6 7 1│7 1 2│1 2 1│2 1 6│1 6 1│
│0 7 0│7 0 5│0 5 0│5 0 9│0 9 0│9 0 7│0 7 0│
├─────┼─────┼─────┼─────┼─────┼─────┼─────┤
│7 1 6│1 6 7│6 7 1│7 1 2│1 2 1│2 1 6│1 6 1│
│0 7 0│7 0 5│0 5 0│5 0 9│0 9 0│9 0 7│0 7 0│
│7 9 9│9 9 7│9 7 1│7 1 9│1 9 4│9 4 9│4 9 5│
├─────┼─────┼─────┼─────┼─────┼─────┼─────┤
│0 7 0│7 0 5│0 5 0│5 0 9│0 9 0│9 0 7│0 7 0│
│7 9 9│9 9 7│9 7 1│7 1 9│1 9 4│9 4 9│4 9 5│
│0 3 0│3 0 2│0 2 0│2 0 7│0 7 0│7 0 9│0 9 0│
├─────┼─────┼─────┼─────┼─────┼─────┼─────┤
│7 9 9│9 9 7│9 7 1│7 1 9│1 9 4│9 4 9│4 9 5│
│0 3 0│3 0 2│0 2 0│2 0 7│0 7 0│7 0 9│0 9 0│
│3 1 6│1 6 1│6 1 7│1 7 6│7 6 8│6 8 9│8 9 9│
├─────┼─────┼─────┼─────┼─────┼─────┼─────┤
│0 3 0│3 0 2│0 2 0│2 0 7│0 7 0│7 0 9│0 9 0│
│3 1 6│1 6 1│6 1 7│1 7 6│7 6 8│6 8 9│8 9 9│
│0 9 0│9 0 3│0 3 0│3 0 4│0 4 0│4 0 3│0 3 0│
└─────┴─────┴─────┴─────┴─────┴─────┴─────┘
Some things to notice:
- The boxes overlap - the second and third columns in the top left box are the first and second in the box to the right of it.
- There are 7x7 boxes. That's why we had a 9x9 grid initially.
- Each place we require a sum has a
0
at the box's centre.
Now we just need to either pass the value at the centre back (if it's non-zero) or sum the numbers in the 3x3 box (if the centre is zero).
To do this we need easy access to the centre number. ,
helps here. It turns the 3x3 grid into a list of 9 items with the centre number at number 4.
4&{
will use {
to pull out the centre value and then compare it with 0: 0=4&{
. This returns a 0
or 1
for true or false, which we then multiply by the sum +/
. If it was zero at the centre we now have our sum as required. If it was not we have zero, so to finish off we just add the centre value 4&{+
.
This gives the verb train (4&{++/*0=4&{)@,
3 3(4&{++/*0=4&{)@,;._3(1&<*1+?)(+.|:)9 9$9$0 9
2 6 9 3 7 9 7
3 47 6 51 5 49 5
3 9 9 6 6 2 8
7 48 6 47 1 37 5
5 4 5 7 7 2 6
5 35 3 49 8 51 9
1 6 6 6 7 4 8
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 calculatingj + 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 involvingputs 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 becomeeach_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}