25
\$\begingroup\$

Task

Given input N, generate and output an NxN grid where each row, column and the two diagonals contain the numbers 1 to N (or 0 to N−1 if that's easier).

Input

The input is a positive integer N. It represents the number of columns and rows in the grid. For this problem, you can assume N will be a reasonable size, 4 ≤ N ≤ 8 or (1 ≤ N ≤ 8 if you go for the bonus below).

Output

The output will be the N×N grid. In the grid, each row only contains the numbers 1 to N, each column only contains the numbers 1 to N, and the two diagonals of length N (the one from (0,0) to (N-1,N-1) and the one from (0,N-1) to (N-1, 0)) only contain the numbers 1 to N. You can use the numbers 0 to N−1. For each N, there are many possible solutions, you only need to print the first one you find. You don't need to print spaces between the numbers.

Constraints

Your code should be able to repeatably produce results for N >= 7. That is, if you're able to actually run and get a solution for N = 7 from your code each time, you're good. In terms of an absolute limit, your code should be able to solve for N = 7 in under 10 minutes each time you run it (i.e., if you depend on random numbers, for the worst case, your code should still finish in under 10 minutes for N = 7).

Examples

  • Input: 4

    One possible output:

    1 2 3 4
    3 4 1 2
    4 3 2 1
    2 1 4 3
    
  • Input: 5

    One possible output:

    1 2 3 4 5
    5 3 1 2 4
    2 5 4 3 1
    4 1 2 5 3
    3 4 5 1 2
    
  • Input: 8

    One possible output:

    1 2 3 4 5 6 7 8
    2 3 1 5 4 8 6 7
    4 1 2 3 7 5 8 6
    6 4 7 8 1 2 3 5
    7 5 8 2 6 3 4 1
    5 8 4 6 2 7 1 3
    8 7 6 1 3 4 5 2
    3 6 5 7 8 1 2 4
    

Scoring

This is , so the shortest code in bytes wins, with one exception. For inputs N = 2, 3 there are no valid solutions. If your code can handles this (run to completion without outputting anything for these cases, or outputting an empty string), and still handles N = 1 (outputs 1 for it), take 20% off your byte count.

\$\endgroup\$
10
  • 1
    \$\begingroup\$ Related, but such a grid won't work here because of the diagonals requirement. \$\endgroup\$
    – xnor
    Commented Dec 20, 2015 at 0:57
  • \$\begingroup\$ I like this challenge but I can't figure out an algorithm for even values of N. This JavaScript code works for N = 1, 5 or 7 though if it helps anyone: for(o="",y=N;y--;o+="\n")for(x=N;x--;)o+=(((N-y)*2+x)%N)+1 \$\endgroup\$
    – user81655
    Commented Dec 20, 2015 at 7:01
  • \$\begingroup\$ Very related, possible duplicate: codegolf.stackexchange.com/q/47846/15599 \$\endgroup\$ Commented Dec 20, 2015 at 7:49
  • \$\begingroup\$ @steveverrill That wasn't code golf though. \$\endgroup\$
    – randomra
    Commented Dec 20, 2015 at 8:12
  • 1
    \$\begingroup\$ Maybe you should be more explicit about the N = 1 case: answers that aim for the bonus should return 1, not the empty string. \$\endgroup\$
    – lynn
    Commented Dec 21, 2015 at 0:47

8 Answers 8

22
\$\begingroup\$

Funciton, non-competitive

UPDATE! Massive performance improvement! n = 7 now completes in under 10 minutes! See explanation at bottom!

This was good fun to write. This is a brute-force solver for this problem written in Funciton. Some factoids:

  • It accepts an integer on STDIN. Any extraneous whitespace breaks it, including a newline after the integer.
  • It uses the numbers 0 to n − 1 (not 1 to n).
  • It fills the grid “backwards”, so you get a solution where the bottom row reads 3 2 1 0 rather than where the top row reads 0 1 2 3.
  • It correctly outputs 0 (the only solution) for n = 1.
  • Empty output for n = 2 and n = 3.
  • When compiled to an exe, takes about 8¼ minutes for n = 7 (was about one hour before the performance improvement). Without compiling (using the interpreter) it takes about 1.5 times as long, so using the compiler is worth it.
  • As a personal milestone, this is the first time I wrote a whole Funciton program without first writing the program in a pseudocode language. I did write it in actual C# first though.
  • (However, this is not the first time I made a change to massively improve the performance of something in Funciton. The first time I did that was in the factorial function. Swapping the order of the operands of the multiplication made a huge difference because of how the multiplication algorithm works. Just in case you were curious.)

Without further ado:

            ┌────────────────────────────────────┐           ┌─────────────────┐
            │                                  ┌─┴─╖ ╓───╖ ┌─┴─╖   ┌──────┐    │
            │                    ┌─────────────┤ · ╟─╢ Ӂ ╟─┤ · ╟───┤      │    │
            │                    │             ╘═╤═╝ ╙─┬─╜ ╘═╤═╝ ┌─┴─╖    │    │
            │                    │               └─────┴─────┘   │ ♯ ║    │    │
            │                  ┌─┴─╖                             ╘═╤═╝    │    │
            │     ┌────────────┤ · ╟───────────────────────────────┴───┐  │    │
          ┌─┴─╖ ┌─┴─╖   ┌────╖ ╘═╤═╝ ┌──────────┐         ┌────────┐ ┌─┴─╖│    │
          │ ♭ ║ │ × ╟───┤ >> ╟───┴───┘        ┌─┴─╖       │ ┌────╖ └─┤ · ╟┴┐   │
          ╘═╤═╝ ╘═╤═╝   ╘══╤═╝          ┌─────┤ · ╟───────┴─┤ << ╟─┐ ╘═╤═╝ │   │
    ┌───────┴─────┘ ┌────╖ │            │     ╘═╤═╝         ╘══╤═╝ │   │   │   │
    │     ┌─────────┤ >> ╟─┘            │       └───────┐      │   │   │   │   │
    │     │         ╘══╤═╝            ┌─┴─╖   ╔═══╗   ┌─┴─╖ ┌┐ │   │ ┌─┴─╖ │   │
    │     │           ┌┴┐     ┌───────┤ ♫ ║ ┌─╢ 0 ║ ┌─┤ · ╟─┤├─┤   ├─┤ Ӝ ║ │   │
    │     │   ╔═══╗   └┬┘     │       ╘═╤═╝ │ ╚═╤═╝ │ ╘═╤═╝ └┘ │   │ ╘═╤═╝ │   │
    │     │   ║ 1 ╟───┬┘    ┌─┴─╖       └───┘ ┌─┴─╖ │   │      │   │   │ ┌─┴─╖ │
    │     │   ╚═══╝ ┌─┴─╖   │ ɓ ╟─────────────┤ ? ╟─┘   │    ┌─┴─╖ │   ├─┤ · ╟─┴─┐
    │     ├─────────┤ · ╟─┐ ╘═╤═╝             ╘═╤═╝   ┌─┴────┤ + ╟─┘   │ ╘═╤═╝   │
  ┌─┴─╖ ┌─┴─╖       ╘═╤═╝ │ ╔═╧═╕ ╔═══╗ ┌───╖ ┌─┴─╖ ┌─┴─╖    ╘═══╝     │   │     │
┌─┤ · ╟─┤ · ╟───┐     └┐  └─╢   ├─╢ 0 ╟─┤ ⌑ ╟─┤ ? ╟─┤ · ╟──────────────┘   │     │
│ ╘═╤═╝ ╘═╤═╝   └───┐ ┌┴┐   ╚═╤═╛ ╚═╤═╝ ╘═══╝ ╘═╤═╝ ╘═╤═╝                  │     │
│   │   ┌─┴─╖ ┌───╖ │ └┬┘   ┌─┴─╖ ┌─┘           │     │                    │     │
│ ┌─┴───┤ · ╟─┤ Җ ╟─┘  └────┤ ? ╟─┴─┐   ┌─────────────┘                    │     │
│ │     ╘═╤═╝ ╘═╤═╝         ╘═╤═╝   │   │╔════╗╔════╗                      │     │
│ │       │  ┌──┴─╖ ┌┐   ┌┐ ┌─┴─╖ ┌─┴─╖ │║ 10 ║║ 32 ║    ┌─────────────────┘     │
│ │       │  │ << ╟─┤├─┬─┤├─┤ · ╟─┤ · ╟─┘╚══╤═╝╚╤═══╝ ┌──┴──┐                    │
│ │       │  ╘══╤═╝ └┘ │ └┘ ╘═╤═╝ ╘═╤═╝     │ ┌─┴─╖ ┌─┴─╖ ┌─┴─╖                  │
│ │     ┌─┴─╖ ┌─┴─╖  ┌─┴─╖  ┌─┴─╖ ╔═╧═╕     └─┤ ? ╟─┤ · ╟─┤ % ║                  │
│ └─────┤ · ╟─┤ · ╟──┤ Ӂ ╟──┤ ɱ ╟─╢   ├───┐   ╘═╤═╝ ╘═╤═╝ ╘═╤═╝                  │
│       ╘═╤═╝ ╘═╤═╝  ╘═╤═╝  ╘═══╝ ╚═╤═╛ ┌─┴─╖ ┌─┴─╖   │     └────────────────────┘
│         └─────┤      │            └───┤ ‼ ╟─┤ ‼ ║   │        ┌──────┐
│               │      │                ╘═══╝ ╘═╤═╝   │        │ ┌────┴────╖
│               │      │                      ┌─┴─╖   │        │ │ str→int ║
│               │      └──────────────────────┤ · ╟───┴─┐      │ ╘════╤════╝
│               │          ┌─────────╖        ╘═╤═╝     │    ╔═╧═╗ ┌──┴──┐
│               └──────────┤ int→str ╟──────────┘       │    ║   ║ │ ┌───┴───┐
│                          ╘═════════╝                  │    ╚═══╝ │ │ ┌───╖ │
└───────────────────────────────────────────────────────┘          │ └─┤ × ╟─┘
           ┌──────────────┐                                  ╔═══╗ │   ╘═╤═╝
╔════╗     │ ╓───╖ ┌───╖  │                              ┌───╢ 0 ║ │   ┌─┴─╖ ╔═══╗
║ −1 ║     └─╢ Ӝ ╟─┤ × ╟──┴──────┐                       │   ╚═╤═╝ └───┤ Ӂ ╟─╢ 0 ║
╚═╤══╝       ╙───╜ ╘═╤═╝         │                       │   ┌─┴─╖     ╘═╤═╝ ╚═══╝
┌─┴──╖ ┌┐ ┌───╖ ┌┐ ┌─┴──╖ ╔════╗ │                       │ ┌─┤   ╟───────┴───────┐
│ << ╟─┤├─┤ ÷ ╟─┤├─┤ << ║ ║ −1 ║ │                       │ │ └─┬─╜ ┌─┐ ┌─────┐   │
╘═╤══╝ └┘ ╘═╤═╝ └┘ ╘═╤══╝ ╚═╤══╝ │                       │ │   └───┴─┘ │   ┌─┴─╖ │
  │         └─┘      └──────┘    │                       │ └───────────┘ ┌─┤ ? ╟─┘
  └──────────────────────────────┘         ╓───╖         └───────────────┘ ╘═╤═╝
                               ┌───────────╢ Җ ╟────────────┐                │
      ┌────────────────────────┴───┐       ╙───╜            │
      │                          ┌─┴────────────────────┐ ┌─┴─╖
    ┌─┴─╖                      ┌─┴─╖                  ┌─┴─┤ · ╟──────────────────┐
    │ ♯ ║ ┌────────────────────┤ · ╟───────┐          │   ╘═╤═╝                  │
    ╘═╤═╝ │                    ╘═╤═╝       │          │     │              ┌───╖ │
┌─────┴───┘    ┌─────────────────┴─┐   ┌───┴───┐    ┌─┴─╖ ┌─┴─╖          ┌─┤ × ╟─┴─┐
│              │                 ┌─┴─╖ │   ┌───┴────┤ · ╟─┤ · ╟──────────┤ ╘═╤═╝   │
│              │ ┌───╖ ┌───╖  ┌──┤ · ╟─┘ ┌─┴─┐      ╘═╤═╝ ╘═╤═╝        ┌─┴─╖ │     │
│         ┌────┴─┤ ♭ ╟─┤ × ╟──┘  ╘═╤═╝   │ ┌─┴─╖ ┌───╖└┐ ┌──┴─╖      ┌─┤ · ╟─┘     │
│         │      ╘═══╝ ╘═╤═╝ ┌───╖ │     │ │ × ╟─┤ Ӝ ╟─┴─┤ ÷% ╟─┐    │ ╘═╤═╝ ┌───╖ │
│   ┌─────┴───┐     ┌────┴───┤ Ӝ ╟─┴─┐   │ ╘═╤═╝ ╘═╤═╝   ╘══╤═╝ │    │   └───┤ Ӝ ╟─┘
│ ┌─┴─╖ ┌───╖ │     │ ┌────╖ ╘═╤═╝   │   └───┘   ┌─┴─╖      │   │    └────┐  ╘═╤═╝
│ │ × ╟─┤ Ӝ ╟─┘     └─┤ << ╟───┘   ┌─┴─╖ ┌───────┤ · ╟───┐  │ ┌─┴─╖ ┌───╖ │    │
│ ╘═╤═╝ ╘═╤═╝         ╘══╤═╝   ┌───┤ + ║ │       ╘═╤═╝   ├──┴─┤ · ╟─┤ × ╟─┘    │
└───┤     └────┐ ╔═══╗ ┌─┴─╖ ┌─┴─╖ ╘═╤═╝ │ ╔═══╗ ┌─┴─╖ ┌─┴─╖  ╘═╤═╝ ╘═╤═╝      │
  ┌─┴─╖ ┌────╖ │ ║ 0 ╟─┤ ? ╟─┤ = ║  ┌┴┐  │ ║ 0 ╟─┤ ? ╟─┤ = ║    │     │ ┌────╖ │
  │ × ╟─┤ << ╟─┘ ╚═══╝ ╘═╤═╝ ╘═╤═╝  └┬┘  │ ╚═══╝ ╘═╤═╝ ╘═╤═╝    │     └─┤ << ╟─┘
  ╘═╤═╝ ╘═╤══╝ ┌┐     ┌┐ │     │     └───┘       ┌─┴─╖   ├──────┘       ╘═╤══╝
    │     └────┤├──┬──┤├─┘     ├─────────────────┤ · ╟───┘                │
    │          └┘┌─┴─╖└┘       │     ┌┐   ┌┐     ╘═╤═╝ ┌┐   ┌┐            │
    └────────────┤ · ╟─────────┘   ┌─┤├─┬─┤├─┐     └───┤├─┬─┤├────────────┘
                 ╘═╤═╝             │ └┘ │ └┘ │         └┘ │ └┘
                   └───────────────┘    │    └────────────┘

Explanation of the first version

The first version took about an hour to solve n = 7. The following explains mostly how this slow version worked. At the bottom I will explain what change I made to get it to under 10 minutes.

An excursion into bits

This program needs bits. It needs lots of bits, and it needs them in all the right places. Experienced Funciton programmers already know that if you need n bits, you can use the formula

2^n-1

which in Funciton can be expressed as

(1 << n) - 1

When doing my performance optimization, it occurred to me that I can calculate the same value much faster using this formula:

¬ (−1 << n)

I hope you’ll forgive me that I didn’t update all the equation graphics in this post accordingly.

Now, let’s say you don’t want a contiguous block of bits; in fact, you want n bits at regular intervals every k-th bit, like so:

                                 LSB
                                  ↓
00000010000001000000100000010000001
                            └──┬──┘
                               k

The formula for this is fairly straight-forward once you know it:

((1 << nk) - 1) / ((1 << k) - 1)

In the code, the function Ӝ takes values n and k and calculates this formula.

Keeping track of used numbers

There are n² numbers in the final grid, and each number can be any of n possible values. In order to keep track of which numbers are permitted in each cell, we maintain a number consisting of n³ bits, in which a bit is set to indicate that a particular value is taken. Initially this number is 0, obviously.

The algorithm begins in the bottom-right corner. After “guessing” the first number is a 0, we need to keep track of the fact that the 0 is no longer permitted in any cell along the same row, column and diagonal:

LSB                               (example n=5)
 ↓
 10000 00000 00000 00000 10000
 00000 10000 00000 00000 10000
 00000 00000 10000 00000 10000
 00000 00000 00000 10000 10000
 10000 10000 10000 10000 10000
                             ↑
                            MSB

To this end, we calculate the following four values:

  • Current row: We need n bits every n-th bit (one per cell), and then shift it to the current row r, remembering every row contains n² bits:

    ((1<<n²)−1)/((1<<n)−1) << n²r

  • Current column: We need n bits every n²-th bit (one per row), and then shift it to the current column c, remembering every column contains n bits:

    ((1<<n³)−1)/((1<<n²)−1) << n²r

  • Forward diagonal: We need n bits every... (did you pay attention? Quick, figure it out!)... n(n+1)-th bit (well done!), but only if we’re actually on the forward diagonal:

    ((1 << n²(n+1))−1) / ((1 << n(n+1))−1) if c = r

  • Backward diagonal: Two things here. First, how do we know if we’re on the backward diagonal? Mathematically, the condition is c = (n − 1) − r, which is the same as c = n + (−r − 1). Hey, does that remind you of something? That’s right, it’s two’s complement, so we can use bitwise negation (very efficient in Funciton) instead of the decrement. Second, the formula above assumes that we want the least significant bit to be set, but in the backward diagonal we don’t, so we have to shift it up by... do you know?... That’s right, n(n − 1).

    ((1 << n²(n-1))−1) / ((1 << n(n-1))−1) << n(n-1) if c = n + ¬r

    This is also the only one where we potentially divide by 0 if n = 1. However, Funciton doesn’t care. 0 ÷ 0 is just 0, don’t you know?

In the code, the function Җ (the bottom one) takes n and an index (from which it calculates r and c by division and remainder), calculates these four values and ors them together.

The brute-force algorithm

The brute-force algorithm is implemented by Ӂ (the function at the top). It takes n (the grid size), index (where in the grid we’re currently placing a number), and taken (the number with n³ bits telling us which numbers we can still place in each cell).

This function returns a sequence of strings. Each string is a full solution to the grid. It’s a complete solver; it would return all solutions if you let it, but it returns them as a lazy-evaluated sequence.

  • If index has reached 0, we’ve successfully filled the whole grid, so we return a sequence containing the empty string (a single solution that covers none of the cells). The empty string is 0, and we use the library function to turn that into a single-element sequence.

  • The check described under performance improvement below happens here.

  • If index has not yet reached 0, we decrement it by 1 to get the index at which we now need to place a number (call that ix).

    We use to generate a lazy sequence containing the values from 0 to n − 1.

    Then we use ɓ (monadic bind) with a lambda that does the following in order:

    • First look at the relevant bit in taken to decide whether the number is valid here or not. We can place a number i if and only if taken & (1 << (n × ix) << i) isn’t already set. If it is set, return 0 (empty sequence).
    • Use Җ to calculate the bits corresponding to the current row, column and diagonal(s). Shift it by i and then or it onto taken.
    • Recursively call Ӂ to retrieve all solutions for the remaining cells, passing it the new taken and the decremented ix. This returns a sequence of incomplete strings; each string has ix characters (the grid filled in up to index ix).
    • Use ɱ (map) to go through the solutions thusly found and use to concatenate i to the end of each. Append a newline if index is a multiple of n, otherwise a space.

Generating the result

The main program calls Ӂ (the brute forcer) with n, index = n² (remember we fill the grid backwards) and taken = 0 (initially nothing is taken). If the result of this is an empty sequence (no solution found), output the empty string. Otherwise, output the first string in the sequence. Note that this means it will evaluate only the first element of the sequence, which is why the solver doesn’t continue until it has found all solutions.

Performance improvement

(For those who already read the old version of the explanation: the program no longer generates a sequence of sequences that needs to be separately turned into a string for output; it just generates a sequence of strings directly. I’ve edited the explanation accordingly. But that wasn’t the main improvement. Here it comes.)

On my machine, the compiled exe of the first version took pretty much exactly 1 hour to solve n = 7. This was not within the given time limit of 10 minutes, so I didn’t rest. (Well, actually, the reason I didn’t rest was because I had this idea on how to massively speed it up.)

The algorithm as described above stops its search and backtracks every time that it encounters a cell in which all the bits in the taken number are set, indicating that nothing can be put into this cell.

However, the algorithm will continue to futilely fill the grid up to the cell in which all those bits are set. It would be much faster if it could stop as soon as any yet-to-be-filled-in cell already has all the bits set, which already indicates that we can never solve the rest of the grid no matter what numbers we put in it. But how do you efficiently check whether any cell has its n bits set without going through all of them?

The trick starts by adding a single bit per cell to the taken number. Instead of what was shown above, it now looks like this:

LSB                               (example n=5)
 ↓
 10000 0 00000 0 00000 0 00000 0 10000 0
 00000 0 10000 0 00000 0 00000 0 10000 0
 00000 0 00000 0 10000 0 00000 0 10000 0
 00000 0 00000 0 00000 0 10000 0 10000 0
 10000 0 10000 0 10000 0 10000 0 10000 0
                                       ↑
                                      MSB

Instead of n³, there are now n²(n + 1) bits in this number. The function that populates the current row/column/diagonal has been changed accordingly (actually, completely rewritten to be honest). That function will still populate only n bits per cell though, so the extra bit we just added will always be 0.

Now, let’s say we are halfway through the calculation, we just placed a 1 in the middle cell, and the taken number looks something like this:

                 current
LSB              column           (example n=5)
 ↓                 ↓
 11111 0 10010 0 01101 0 11100 0 11101 0
 00011 0 11110 0 01101 0 11101 0 11100 0
 11111 0 11110 0[11101 0]11100 0 11100 0    ← current row
 11111 0 11111 0 11111 0 11111 0 11111 0
 11111 0 11111 0 11111 0 11111 0 11111 0
                                       ↑
                                      MSB

As you can see, the top-left cell (index 0) and the middle-left cell (index 10) are now impossible. How do we most efficiently determine this?

Consider a number in which the 0th bit of each cell is set, but only up to the current index. Such a number is easy to calculate using the familiar formula:

((1 << (n+1)i) - 1) / ((1 << (n+1)) - 1)

What would we get if we added these two numbers together?

LSB                                               LSB
 ↓                                                 ↓
 11111 0 10010 0 01101 0 11100 0 11101 0           10000 0 10000 0 10000 0 10000 0 10000 0        ╓───╖
 00011 0 11110 0 01101 0 11101 0 11100 0     ║     10000 0 10000 0 10000 0 10000 0 10000 0            ║
 11111 0 11110 0 11101 0 11100 0 11100 0  ═══╬═══  10000 0 10000 0 00000 0 00000 0 00000 0  ═════   ╓─╜
 11111 0 11111 0 11111 0 11111 0 11111 0     ║     00000 0 00000 0 00000 0 00000 0 00000 0  ═════   ╨
 11111 0 11111 0 11111 0 11111 0 11111 0           00000 0 00000 0 00000 0 00000 0 00000 0          o
                                       ↑                                                 ↑
                                      MSB                                               MSB

The result is:

             OMG
              ↓
        00000[1]01010 0 11101 0 00010 0 00011 0
        10011 0 00001 0 11101 0 00011 0 00010 0
═════   00000[1]00001 0 00011 0 11100 0 11100 0
═════   11111 0 11111 0 11111 0 11111 0 11111 0
        11111 0 11111 0 11111 0 11111 0 11111 0

As you can see, the addition overflows into the extra bit that we added, but only if all the bits for that cell are set! Therefore, all that’s left to do is to mask out those bits (same formula as above, but << n) and check if the result is 0:

00000[1]01010 0 11101 0 00010 0 00011 0    ╓╖    00000 1 00000 1 00000 1 00000 1 00000 1         ╓─╖ ╓───╖
10011 0 00001 0 11101 0 00011 0 00010 0   ╓╜╙╖   00000 1 00000 1 00000 1 00000 1 00000 1        ╓╜ ╙╖    ║
00000[1]00001 0 00011 0 11100 0 11100 0   ╙╥╥╜   00000 1 00000 1 00000 0 00000 0 00000 0  ═════ ║   ║  ╓─╜
11111 0 11111 0 11111 0 11111 0 11111 0   ╓╜╙╥╜  00000 0 00000 0 00000 0 00000 0 00000 0  ═════ ╙╖ ╓╜  ╨
11111 0 11111 0 11111 0 11111 0 11111 0   ╙──╨─  00000 0 00000 0 00000 0 00000 0 00000 0         ╙─╜   o

If it is not zero, the grid is impossible and we can stop.

  • Screenshot showing solution and running time for n = 4 to 7.
\$\endgroup\$
2
  • 3
    \$\begingroup\$ HOLY FUCK. Dude, that's impressive. \$\endgroup\$
    – Deusovi
    Commented Dec 21, 2015 at 7:51
  • 1
    \$\begingroup\$ I second @Deusovi's comment, thank you for putting so much effort into this \$\endgroup\$ Commented Dec 21, 2015 at 23:06
7
\$\begingroup\$

Haskell, 790*0.80=632 bytes

import Data.List
import Control.Monad
import Data.Array
s r=let{h as bs=[(a,b)|a<-as,b<-bs];(&)m k=(\(Just x)->x)$lookup k m;j=Just;n=Nothing;c=[1..r];q=delete;u=h[1..r]c;o=[(s,[u |u<-[h[1..r][c]|c<-c]++[h[r]c|r<-[1..r]]++[zip[1..r][1..r],zip[1..r][r,r-1..1]],s`elem`u])|s<-u];k=foldr(>=>)j;a p d g0=k[t p d2|d2<-q d(g0!p)]g0;t p d g0|not(d`elem`(g0!p))=j g0|[]<-v=n|[d2]<-v=k[t s2 d2|s2<-[(s,delete s$nub(concat(o&s)))|s<-u]&p]g1|True=k[l[s|s<-u,not(d`elem`v)]|u<-o&p]g1 where{v=q d(g0!p);g1=g0//[(p,v)];l[]_=n;l[d3]g=a d3 d g;l _ r=j r};w g0|and[case g0!s of{[_]->True;_->False}|s<-u]=j g0|True=msum[a s' d g0>>=w|d<-g0!s']where(_,s')=minimumBy(\(a,_)(b,_)->compare a b)[(l,s)|s<-u,let v=g0!s;l=length v,l>1]}in fmap(fmap(\[x]->x))$w$array((1,1),(r,r))[((i,j),[1..r])|i<-[1..r],j<-[1..r]]

I noticed this problem is very similar to sudoku. I remember an old sudoku solver I wrote in Haskell based on this other one in Python. This is my first code golf post or attempt.

This fulfills the bonus because it returns Nothing for n=2,3 and Just <result> for n>=4, where <result> is a 2D array of integral values.

See here for online interpreter. That code is actually longer than the one in the post because the online interpreter has more strict requirements as to what forms a complete program (rules say a submission can be a function). This submission takes input as a function argument.

\$\endgroup\$
3
  • 1
    \$\begingroup\$ A few quick tips: a) you define c=[1..r], so you can use it within o and w. b) minimumBy(\(a,_)(b,_)->compare a b)[...] is head$sortOn fst[...]. c) the v in v=g0!s is only used once, so don't define it at all: l=length$g0!s. d) you still have some two letter parameter names. e) replace True with 1<2 and False with 2<1. f) and[case g0!s of{[_]->True;_->False}|s<-u] is all((==1).length.(g0!))u. \$\endgroup\$
    – nimi
    Commented Dec 20, 2015 at 14:53
  • \$\begingroup\$ Quick tips, part II: g) (&)m k= can be defined infix: m&k=. h) not(delem(g0!p)) is notElem d$g0!p. i) concat(...) is id=<<(...). j) use an infix operator for h, e.g. as%bs=. \$\endgroup\$
    – nimi
    Commented Dec 20, 2015 at 18:31
  • 3
    \$\begingroup\$ Quick meta tips: you can delimit code that has backticks in it correctly using double backticks ​``like`this``​! \$\endgroup\$
    – lynn
    Commented Dec 21, 2015 at 15:06
4
\$\begingroup\$

Pyth, 41 bytes

#Km.SQQI.AmqQl{d+.TK.Tm,@@Kdd@@Kt-QddQB;K
#                                      ;   # while(True)
 Km.SQQ                                    # K = random QxQ 2d list
       I                               ;   # if ...
        .A                                 # all of...
          m                          Q     # map(range(Q))...
                +                          # concat
                 .TK                       # transpose K
                    .Tm,@@Kdd@@Kt-Qdd      # diagonals of K
                      m             d      # map(range(d))
                       ,                   # 2-elem list of...
                        @@Kdd              # K[n][n]
                             @@Kt-Qd       # and K[len(K)-n-1][n]
                    .T                     # transpose
           qQl{d                           # subarrays have no dups...
                                      B;   # ... then, break
                                        K  # output final result

Brute force ftw!

Since this basically keeps trying random shuffles until it works (well, it keeps trying n * [shuffle(range(n))]), it takes a really, really long time. Here are some test runs to give you an idea of how long it takes:

llama@llama:~$ time echo 4 | pyth <(echo '#Km.SQQI.AmqQl{d+.TK.Tm,@@Kdd@@Kt-QddQB;K')               
[[2, 1, 0, 3], [0, 3, 2, 1], [3, 0, 1, 2], [1, 2, 3, 0]]
echo 4  0.00s user 0.00s system 0% cpu 0.001 total
pyth <(echo '#Km.SQQI.AmqQl{d+.TK.Tm,@@Kdd@@Kt-QddQB;K')  0.38s user 0.00s system 96% cpu 0.397 total

That's only 4x4, and it runs in a little under half a second. I'm actually cheating because this is the best out of a few trials—most of them take over a second or two.

I've yet to get a timing on 5x5 (it ran to completion once, but that was in a REPL and I wasn't timing it).

Note that the rule for the time limit was only edited into the question after this answer was posted.

\$\endgroup\$
6
  • \$\begingroup\$ I suppose this can't do 7x7 within ten minutes? ^^ \$\endgroup\$
    – lynn
    Commented Dec 20, 2015 at 15:53
  • \$\begingroup\$ @Mauris Well, sometimes it can... ;) Is that a requirement I missed? I don't see anything mentioning a time limit in the question. \$\endgroup\$
    – Doorknob
    Commented Dec 20, 2015 at 16:12
  • \$\begingroup\$ I see it in the comments, (not new comment, 12 hours ago) \$\endgroup\$
    – edc65
    Commented Dec 20, 2015 at 17:02
  • \$\begingroup\$ Sorry about that, I didn't think of it until someone mentioned it, I'll edit the challenge now \$\endgroup\$ Commented Dec 20, 2015 at 18:46
  • 1
    \$\begingroup\$ +1 for the abstract ASCII art in your commented version. :) \$\endgroup\$ Commented Dec 20, 2015 at 20:43
3
\$\begingroup\$

Python 3, 275 260 Bytes*0.8= 220 208 Bytes

Recursive/backtracking approach. R is the recursive function, l is the coLumn, w is the roW, K is the next entry.

I chose to put it in a 1d array and pretty-print it at the end to make indices simpler.

r=range
n=(int)(input())
def R(A,I):
 l=I%n;w=I//n
 if I==n*n:[print(A[i*n:i*n+n])for i in r(n)];exit()
 for K in r(n):
  if all([all([A[i*n+l]!=K,w!=l or A[i+n*i]!=K,w!=n-1-l or A[n*i+n-i-1]!=K])for i in r(w)]+[A[w*n+i]!=K for i in r(l)]):R(A+[K],I+1)
R([],0)

Ungolfed version:

def Recurse(A,I,n):
 column=I%n
 row=I//n
 if I==n*n:
     [print(*A[i*n:i*n+n])for i in range(n)] # output
     exit() #end recursion
 for K in range(n):
    # try every possibility. Test if they satisfy the constraints:
    # if so, move the index on. If none of them do, we'll return None.
    # if a child returns None, we'll move onto the next potential child:
    # if all of them fail it will backtrack to the next level.
    if all([
        all([
            A[i*n+column]!=K, # column constraint
            row!=column or A[i+n*i]!=K, # diagonal constraint
            row!=n-1-column or A[n*i+n-i-1]!=K # antidiagonal constraint
            ]) for i in range(row)
        ]+[
            A[row*n+i]!=K for i in range(column) # row constraint
            ]):
        Recurse(A+[K],I+1,n)

Recurse([],0,(int)(input()))
\$\endgroup\$
3
\$\begingroup\$

SWI-Prolog, 326*0.80 = 260.8 bytes

:-use_module(library(clpfd)).
a(N):-l(N,R),m(l(N),R),append(R,V),V ins 1..N,transpose(R,C),d(0,R,D),maplist(reverse,R,S),d(0,S,E),m(m(all_distinct),[R,C,[D,E]]),m(label,R),m(p,R).
l(L,M):-length(M,L).
d(X,[H|R],[A|Z]):-nth0(X,H,A),Y is X+1,(R=[],Z=R;d(Y,R,Z)).
p([H|T]):-write(H),T=[],nl;write(' '),p(T).
m(A,B):-maplist(A,B).

Edit: saved 16 bytes thanks to @Mat

Usage

Call a(5). in your interpreter for N=5. This returns false for N=2 or N=3.

Since it uses the CLPFD library this is not pure bruteforce. This program can find a solution for N=20 in about 15 seconds on my computer.

Ungolfed + explanations:

This basically works like a Sudoku solver, except that the blocks constraints are replaced with the diagonals constraints.

:-use_module(library(clpfd)).      % Import Constraints library

a(N):-
    l(N,R),                        % R is a list of length N
    maplist(l(N),R),               % R contains sublists, each of length N
    append(R,V),                   
    V ins 1..N,                    % Each value in the matrix is between 1 and N
    maplist(all_distinct,R),       % Values must be different on each row
    transpose(R,C),
    maplist(all_distinct,C),       % Values must be different on each column
    d(0,R,D),
    maplist(reverse,R,S),          
    d(0,S,E),
    all_distinct(D),               % Values must be different on the diagonal
    all_distinct(E),               % Values must be different on the "anti"-diagonal
    maplist(label,R),              % Affects actual values to each element
    maplist(p,R).                  % Prints each row

l(L,M):-length(M,L).               % True if L is the length of M

d(X,[H|R],[A|Z]):-nth0(X,H,A),Y is X+1,(R=[],Z=R;d(Y,R,Z)). % True if the third argument is the diagonal of the second argument

p([H|T]):-write(H),T=[],nl;write(' '),p(T).  % Prints a row separated by spaces and followed by a new line
\$\endgroup\$
6
  • \$\begingroup\$ Very nice! You can save a bytes with: maplist(maplist(all_distinct), [R,C,D,E]) \$\endgroup\$
    – mat
    Commented Dec 21, 2015 at 14:42
  • 1
    \$\begingroup\$ @mat Thanks for the suggestion, saves 16 bytes. I need to use [R,C,[D,E]] though, because E and D are simple lists. \$\endgroup\$
    – Fatalize
    Commented Dec 21, 2015 at 15:05
  • \$\begingroup\$ Right, very nice workaround! \$\endgroup\$
    – mat
    Commented Dec 21, 2015 at 15:08
  • 2
    \$\begingroup\$ @Fatalize Just to let you know, your solution was the most impressive as it is the only one that go solve N=20 \$\endgroup\$ Commented Dec 21, 2015 at 21:51
  • 1
    \$\begingroup\$ @Zequ Thanks! But that's mostly due to the amazing CLPFD library of Prolog, which does the heavy lifting in problems like these :) \$\endgroup\$
    – Fatalize
    Commented Dec 21, 2015 at 21:56
2
\$\begingroup\$

CJam, 87 bytes - 20% bonus = 69.6 bytes

qi__"@I/l
ŤˏūȻ
܀ᅀ൹৽჈͚
㑢鴑慚菥洠㬝᚜
"N/=:i0+\,m!f=`1LL](4e<=

Hardcodes the answers. Contains some unprintables. Works for N = 1 through N = 8.

Basically, each line in that mysterious string contains indices into the list of permutations of range(N), encoded as Unicode characters.

=:i0+\,m!f= indexes into the list of permutations, adding a 0 to the end of the list of indices first, representing the bottom row 0 1 2 ... N-1. For N < 4, the resulting 2D array is nonsense.

`1LL] creates a list [N, pretty output, 1, "", ""]. Then, (4e<= pops the first element out of this list, N, and retrieves the min(N, 4) % 4th element from the rest of it. For N >= 4, that's the output, and otherwise it's the special-case outputs for the first three cases.

Try it here.

\$\endgroup\$
0
\$\begingroup\$

C++, 672*0.80 645*0.80 = 516 bytes

#include <iostream>
int **b,**x,**y,*d,*a,n;
#define R(i) for(i=0;i<n;i++){
#define E(e) e=new int[n];
int f(int c,int r) {int i=0,p=c-1;if(r>=n)return 1;if(c == n + 1)return f(1,r+1);R(i)int m=r==i,s=r+i==n-1;if(!b[r][i]&&!x[r][p]&&!(m&&d[p])&&!(s&&a[p])&&!y[i][p]){b[r][i]=c;x[r][p]=1;y[i][p]=1;if(m)d[p]=1;if(s)a[p]=1;if(f(c+1,r))return 1;b[r][i]=0;x[r][p]=0;y[i][p]=0;if(m)d[p]=0;if(s)a[p]=0;}}return 0;}
int main(){std::cin>>n;int i=0,j=0;b=new int*[n];x=new int*[n];y=new int*[n];E(d);E(a);R(i)E(b[i]);E(x[i]);E(y[i]); d[i]=0;a[i]=0;R(j)b[i][j]=0;x[i][j]=0;y[i][j]=0;}}if(f(1,0)){R(i)R(j)std::cout<<b[i][j];}std::cout<<std::endl;}}}

Try it online here

Since a couple answers have already been posted, I thought I would post the golfed version of the code I used to generated the output for the examples. This is my first time answering a , so all feedback is welcomed. :)

Ungolfed + explanations:

Essentially, the code is brute-forcing a solution. It starts in the first row, with 0. It starts in the first spot, if that spots passes all the checks, it moves onto to the next number. If it fills the row, it moves onto the next row. If it's done all of the rows, that means a solution has been found. If the spot doesn't pass all of the checks, it moves onto the next spot. If it's done the row, then it backtracks, as a number in one of the previous rows prevents a solution from being possible.

#include <iostream>

// global variables to save bytes on passing these are function arguments
int **b, // this will store the state of the board
    **x, // if x[i][j] is true, row i of b contains the number j
    **y, // if y[i][j] is true, column i of b contains the number j
    *d,  // if d[i] the main diagonal of b contains i
    *a,  // if a[i] the antidiagonal of a contains i
    n;

// preprocessor to save bytes on repeated statements
#define R(i) for(i=0;i<n;i++){
#define E(e) e=new int[n];

// Recursively looks for a solution 
// c - the current number to insert in row r
// r - the current row to fill
int f (int c, int r) {
        int i=0,p=c-1;
        if (r >= n) return 1;             // we are done
        if (c == n + 1) return f(1,r+1);  // this row is full, move to the next row
        R(i)                              // go through the positions in this row,
                                                                            // trying to fill them with c
                int m=r==i, s=r+i==n-1;   // check if this position (r,i) is on ones
                                                                            // of the diagonals
                // if this spot isn't filled, and the row (r), column (i) and diagonals
                // (if it's on the diagonal) doesn't contain the number, fill the spot
                if (!b[r][i] && !x[r][p] && !(m&&d[p]) && !(s&&a[p]) && !y[i][p]) {
                        // fill the spot, and indicate that this row, column and diagonals 
                        // contain this number, c
                        b[r][i]=c; x[r][p]=1; y[i][p]=1;
                        if (m) d[p]=1; if (s)a[p]=1;

                        // move onto to the next number, if you find a solution, stop
                        if (f(c+1,r)) return 1;

                        // with this number in this spot, a solution is impossible, clear
                        // its, and clear the checks
                        b[r][i]=0; x[r][p]=0; y[i][p]=0;
                        if (m) d[p]=0; if (s) a[p]=0;
                }
        }

        return 0; // a solution wasn't found
}

int main() {
        std::cin >> n; // get n from STDIN

        // initialization 
        int i=0,j=0;
        b=new int*[n]; x=new int*[n]; y=new int*[n];
        E(d); E(a);
        R(i)
                E(b[i]); E(x[i]); E(y[i]); // initialization the inner arrays of b, x, y
                d[i]=0; a[i]=0;

                R(j)
                        b[i][j]=0; x[i][j]=0; y[i][j]=0; // ensure each point is initialized as 0
                }
        }

        // find a solution starting at the top-left corner and print it if it finds one
        if (f(1,0)) {
                R(i)
                        R(j)
                                std::cout<<b[i][j];
                        }
                        std::cout<<std::endl;
                }
        }
}
\$\endgroup\$
1
  • \$\begingroup\$ After rereading the code, I realized some of the checks may not be necessary, such as the if (x[r][p]) return f(c+1,r);. I'm working on shortening it. \$\endgroup\$ Commented Dec 20, 2015 at 20:41
0
\$\begingroup\$

Clojure, (215 + 258) * 0.8 = 378.4 (174 + 255) * 0.8 = 343.2

Split into two parts: error counting S and an anonymous function which does the actual optimization via Tabu search.

Update: shorter S (counts distinct values within groups), less optimal starting state (no shuffle).

(defn S[I n](count(mapcat set(vals(apply merge-with concat(flatten(for[R[(range n)]i R j R v[[(I(+(* n i)j))]]][{[1 i]v}{[2 j]v}(if(= i j){3 v})(if(=(- n 1 i)j){4 v})])))))))
#(if-let[s({1[0]2()3()}%)]s(loop[I(vec(for[R[(range %)]i R j R]i))P #{}](let[[s I](last(sort-by first(for[R[(range(count I))]i R j R I[(assoc(assoc I i(I j))j(I i))]:when(not(P I))][(S I %)I])))](if(=(*(+(* % 2)2)%)s)(partition % I)(recur I(conj P I))))))

Single core benchmarks (in milliseconds) for 4, 5, 6 and 7 run 3 times:

[[  131.855337   132.96267    138.745981]
 [ 1069.187325  1071.189488  1077.339372]
 [ 9114.736987  9206.65368   9322.656693]
 [36546.309408 36836.567267 36928.346312]]

Original:

(defn S[I n](apply +(flatten(for[p(concat(partition n I)(for[p(apply map vector(partition n(range(count I))))](map I p))[(take-nth(inc n)I)][(rest(butlast(take-nth(dec n)I)))])](remove #{1}(vals(frequencies p)))))))
#(if-let[s({1[0]2()3()}%)]s(loop[I(vec(flatten(map shuffle(repeat %(range %)))))P #{}](let[[s I](first(sort-by first(for[R[(range(count I))]i R j R I[(assoc(assoc I i(I j))j(I i))]:when(not(P I))][(S I %)I])))](if(= s 0)(partition % I)(recur I(conj P I))))))

I wish S was shorter, but because it only counts occurrences of more than one / partition the stopping criterion is simple (= s 0).

Many CPU cycles are wasted for non-useful swaps, for example it doesn't improve the score if you swap 2 with 2, and you don't need to swap numbers between rows as they all have distinct values to begin with.

Benchmarks with Intel 6700K (in milliseconds):

(defn S[I n]( ... )
(def F #( ... ))

(defmacro mytime[expr]
  `(let [start# (. System (nanoTime)) ret# ~expr]
     (/ (double (- (. System (nanoTime)) start#)) 1000000.0)))

(pprint(vec(for[n[4 5 6 7]](vec(sort(repeatedly 5 #(mytime (F n)))))))

[[  43.445902   45.895107   47.277399   57.681634    62.594037]
 [ 222.964582  225.467034  240.532683  330.237721   593.686911]
 [2285.417473 2531.331068 3002.597908 6361.591714  8331.809410]
 [3569.62372  4779.062486 5725.905756 7444.941763 14120.796615]]

Multithreaded with pmap:

[[   8.881905  16.343714   18.87262  18.9717890   22.194430]
 [  90.963870 109.719332  163.00299  245.824443  385.365561]
 [ 355.872233 356.439256 1534.31059 2593.482767 3664.221550]
 [1307.727115 1554.00260 2068.35932 3626.878526 4029.543011]]
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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