Как заполнить матрицу(игра Судоку) числами в заданном диапазоне, без повтора в строке и столбце?

Рейтинг: 1Ответов: 4Опубликовано: 07.04.2023

Класс содержит следующие методы:

private static long counter; // для подсчета созданных игровых досок
private static int[][] data;

public static int[][] getSuccessRandomTable() { // проверка созданного игрового поля, на правильность заполнения
    LocalDateTime from = LocalDateTime.now();
    boolean isValid;
    int[][] data;
    do {
        data = getRandomTable();
        isValid = TableValidator.checkTable(data);
        TableOut.printTable(data);
    }
    while (!isValid);
    LocalDateTime to = LocalDateTime.now();
    System.out.println("Заняло времени в секундах " + Duration.between(from, to).toSeconds());
    return data;
}

private static int[][] getRandomTable() { //создание игрового поля
    int[][] data = new int[Constants.TABLE_SIZE][Constants.TABLE_SIZE];
    final int min = 1; // Минимальное число для диапазона
    final int max = 9; // Максимальное число для диапазона

    for (int i = 0; i < data.length; i++) {
        for (int j = 0; j < data.length; j++) {
            int rnd = rnd(min, max);
            data[i][j] = rnd;
        }

    }
    counter++;
    System.out.println("Игровых досок создано " + counter);
    return data;
}

private static int rnd(int min, int max) { //генерация случайного числа от 1 до 9
    max -= min;
    return (int) (Math.random() * ++max) + min;
}

Метод getRandomTable() заполняет матрицу 9*9 значениями из диапазона 1-9.

Метод getSuccessRandomTable() проверяет заполненную матрицу на валидность

Пытаюсь понять, как добавить проверку т.е. доработать getRandomTable() так, чтобы он заполнял построчно значениями из диапазона, без повтора по вертикали и горизонтали, т.е. в ячейку [0][0] записывает число от 1-9, пусть будет 3, шагает к следующей ячейке, проверяю линию, тройка есть, значит нужно записать число от 1-9 за исключением 3 и так далее. В дальнейшем потребуется сделать дополнительную проверку на"малые квадраты"(data[0][0]-data[0][2]; data[1][0]-data[1][2]; data[2][0]-data[2][2]), чтобы в них значения так же были только от 1-9 без повтора.

малые квадраты

Ответы

▲ 1Принят

Вариант с "истинно" случайной матрицей:

  • создать множество свободных индексов для каждого значения от 0 до n
  • для каждой строки, построить список доступных индексов, которые могут быть выбраны
  • выбрать случайно ячейку c доступным индексом и записать туда текущее значение, удалить индекс из списка свободных
  • вероятна ситуация, когда доступных ячеек не окажется, тогда придется очистить матрицу от текущего значения и повторить попытку.

Вариант реализации (без Stream API, с закешированным множеством индексов и оптимизированной очисткой):

public static int[][] getRandomTable() {
    return getRandomTable(9);
}

public static int[][] getRandomTable(final int n) {
    int[][] arr = new int[n][n];
    
    Random rand = new Random();

    // кеш индексов
    Set<Integer> indexes = new LinkedHashSet<>();
    List<Integer> values = new ArrayList<>();
    for (int i = 0; i < n;) {
        indexes.add(i);
        values.add(++i);
    }
    // рандомизировать входные значения
    Collections.shuffle(values);
    
    int attempt = 1;
    for (int k = 0; k < n; k++) {
        int v = values.get(k);

        Set<Integer> free = new LinkedHashSet<>(indexes);
        boolean filled = true;

        int row;
        for (row = 0; row < n; row++) {
            int[] r = arr[row];

            List<Integer> available = new ArrayList<>();
            for (int c = 0; c < n; c++) {
                if (r[c] == 0 && free.contains(c)) {
                    available.add(c);
                }
            }
            
            if (available.isEmpty()) {
                filled = false;
                break;
            }

            int i = rand.nextInt(available.size());
            r[available.get(i)] = v;
            
            free.remove(available.get(i));
        }
        if (!filled) {
            System.out.println("Bad solution, failed in row: " + row);
            for (int i = 0; i < row; i++) {
                for (int j = 0; j < n; j++) {
                    if (arr[i][j] == v) {
                        arr[i][j] = 0;
                        break;
                    }
                }
            }
            k--;
            attempt++;
        } else {
            System.out.println("FILLED: " + v + "; attempt=" + attempt);
            attempt = 1;
        }
    }

    return arr;
}

Результаты теста out(getRandomTable()); с рандомизированными значениями:

FILLED: 8; attempt=1
FILLED: 6; attempt=1
FILLED: 4; attempt=1
FILLED: 1; attempt=1
FILLED: 3; attempt=1
FILLED: 2; attempt=1
Bad solution, failed in row: 6
Bad solution, failed in row: 6
Bad solution, failed in row: 8
FILLED: 5; attempt=4
FILLED: 7; attempt=1
FILLED: 9; attempt=1
3  5  7  2  8  6  9  1  4
8  2  9  7  6  1  4  5  3
6  7  1  9  5  4  3  8  2
5  9  8  1  7  3  2  4  6
1  6  3  4  9  2  8  7  5
4  8  6  3  2  5  7  9  1
2  3  5  8  4  7  1  6  9
7  4  2  5  1  9  6  3  8
9  1  4  6  3  8  5  2  7
----
▲ 1

Как и в случае генерации одномерного массива со случайным расположением элементов из заданного набора значений без повторений следует сгенерировать матрицу, для которой будет выполняться условие уникальности всех элементов в строках и колонках, а затем перемешать элементы внутри матрицы. Но если для одномерного массива достаточно использовать стандартный метод Collections.shuffle (см. Как заполнить Arraylist случайными цифрами?; как поместить на случайные индексы в массиве конкретные числа), для перемешивания матрицы придётся реализовать методы для перестановки двух строк / колонок / транспонирования матрицы / циклического сдвига строк и т.п..

private static int[][] generateTable() {
    int[][] arr = new int[9][9];
    
    Random r = new Random();
    
    for (int i = 0; i < arr.length;) {
        int j = r.nextInt(i + 1);
        arr[0][i] = arr[0][j];
        arr[0][j] = ++i;
    }
    // случайный сдвиг влево(1) или вправо(8)
    int shift = r.nextBoolean() ? 1 : arr.length - 1;
    for (int i = 1; i < arr.length; i++) {
        for (int j = 0; j < arr.length; j++) {
            arr[i][j] = arr[i - 1][(j + shift) % arr.length];
        }
    }
    return arr;
} 

private static void transpose(int[][] arr) {
    for (int i = 0, n = arr.length; i < n; i++) {
        for (int j = i + 1, m = arr[i].length; j < m; j++) {
            int tmp = arr[i][j];
            arr[i][j] = arr[j][i];
            arr[j][i] = tmp;
        }
    }
}

private static void swapRows(int r1, int r2, int[][] arr) {
    int[] tmp = arr[r1];
    arr[r1] = arr[r2];
    arr[r2] = tmp;
}

private static void swapCols(int c1, int c2, int[][] arr) {
    for (int i = 0, n = arr.length; i < n; i++) {
        int tmp = arr[i][c1];
        arr[i][c1] = arr[i][c2];
        arr[i][c2] = tmp;
    }
}

private static void rotateRows(int i, int[][] arr) {
    Collections.rotate(Arrays.asList(arr), i);
}

Тогда можно сгенерировать матрицу, используя случайное количество различных перестановок от 20 до 100

public static int[][] getRandomTable() {
    int[][] arr = generateTable();
    
    Random r = new Random();
    int shuffles = 20 + r.nextInt(81);
    
    while (shuffles-- > 0) {
        int i = r.nextInt(9);
        int j = r.nextInt(9);
        if (i == j) {
            if (r.nextBoolean())
                transpose(arr);
            else 
                rotateRows(r.nextBoolean() ? i : -i, arr);
        } else if (r.nextBoolean()) {
            swapRows(i, j, arr);
        } else{
            swapCols(i, j, arr);
        }
    }

    return arr;
}

Или же используя алгоритм Фишера -- Йетса с определённым количеством перестановок по строкам и столбцам:

public static int[][] getRandomTable() {
    int[][] arr = generateTable();
    
    Random r = new Random();
    for (int i = arr.length; i-- > 0;) {
        int j = r.nextInt(i + 1);
        swapRows(i, j, arr);
    }
    for (int i = arr.length; i-- > 0;) {
        int j = r.nextInt(i + 1);
        swapCols(i, j, arr);
    }
    if (r.nextBoolean()) {
        transpose(arr);
    }

    return arr;
}

Также могут быть разные вариации.

Примеры сгенерированных матриц:

int t = 5;
while (t-- > 0) {
    int[][] arr = getRandomTable();
    for (int[] r : arr) {
        System.out.println(Arrays.toString(r));
    }
    System.out.println("----");
}

Результаты для алгоритма Фишера -- Йетса и рандомизированного начального массива

[1, 3, 4, 5, 6, 2, 8, 7, 9]
[2, 7, 9, 6, 1, 4, 3, 5, 8]
[7, 4, 6, 8, 3, 5, 2, 9, 1]
[4, 5, 8, 1, 2, 9, 7, 6, 3]
[9, 6, 3, 2, 4, 8, 5, 1, 7]
[8, 1, 7, 4, 9, 3, 6, 2, 5]
[5, 9, 1, 3, 7, 6, 4, 8, 2]
[3, 2, 5, 9, 8, 7, 1, 4, 6]
[6, 8, 2, 7, 5, 1, 9, 3, 4]
----
[7, 2, 3, 1, 6, 4, 8, 9, 5]
[4, 9, 6, 2, 8, 5, 3, 1, 7]
[2, 6, 4, 3, 5, 9, 7, 8, 1]
[8, 7, 1, 5, 2, 3, 9, 4, 6]
[5, 1, 8, 9, 3, 7, 6, 2, 4]
[1, 3, 7, 8, 4, 2, 5, 6, 9]
[9, 8, 5, 6, 7, 1, 4, 3, 2]
[6, 5, 9, 4, 1, 8, 2, 7, 3]
[3, 4, 2, 7, 9, 6, 1, 5, 8]

Результаты для рандомизированного количества перестановок:

[6, 7, 4, 5, 8, 2, 1, 9, 3]
[5, 2, 8, 9, 1, 3, 7, 4, 6]
[9, 3, 1, 4, 7, 6, 2, 8, 5]
[4, 6, 7, 8, 2, 5, 3, 1, 9]
[2, 8, 5, 3, 9, 1, 4, 6, 7]
[1, 9, 3, 7, 6, 4, 5, 2, 8]
[8, 5, 2, 1, 3, 9, 6, 7, 4]
[3, 1, 9, 6, 4, 7, 8, 5, 2]
[7, 4, 6, 2, 5, 8, 9, 3, 1]
----
[9, 3, 4, 5, 6, 2, 1, 8, 7]
[3, 8, 1, 2, 4, 7, 6, 9, 5]
[5, 2, 3, 1, 9, 6, 8, 7, 4]
[1, 6, 2, 8, 5, 9, 7, 4, 3]
[7, 5, 9, 4, 8, 1, 3, 2, 6]
[2, 7, 8, 6, 3, 4, 9, 5, 1]
[8, 9, 6, 7, 1, 5, 4, 3, 2]
[6, 4, 7, 9, 2, 3, 5, 1, 8]
[4, 1, 5, 3, 7, 8, 2, 6, 9]
▲ 1

Это не настоящая случайная матрица. Но тому, кто решится проверять, будет сложно это доказать.

Алгоритмом Фишера-Йейтса генерируем случайную перестановку длины девять:

516782394

Строим из неё матрицу смещая строки на единицу. В такой матрице числа в строках и столбцах уникальны, но виден порядок:

516782394
167823945
678239451
782394516
823945167
239451678
394516782
945167823
451678239

С помощью алгоритма Фишера-Йейтса случайно перемешиваем строки и столбцы. Всё:

public class Temp {

    private static int[][] randomTable(int n) {
        int[] p = randomPermutation(n);
        int[] pi = randomPermutation(n);
        int[] pj = randomPermutation(n);

        int[][] table = new int[n][n];
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < n; ++j) {
                table[pi[i]][pj[j]] = 1 + p[(i + j) % n];
            }
        }
        return table;
    }

    private static int[] randomPermutation(int n) {
        int[] p = new int[n];
        for (int i = 1; i < n; ++i) {
            int j = (int)(Math.random() * (i + 1));
            p[i] = p[j];
            p[j] = i;
        }
        return p;
    }

    public static void main(String... args) {
        int[][] table = randomTable(9);
        for (int i = 0; i < table.length; ++i) {
            for (int j = 0; j < table[i].length; ++j) {
                System.out.print(table[i][j]);
            }
            System.out.println();
        }
    }

}
▲ 0

Решение порождает случайную судоку-матрицу с (я надеюсь) достаточно равномерным распределением. Это рекурсивный поиск. Из незаполненных полей выбираются поля с наименьшей степенью свободы. Из них случайно выбирается одно поле. Все доступные для этого поля значения перебираются в случайном порядке.

Этот алгоритм может породить любую возможную матрицу. Обычно он работает достаточно быстро. Он всегда строит какую-то матрицу. Но так как это именно случайный поиск гарантий времени работы нет.

random(n) - случайное целое в диапазоне [0, n);

shuffle(a) - случайное перемешивание массива алгоритмом Фишера-Йейтса;

RandomTable(m) - создаёт объект-калькулятор судоку матрицы размера m2×m2 (0 ≤ m ≤ 7);

RandomTable.print() - ищет случайную судоку матрицу и печатает её;

RandomTable.board - доска для поиска. Это линейный массив размера m4. Для m = 3 доска отображается на матрицу так:

 0  1  2  3  4  5  6  7  8
 9 10 11 12 13 14 15 16 17
...
72 73 74 75 76 77 78 79 80

Доска хранит значения как степени двойки:

Значение в матрице    Значение на доске
 <не определено>                    0
        1                1 << 0 =   1
        2                1 << 1 =   2
        3                1 << 2 =   4
       ...
        9                1 << 8 = 256

RandomTable.i, .j, .k - отображают координату на доске (k) в координаты матрицы (i, j);

RandomTable.occupied(k) - возвращает битовую маску, которая хранит все значения недоступные для индекса k на доске;

RandomTable.select() - отыскивает на доске наиболее занятые позиции, случайно выбирает из них одну;

RandomTable.avaiable(k) - для позиции k строит список всех доступных значений и случайно его перемешивает;

RandomTable.search() - рекурсивный поиск позиции на доске. m4 раз повторяет следующую процедуру: случайно отбирает наименее свободную позицию (select), строит для неё случайно перемешанный список доступных значения (available), пробует каждое значение и рекурсивно продолжает поиск. Если позиция найдена, немедленно возвращает true (в этот момент на доске нет нулей). Если - нет, стирает значение, возвращает false чтобы вызов верхнего уровня перешел к следующему значению.

Выбор наименее свободной позиция для продолжения поиска радикально сокращает время перебора. Случайные выборы и перемешивания улучшают равномерность выбора матриц.

В решении осталось место для существенной оптимизации. Неэффективно написаны occupied и select. Я оставил их в таком виде, чтобы не раздувать код.

public class SudokuRandomMatrix {

    private static int random(int n) { return (int)(Math.random() * n); }

    private static void shuffle(long[] a) {
        for (int i = 1; i < a.length; ++i) {
            int j = random(i + 1);
            long tmp = a[i];
            a[i] = a[j];
            a[j] = tmp;
        }
    }

    private static class RandomTable {
        private final int m;
        private final int n;
        private final long[] board;

        public RandomTable(int m) {
            this.m = m;
            n = m * m;
            board = new long[n * n];
        }

        public void print() {
            java.util.Arrays.fill(board, 0);
            search(board.length);

            for (int i = 0; i < n; ++i) {
                for (int j = 0; j < n; ++j) {
                    int v = 1 + Long.numberOfTrailingZeros(board[k(i, j)]);
                    System.out.print(String.format("%3d", v));
                }
                System.out.println();
            }
        }

        private int i(int k) { return k / n; }
        private int j(int k) { return k % n; }
        private int k(int i, int j) { return n * i + j; }

        private long occupied(int k) {
            final int ii = i(k);
            final int jj = j(k);

            long occupied = 0;
            for (int j = 0; j < n; ++j) {
                occupied |= board[k(ii, j)];
            }
            for (int i = 0; i < n; ++i) {
                occupied |= board[k(i, jj)];
            }
            final int i0 = ii - ii % m;
            final int j0 = jj - jj % m;
            for (int i = i0; i < i0 + m; ++i) { 
                for (int j = j0; j < j0 + m; ++j) { 
                    occupied |= board[k(i, j)];
                }
            }
            return occupied;
        }

        private int select() {
            int max = 0;
            int c = 0;
            int[] ks = new int[board.length];

            for (int k = 0; k < board.length; ++k) {
                if (board[k] == 0) {
                    int v = Long.bitCount(occupied(k));
                    if (v > max) {
                        ks[0] = k;
                        c = 1;
                        max = v;
                    } else if (v == max) {
                        ks[c] = k;
                        ++c;
                    }
                }
            }
            return ks[random(c)];
        }

        private long[] available(int k) {
            long occupied = occupied(k);

            long[] available = new long[n - Long.bitCount(occupied)];
            int c = 0;
            for (int b = 0; b < n; ++b) {
                long v = 1L << b;
                if ((v & occupied) == 0) {
                    available[c] = v;
                    ++c;
                }
            }
            shuffle(available);
            return available;
        }

        private boolean search(int s) {
            if (s == 0) {
                return true;
            }
            final int k = select();
            for (long v : available(k)) {
                board[k] = v;
                if (search(s - 1)) {
                    return true;
                }
            }
            board[k] = 0;
            return false;
        }
    }

    public static void main(String... args) {
        new RandomTable(Integer.parseInt(args[0])).print();
    }
}
$ javac SudokuRandomMatrix.java

$ time java SudokuRandomMatrix 3
  1  9  2  6  3  8  7  4  5
  8  5  3  7  1  4  2  9  6
  4  7  6  2  9  5  1  3  8
  9  8  1  3  4  6  5  7  2
  5  2  7  1  8  9  4  6  3
  6  3  4  5  2  7  8  1  9
  7  6  9  8  5  1  3  2  4
  2  1  8  4  6  3  9  5  7
  3  4  5  9  7  2  6  8  1

real  0m0.065s
user  0m0.072s
sys   0m0.004s

$ time java SudokuRandomMatrix 4
  7 15 10 14 12  1  8  2 11  5  4  9 16 13  3  6
  8 13  5 12 11  4 14  6 15  3  7 16  9  2  1 10
  2  3 16  1  9 15  5 10  6 13 12 14  8  7  4 11
  4  9 11  6  7 13 16  3  2  1  8 10 12 15 14  5
  3  7 14 16 10  5 12  4 13  6  9  8  2  1 11 15
 11  5  8 13 16  2  1 15 14 12 10  3  4  6  7  9
 10  1 12  9  8  6 11 13  4  2 15  7 14  5 16  3
 15  6  2  4  3  7  9 14  1 11 16  5 10  8 12 13
  9 16 15  2  6 12  4  5  7 10 13  1  3 11  8 14
  5 14 13 10  2  3  7 16  8  9  6 11  1  4 15 12
  6  8  3  7  1 10 15 11 12 16 14  4  5  9 13  2
 12  4  1 11 14  9 13  8  3 15  5  2  6 16 10  7
 13 12  6  5  4 11  3  9 16 14  1 15  7 10  2  8
 16 10  4  8 15 14  2 12  5  7 11  6 13  3  9  1
 14 11  7  3  5 16 10  1  9  8  2 13 15 12  6  4
  1  2  9 15 13  8  6  7 10  4  3 12 11 14  5 16

real  0m0.080s
user  0m0.096s
sys   0m0.016s

$ time java SudokuRandomMatrix 5
 20  4 24 12 14 17 11 21  6 23 19  1 10  8  3  7 15  9 18 22  5 25 13  2 16
  9 15 22 25  6  1  7  8  5  2 20 23 24 12 21 11 13 16 19  3 17 10  4 14 18
 17 10 16  8  5  9 18 13 20  3 11 15 22 14  4 23  6 21 25  2 12  7 24 19  1
 18  2 11  1 23 19 22 12 16 24  7 25 13  5  6  8 17  4 14 10  3 15 20  9 21
 21 13  3 19  7 14  4 15 25 10  2 17  9 16 18 24 20 12  1  5 22 11  6  8 23
  8 21  6  5  4 23 24 25  3 15 14 12  7 10 20  2 18 22 11  9 16 19 17  1 13
  7 17 19 24 18 20 16  2 21 22 23 11  6 15 25  4  8 13  3  1  9 14 10 12  5
 14 12 23 11  1  7 13 19 10 17 18  4  5 21  9 15 24 25 16  6  8 20 22  3  2
 13 22 15 10 25  8 12  9 14 18 24  2 16  3  1 19  7  5 17 20 23 21 11  4  6
  2  3 20  9 16  6  1  4 11  5 17  8 19 13 22 10 21 14 23 12 25 18  7 15 24
 24  5 25  2 13 18 14  6 22  9 12 10 20 23 15 16  1 11  8 21 19 17  3  7  4
 19  8  7  6 20 10 21 11  1 25  5  3 17 24 16 18 23  2  9  4 14 12 15 13 22
 22 18 10 16 15  3  2 23 19  4 13 21 14 25  8 17 12 24  6  7 20  1  5 11  9
  3 14 17  4  9 24 20 16 12  7  6 19 11  1  2 13  5 10 22 15 18  8 23 21 25
 12  1 21 23 11 15 17  5  8 13  9 22  4 18  7 25 14  3 20 19 24 16  2  6 10
 23 11  8  3 17 12 15 18  9 20 25  6  2  4 14 21 22  1 10 24 13  5 19 16  7
 15 19  4 20 24 21 25 14  2  8 16  7  1 22  5  6  3 17 13 11 10  9 18 23 12
 25  6 14  7 21 13 19 10 24  1 15  9 18 17 12  5 16 20  2 23  4  3  8 22 11
  5  9 12 18 10 16 23 22 17  6  8 13  3 11 19 14  4  7 15 25  1  2 21 24 20
  1 16  2 13 22  4  5  3  7 11 10 24 21 20 23  9 19 18 12  8 15  6 25 17 14
  6 25 18 14  8 11  9 20  4 12  1  5 23  7 13  3  2 15 24 17 21 22 16 10 19
 11 20  1 21  3 22  8 24 13 14  4 16 25  2 17 12 10 19  7 18  6 23  9  5 15
  4  7 13 17 12  2  6  1 15 19 21 18  8  9 10 22 25 23  5 16 11 24 14 20  3
 10 24  5 15 19 25  3  7 23 16 22 14 12  6 11 20  9  8 21 13  2  4  1 18 17
 16 23  9 22  2  5 10 17 18 21  3 20 15 19 24  1 11  6  4 14  7 13 12 25  8

real  0m7.116s
user  0m7.132s
sys   0m0.072s

$ time java SudokuRandomMatrix 6
 25 28 11  4 22 34 12  5  9 36 30  3 33 14 31 13  2 32 19  6 26  8 10 21 15 16 24 17 29  7 18 23 27 20  1 35
  7 32 16  8  3  9 20 19 28 10  6 31 22  1 11 18 35 21 23 15 29 33 12 34 27 26 25 36  5 30 13  4 17 14  2 24
 13 29 10 21  2 19 16  4  8 26 14  7  6 34 17 15 27  9 18 35 30 20 24 22  3 23  1 31 33 28 25 36  5 32 11 12
 20  6 36 12 14 27 15 34 24 21  1 17 23 28 26 25 10 19  9 32  2  5 13  3  8 35  4 18 11 22 30  7 33 31 29 16
  5 26  1 17 35 24 11 25 32 23 18 33 30 20  4  3 12 29  7 16 27 36 31 28  2 21 13  6 14 19 22 10  8  9 15 34
 23 30 18 31 15 33  2 27 35 13 22 29  7 24  8  5 36 16 17  4 25 14  1 11 20 32 34  9 12 10 28 21  6  3 19 26
 12 23 33 19 10  3  1 24  5 27 35  8  9 36 16 32 34 11 26 13 14 28 22  7  6 25 18 21 15  4 29  2 20 17 30 31
 35 24 22 13 27  6 32 33 31 12 23  4 18 17  1 28 30 20 10  9 34  2 21  8 36  5 26 14  7 29 19 16 11 25  3 15
 26 18 14  1 34 17 28 30 11 25 29 16  2 27 10 35 21 15 33 12 19  4 36  5 22  3 20 32 31 24  6  8  9 13 23  7
 21 11 15 25  5 29 36 17 22 20 13  2 24  8  6 12  7 31 27 23  3 35 30 32 33  9 19 10 16  1 14 34 18 28 26  4
 32  2 30  9 20 31 19  7 26 14 15 18  3 22 25 33  5  4 11 17  6  1 16 29 12 34  8 28 23 13 21 24 10 36 35 27
 36  8  7 16 28  4  6 10 34  9  3 21 19 29 13 14 23 26 15 24 20 31 18 25 11 30 35  2 17 27  1 12 22  5 32 33
 10 21  3 34 33 36 35 13  7 22 19  9  5 16 14  4 26 23 31 29 11 24  6 18  1 27 32 30  2 17 15 28 25  8 12 20
 22 27  5 28  7 25 31 16  1  6 24 26 20 33 18 21 15  2 30  3  9 12 17  4 23  8 14 29 35 11 34 13 36 19 10 32
 31 35 12 11 17 26 10 23 21  2 25 28 34 19  9  8  1 22 32 20 16 13 14 33  7 24 15  4  3 36  5 29 30 27  6 18
  1  4  6 15 24 14 34 20 29  8 27 12 36 32  7 30 17 35 21 28  5 22 26 10 18 13 31 25 19 16  3 33 23 11  9  2
 29 20  2 30 32  8 18 14  4 33 11 15 31 13 28 24  3 25 35 36 23 27 34 19 10 12  5 22  9  6  7 17 21 26 16  1
 19 16 13 18  9 23 30 32  3 17 36  5 11 12 27 29  6 10  1  7  8 25 15  2 21 28 33 20 34 26 35 22 31 24  4 14
 16 19 26 24 36 20 33  1  2  5 10 35 21 25 23  9 32  6 14 18 12  7 29 31  4 22 27 15  8  3 17 11 34 30 28 13
  3  1 35 27 13 21 25 26 14 32 31 36  8  4 33 34 11 12 20 19 22 17 28 15  9 29 30 24 10 23  2  6 16 18  7  5
 34 12  9  2 25 11 29  8 16  4 21  6 14  7 36 22 19 17  3 27 32 26 35 30 13 33 28  5 20 18 23 31 15  1 24 10
 30 15 31 22  6  5  7 28 20 34 17 13 10  3  2 26 29 18 24  8 36 23  4  1 16 14 11 19 21 35 33 27 32 12 25  9
 14 33 28 23  4 10 24 18 19 11 12 22 15 30 35 27 31 13  6  5 21 34  9 16 32  7 17  1 25  2 36  3 26 29 20  8
 18 17 29  7  8 32 27  3 23 15  9 30 16  5 24 20 28  1 25  2 33 10 11 13 26 31  6 12 36 34  4 35 14 22 21 19
 24  3  4  5 12 35 14  6 10 30 34 20 32 11 15 31 22  8 16 21 18 19 25 26 17 36 23  7 28 33 27  9  1  2 13 29
 11 22 19 10 23 15 26 35 25 18 28 32  1 21 29  7  9 33 13 31 17  3  5 36 34 20  2 27  4 12  8 30 24 16 14  6
 28 36 21 26  1  7  9  2 13  3 33 27 12  6 20 17 25 30 22 34  4 11  8 14 19 18 29 16 24 32 10  5 35 15 31 23
  9 14 25  6 18 13 21 29 17 16  7 23 28 26 19 36 24 27  2  1 35 15 33 20 31 10  3  8 30  5 11 32 12  4 34 22
 27 34 17 33 29  2  8 31 12  1  4 11 35 10  3 16 18  5 28 30 24  6 32 23 14 15 22 26 13  9 20 19  7 21 36 25
  8 31 32 20 16 30 22 15 36 24  5 19  4 23 34  2 13 14 29 10  7  9 27 12 35  6 21 11  1 25 26 18  3 33 17 28
  2 13 34 35 31 28  4 21 27 29 16 14 25  9  5 11 20 24 12 26  1 18 23  6 30 17  7  3 22  8 32 15 19 10 33 36
 17  5 24 14 19 18  3 22 15 35 26 10 27  2 12  6 33 28 34 11 13 16 20  9 29  1 36 23 32 21 31 25  4  7  8 30
 15  9 27 36 21 22  5 11  6 28 32  1 13 35 30 19 14  7  8 33 10 29  3 24 25  4 12 34 26 31 16 20  2 23 18 17
 33 25 20 29 11 16 17  9 30 19  8 24 26 18 21 23  4 34 36 14 31 32  7 27  5  2 10 13  6 15 12  1 28 35 22  3
  6  7 23  3 30  1 13 12 18 31 20 25 17 15 32 10  8 36  4 22 28 21  2 35 24 19 16 33 27 14  9 26 29 34  5 11
  4 10  8 32 26 12 23 36 33  7  2 34 29 31 22  1 16  3  5 25 15 30 19 17 28 11  9 35 18 20 24 14 13  6 27 21

real  20m4.585s
user  20m2.584s
sys   0m0.988s