Going to do a little bit of rapid fire posting on this subject. Some rapid, and
not so much rapid. Planning on some code and some ranting.
--------
This is one of those things that I have been meaning to talk about for a very long
time. But I have been refraining because it hits a little to close to some of the
other things that I have been working on. But I think I'm finally far enough
removed from those things to give this a chat.
When I first started looking into making my own sudoku squares, I was only interested
in the final solution. I hit Google and I started reading everything. Unfortunately,
none of it struck my fancy. Fortunately, the lack of anything interesting sent me down
a completely different path.
(Yes, I take issue with the ways that other folks make sudoku grids. I could totally
nerd rant about this for hours.)
One day I was on lunch at my regular job. I pulled out my little book of sudoku and
started doing the zen thing. I was looking for a transform in the numbers that I
could use and that wasn't a same-old-same-old transform that I had already read
about so many times.
Those values can be swapped and the grid remains valid. I had found a transform
that wasn't a simple row/col swap. I could so totally use this to mix-up a sudoku
grid in a way that isn't... uninteresting.
* I later found out that is called The Deadly Pattern. The bane of many sudoku
players/creators. I am comletely befuddled as to why this hasn't been exploited
in other ways before. Herm.
I whipped out Terminal and Geany. Started doing the Python thing. OMG looking
for those was a lot harder than I expected. Not hard exactly, just difficult
for me to see the interators required - and they are *deep*. It took me about two
weeks to get it all straight. But once I did, I was off and running.
I threw in some known grids, did a bunch of Deadly Pattern swaps, and compared.
Worked like a charm. Damn near all of the static patterns in the original grid
were completely gone. This is so far removed from doing row/col swaps and
other "regular/mundane" transforms.
Happy Happy Joy Joy
As good as that is, it still left me with having to come up with a starting
point. I mean, I didn't want to keep tossing in known grids because that would
defeat the purpose of creating my own grids. I could now shuffle like a mofo, but
I still needed that initial starting point.
Enter: Putting the Pattern In
Instead of putting in someone else's grid, I came up a template that is rife
with The Deadly Pattern.
Maybe not a ton of TDP, but that's the most that I can pack in given the size
of the grid. There are a total of 36 in that little gem. And I can create that
using pure code, or just hard-code it in. Now, I usually don't like to hard-code
such things, but no prob in this case because the pattern in that pattern is in it's
purest and reduced form.
If you scan that template for TDP, you will find 36 of them. However, you will
only be able to do 9 of them at the most. Why? Because when you swap some TDP,
you will invalidate other TDPs.
If you swap one of those TDPs, then the other one is no longer a TDP. Make sense?
Something you will have to take into account if you decide to implement.
But there is no reason to stop at 9 at the most. After you do the first round
of scanning and swapping, do it another round of scanning and swapping. And again
and again, almost as many times as you want.
Yeah, *almost* as many times as you want. One thing that threw me for a loop was
letting my code do the same swaps over and over again.
code:
. 0 . 4 . .
. 4 . 0 . .
. . . . . .
If you swaps those two values for those two blocks, don't swap them again. My first
trial with this, I had 3 comps running for 9 hours, and I was expecting awesomeness.
However, I got next to nowhere because my code was swapping one round, then unswapping
the next. Once I threw the swaps into a signature list and pruned against that, I
got the awesomeness that I was hoping for.
I was also topping-out the swaps. Just because it's fun, take a look at some data:
R# = the round number for scanning and swapping
F = how many TDPs found
SP = TDPs found pruned against signature list
SD = how many swaps were actually done
RT = running total of swaps done
Followed by the final output.
At the very end, just after it tops-out, notice that two TDPs are found. However,
they are pruned out because those two swaps had already been done. Without the
signatures, just running and running in circles (3 comps @ 9 hrs each in my
case, yech).
Where it tops-out is *highly* variable. I've seen some top-out at 15, and
others at around 30. That is way too much variance for me to want to do the math
on. I can't imagine trying to build that tree.
I've had this coded for quite a long time, but I haven't done any real speed
tests. I know it's less than 1s, and that's good enough for me. And that's
using Python without any optimizing at all.
Okay, so here is some code that starts with the template and does the
Deadly Pattern thing.
I completely stripped the code of all non-essentials (including comments)
and hard-coded a few parts here-n-there. Noticed that the only import is
'random' and the rest is stock Python.
Another thing to notice is that my main data structure is 4d. I did this
because interating over a block is so much easier than doing 2d acrobatics.
Egads, I can't imagine trying to do this in 2d. This is why I titled this
thread 3x3x3x3.
One thing that I did not include is a way to validate that the state is
a valid sudoku grid. They are all valid because of the way that this
algorithm works. No need to validate. Boom.
Another thing that I did not include is permutations.... to an extent.
No row/col swaps, mirroring, et al. I'll get into this later. However,
I did include value re-mapping because it is inconsquential to do so
on several levels. Value re-mapping is whatever in terms of code and
symbols. Completely cheeky, so why not?
I'll get into the other perms later. Things like rol/col swaps, mirroring,
flips, and all that. Later. Nerd rant me thinks.
Pretty much do whatever with this code and/or algorithm in general.
Give it a few runs, modify, and share. I think it would be great if
somebody implemented in their own way and really gave it a go.
code:
#!/usr/bin/env python
import random
dr_inter=30
def cout(cme):
bseq=['1','2','3','4','5','6','7','8','9']
random.shuffle(bseq)
f=''
print('')
for by in range(3):
for cy in range(3):
c=''
for bx in range(3):
for cx in range(3):
c=c+bseq[cme[by][bx][cy][cx]]
f=f+bseq[cme[by][bx][cy][cx]]
c=c+' '
print(c)
print('')
print(f)
print('')
return
def deadly_scan(drgrid):
cas=[[0, 1], [0, 2], [1, 2]]
sdr=[]
for by in range(3):
for cy in cas:
cy0=cy[0]
cy1=cy[1]
for bx in cas:
bx0=bx[0]
bx1=bx[1]
for cx0 in range(3):
for cx1 in range(3):
t0=drgrid[by][bx0][cy0][cx0]
t1=drgrid[by][bx1][cy1][cx1]
t2=drgrid[by][bx0][cy1][cx0]
t3=drgrid[by][bx1][cy0][cx1]
if (t0==t1) and (t2==t3):
if t0<t2:
sdr.append([ by,bx0,cy0,cx0, by,bx1,cy1,cx1, t0,t2 ])
else:
sdr.append([ by,bx0,cy0,cx0, by,bx1,cy1,cx1, t2,t0 ])
for bx in range(3):
for cx in cas:
cx0=cx[0]
cx1=cx[1]
for by in cas:
by0=by[0]
by1=by[1]
for cy0 in range(3):
for cy1 in range(3):
t0=drgrid[by0][bx][cy0][cx0]
t1=drgrid[by1][bx][cy1][cx1]
t2=drgrid[by0][bx][cy0][cx1]
t3=drgrid[by1][bx][cy1][cx0]
if (t0==t1) and (t2==t3):
if t0<t2:
sdr.append([ by0,bx,cy0,cx0, by1,bx,cy1,cx1, t0,t2 ])
else:
sdr.append([ by0,bx,cy0,cx0, by1,bx,cy1,cx1, t2,t0 ])
return sdr
def scan_dr(mgrid):
dr_sig=[]
for i in range(dr_inter):
rlist=[]
rlist2=deadly_scan(mgrid)
random.shuffle(rlist2)
for dr in rlist2:
if dr not in dr_sig:
rlist.append(dr)
rmask=[[[[1,1,1],[1,1,1],[1,1,1]],[[1,1,1],[1,1,1],[1,1,1]],[[1,1,1],[1,1,1],[1,1,1]]],
[[[1,1,1],[1,1,1],[1,1,1]],[[1,1,1],[1,1,1],[1,1,1]],[[1,1,1],[1,1,1],[1,1,1]]],
[[[1,1,1],[1,1,1],[1,1,1]],[[1,1,1],[1,1,1],[1,1,1]],[[1,1,1],[1,1,1],[1,1,1]]]]
for dr in rlist:
by0=dr[0];bx0=dr[1]
cy0=dr[2];cx0=dr[3]
by1=dr[4];bx1=dr[5]
cy1=dr[6];cx1=dr[7]
val0=dr[8];val1=dr[9]
if by0==by1:
m0=rmask[by0][bx0][cy0][cx0]
m1=rmask[by1][bx1][cy1][cx1]
m2=rmask[by0][bx0][cy1][cx0]
m3=rmask[by1][bx1][cy0][cx1]
mbit=m0&m1&m2&m3
if mbit:
nval0=mgrid[by0][bx0][cy0][cx0]
nval2=mgrid[by0][bx0][cy1][cx0]
mgrid[by0][bx0][cy0][cx0]=nval2
mgrid[by1][bx1][cy1][cx1]=nval2
mgrid[by0][bx0][cy1][cx0]=nval0
mgrid[by1][bx1][cy0][cx1]=nval0
rmask[by0][bx0][cy0][cx0]=0
rmask[by1][bx1][cy1][cx1]=0
rmask[by0][bx0][cy1][cx0]=0
rmask[by1][bx1][cy0][cx1]=0
dr_sig.append(dr)
else:
m0=rmask[by0][bx0][cy0][cx0]
m1=rmask[by1][bx1][cy1][cx1]
m2=rmask[by0][bx0][cy0][cx1]
m3=rmask[by1][bx1][cy1][cx0]
mbit=m0&m1&m2&m3
if mbit:
nval0=mgrid[by0][bx0][cy0][cx0]
nval2=mgrid[by0][bx0][cy0][cx1]
mgrid[by0][bx0][cy0][cx0]=nval2
mgrid[by1][bx1][cy1][cx1]=nval2
mgrid[by0][bx0][cy0][cx1]=nval0
mgrid[by1][bx1][cy1][cx0]=nval0
rmask[by0][bx0][cy0][cx0]=0
rmask[by1][bx1][cy1][cx1]=0
rmask[by0][bx0][cy0][cx1]=0
rmask[by1][bx1][cy1][cx0]=0
dr_sig.append(dr)
return mgrid
def main():
state=[[[[1,0,5],[6,4,2],[3,8,7]],[[4,3,8],[0,7,5],[6,2,1]],[[7,6,2],[3,1,8],[0,5,4]]],
[[[2,1,3],[7,5,0],[4,6,8]],[[5,4,6],[1,8,3],[7,0,2]],[[8,7,0],[4,2,6],[1,3,5]]],
[[[0,2,4],[8,3,1],[5,7,6]],[[3,5,7],[2,6,4],[8,1,0]],[[6,8,1],[5,0,7],[2,4,3]]]]
state=scan_dr(state)
cout(state)
return 0
if __name__ == '__main__':
main()
Let's start with back-tracking to create sudoku grids. I do not take
issue with back-tracking in general. It has it's uses. For example,
to create mazes. It can also be a quick way to come up with valid
solutions for further study. However, I do take issue with using
back-tracking as the only way to create sudoku grids. It is a good
starting point, but stopping at back-tracking does not suit me at
all. Why? Because sudoku has rules and patterns, and that is what
I am after. Insight and deeper understanding. For me, back-tracking
is a starting point and not an end.
And now permutations. Yes and no. But mostly no.
When perming a sudoku grid, you can swap rows and columns on two
levels. You can swap row/col of entire blocks. And you can swap row/col
of cells within the same block. I'm going to assume that this is
evident or easy enough to not show any explicit examples of this.
However, let's take a look at some notes to show some of the reasons
why I take issue with how some folks use row/col swaps.
There are 3 blocks. Within each block, there are 3 cells. So, let's
talk about shuffling 3 things.
I couldn't tell you how many times I read people saying to do bunches
and bunches of row/col swaps. But you are dealing with 3 things at
a time and doing bunches-n-bunches of swaps is a waste of time. That is,
more swaps of 3 things is not going to mix things up even more. Ugh!
Simple perm of 3 things:
code:
(0,1,2) (1,0,2) (2,0,1)
(0,2,1) (1,2,0) (2,1,0)
No matter how many times you do swaps, you are going to end up with
one of those solutions. Cut to the chase and go straight to one of
those solutions. It's a small set and no prob at all to hard-code.
And one thing that people tend to leave out is the fact that the
original starting point is a possibility in the final output. No
matter how many times you do this-n-that, you can still end up right
back where you started. More ugh!
You have 3 things and you want to shuffle them.
Rule #1: you can't have the orininal as a solution
Rule #2: all values must be different
code:
(0,1,2) original
(1,2,0) valid
(2,0,1) valid
If those are your rules, then only two valid solutions.
(Do those nums as a group look familiar? There is another avenue in
there to be explored. Heh.)
You have 3 things and you want to shuffle them.
Rule #1: you can't have the original as a solution
Rule #2: at least two must be different
code:
------- (1,0,2) (2,0,1)
(0,2,1) (1,2,0) (2,1,0)
And there they are. Same as the perms above sans original.
Refine your chaos, people. Give it a hint of shape. Just a
tad is all it takes.
My data structure for sudoku grids is 4d. Doing those row/col swaps
is ridiculously easy compared to doing the same to a 2d structure.
What about re-mapping or perming the values? Trivial and cheeky.
I see so many people getting caught-up in the symbols themselves.
It's not the symbols that matter when it comes to the kind of math
I enjoy doing the most. What matters are their properties as a group
and/or their relationships.
"Just change all of the values and you've got a whole new sudoku
puzzle!"
No.
"Just do a bunch of rol/col swaps, flip it, mirror it, and you've got
a whole new puzzle!"
Again, no.
Even with all of that, the relationships remain the same.
So, why bother with row/col swaps and re-mapping values? Human perception,
my friends. If you use the right kind of chaos in the right places,
it will, at a glance, look like a very different puzzle. Even though all
of the relationships will remain the same, how many humans will notice?
I think this would make for an interesting social experiment.
Let's back up a little bit to row/col swaps and talk about flipping,
mirroring, and rotating.
Flipping on either axis is the same as a specific set of row/col swaps.
Hopefully evident without explicit examples. Yes?
What about mirroring along a diagonal or rotating 90? Let's take a look.
If you look closely enough, you will see that each one of those operations
is a permutation of the others. But none can be achieved using regular
row/col swaps of the original. What does this mean? It means that just
one of the four operations is all it takes to do something that regular
row/col swaps can't do. One is all it takes.
I say give it a 50/50 chance that you do one of those operations. The
easiest to implement being (x,y)=(y,x).
Which to do first? Rol/col swaps or (x,y)=(y,x)? It doesn't matter.
Think about it for a minute.
So, why didn't I include these kind of transforms in the code that I
posted? No real need because of the DP transform that my code uses. And
because I just plain didn't want to just yet. Just yet.
Oh, crap. There is something that I forgot in the above code. I have the variable dr_inter as a
global at the top. I originally had this a parameter passed to the sub-routine, but moved it
because it was so much easier to change it at the top. Might want to change that back to
passed parameter if you feel the need.
(BTW, I was a bit tipsy when I wrote that last post. Amazing that I can still write halfway
decently after a pint of whiskey and a few lagers.)
Personally, I really don't feel the need to use row/col swaps.
But here's the sub to do it:
code:
def shuffle_sudo_4d(sgrid):
rs=[[0,2,1],[1,0,2],[1,2,0],[2,0,1],[2,1,0]]
nby=random.choice(rs)
nbx=random.choice(rs)
ncy=[random.choice(rs)]
ncy.append(random.choice(rs))
ncy.append(random.choice(rs))
ncx=[random.choice(rs)]
ncx.append(random.choice(rs))
ncx.append(random.choice(rs))
tgrid=[[[[0,0,0],[0,0,0],[0,0,0]],[[0,0,0],[0,0,0],[0,0,0]],[[0,0,0],[0,0,0],[0,0,0]]],
[[[0,0,0],[0,0,0],[0,0,0]],[[0,0,0],[0,0,0],[0,0,0]],[[0,0,0],[0,0,0],[0,0,0]]],
[[[0,0,0],[0,0,0],[0,0,0]],[[0,0,0],[0,0,0],[0,0,0]],[[0,0,0],[0,0,0],[0,0,0]]]]
rotbit=random.choice([0,1])
for by in range(3):
for bx in range(3):
for cy in range(3):
for cx in range(3):
if rotbit:
tgrid[by][bx][cy][cx]=sgrid[nby[by]][nbx[bx]][ncy[by][cy]][ncx[bx][cx]]
else:
tgrid[bx][by][cx][cy]=sgrid[nby[by]][nbx[bx]][ncy[by][cy]][ncx[bx][cx]]
return tgrid
Quick-n-dirty hard-code. No big deal.
One last thing that I want to touch on.
See when I get back.
If you were to do all of those row/col swaps and a value re-map, it would
still be valid. As a matter of fact, the solution would pretty much follow
the same path with regards to the relationships.
I do think that this would make for an interesting social experiment. Like
on Monday, Wednesday, and Friday, do the row/col swaps and value re-map, and
then post it as a new puzzle. See how long before someone notices and says
something.
The other thing that I want to touch on is The Deadly Pattern. It sure is
fun. That was all it took and I came up with a way to make sudoku grids
that no one else has even come close to. One simple little pattern.
However, TDP can be generalized. It really is far more versatile and exploitable
than what I have presented here.
Simple, goofy, and perhaps even elegant.
To me, this is Shaking the Pillars of Heaven.
"Questions?" LOL sure ... Can you make a 9x9x9 3D sudoku using numbers 1 to 9 where each layer in x, y and z is a different valid classic sudoku puzzle so basically 27 different valid puzzles in one 9x9x9 grid? Without a single one repeating including no repetitions of rotated variants. It should only have one solution obviously but what is the minimum number of prefilled cells that still allow for only one solution? I wouldn't put it past you that you've done it already but who can honestly say they understand everything you've been talking about
Also is it possible to calculate how many truly different and valid such 9x9x9 puzzles can exist?
I have tossed this idea around a bit quite some time ago. Some of the
ideas that I have used in similiar work apply very well to this.
Instead of thinking of a full sudoku cube, let's take a look at just
one of the sub-cubes. You know, one of the 3x3x3 chunks. Any slice in
any direction will have exactly 1 of each number. So, let's stack up
some X's like so:
code:
X.. ... ...
... .X. ...
... ... ..X
If you stack those on top of each other into a cube, you will have one
X in each plane in each direction. I think of them as 3d transversals.
Know what I mean?
Each number has to have that property in each sub-cube. Fit nine of those
bad boys into a 3x3x3 space. The only way that I know how to do that is
with a latin cube and an orthogonal.
Those first 3 squares, when stacked, form a latin cube. Any plane in any
direction is a latin square. Next, we have a latin square that is orthogonal
to the first three squares. Putting that together, you can create a single
sub-cube for a sudoku cube. We now have 0-8 in a cube. Anyway slice you
take from that cube in any direction will be 0-8.
Now we need two more cubes made almost exactly the same way. The only we
are going to change is the orthogonal. Check it:
We now have 3 sub-cubes that we can assemble into a full-sized sudoku
cube.
Let's go back to the latin cube:
code:
012 120 201
201 012 120
120 201 012
So, that exists in 3d space. Where you see a 0, but in cube0. Where you
see a 1, put in cube1. Where you see a 2, put in cube2. Tada. Instant
sudoku cube.
But is it fun or is it boring?
Let's take a look at the sudoku at z=0:
Wait...that's not z=0. That is actually z=3 and it is rotated 90 degrees.
Not rotated as a whole, but the sub-cubes in-place. Yes, you can rotate
in-place and things will still remain valid.
Going to take the top row, just (0,4,8), and rotate them. Watch this:
See? still valid. And holds true for all of the numbers.
Pretty cool, but can be annoying. There have been more than a few times
when I have accidentally mixed-up an axis here-n-there. But, because
remains valid, might not even notice. One day I mixed-up some indexing
while playing with orthogonals and it took me over 2 weeks to notice
because the validity remained intact.
Fun or boring? And bear in mind that all of the planes in the cube will
look almost exactly like that.
At a glance, I would call it boring. But I can see all sorts of
patternliciousness in there just waiting to be taken advantage of. And
those patterns exist in all 3 dimensions. Can they be manipulated? Can
they be broken?
So, possible combinations and all that. I have no clue. The tree of
possibilities gets too big too quickly for me. I do know that some folks
were working on the Min17 problem and completely reduced regular sudoku
grids. No clue how they did it.
Minimum clues with one solution? I imagine somewhere around 150. But that's
a wild guess based on Min17.
I might play with a sudoku cube to see if I can break it. You know, just
to see if I can make each plane unique. Maybe.
Cobbled this together last night. Then I spend the morning trying to
update matplotlib for ortho rendering. Lots of hard coding with notes
left in. I even left some bonus code commented out in the main data
loop. Wanna see a plane? No prob. Wanna see just the 0's? No prob.
This is my first time actually touching code for this. When it comes to
things like this, I work so much faster and better with graph paper and
pencil. I used 3 sheets so far! OMG! Maybe later I'll bust out my dry
erases board (I found my markers finally). Some of my notes actually
consist of photographs of my dry erase board. No tech like low tech.
code:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.set_proj_type('ortho')
#372 480 561
#804 615 723
#156 237 048
#615 723 804
#237 048 156
#480 561 372
#048 156 237
#561 372 480
#723 804 615
c0=[[[3,7,2],[8,0,4],[1,5,6]],[[4,8,0],[6,1,5],[2,3,7]],[[5,6,1],[7,2,3],[0,4,8]]]
c1=[[[6,1,5],[2,3,7],[4,8,0]],[[7,2,3],[0,4,8],[5,6,1]],[[8,0,4],[1,5,6],[3,7,2]]]
c2=[[[0,4,8],[5,6,1],[7,2,3]],[[1,5,6],[3,7,2],[8,0,4]],[[2,3,7],[4,8,0],[6,1,5]]]
#012 120 201
#201 012 120
#120 201 012
tcube=[[[0,1,2],[2,0,1],[1,2,0]],[[1,2,0],[0,1,2],[2,0,1]],[[2,0,1],[1,2,0],[0,1,2]]]
mcube=[[[c0,c1,c2],[c2,c0,c1],[c1,c2,c0]],[[c1,c2,c0],[c0,c1,c2],[c2,c0,c1]],[[c2,c0,c1],[c1,c2,c0],[c0,c1,c2]]]
#ax.text(0,0,0, '0')
nscale=3
buff=1
for bz in range(3):
for by in range(3):
for bx in range(3):
for cz in range(3):
for cy in range(3):
for cx in range(3):
#nv=mcube[bz][by][bx][cz][cy][cx]
#if nv==0:
#nz=(bz*3+cz+buff*bz)*nscale
#ny=(by*3+cy+buff*by)*nscale
#nx=(bx*3+cx+buff*bx)*nscale
#ax.text(nx,ny,nz, nv)
if by==0 and cy==0:
nv=mcube[bz][by][bx][cz][cy][cx]
nz=(bz*3+cz+buff*bz)*nscale
ny=(by*3+cy+buff*by)*nscale
nx=(bx*3+cx+buff*bx)*nscale
ax.text(nx,ny,nz, nv)
vmax=(2*3+2+buff*2)*nscale+1
ax.set_xlim(-1, vmax)
ax.set_ylim(-1, vmax)
ax.set_zlim(-1, vmax)
ax.set_xlabel('X axis')
ax.set_ylabel('Y axis')
ax.set_zlabel('Z axis')
plt.show()
How did I come up with that in no time at all? Because I've done the 2d
version a long time ago. Plus I have been playing with latin squares and
orthos on-n-off for a few years. Interesting little basterds, aren't
they?
The very first square is just a regular 3x3 latin square. The one to the
right of it, the one in the middle, is just the first square with +1%3.
And the last square, top row on the right, is another +1%3 (or +2%3 on
the first square).
And then we have the ortho all by itself in it's own row. It is orthogonal
to the first square. And, because of how the top row was created, it is
also orthogonal to the other two in the top row. That lonely little ortho
is orthogonal to all squares in the top row. Pretty slick, eh?
So, what's the deal? Well, going to use that lonely little ortho as a mask
of sorts. Copy the nums, in a matter of speaking, and put it all together.
Latin squares, ortho, and modulo magic = awesome sudo sub-cube.
But I made 3 of these, and all I changed was the ortho. For changing the
ortho, all I did was the exact same +1%3 trick that I used for the first
latin cube.
These tricks are simple. Baubles, really. And yet something awesome is
popping out of the other end. This is why I love the original KPT. A
small handfull of simple tricks put together in an amazing way.
And I did the above on graph paper. That still cracks me up.
Small pieces of understanding making something so much more.
Love it.
So, I was trying to get some sleep, but my brain wouldn't shut-up.
There is a neat little pattern trick with latin squares of size 3. I
kept running the vectors through my head for a latin cube of size 3.
Turns out that the pattern trick holds true even in 3d.
This pattern trick solves a problem that I had a long time ago. And
it solves a problem with previous sudoku cube.
And this trick is wicked easy.
The sudoku cube that this code produces is pretty much mathematically
identical to the previous, but it is largely 'unshuffled', and it is *so*
much easier to see the patterns.
The 2d nugget looks like this:
code:
ny=(cy+bx)%3
nx=(cx+by)%3
The magic little 3d nugget looks like this:
code:
nz=(cz+by)%3
ny=(cy+bx)%3
nx=(cx+bz)%3
Just some shifting with modulo. As a matter of fact, that is how I made
c0 in the following code.
If you look at the planes of c0, it should be easy to see that all I did
was shift to the right and down. Which is funny because c0 is actually
a latin cube with bits... it's just presented differently.
code:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.set_proj_type('ortho')
c0=[[[0,1,2],
[3,4,5],
[6,7,8]],
[[8,6,7],
[2,0,1],
[5,3,4]],
[[4,5,3],
[7,8,6],
[1,2,0]]]
# 3x3x3
mcube=[[[0,0,0],[0,0,0],[0,0,0]],
[[0,0,0],[0,0,0],[0,0,0]],
[[0,0,0],[0,0,0],[0,0,0]]]
# build each sub-cube and toss it in
for bz in range(3):
for by in range(3):
for bx in range(3):
# 3x3x3
tcube=[[[0,0,0],[0,0,0],[0,0,0]],
[[0,0,0],[0,0,0],[0,0,0]],
[[0,0,0],[0,0,0],[0,0,0]]]
for cz in range(3):
for cy in range(3):
for cx in range(3):
nz=(cz+by)%3
ny=(cy+bx)%3
nx=(cx+bz)%3
tcube[cz][cy][cx]=c0[nz][ny][nx]
mcube[bz][by][bx]=tcube
#ax.text(0,0,0, '0')
nscale=3
buff=1
for bz in range(3):
for by in range(3):
for bx in range(3):
for cz in range(3):
for cy in range(3):
for cx in range(3):
nv=mcube[bz][by][bx][cz][cy][cx]
#if nv==0:
#nz=(bz*3+cz+buff*bz)*nscale
#ny=(by*3+cy+buff*by)*nscale
#nx=(bx*3+cx+buff*bx)*nscale
#ax.text(nx,ny,nz, nv)
if bz==0 and cz==0:
nv=mcube[bz][by][bx][cz][cy][cx]
nz=(bz*3+cz+buff*bz)*nscale
ny=(by*3+cy+buff*by)*nscale
nx=(bx*3+cx+buff*bx)*nscale
ax.text(nx,ny,nz, nv)
vmax=(2*3+2+buff*2)*nscale+1
ax.set_xlim(-1, vmax)
ax.set_ylim(-1, vmax)
ax.set_zlim(-1, vmax)
ax.set_xlabel('X axis')
ax.set_ylabel('Y axis')
ax.set_zlabel('Z axis')
plt.show()
I thought that this solved one of my old problems, but it doesn't.
And I thought that a particular pattern being in a sudoku cube would be good, but it's not.
That is as far as I can take sudoku cubes. Adding a third dimension puts too much
constraint on the whole thing. No matter how you cut it, it will always look the same.