rewrite in go; added multi-threading support; added support for variable grid sizes

This commit is contained in:
overflowerror 2021-01-03 22:00:04 +01:00
parent c3a9124036
commit 3b453ec26e
2 changed files with 454 additions and 379 deletions

379
main.c
View file

@ -1,379 +0,0 @@
#include <string.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#define MAX_LIST_LENGTH 9
typedef struct {
int length;
int list[MAX_LIST_LENGTH];
} list_t;
typedef struct {
list_t field[9][9];
} field_t;
void setAll(list_t* list) {
list->length = MAX_LIST_LENGTH;
for(int i = 0; i < MAX_LIST_LENGTH; i++) {
list->list[i] = i + 1;
}
}
void output(field_t field) {
for (int y = 0; y < 9; y++) {
for (int x = 0; x < 9; x++) {
if (field.field[x][y].length == 0) {
putchar('#');
} else if (field.field[x][y].length == 1) {
putchar(field.field[x][y].list[0] + '0');
} else {
putchar('?');
}
}
putchar('\n');
}
}
field_t readField(const char* input) {
int x = 0, y = 0;
field_t field;
size_t length = strlen(input);
for (size_t i = 0; i < length; i++) {
char c = input[i];
bool inc = false;
if (c == ' ') {
setAll(&(field.field[x][y]));
inc = true;
} else if (c >= '1' && c <= '9') {
field.field[x][y].length = 1;
field.field[x][y].list[0] = c - '0';
inc = true;
}
if (inc) {
x++;
if (x >= 9) {
x = 0;
y++;
}
if (y >= 9) {
break;
}
}
}
return field;
}
static inline void removeFromList(list_t* list, int i) {
for (int li = i + 1; li < list->length; li++) {
list->list[li - 1] = list->list[li];
}
list->length--;
}
static inline int eliminate(field_t *field) {
int eliminated = 0;
// columns
for (int x = 0; x < 9; x++) {
unsigned long bitfield = 0x1ff;
for (int y = 0; y < 9; y++) {
list_t* list = &(field->field[x][y]);
if (list->length == 1) {
bitfield &= ~(1 << (list->list[0] - 1));
}
}
for (int y = 0; y < 9; y++) {
list_t* list = &(field->field[x][y]);
if (list->length == 1) {
continue;
}
for (int i = 0; i < list->length; i++) {
if (!(bitfield & (1 << (list->list[i] - 1)))) {
eliminated++;
removeFromList(list, i);
i--;
}
}
}
}
// rows
for (int y = 0; y < 9; y++) {
unsigned long bitfield = 0x1ff;
for (int x = 0; x < 9; x++) {
list_t* list = &(field->field[x][y]);
if (list->length == 1) {
bitfield &= ~(1 << (list->list[0] - 1));
}
}
for (int x = 0; x < 9; x++) {
list_t* list = &(field->field[x][y]);
if (list->length == 1) {
continue;
}
for (int i = 0; i < list->length; i++) {
if (!(bitfield & (1 << (list->list[i] - 1)))) {
eliminated++;
removeFromList(list, i);
i--;
}
}
}
}
for (int xs = 0; xs < 3; xs++) {
for (int ys = 0; ys < 3; ys++) {
unsigned long bitfield = 0x1ff;
for (int x = xs * 3; x < (xs + 1) * 3; x++) {
for (int y = ys * 3; y < (ys + 1) * 3; y++) {
list_t* list = &(field->field[x][y]);
if (list->length == 1) {
bitfield &= ~(1 << (list->list[0] - 1));
}
}
}
for (int x = xs * 3; x < (xs + 1) * 3; x++) {
for (int y = ys * 3; y < (ys + 1) * 3; y++) {
list_t* list = &(field->field[x][y]);
if (list->length == 1) {
continue;
}
for (int i = 0; i < list->length; i++) {
if (!(bitfield & (1 << (list->list[i] - 1)))) {
eliminated++;
removeFromList(list, i);
i--;
}
}
}
}
}
}
return eliminated;
}
typedef enum {
solved, unsolved, error
} state_t;
typedef struct {
state_t state;
field_t field;
} solution_t;
static inline state_t getState(field_t field) {
for (int x = 0; x < 9; x++) {
for (int y = 0; y < 9; y++) {
int l = field.field[x][y].length;
switch(l) {
case 0:
return error;
case 1:
continue;
default:
return unsolved;
}
}
}
return solved;
}
typedef struct {
int length;
struct guess {
int x;
int y;
int n;
}* guesses;
} guesslist_t;
static inline guesslist_t possibleGuesses(field_t field) {
guesslist_t list;
list.guesses = NULL;
list.length = 0;
for (int x = 0; x < 9; x++) {
for (int y = 0; y < 9; y++) {
int l = field.field[x][y].length;
if (l > 1) {
void* tmp = realloc(list.guesses, (list.length + l) * sizeof(struct guess));
if (tmp == NULL) {
fprintf(stderr, "panic: malloc failed\n");
exit(1);
}
list.guesses = tmp;
for (int i = 0; i < l; i++) {
list.guesses[list.length++] = (struct guess) {
x, y, field.field[x][y].list[i]
};
}
}
}
}
return list;
}
static inline void freeGuesses(guesslist_t guesses) {
free(guesses.guesses);
}
solution_t solve(field_t field) {
int n;
while ((n = eliminate(&field))) {
printf("possibilities eliminated: %d\n", n);
}
state_t state = getState(field);
if (state == solved || state == error) {
return (solution_t) {
state, field
};
} else {
printf("no solution found using basic rules\n");
guesslist_t list = possibleGuesses(field);
for (int i = 0; i < list.length; i++) {
field_t field_ = field;
struct guess guess = list.guesses[i];
printf("guessing: %d,%d = %d\n", guess.x + 1, guess.y + 1, guess.n);
field_.field[guess.x][guess.y].length = 1;
field_.field[guess.x][guess.y].list[0] = guess.n;
solution_t solution = solve(field_);
if (solution.state == solved) {
freeGuesses(list);
return solution;
}
}
freeGuesses(list);
return (solution_t) {
error,
field
};
}
}
const char* wikipedia = "\
53 7 \
6 195 \
98 6 \
8 6 3\
4 8 3 1\
7 2 6\
6 28 \
419 5\
8 79\
";
const char* medium = "\
47 53 \
9 8 \
1 2 5 \
1 7 5 4\
39 1 \
65 9 3\
95 172 6\
28 1\
7 64 9 \
";
const char* hard = "\
29 4 \
31 5 26\
96 \
2 83 \
1 98 5\
57 \
768 4\
6 2 9\
4 3\
";
const char* very_difficult = "\
21 6 4 \
5 9 \
4 2 1\
84 5 \
1 2\
4 75\
7 6 4\
3 9 \
8 3 61 \
";
const char* hardest = "\
8 \
36 \
7 9 2 \
5 7 \
457 \
1 3 \
1 68\
85 1 \
9 4 \
";
int main() {
field_t field = readField(hardest);
output(field);
solution_t solution = solve(field);
printf("\n\n");
switch(solution.state) {
case error:
printf("error\n");
break;
case solved:
printf("solved\n");
break;
case unsolved:
printf("unsolved\n");
break;
default:
break;
}
output(solution.field);
return 0;
}

454
main.go Normal file
View file

@ -0,0 +1,454 @@
package main
import (
"fmt"
"log"
"os"
"runtime"
"sync/atomic"
"time"
)
/*const size = 9
const blocks = 3
const blockSize = size / blocks*/
const size = 16
const blocks = 4
const blockSize = size / blocks
const maskZero = (1 << size) - 1
const guessChanSize = 1<<20
const eliminatedChanSize = 1<<15
type field [size][size][]int
var guesses = make(chan field, guessChanSize)
var solution = make(chan field, 1)
var eliminatedChan = make(chan int, eliminatedChanSize)
// 1 because of the first guess = starting point
var possibilities int64 = 1
var globalEliminated int64 = 0
func eliminate(field *field) *int {
eliminated := 0
// columns
for x := 0; x < size; x++ {
set := 0
bitfield := maskZero
for y := 0; y < size; y++ {
if len(field[x][y]) == 1 {
set++
mask := 1 << (field[x][y][0] - 1)
if bitfield & mask == 0 {
// duplicate number
return nil
}
bitfield &= mask ^ maskZero
}
}
for y := 0; y < size; y++ {
if len(field[x][y]) != 1 {
length := len(field[x][y])
for i := 0; i < length; i++ {
if (bitfield & (1 << (field[x][y][i] - 1))) == 0 {
eliminated++
field[x][y][i] = field[x][y][length - 1]
field[x][y] = field[x][y][:length - 1]
length--
i--
}
}
if length == 0 {
return nil
}
}
}
}
// rows
for y := 0; y < size; y++ {
set := 0
bitfield := maskZero
for x := 0; x < size; x++ {
if len(field[x][y]) == 1 {
set++
mask := 1 << (field[x][y][0] - 1)
if bitfield & mask == 0 {
// duplicate number
return nil
}
bitfield &= mask ^ maskZero
}
}
for x := 0; x < size; x++ {
if len(field[x][y]) != 1 {
length := len(field[x][y])
for i := 0; i < length; i++ {
if (bitfield & (1 << (field[x][y][i] - 1))) == 0 {
eliminated++
field[x][y][i] = field[x][y][length - 1]
field[x][y] = field[x][y][:length - 1]
length--
i--
}
}
if length == 0 {
return nil
}
}
}
}
// blocks
for sx := 0; sx < blocks; sx++ {
for sy := 0; sy < blocks; sy++ {
set := 0
bitfield := maskZero
for x := sx * blockSize; x < (sx + 1) * blockSize; x++ {
for y := sy * blockSize; y < (sy + 1) * blockSize; y++ {
if len(field[x][y]) == 1 {
set++
mask := 1 << (field[x][y][0] - 1)
if bitfield & mask == 0 {
// duplicate number
return nil
}
bitfield &= mask ^ maskZero
}
}
}
for x := sx * blockSize; x < (sx + 1) * blockSize; x++ {
for y := sy * blockSize; y < (sy + 1) * blockSize; y++ {
if len(field[x][y]) != 1 {
length := len(field[x][y])
for i := 0; i < length; i++ {
if (bitfield & (1 << (field[x][y][i] - 1))) == 0 {
eliminated++
field[x][y][i] = field[x][y][length-1]
field[x][y] = field[x][y][:length-1]
length--
i--
}
}
if length == 0 {
return nil
}
}
}
}
}
}
return &eliminated
}
type result int
const (
unsolved result = iota
solved
wrong
)
func check(field *field) result {
for _, column := range field {
for _, row := range column {
switch len(row) {
case 0:
return wrong
case 1:
// check next
default:
return unsolved
}
}
}
return solved
}
func copyField(fieldToCopy field) field {
var fieldCopy field
for x := 0; x < size; x++ {
for y := 0; y < size; y++ {
fieldCopy[x][y] = make([]int, len(fieldToCopy[x][y]))
copy(fieldCopy[x][y], fieldToCopy[x][y])
}
}
return fieldCopy
}
func addGuesses(field field) {
for x := 0; x < size; x++ {
for y := 0; y < size; y++ {
if len(field[x][y]) > 1 {
for _, value := range field[x][y] {
newField := copyField(field)
newField[x][y] = []int{value}
atomic.AddInt64(&possibilities, 1)
guesses <- newField
}
// we don't need to add more
return
}
}
}
}
func removePossibility() {
if atomic.AddInt64(&possibilities, -1) <= 0 {
fmt.Println("no solution found")
os.Exit(1)
}
}
func worker() {
workerLoop:
for {
field := <- guesses
eliminateLoop:
for {
eliminated := eliminate(&field)
if eliminated == nil {
removePossibility()
continue workerLoop
} else if *eliminated == 0 {
break eliminateLoop
} else {
eliminatedChan <- *eliminated
}
}
switch check(&field) {
case wrong:
removePossibility()
continue workerLoop
case solved:
solution <- field
// we don't need the worker anymore
break workerLoop
case unsolved:
addGuesses(field)
removePossibility()
continue workerLoop
}
}
}
func all() []int {
var list []int
for i := 0; i < size; i++ {
list = append(list, i + 1)
}
return list
}
func readField(input string) field {
var field field
x := 0
y := 0
for _, c := range input {
inc := false
if c == ' ' || c == '-' {
field[x][y] = all()
inc = true
} else if c >= '0' && c <= '9' {
field[x][y] = []int{int(c - '0')}
inc = true
} else if c >= 'a' && c <= 'z' {
field[x][y] = []int{int(c - 'a' + 10)}
inc = true
} else {
// ignore
}
if inc {
x++
if x >= size {
x = 0
y++
}
if y >= size {
break
}
}
}
for x = 0; x < size; x++ {
for y = 0; y < size; y++ {
if field[x][y] == nil {
log.Fatal("not all cells filled")
} else {
for _, value := range field[x][y] {
if value < 1 || value > size {
log.Fatal("cell range exceeded")
}
}
}
}
}
return field
}
func printField(field field) {
for y := 0; y < size; y++ {
for x := 0; x < size; x++ {
if len(field[x][y]) == 0 {
fmt.Print("#")
} else if len(field[x][y]) == 1 {
value := field[x][y][0]
if value < 10 {
fmt.Printf("%c", rune(value + '0'))
} else {
fmt.Printf("%c", rune(value - 10 + 'a'))
}
} else {
fmt.Print(" ")
}
}
fmt.Print("\n")
}
}
func eliminiatedCounter() {
for {
eliminiated := <- eliminatedChan
globalEliminated += int64(eliminiated)
}
}
/*const wikipedia = `
53--7----
6--195---
-98----6-
8---6---3
4--8-3--1
7---2---6
-6----28-
---419--5
----8--79
`
const medium = `
47-53----
-9-----8-
-1--2--5-
1--7--5-4
--39--1--
---65-9-3
95--172-6
28------1
7---64-9-
`
const hard = `
---29--4-
--31-5-26
--96-----
2----83--
1----98-5
-57------
768-----4
----6-2-9
----4---3
`
const veryDifficult = `
-21-6-4--
---5---9-
4----2--1
84--5----
1-------2
----4--75
7--6----4
-3---9---
--8-3-61-
`
const hardest = `
8--------
--36-----
-7--9-2--
-5---7---
----457--
---1---3-
--1----68
--85---1-
-9----4--
`*/
const test16x16 = `
c3-8---g7f---4-e
---ac-3-----g2-7
-b-d--8-5-9---f-
-9-4--2---gdb-53
--f-----27b---4-
4--------5-g9-7c
---e3----c--85--
-c951--e8-a4f---
---g48-56--a1ed-
--d9--b----c5---
5a-6f-e--------b
-e---7d2-----8--
a2-f85---g--4-e-
-4---a-3-2--6-1-
g-5c-----4-e2---
6-e---gfa---3-c5
`
func main() {
field := readField(test16x16)
printField(field)
guesses <- field
fmt.Println()
threads := runtime.GOMAXPROCS(0)
fmt.Printf("number of threads: %d\n", threads)
for x := 0; x < threads; x++ {
go worker()
}
go eliminiatedCounter()
for {
fmt.Printf("queued: %10d, remaining: %10d, eliminated: %d (+ %d) \r", len(guesses), possibilities, globalEliminated, len(eliminatedChan))
time.Sleep(10 * time.Millisecond)
if len(solution) > 0 {
break
}
}
solution := <- solution
fmt.Println()
fmt.Println()
fmt.Println()
fmt.Println("solved")
printField(solution)
}