34
$\begingroup$

Well we've done sudoku on keyboards, so the next step is obviously putting a Sudoku on a Mobius strip... of course...

I present, the world's very first MOBIUS SUDOKU:

enter image description here


Now this sudoku is going to be a bit different, because how you solve it, is up to you... You can build it, create an excel diagram, make a 3D model, solve on paper etc. Whatever works for you, feel free to go for it! As long as the answer shows an understandable solution path, then the method of solving is up to you.

Here is a link to a printable PDF version via google drive, assembly instructions included - PRINTABLE VERSION

(I highly recommend printing and building one to help with your solve)


enter image description here

Google Sheets version

(The left most square backs onto the right most square, and the middle squares back onto each other. The ends are then attached as indicated.)

RULES:

  • Normal sudoku rules apply for rows and boxes - boxes are split into groups of 3 indicated by the thicker lines.
  • Columns ‘wrap around’ over the edge of the mobius strip – 6 cells per column as a result, normal sudoku rules apply on these 6 cells.
  • No number will ever be orthogonally adjacent to itself, including across the thicker borders separating the sets of 3 boxes.
  • There are 18 total boxes, and as a result 18 total middle cells. There are 2 of each number, 1-9, in the middle cells.

Please provide a solution path in your answer! How you portray the solution is up to you, feel free to be as creative as you want, just please add some explanation of key points etc. I won’t necessarily accept the first answer, but the answer that best does this :)

Enjoy!!!


This puzzle has been translated into a CodeGolf challenge by @Taco タコス.

$\endgroup$
17
  • 1
    $\begingroup$ To clarify, this is solvable without printing, it will just probably be very difficult to wrap your head around without seeing it in front of you :) $\endgroup$ Commented Sep 20, 2021 at 17:23
  • 1
    $\begingroup$ @Tacoタコス hmm for once I’m torn on that one. It would be quite interesting to see how someone would solve this with code, considering a lot of the puzzle is figuring out the mechanics. I’m going to say I’d be happy for someone to solve this with code, but I’d request that they don’t post the solution until after someone’s posted a ‘normal’ answer. Feel free to go for it if you think you could solve that way! $\endgroup$ Commented Sep 20, 2021 at 18:02
  • 1
    $\begingroup$ @Tacoタコス if you do somehow manage to code this one out, feel free to post now :) $\endgroup$ Commented Sep 21, 2021 at 0:17
  • 1
    $\begingroup$ @Oliphaunt-reinstateMonica I can provide you with a link to a copy of it in google sheets if you want? The printable link had to be a pdf as I created it in word and when copying it into both google docs and word online the formatting completely messed up, but now solved I could def add a google sheets version $\endgroup$ Commented Sep 21, 2021 at 13:13
  • 1
    $\begingroup$ Can I make a code golf challenge out of this, with proper attribution of course. I’m sure other developers would also be interested in solving it, and I’m interested in seeing the many solutions available 🙃 I'm in chat if you prefer to talk there. $\endgroup$
    – Taco
    Commented Sep 21, 2021 at 15:18

2 Answers 2

18
$\begingroup$

First, build a spreadsheet. This has links surrounding it so that I can see where things will be relative to each other.

Then

we start with 9s (since that's the commonest number in the grid)
step 1
Then 3s and 4s:
step 2
Then more hunting in promising areas (2 missing) and their derivatives:
step 3
Another area came out pretty well:
step 4
Tricksy around the paper step. U6:U7 must contain a 4, so I11 can't be a 4:
step 5
I'm sure you all caught my earlier deliberate error with the 57:
step 6
Then we use the fact that there are already 2 central 7s to disambiguate the central 67:
step 7
Which helps get the whole row out:
step 8
Now 6s are relatively abundant on the grid, so we search and find a 6 that resolves. Near that are a few more answers (using the fact that there were already 2 middle 5s to get the highlighted 8:
step 9
We're able to continue on this, using the fact that there are already 2 middle 7s. This gives us a middle 1, which allows us to resolve another 3:
step 10
More sudoku tricks. Here that the 2s in the right block resolve the highlighted 12:
step 11
Only 4 and 9 can be in the middle (they're the only ones with 1 middle square), so:
step 12
More sudoko-ing:
step 13
And with a few more steps (including the no adjacent sameness over the border) and we're done!:
step 14

$\endgroup$
5
  • $\begingroup$ Very nice puzzle, by the way. Amazing idea. Felt like I was going in loops... $\endgroup$
    – Dr Xorile
    Commented Sep 20, 2021 at 23:09
  • $\begingroup$ Correct of course! You also used the same method I used while test solving which I thinks probably the easiest way of solving. Did you need to build it out of interest? Tick coming soon! $\endgroup$ Commented Sep 21, 2021 at 0:16
  • $\begingroup$ I sat and thought for a few minutes about the easiest way to solve it and decided that a spreadsheet was going to be the easiest way to see all the related components. I don't know if that answers your question? I didn't need to build it in the hunter-gatherer sense of the word. No rush on the tick. I know the deal :-) $\endgroup$
    – Dr Xorile
    Commented Sep 21, 2021 at 2:32
  • $\begingroup$ Impressed that you managed to put the spreadsheet together without having a strip in front of you! Even building this puzzle using a spreadsheet with about 3 prototypes in front of me I still got it wrong :P Glad you enjoyed! $\endgroup$ Commented Sep 21, 2021 at 2:43
  • 3
    $\begingroup$ Oh, sorry. I see what you mean. Yeah, I didn't build it. I let the spreadsheet handle the inverting with some judicious OFFSET, ROW, and COLUMN formulae (I think visible in the link). I also had it do the counting for me, which was especially useful for keeping track of the number of middle cells, which would otherwise make me go squint. $\endgroup$
    – Dr Xorile
    Commented Sep 21, 2021 at 6:07
13
$\begingroup$

I thought I'd try my hand at writing a program for Mobius sudoku. I used the Z3 SMT solver (Python wrapper) for this. There isn't much of a "solution path" to describe: most of the constraints translate fairly directly, even the wrapping around of the columns. The main difficulty was

constraining the middle cells such that each number 1-9 appeared exactly twice. The z3.AtMost and z3.AtLeast methods don't work on symbolic expressions, so you can't say, e.g., exactly ("at most and at least") 2 of the middle cells should equal 1. I resorted to enforcing terms like sum(middle_cells) == sum(one_through_nine_twice), sum(middle_cells_squared) == sum(one_through_nine_twice_squared), and so on for increasing exponents. I think Z3 had some issues with the values getting too large, as it would return Unsatisfiable if I included terms with powers greater than 11. This worked to get the solution to this instance, and 11 terms is likely enough for how constrained the numbers here are, but I imagine there's a cleverer way to do this. EDIT - See @FliegendeWurst's better constraint for this in the comments

The other constraint which required some care was:

"No number will ever be orthogonally adjacent to itself, including across the thicker borders separating the sets of 3 boxes." I.e. if you go left/right/up/down 1 cell from a number, you won't find the same number. The column constraint already takes care of up/down; we really only need to check left/right at the borders between groups of blocks (since the row constraints already take care of this within block groups), but it doesn't hurt to check left/right distinctness everywhere. Checking right-distinctness suffices (left-distinctness would just duplicate the constraints), but we have to make sure to include the connections between the two strips (which are flipped vertically).

Here's the Python code:

 import numpy as np
 from z3 import And, Distinct, Int, Solver

 strip1_vals = [
     [4, 0, 1, 0, 5, 3, 7, 0, 2, 0, 8, 1, 0, 9, 7, 0, 0, 2, 6, 0, 2, 8, 0, 9, 0, 3, 0],
     [0, 7, 0, 4, 0, 0, 6, 0, 0, 6, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 9, 0, 2, 0, 6, 0, 4],
     [9, 0, 0, 0, 2, 0, 0, 1, 8, 2, 0, 3, 0, 0, 6, 0, 1, 4, 0, 7, 0, 0, 0, 3, 0, 0, 2],
 ]
 strip2_vals = [
     [1, 0, 4, 0, 0, 5, 0, 3, 7, 1, 9, 0, 0, 0, 4, 0, 0, 8, 5, 0, 0, 9, 0, 0, 4, 3, 0],
     [0, 6, 0, 0, 0, 9, 8, 0, 0, 0, 0, 8, 3, 0, 0, 4, 5, 0, 0, 8, 0, 0, 0, 3, 0, 0, 7],
     [3, 7, 0, 1, 4, 0, 0, 9, 0, 3, 0, 5, 1, 0, 8, 9, 0, 0, 4, 0, 9, 6, 0, 2, 0, 0, 1],
 ]

 strip1 = np.array(
     [[Int(f"s0r{row}c{col}") for col in range(27)] for row in range(3)]
 )
 strip2 = np.array(
     [[Int(f"s1r{row}c{col}") for col in range(27)] for row in range(3)]
 )
 # 3x3 squares
 blocks = [*np.split(strip1, 9, axis=1), *np.split(strip2, 9, axis=1)]
 block_groups = [*np.split(strip1, 3, axis=1), *np.split(strip2, 3, axis=1)]
 cols = np.concatenate([np.fliplr(strip1), strip2]).T
 # connect the two strips together, add the start of strip1 at the end
 # so the constraint wraps around
 rows = np.concatenate(
     (strip1, np.flipud(np.fliplr(strip2)), strip1[:, :1]), axis=1
 ).T
 centers = [block[1, 1] for block in blocks]
 mid_vals = [*range(1, 10), *range(1, 10)]

 constraints = {
     "one_through_nine": [
         And((1 <= c), (c <= 9)) for c in [*strip1.flat, *strip2.flat]
     ],
     "boxes_distinct": [Distinct(*block.flat) for block in blocks],
     "rows_distinct": [
         Distinct(*row)
         for block_group in block_groups for row in block_group
     ],
     "cols_distinct": [Distinct(*col) for col in cols],
     "right_distinct" : [
         Distinct(*left_right)
         for row in rows for left_right in zip(row[:-1], row[1:])
     ],
     "middle_cells": [
         sum(c ** i for c in centers) == sum(c ** i for c in mid_vals)
         for i in range(1, 11)
     ],
     "filled_entries": [
         *[
             strip1[i, j] == strip1_vals[i][j]
             for i in range(len(strip1))
             for j in range(len(strip1[0]))
             if strip1_vals[i][j] != 0
         ],
         *[
             strip2[i, j] == strip2_vals[i][j]
             for i in range(len(strip2))
             for j in range(len(strip2[0]))
             if strip2_vals[i][j] != 0
         ],
     ],
 }

 solver = Solver()
 for cs in constraints.values():
     solver.add(*cs)
 solver.check()
 assignment = solver.model()
 strip1_sol = np.array(
     [
         [assignment.evaluate(entry).as_long() for entry in row]
         for row in strip1
     ]
 )
 strip2_sol = np.array(
     [
         [assignment.evaluate(entry).as_long() for entry in row]
         for row in strip2
     ]
 )

 for row in strip1_sol:
     print("||", end=" ")
     for i, c in enumerate(row):
         print(c, end=" ")
         if i % 9 == 8:
             print("||", end=" ")
         elif i % 3 == 2:
             print("|", end=" ")
     print()

 print()
 for row in strip2_sol:
     print("||", end=" ")
     for i, c in enumerate(row):
         print(c, end=" ")
         if i % 9 == 8:
             print("||", end=" ")
         elif i % 3 == 2:
             print("|", end=" ")
     print()
 

And the final solution:

Sudoku solution

$\endgroup$
7
  • 1
    $\begingroup$ Oh wow someone did manage with code! Good job, didn’t think it would be possible :) $\endgroup$ Commented Sep 21, 2021 at 13:09
  • 2
    $\begingroup$ I already had a Sudoku solver written similar to this from some time ago, so it was fun to revisit that with a twist =) thanks for the puzzle! $\endgroup$
    – Nathan
    Commented Sep 21, 2021 at 13:26
  • $\begingroup$ This is the route I was going to take but with C# 😂 +1 $\endgroup$
    – Taco
    Commented Sep 21, 2021 at 14:30
  • $\begingroup$ @Tacoタコス Ah, I was wondering what your approach would have been. Sorry if I sniped using z3 / an SMT solver from you. Did you have any better solutions for the middle cells? $\endgroup$
    – Nathan
    Commented Sep 21, 2021 at 15:12
  • 1
    $\begingroup$ My solution for the middle cells was: for num in range(1, 10): s.add(sum(If(cells[y][x] == num, 1, 0) for y in (1, 4) for x in range(1, 26, 3)) == 2) (essentially calculating how often each number appears and constraining that sum to 2) $\endgroup$ Commented Sep 22, 2021 at 8:58

Not the answer you're looking for? Browse other questions tagged or ask your own question.