Quanti puzzle di Sudoku esistono?


10

Questo non è un risolutore di Sudoku, né un controllore di Sudoku.

La tua sfida è scrivere una funzione o uno script che, dati come input la dimensione "blocco" di un puzzle di Sudoku 2D (che è 3 per la classica scheda 9x9 , 4 per una scheda 16x16 , ecc.) Calcolerà un'approssimazione del numero di puzzle (soluzioni) distinti che esistono per quella dimensione.

Ad esempio, dato l'ingresso 3, il programma dovrebbe stampare un'approssimazione, con la precisione desiderata, del numero 6.670.903.752.021.072.936.960 che è il numero noto di puzzle Sudoku 9x9 distinti , o 5.472.730.538 quando si tiene conto delle varie simmetrie. La soluzione dovrebbe indicare se le simmetrie vengono contate o ignorate.

"Precisione desiderata" non viene definita: il programma potrebbe essere eseguito per un determinato periodo di tempo e quindi emettere il suo risultato, oppure calcolarlo fino a un determinato numero di cifre significative, o addirittura funzionare per sempre, stampando approssimazioni sempre migliori. Il punto è che dovrebbe essere possibile farlo calcolare il risultato con qualsiasi precisione richiesta, in un tempo finito. (Quindi "42" non è una risposta accettabile.) Limitare la precisione del risultato ai galleggianti disponibili della macchina è accettabile.

Nessun accesso alle risorse online, nessuna memorizzazione del codice sorgente nel nome del file, ecc.


PS: So che questo è un problema difficile (NP completo se non sbaglio). Ma questa domanda richiede solo una soluzione statistica approssimativa. Ad esempio, puoi provare configurazioni casuali che soddisfano uno (o meglio due) vincoli, calcolare quanti di questi esistono e quindi verificare con quale frequenza ottieni un puzzle che soddisfa tutti e tre i vincoli. Funzionerà in un tempo decente per piccole dimensioni (sicuramente per dimensioni = 3 e forse 4) ma l'algoritmo dovrebbe essere abbastanza generico da funzionare per qualsiasi dimensione.

Vince il miglior algoritmo.


PS2: sono passato dal code-golf al code-challenge per riflettere meglio la difficoltà del problema e incoraggiare soluzioni più intelligenti, rispetto a quelle stupide ma ben giocate. Ma dal momento che apparentemente il "miglior algoritmo" non è chiaro, lasciami provare a definirlo correttamente.

Dato abbastanza tempo e trascurando i fattori costanti (tra cui CPU e velocità dell'interprete), o equivalentemente, considerando il loro comportamento asintotico, quale soluzione converrebbe nel risultato esatto il più veloce?


11
Non è davvero un problema davvero difficile ? Stai solo chiedendo il modo più breve per produrre una funzione per produrre i numeri {1, 1, 288, 6e21} o per estenderlo in qualche modo a n> 3?
algoritmo

La soluzione esatta è un problema incredibilmente difficile, ma un'approssimazione può essere calcolata con alcuni campionamenti casuali e alcuni secondi del tempo CPU moderno. Naturalmente sono benvenute soluzioni più intelligenti!
Tobia,

2
@Tobia questo approccio è stato usato per trovare il numero approssimativo di posizioni del cubo di rubik che richiedono N mosse per risolvere kociemba.org/cube.htm, quindi è possibile ottenere un'approssimazione in questo modo. Tuttavia, se scrivo un programma che risolve ogni riga e quindi testa per vedere se le colonne e i quadrati sono risolti, avrà (9!) ^ 9 = 1E50 possibilità di forza bruta, di cui solo 6E21 sono hit (per la domanda .) Occorreranno in media 1,6 E28 tentativi per colpo. È piuttosto lento. Ora, se potessi assicurarmi che sia le righe che i col siano corretti e controllino solo i quadrati, starei andando da qualche parte. Ah! Ho un'idea ...
Level River St

@steveverrill See? :-)
Tobia,

Non esiste una soluzione analitica?
Newbrict

Risposte:


3

C ++

Quello che presenterò qui è un algoritmo, illustrato con un esempio per un caso 3x3. Potrebbe teoricamente essere esteso al caso NxN, ma ciò richiederebbe un computer molto più potente e / o alcune modifiche ingegnose. Citerò alcuni miglioramenti mentre procederò.

Prima di andare oltre, notiamo le simmetrie della griglia di Sudoku, cioè le trasformazioni che portano ad un'altra griglia in modo banale. Per la dimensione del blocco 3, le simmetrie sono le seguenti:

Simmetria orizzontale

**The N=3 sudoku is said to consist of 3 "bands" of 3 "rows" each**
permute the three bands: 3! permutations = 6
permute the rows in each band: 3 bands, 3! permutations each =(3!)^3=216

Simmetria verticale

**The N=3 sudoku is said to consist of 3 "stacks" of 3 "columns" each.**
the count is the same as for horizontal.

Si noti che i riflessi orizzontali e verticali della griglia possono essere raggiunti da una combinazione di questi, quindi non è necessario contarli. C'è ancora una simmetria spaziale da considerare, che sta trasponendo, di cui è un fattore 2. Questo dà la simmetria spaziale totale di

2*(N!*(N!)^N)^2 = 2*(6*216)^2=3359232 spatial symmetries for the case N=3.

Poi c'è un'altra, molto importante simmetria, chiamata rietichettatura.

Relabelling gives a further (N^2)!=9!=362880 symmetries for the case N=3. So the total 
number of symmetries is 362880*3359232=1218998108160.

Il numero totale di soluzioni non può essere trovato semplicemente moltiplicando il numero di soluzioni uniche di simmetria per questo numero, poiché esiste un numero (inferiore all'1%) di soluzioni automorfe. Ciò significa che per queste soluzioni speciali esiste un'operazione di simmetria che le mappa su se stesse o operazioni multiple di simmetria che le mappano sulla stessa altra soluzione.

Per stimare il numero di soluzioni, ho affrontato il problema in 4 passaggi:

1. Riempi un array r[362880][12]con tutte le possibili permutazioni dei numeri da 0 a 8. (questa è la programmazione, ed è in C, quindi non useremo da 1 a 9.) Se sei astuto noterai che il secondo pedice è 12 non 9. Questo perché, nel fare ciò, tenendo presente che la considereremo come una "riga", calcoliamo anche altri tre numeri interi in r[9,10,11] == 1<<a | 1<<b | 1<<ccui 9,10,11 si riferiscono al primo, secondo e terzo stack e a, b, c sono i tre numeri presenti in ogni pila per quella riga.

2. Riempi un array bcon tutte le possibili soluzioni di una banda di 3 file. Per mantenerlo ragionevolmente piccolo, includere solo quelle soluzioni in cui la riga superiore è 012.345.678. Lo faccio con la forza bruta, generando tutte le possibili file centrali e ANDing r[0][10,11,12]con r[i][10,11,12]. Qualsiasi valore positivo significa che ci sono due numeri identici nello stesso quadrato e la banda non è valida. Quando esiste una combinazione valida per le prime due righe, cerco la terza riga (in basso) con la stessa tecnica.

Ho dimensionato l'array come b [2000000] [9] ma il programma trova solo 1306368 soluzioni. Non sapevo quanti ce ne fossero, quindi ho lasciato la dimensione dell'array in quel modo. Questa è in realtà solo la metà delle possibili soluzioni per una singola banda (verificata su wikipedia), perché eseguo solo la scansione della terza riga dal valore corrente per iverso l'alto. La restante metà delle soluzioni può essere trovata in modo banale scambiando la 2a e la 3a fila.

Il modo in cui le informazioni sono archiviate nell'array bè inizialmente un po 'confuso. invece di utilizzare ogni numero intero per memorizzare i numeri 0..8trovati in una data posizione, qui ogni numero intero considera uno dei numeri 0..8e indica in quali colonne può essere trovato. quindi b[x][7]==100100001indicherebbe che per la soluzione x il numero 7 si trova nelle colonne 0,5 e 8 (da destra a sinistra). La ragione di questa rappresentazione è che dobbiamo generare il resto delle possibilità per la banda rietichettando, e questo la rappresentazione rende conveniente farlo.

I due passaggi precedenti comprendono l'installazione e richiedono circa un minuto (forse meno se rimuovessi l'output di dati non necessari. I due passaggi seguenti sono la ricerca effettiva).

3 Cerca in modo casuale soluzioni per le prime due bande che non si scontrino (ovvero non hanno lo stesso numero due volte in una determinata colonna. Scegliamo una soluzione casuale per la banda 1, assumendo sempre la permutazione 0, e una soluzione casuale per la banda 2 con una permutazione casuale. Un risultato si trova normalmente in meno di 9999 tentativi (frequenza di hit del primo stadio nell'intervallo delle migliaia) e richiede una frazione di secondo. Per permutazione, intendo che per la seconda banda prendiamo una soluzione da b [] [] dove la prima riga è sempre 012.345.678 e rietichettala in modo che sia possibile ogni possibile sequenza di numeri sulla prima riga.

4 Quando viene trovato un hit nel passaggio 3, cercare una soluzione per la terza banda che non si scontra con le altre due. Non vogliamo fare un solo tentativo, altrimenti il ​​tempo di elaborazione per il passaggio 3 sarebbe sprecato. D'altra parte non vogliamo fare uno sforzo eccessivo in questo.

Solo per divertimento, ieri sera l'ho fatto nel modo più stupido possibile, ma era comunque interessante (perché non è stato per anni, quindi ho trovato un gran numero di soluzioni in raffica). Ci è voluta tutta la notte per ottenere un punto dati, anche con il piccolo hack (!z)Ho fatto per interrompere l'ultimo kciclo non appena sappiamo che questa non è una soluzione valida (che lo fa funzionare quasi 9 volte più veloce). Ha trovato 1186585 soluzioni per la griglia completa dopo aver cercato tutti i 362880 rilievi di tutte le 1306368 soluzioni canoniche per l'ultimo blocco, per un totale di 474054819840 possibilità. Questo è un tasso di successo di 1 su 400000 per il secondo stadio. Proverò di nuovo presto con una ricerca casuale piuttosto che una scansione. Dovrebbe dare una risposta ragionevole in pochi milioni di tentativi, che dovrebbe richiedere solo pochi secondi.

La risposta globale dovrebbe essere (362880 * (1306368 * 2)) ^ 3 * hit rate = 8,5E35 * hit rate. Calcolando indietro dal numero nella domanda, mi aspetto un tasso di successo di 1 / 1,2E14. Quello che ho ottenuto finora con il mio singolo datapoint è 1 / (400000 * 1000) che è fuori di un fattore di circa un milione. Questa potrebbe essere un'anomalia del caso, un errore nel mio programma o un errore in matematica. Non saprò di che si tratta finché non eseguirò qualche altro test.

Lascio questo qui per stanotte. Il testo è un po 'scadente, lo sistemerò presto e spero di aggiungere altri risultati, e forse qualche parola su come renderlo più veloce e come estendere il concetto a N = 4. Non credo che apporterò troppe modifiche al mio programma, comunque :-)

Ah .. il programma:

#include "stdafx.h"
#define _CRT_RAND_S
#include <algorithm>  
#include <time.h>

unsigned int n[] = { 0,1,2,3,4,5,6,7,8 }, r[362880][12], b[2000000][9],i,j,k,l,u,v,w,x,y,z;

int main () {

  //Run through all possible permutations of n[] and load them into r[][] 
  i=0;  
  do {
      r[i][9] = r[i][10] = r[i][11]=0;
      for (l = 0; l < 9; l++){
          r[i][l] = n[l];
          r[i][9 + l / 3] |= 1 << n[l];
      }
      if((i+1)%5040==0) printf("%d%d%d %d%d%d %d%d%d %o %o %o %o \n"
          ,r[i][0],r[i][1],r[i][2],r[i][3],r[i][4],r[i][5],r[i][6],r[i][7],r[i][8],r[i][9],r[i][10],r[i][11],r[i][9]+r[i][10]+r[i][11]);
      i++;
  } while ( std::next_permutation(n,n+9) );

  //Initialise b[][]
  for (l = 0; l<2000000; l++) for (k = 0; k<9; k++) b[l][k]=0;
  //fill b[][] with all solutions of the first band, where row0 ={0,1,2,3,4,5,6,7,8} and row1<row2 
  l=0;
  for (i = 0; i<362880; i++) 
  if (!(r[0][9] & r[i][9] | r[0][10] & r[i][10] | r[0][11] & r[i][11])){printf("%d %d \n",i,l);
     for (j=i; j<362880;j++) 
       if(!(r[0][9]&r[j][9] | r[0][10]&r[j][10] | r[0][11]&r[j][11] | r[j][9]&r[i][9] | r[j][10]&r[i][10] | r[j][11]&r[i][11] )){
           for (k = 0; k < 9; k++){
               b[l][r[0][k]]|=1<<k;
               b[l][r[i][k]]|=1<<k;
               b[l][r[j][k]]|=1<<k;
            } 
            l++;
       }
//        printf("%d%d%d %d%d%d %d%d%d %o %o %o %o \n"
//        ,r[i][0],r[i][1],r[i][2],r[i][3],r[i][4],r[i][5],r[i][6],r[i][7],r[i][8],r[i][9],r[i][10],r[i][11],r[i][9]+r[i][10]+r[i][11]);
//        printf("%d%d%d %d%d%d %d%d%d %o %o %o %o \n"
//        ,r[j][0],r[j][1],r[j][2],r[j][3],r[j][4],r[j][5],r[j][6],r[j][7],r[j][8],r[j][9],r[j][10],r[j][11],r[j][9]+r[j][10]+r[j][11]);
//        printf("%d %d %o %o %o %o %o %o %o %o %o \n",i,l,b[l][0],b[l][1],b[l][2],b[l][3],b[l][4],b[l][5],b[l][6],b[l][7],b[l][8]);
  }

  // find a random solution for the first 2 bands
  l=0;
  do{
      rand_s(&u); u /= INT_MIN / -653184; //1st band selection
      rand_s(&v); v /= INT_MIN / -181440; //2nd band permutation
      rand_s(&w); w /= INT_MIN / -653184; //2nd band selection
      z = 0;
      for (k = 0; k < 9; k++) z |= b[u][k] & b[w][r[v][k]];
      l++;
  } while (z);
  printf("finished random after %d tries \n",l);
  printf("found solution with top band %d permutation 0, and middle band %d permutation %d \n",u,w,v);
  getchar();

  // scan all possibilities for the last band
  l=0;
  for (i = 0; i < 362880; i++) for (j = 0; j < 1306368; j++){
              z=0;
              for(k=0;(k<9)&&(!z);k++) z|= b[u][k] & b[j][r[i][k]] | b[j][r[i][k]] & b[w][r[v][k]];
              if (!z){ l++; printf("solution %d : i= %d j=%d",l,i,j); }
  }
  printf("finished bottom band scan at %d millisec \n", clock()); getchar();
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.