Compare commits

..

23 Commits

Author SHA1 Message Date
74f1fb63e1 Have the ability to do partial workloads.
(Read: Split searches among multiple machines.)
2025-01-27 00:13:00 +01:00
a7feb7101a precommit 2025-01-25 19:45:52 +01:00
28ecc74d7a Allows setting of cores to use (sorta fixes #8) 2025-01-25 19:45:31 +01:00
6d6db0ed28 #9 Make use of the Atomic package to keep track of solver.counter. 2025-01-25 19:08:31 +01:00
53953cf47c Fixing a leftover of #6, refactored to int64. 2025-01-25 17:54:18 +01:00
acf6ad1bb9 #7 (Should) fix end-state issue. 2025-01-25 17:32:11 +01:00
26b78420a2 #6 Improved estimation algorithm 2025-01-25 14:34:51 +01:00
582e268d56 #3 Prepare for release 2025-01-24 00:23:00 +01:00
5d02036f9e #1 Add README.md 2025-01-24 00:14:34 +01:00
6d2ac3675b Cleaning 2025-01-24 00:07:34 +01:00
6cd8deed23 Improved parameter handling. 2025-01-23 23:55:40 +01:00
c02aea543c #4 Embed _blocks.csv_ 2025-01-23 23:39:57 +01:00
c9b05e7026 #2 Proper usage of range 2025-01-23 23:11:30 +01:00
81edc8962d Accept commandline parameters. 2025-01-23 22:39:59 +01:00
ef2db7184e Add verbosity and progress. 2025-01-23 20:26:43 +01:00
0c31f7953d misc 2025-01-21 21:44:21 +01:00
8ec56bc3d8 Enable nicer logging. 2025-01-21 21:43:27 +01:00
5a9f22edb7 Minor touchups 2025-01-21 21:37:53 +01:00
458e5ac101 Rework how potential solutions are presented. 2025-01-21 21:37:07 +01:00
e7800660e9 Make solver.Run() nicer, readable. 2025-01-21 21:32:50 +01:00
57ac3a1d16 precommit 2025-01-21 21:26:49 +01:00
ba2aa13999 Remove debug 2025-01-21 21:26:17 +01:00
4017ecc1e1 "Temporary" removal of unused functions. 2025-01-21 21:25:36 +01:00
16 changed files with 675 additions and 227 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
builds

87
README.md Normal file
View File

@ -0,0 +1,87 @@
# Sudoku Funpark
Creating worlds most inefficient Sudoku solver, by trying every option, without any smart approach.
_(This was a learning project to get a better grasp of Golang. But more important; It was fun to do!.)_
## Goals
* Create the most inefficient Sudoku solver imaginable
* Be a learning experience for the Go programming language
I wrote [a blog post](https://blog.ligthert.net/posts/exploration-fun-and-process-cycles-of-sudoku/) about this.
## Usage
To use the sudoku solver, run the binary with all the parameters available:
```
Usage of ./sudoku-funpark:
-row1 string
1st row of the sudoku puzzle. (default "000000000")
-row2 string
2nd row of the sudoku puzzle. (default "000000000")
-row3 string
4rd row of the sudoku puzzle. (default "000000000")
-row4 string
4th row of the sudoku puzzle. (default "000000000")
-row5 string
5th row of the sudoku puzzle. (default "000000000")
-row6 string
6th row of the sudoku puzzle. (default "000000000")
-row7 string
7th row of the sudoku puzzle. (default "000000000")
-row8 string
8th row of the sudoku puzzle. (default "000000000")
-row9 string
9th row of the sudoku puzzle. (default "000000000")
```
Instead of using the 3x3 blocks with 3x3 digits, it uses horizontal rows from top to bottom.
## Example
To see the solver in action, run the tool with the following parameters.
For a short running (~15 seconds) example:
> $ ./sudoku-funpark -row1 769104802 -row2 154800060 -row3 832700154 -row4 600900328 -row5 045328670 -row6 328670945 -row7 597410280 -row8 006283090 -row9 200590006
For a long running (~1 hours 15 minutes) example:
> $ ./sudoku-funpark -row1 769104802 -row2 154800060 -row3 002700150 -row4 600900308 -row5 045328670 -row6 328670945 -row7 597410280 -row8 006283090 -row9 200590006
The outpot (of the short running parameters) will look something like this:
```
./sudoku-funpark -row1 769104802 -row2 154800060 -row3 832700154 -row4 600900328 -row5 045328670 -row6 328670945 -row7 597410280 -row8 006283090 -row9 200590006
2025/01/24 00:05:58 Loading blocks
2025/01/24 00:05:58 Loaded blocks (34.587221ms)
2025/01/24 00:05:58 Populating blocks
2025/01/24 00:05:58 Populated blocks (438.73054ms)
2025/01/24 00:05:58 Number of (potential) solutions: 26542080
2025/01/24 00:05:58 Validating solutions
2025/01/24 00:05:59 Processing: 6% (1729332/26542080); Rate (avg): 1729330/sec for 1.000003166s; Time left (est.): 14 seconds
2025/01/24 00:06:00 Processing: 13% (3461753/26542080); Rate (avg): 1732418/sec for 1.000002285s; Time left (est.): 13 seconds
2025/01/24 00:06:01 Processing: 19% (5228965/26542080); Rate (avg): 1767215/sec for 1.000019297s; Time left (est.): 12 seconds
2025/01/24 00:06:02 Processing: 26% (6996958/26542080); Rate (avg): 1767992/sec for 1.000200176s; Time left (est.): 11 seconds
2025/01/24 00:06:03 Processing: 33% (8767450/26542080); Rate (avg): 1770495/sec for 1.000016352s; Time left (est.): 10 seconds
2025/01/24 00:06:04 Processing: 39% (10576900/26542080); Rate (avg): 1809450/sec for 1.000014638s; Time left (est.): 8 seconds
2025/01/24 00:06:05 Processing: 46% (12400058/26542080); Rate (avg): 1823158/sec for 1.000352862s; Time left (est.): 7 seconds
2025/01/24 00:06:06 Processing: 53% (14185155/26542080); Rate (avg): 1785095/sec for 1.000254888s; Time left (est.): 6 seconds
2025/01/24 00:06:07 Processing: 60% (15968402/26542080); Rate (avg): 1783245/sec for 1.000002305s; Time left (est.): 5 seconds
2025/01/24 00:06:08 Processing: 66% (17655770/26542080); Rate (avg): 1687370/sec for 1.000068309s; Time left (est.): 5 seconds
2025/01/24 00:06:09 Processing: 73% (19442885/26542080); Rate (avg): 1787111/sec for 1.000006984s; Time left (est.): 3 seconds
2025/01/24 00:06:10 Processing: 79% (21183545/26542080); Rate (avg): 1740661/sec for 1.000002395s; Time left (est.): 3 seconds
2025/01/24 00:06:11 Processing: 86% (22998945/26542080); Rate (avg): 1815402/sec for 1.000113534s; Time left (est.): 1 second
2025/01/24 00:06:12 Processing: 90% (24109203/26542080); Rate (avg): 1110261/sec for 1.000312346s; Time left (est.): 2 seconds
2025/01/24 00:06:13 Processing: 100% (26542080/26542080); Rate (avg): 0/sec for 1.000117421s; Time left (est.): N/A
2025/01/24 00:06:13 Validated solutions (15.002654066s)
2025/01/24 00:06:13
Solution #1:
769154832
154832769
832769154
671945328
945328671
328671945
597416283
416283597
283597416
```
# Caveats
While this may very well solve all possible Sudoku puzzles (including the one [designed against brute force algorithms](https://en.wikipedia.org/wiki/Sudoku_solving_algorithms)), the blanks in the puzzle, the harder it is, the more possible solutions there are, the more solutions it needs to parse, the longer it takes. As this is a computational heavy program, the more CPU you throw against it the faster it will solve issues.

View File

@ -2,10 +2,22 @@
version: '3'
vars:
APP: sudoku-funpark
BUILD_DIR: builds
tasks:
default:
cmds:
- go run .
- go run . --help
silent: true
run_short:
cmds:
- go run . -row1 769104802 -row2 154800060 -row3 832700154 -row4 600900328 -row5 045328670 -row6 328670945 -row7 597410280 -row8 006283090 -row9 200590006
silent: true
run_long:
cmds:
- go run . -row1 769104802 -row2 154800060 -row3 002700150 -row4 600900308 -row5 045328670 -row6 328670945 -row7 597410280 -row8 006283090 -row9 200590006
silent: true
precommit:
cmds:
@ -15,3 +27,9 @@ tasks:
cmds:
- golangci-lint run
silent: true
build:
cmds:
- mkdir -p {{.BUILD_DIR}}
- rm {{.BUILD_DIR}}/* || true
- go tool dist list | grep -v android | grep -v ios | grep -v wasip1 | awk -F '/' '{printf "echo Compiling %s/%s; env CGO_ENABLED=1 GOOS=%s GOARCH=%s go build -o {{.BUILD_DIR}}/{{.APP}}.%s-%s\n",$1,$2,$1,$2,$1,$2 }' | sh
- for i in `ls {{.BUILD_DIR}}/*windows*`; do mv -v $i $i.exe; done

View File

@ -1,7 +1,10 @@
package main
import "gitea.ligthert.net/golang/sudoku-funpark/solver"
import (
"gitea.ligthert.net/golang/sudoku-funpark/solver"
)
func main() {
// Run the meat of the program
solver.Run()
}

View File

@ -1,80 +0,0 @@
Compat Matrix
1: 1 2 3 4 7
2: 1 2 3 5 8
3: 1 2 3 6 9
4: 1 4 5 6 7
5: 2 4 5 6 8
6: 3 4 5 6 9
7: 1 4 7 8 9
8: 2 5 7 8 9
9: 3 6 7 8 9
// counter: 123456789
// 1st Digit: 1 (49)
// 2nd Digit: 2 (50)
// 3rd Digit: 3 (51)
// 4th Digit: 4 (52)
// 5th Digit: 5 (53)
// 6th Digit: 6 (54)
// 7th Digit: 7 (55)
// 8th Digit: 8 (56)
// 9th Digit: 9 (57)
// 362880
// blocks := generate_blocks()
// fmt.Println(len(blocks))
// print_block(blocks[0])
// print_block(blocks[1])
// for i := range blocks {
// fmt.Println(blocks[i])
// }
row1 := "769104802"
row2 := "154800060"
row3 := "002700150"
row4 := "600900308"
row5 := "045328670"
row6 := "328670945"
row7 := "597410280"
row8 := "006283090"
row9 := "200590006"
row1 := "769154832"
row2 := "154832769"
row3 := "832769154"
row4 := "671945328"
row5 := "945328671"
row6 := "328671945"
row7 := "597416283"
row8 := "416284597"
row9 := "283597416"
// for rows1_index := range solver.row1s {
// for rows2_index := range solver.row2s {
// for rows3_index := range solver.row3s {
// for rows4_index := range solver.row4s {
// for rows5_index := range solver.row5s {
// for rows6_index := range solver.row6s {
// for rows7_index := range solver.row7s {
// for rows8_index := range solver.row8s {
// for rows9_index := range solver.row9s {
// go solver.routine_validator(rows1_index, rows2_index, rows3_index, rows4_index, rows5_index, rows6_index, rows7_index, rows8_index, rows9_index)
// // if solver.validate_combination(solver.row1s[rows1_index], solver.row2s[rows2_index], solver.row3s[rows3_index], solver.row4s[rows4_index], solver.row5s[rows5_index], solver.row6s[rows6_index], solver.row7s[rows7_index], solver.row8s[rows8_index], solver.row9s[rows9_index]) {
// // solver.solutions = append(solver.solutions, solver.render_combination(solver.row1s[rows1_index], solver.row2s[rows2_index], solver.row3s[rows3_index], solver.row4s[rows4_index], solver.row5s[rows5_index], solver.row6s[rows6_index], solver.row7s[rows7_index], solver.row8s[rows8_index], solver.row9s[rows9_index]))
// // }
// // solver.counter = solver.counter + 1
// // if solver.counter%1000000 == 0 {
// // percentage = (float32(solver.counter) / (float32(solver.iter) / 100))
// // fmt.Println("Processing:", percentage, "%; Procs:", runtime.NumGoroutine())
// // }
// }
// }
// }
// }
// }
// }
// }
// }
// }

View File

Can't render this file because it is too large.

View File

@ -1,69 +1,40 @@
package solver
import (
"encoding/csv"
"io"
"embed"
"log"
"os"
"strconv"
"strings"
"time"
)
// Dirty AF. Not happy with this. 😒
//
//go:embed blocks.csv
var f embed.FS
func (solver *Solver) load_blocks() {
defer solver.timeTrack(time.Now(), "Loaded blocks")
log.Println("Loading blocks")
var blocks []int
file, err := os.Open("blocks.csv")
file, err := f.ReadFile("blocks.csv")
if err != nil {
panic(err)
}
defer file.Close()
r := csv.NewReader(file)
for {
record, err := r.Read()
if err == io.EOF {
break
}
if err != nil {
log.Fatal(err)
}
temp := strings.Split(string(file), "\n")
block, _ := strconv.Atoi(record[0])
for _, line := range temp {
block, _ := strconv.Atoi(string(line))
if block == 0 { // Ignore new-line at the end of the file.
continue
}
blocks = append(blocks, block)
}
solver.blocks = blocks
}
func (solver *Solver) generate_blocks() []int {
var blocks []int
decvals := [9]int{49, 50, 51, 52, 53, 54, 55, 56, 57}
for counter := 123456789; counter <= 987654321; counter++ {
// Convert number to string ([]byte)
digits := strconv.Itoa(counter)
// Check if every number is only represented only once
var valid bool
valid = true
for decval := range decvals {
var count int
for digit := range digits {
if digits[digit] == byte(decvals[decval]) {
count = count + 1
}
}
if count != 1 {
valid = false
}
}
if valid {
blocks = append(blocks, counter)
}
}
return blocks
}

164
solver/flags.go Normal file
View File

@ -0,0 +1,164 @@
package solver
import (
"flag"
"fmt"
"log"
"os"
"runtime"
)
func (solver *Solver) parse_flags() {
// Define variables
var row1 string
var row2 string
var row3 string
var row4 string
var row5 string
var row6 string
var row7 string
var row8 string
var row9 string
var split int
var part int
// Define parameters
flag.StringVar(&row1, "row1", "000000000", "1st row of the sudoku puzzle.")
flag.StringVar(&row2, "row2", "000000000", "2nd row of the sudoku puzzle.")
flag.StringVar(&row3, "row3", "000000000", "4rd row of the sudoku puzzle.")
flag.StringVar(&row4, "row4", "000000000", "4th row of the sudoku puzzle.")
flag.StringVar(&row5, "row5", "000000000", "5th row of the sudoku puzzle.")
flag.StringVar(&row6, "row6", "000000000", "6th row of the sudoku puzzle.")
flag.StringVar(&row7, "row7", "000000000", "7th row of the sudoku puzzle.")
flag.StringVar(&row8, "row8", "000000000", "8th row of the sudoku puzzle.")
flag.StringVar(&row9, "row9", "000000000", "9th row of the sudoku puzzle.")
flag.IntVar(&solver.numcpus, "numcpu", runtime.NumCPU(), "Number of CPU cores to assign to this task.")
flag.IntVar(&split, "split", 1, "Split the tasks in n parts. This depends on the availability of the first row.")
flag.IntVar(&part, "part", 1, "Process part x in n parts. Cannot be lower than 1, or higher than specified in split.")
// Parse the flags
flag.Parse()
// Process any changes to the CPU usage.
if solver.numcpus <= 0 {
log.Printf("ERROR: Number of CPU cores must be 1 or higher.\n\n")
solver.print_Usage()
os.Exit(1)
}
if solver.numcpus != runtime.NumCPU() {
runtime.GOMAXPROCS(solver.numcpus)
}
// Process rows
if row1 == "000000000" || row2 == "000000000" || row3 == "000000000" || row4 == "000000000" || row5 == "000000000" || row6 == "000000000" || row7 == "000000000" || row8 == "000000000" || row9 == "000000000" {
log.Printf("ERROR: All parameters must be entered.\n\n")
solver.print_Usage()
os.Exit(1)
}
// Validate the row (never trust user input)
solver.validate_row("row1", row1)
solver.validate_row("row2", row2)
solver.validate_row("row3", row3)
solver.validate_row("row4", row4)
solver.validate_row("row5", row5)
solver.validate_row("row6", row6)
solver.validate_row("row7", row7)
solver.validate_row("row8", row8)
solver.validate_row("row9", row9)
// Put entries in into the struct
solver.row1 = row1
solver.row2 = row2
solver.row3 = row3
solver.row4 = row4
solver.row5 = row5
solver.row6 = row6
solver.row7 = row7
solver.row8 = row8
solver.row9 = row9
// Process workload splitting
// Ensure split and part are 1 or higher
if split <= 0 || part <= 0 {
log.Printf("ERROR: '-split' and '-part' need to be 1 or higher.\n")
solver.print_Usage()
os.Exit(1)
}
// Ensure part is between 1 and split
if part > split {
log.Printf("ERROR: '-part' cannot be bigger than `-split`.\n")
solver.print_Usage()
os.Exit(1)
}
solver.split = split
solver.part = part
}
func (solver *Solver) validate_row(name string, row string) {
var found bool
var double bool
count := make(map[rune]int)
// 1. Make sure the row is 9 in length
if len(row) != 9 {
log.Printf("ERROR: Invalid length of %s (%s), must be 9 numbers\n\n", name, row)
solver.print_Usage()
os.Exit(1)
}
// 2. Ensure all digits are numbers
for _, value := range row {
found = solver.valid_char(value)
}
if !found {
log.Printf("ERROR: Invalid character of %s (%s), must be 9 numbers\n\n", name, row)
solver.print_Usage()
os.Exit(1)
}
// 3. Ensure all digits (except zero) are there only once
for _, digits := range row {
count[digits] = count[digits] + 1
}
for key, value := range count {
if value > 1 && key != 48 {
double = true
}
}
if double {
log.Printf("ERROR: Double character of %s (%s), numbers between 1 and 9 may only be entered once\n\n", name, row)
solver.print_Usage()
os.Exit(1)
}
}
func (solver *Solver) valid_char(char rune) bool {
var valid bool
decvals := [10]int{48, 49, 50, 51, 52, 53, 54, 55, 56, 57}
for _, value := range decvals {
if char == rune(value) {
valid = true
}
}
return valid
}
func (solver *Solver) print_Usage() {
fmt.Fprintf(flag.CommandLine.Output(), "Usage of %s:\n", os.Args[0])
fmt.Fprintf(flag.CommandLine.Output(), "\nPut every row of a Sudoku puzzle as paramters.\nUse '0' for what is currently blank in the puzzle you wish to solve.\n\n")
fmt.Fprintf(flag.CommandLine.Output(), "Example: %s -row1 ... -row2 ... -row3 ... (etc)\n\n", os.Args[0])
flag.PrintDefaults()
}

13
solver/printers.go Normal file
View File

@ -0,0 +1,13 @@
package solver
import (
"fmt"
"log"
)
func (solver *Solver) print_solutions() {
for solution_index, solution := range solver.solutions {
log.Printf("\nSolution #%d:", solution_index+1)
fmt.Println(solution)
}
}

View File

@ -1,18 +1,37 @@
package solver
import (
"fmt"
"runtime"
"log"
"strconv"
"time"
)
func (solver *Solver) populate_blocks() {
defer solver.timeTrack(time.Now(), "Populated blocks")
log.Println("Populating blocks")
solver.find_blocks(&solver.row1, &solver.row1s)
solver.find_blocks(&solver.row2, &solver.row2s)
solver.find_blocks(&solver.row3, &solver.row3s)
solver.find_blocks(&solver.row4, &solver.row4s)
solver.find_blocks(&solver.row5, &solver.row5s)
solver.find_blocks(&solver.row6, &solver.row6s)
solver.find_blocks(&solver.row7, &solver.row7s)
solver.find_blocks(&solver.row8, &solver.row8s)
solver.find_blocks(&solver.row9, &solver.row9s)
// This calculates and stores the total number of solutions to validate.
solver.iter = int64(len(solver.row1s)) * int64(len(solver.row2s)) * int64(len(solver.row3s)) * int64(len(solver.row4s)) * int64(len(solver.row5s)) * int64(len(solver.row6s)) * int64(len(solver.row7s)) * int64(len(solver.row8s)) * int64(len(solver.row9s))
}
func (solver *Solver) find_blocks(row *string, rows *[]int) {
// Declare selection
var selection []int
var curr_blocks []int
func_row := *row
// fmt.Println(row)
for letter := range func_row {
if len(selection) == 0 {
@ -22,9 +41,9 @@ func (solver *Solver) find_blocks(row *string, rows *[]int) {
selection = nil
}
for block := range curr_blocks {
for _, block := range curr_blocks {
curr_row := strconv.Itoa(curr_blocks[block])
curr_row := strconv.Itoa(block)
if func_row[letter] == curr_row[letter] {
found_row, _ := strconv.Atoi(curr_row)
@ -64,73 +83,99 @@ func (solver *Solver) check_combinations() {
}
}
func (solver *Solver) routine_row1(index1 int) {
for index2 := range solver.row2s {
go solver.routine_row2(index1, index2)
}
}
func (solver *Solver) routine_row2(index1 int, index2 int) {
for index3 := range solver.row3s {
go solver.routine_row3(index1, index2, index3)
}
}
func (solver *Solver) routine_row3(index1 int, index2 int, index3 int) {
for index4 := range solver.row4s {
go solver.routine_row4(index1, index2, index3, index4)
}
}
func (solver *Solver) routine_row4(index1 int, index2 int, index3 int, index4 int) {
for index5 := range solver.row5s {
go solver.routine_row5(index1, index2, index3, index4, index5)
}
}
func (solver *Solver) routine_row5(index1 int, index2 int, index3 int, index4 int, index5 int) {
for index6 := range solver.row6s {
go solver.routine_row6(index1, index2, index3, index4, index5, index6)
}
}
func (solver *Solver) routine_row6(index1 int, index2 int, index3 int, index4 int, index5 int, index6 int) {
for index7 := range solver.row7s {
go solver.routine_row7(index1, index2, index3, index4, index5, index6, index7)
}
}
func (solver *Solver) routine_row7(index1 int, index2 int, index3 int, index4 int, index5 int, index6 int, index7 int) {
for index8 := range solver.row8s {
go solver.routine_row8(index1, index2, index3, index4, index5, index6, index7, index8)
}
}
func (solver *Solver) routine_row8(index1 int, index2 int, index3 int, index4 int, index5 int, index6 int, index7 int, index8 int) {
for index9 := range solver.row9s {
go solver.routine_row9(index1, index2, index3, index4, index5, index6, index7, index8, index9)
}
}
func (solver *Solver) routine_row9(index1 int, index2 int, index3 int, index4 int, index5 int, index6 int, index7 int, index8 int, index9 int) {
go solver.routine_validator(index1, index2, index3, index4, index5, index6, index7, index8, index9)
}
func (solver *Solver) routine_validator(rows1_index int, rows2_index int, rows3_index int, rows4_index int, rows5_index int, rows6_index int, rows7_index int, rows8_index int, rows9_index int) {
var percentage float32
// solver.counter = solver.counter + 1
solver.counter.Add(1)
if solver.validate_combination(solver.row1s[rows1_index], solver.row2s[rows2_index], solver.row3s[rows3_index], solver.row4s[rows4_index], solver.row5s[rows5_index], solver.row6s[rows6_index], solver.row7s[rows7_index], solver.row8s[rows8_index], solver.row9s[rows9_index]) {
solver.solutions = append(solver.solutions, solver.render_combination(solver.row1s[rows1_index], solver.row2s[rows2_index], solver.row3s[rows3_index], solver.row4s[rows4_index], solver.row5s[rows5_index], solver.row6s[rows6_index], solver.row7s[rows7_index], solver.row8s[rows8_index], solver.row9s[rows9_index]))
}
solver.counter = solver.counter + 1
}
if solver.counter%1000000 == 0 {
percentage = (float32(solver.counter) / (float32(solver.iter) / 100))
fmt.Println("Processing:", percentage, "%; Procs:", runtime.NumGoroutine())
func (solver *Solver) tracker() {
defer solver.timeTrack(time.Now(), "Validated solutions")
log.Println("Validating solutions")
// Determine if the main-loop is done
var done bool
// Tracking progress in percentages
var percentage float32
// Tracking progress in validated solutions
var track int
// Tracking the rate, starting point
var rate_start int64
// Tracking the rate, difference between previous iterations
var rate_diff int64
// Tracking duration
var timer_start = time.Now()
// Prevent division-by-zero error when establishing `rate_diff`
time.Sleep(time.Second)
// Estimation how long it will take
var est_fin string
// for solver.iter != solver.counter { // Start for-loop
for !done {
// Determine how far we are.
percentage = (float32(solver.counter.Load()) / (float32(solver.iter) / 100))
// Reset the loop
rate_diff = solver.counter.Load() - rate_start
if track <= int(percentage) || rate_diff == 0 { // Start if-statement
// Make sure something happened, making rate_start the only reliable variable
if rate_diff == 0 {
percentage = 100
solver.counter.Store(solver.iter)
done = true
}
timer_elapsed := time.Since(timer_start)
solver.rates = append(solver.rates, rate_diff)
rate_avg := solver.calc_avg()
// Estimate when this is finished
if rate_diff == 0 {
est_fin = "N/A"
} else {
est_fin = solver.secondsToHuman((solver.iter - solver.counter.Load()) / rate_avg)
}
// Printing the progress
log.Println("Processing: " + strconv.Itoa(int(percentage)) + "% (" + strconv.FormatInt(solver.counter.Load(), 10) + "/" + strconv.Itoa(int(solver.iter)) + "); Rate: " + strconv.FormatInt(rate_diff, 10) + "/sec for " + timer_elapsed.String() + "; Time left (est.): " + est_fin)
// After we are done printing, exit this for-loop
if percentage == 100 {
break
}
// Wrap up the loop or break
if int(percentage) > track {
track = int(percentage)
} else {
track = track + 1
}
timer_start = time.Now()
}
// Resert the rate counter
rate_start = solver.counter.Load()
// Sleep for a second
time.Sleep(1 * time.Second)
} // End for-loop
}
func (solver *Solver) validate_combination(row1 int, row2 int, row3 int, row4 int, row5 int, row6 int, row7 int, row8 int, row9 int) bool {
@ -188,3 +233,15 @@ func (solver *Solver) validate_combination(row1 int, row2 int, row3 int, row4 in
return retval
}
func (solver *Solver) calc_avg() (avg int64) {
var avg_sum int64
for _, value := range solver.rates {
avg_sum += value
}
avg = avg_sum / int64(len(solver.rates))
return
}

View File

@ -1,40 +1,43 @@
package solver
import (
"fmt"
"log"
"runtime"
"strconv"
)
func Run() {
// Instantiate the Solver interface
solver := Solver{}
solver.row1 = "769104802"
solver.row2 = "154800060"
solver.row3 = "002700150"
solver.row4 = "600900308"
solver.row5 = "045328670"
solver.row6 = "328670945"
solver.row7 = "597410280"
solver.row8 = "006283090"
solver.row9 = "200590006"
// Parse and handle flags
solver.parse_flags()
// Report number of CPUs being used, if set.
if runtime.NumCPU() != solver.numcpus {
log.Println("Using " + strconv.Itoa(solver.numcpus) + " CPUs, (was " + strconv.Itoa(runtime.NumCPU()) + ")")
}
// Load blocks from CSV file
solver.load_blocks()
fmt.Println("Total blocks:", len(solver.blocks))
// Find rows that fit with the entered rows
solver.find_blocks(&solver.row1, &solver.row1s)
solver.find_blocks(&solver.row2, &solver.row2s)
solver.find_blocks(&solver.row3, &solver.row3s)
solver.find_blocks(&solver.row4, &solver.row4s)
solver.find_blocks(&solver.row5, &solver.row5s)
solver.find_blocks(&solver.row6, &solver.row6s)
solver.find_blocks(&solver.row7, &solver.row7s)
solver.find_blocks(&solver.row8, &solver.row8s)
solver.find_blocks(&solver.row9, &solver.row9s)
solver.populate_blocks()
solver.iter = int64(len(solver.row1s)) * int64(len(solver.row2s)) * int64(len(solver.row3s)) * int64(len(solver.row4s)) * int64(len(solver.row5s)) * int64(len(solver.row6s)) * int64(len(solver.row7s)) * int64(len(solver.row8s)) * int64(len(solver.row9s))
fmt.Println("Number of iterations:", solver.iter)
// If needed, split the workload
// May exit and throw an error if the work load isn't viable
if solver.split != 1 {
solver.select_workload()
}
solver.check_combinations()
// Print the total number of solutions to validate
log.Println("Number of (potential) solutions:", solver.iter)
fmt.Println(solver.solutions)
// Check the number of solutions
go solver.check_combinations()
solver.tracker()
// Print the valid solutions
solver.print_solutions()
}

61
solver/split.go Normal file
View File

@ -0,0 +1,61 @@
package solver
import (
"log"
"os"
"strconv"
"time"
)
// Perform some checks
// and
// Modify solver.row1s so it limits the workload to what is only desired.
func (solver *Solver) select_workload() {
if solver.split > len(solver.row1s) {
log.Println("ERROR: Unable to divide the workload in " + strconv.Itoa(solver.split) + " parts, when only " + strconv.Itoa(len(solver.row1s)) + " are available.\n\n")
os.Exit(1)
}
defer solver.timeTrack(time.Now(), "Workload set")
log.Println("Setting workload")
log.Println("We are agent " + strconv.Itoa(solver.part) + " of " + strconv.Itoa(solver.split))
workloads := solver.split_workload()
solver.set_workload(workloads)
}
// Determine how workload should be split among the agents
func (solver *Solver) split_workload() []int {
agents := make([]int, solver.split)
var tracker int
var tasks int = len(solver.row1s)
for tasks != 0 {
agents[tracker] += 1
tasks -= 1
tracker += 1
if tracker == solver.split {
tracker = 0
}
}
return agents
}
// Set the workload by setting solver.row1s
func (solver *Solver) set_workload(agents []int) {
var start int = 0
var finish int = 0
for key, value := range agents {
if key == solver.part-1 {
finish = start + value
break
} else {
start += value
}
}
// Set the shortened set of instructions
solver.row1s = solver.row1s[start:finish]
// Recalculate how much we need to grind through
solver.iter = int64(len(solver.row1s)) * int64(len(solver.row2s)) * int64(len(solver.row3s)) * int64(len(solver.row4s)) * int64(len(solver.row5s)) * int64(len(solver.row6s)) * int64(len(solver.row7s)) * int64(len(solver.row8s)) * int64(len(solver.row9s))
}

View File

@ -1,7 +1,6 @@
package solver
import (
"fmt"
"strconv"
)
@ -17,12 +16,5 @@ func (solver *Solver) render_combination(row1 int, row2 int, row3 int, row4 int,
row8s := strconv.Itoa(row8)
row9s := strconv.Itoa(row9)
// combination = row1s + "\n" + row2s + "\n" + row3s + "\n" + row4s + "\n" + row5s + "\n" + row6s + "\n" + row7s + "\n" + row8s + "\n" + row9s + "\n"
return row1s + "\n" + row2s + "\n" + row3s + "\n" + row4s + "\n" + row5s + "\n" + row6s + "\n" + row7s + "\n" + row8s + "\n" + row9s + "\n"
}
func (solver *Solver) print_block(block int) {
digits := strconv.Itoa(block)
fmt.Printf("%c %c %c\n%c %c %c\n%c %c %c\n\n", digits[0], digits[1], digits[2], digits[3], digits[4], digits[5], digits[6], digits[7], digits[8])
}

56
solver/timers.go Normal file
View File

@ -0,0 +1,56 @@
package solver
import (
"log"
"math"
"strconv"
"time"
)
func (solver *Solver) timeTrack(start time.Time, msg string) {
elapsed := time.Since(start)
log.Printf("%s (%s)", msg, elapsed)
}
// Stolen from https://socketloop.com/tutorials/golang-convert-seconds-to-human-readable-time-format-example
func (solver *Solver) plural(count int, singular string) (result string) {
if (count == 1) || (count == 0) {
result = strconv.Itoa(count) + " " + singular + " "
} else {
result = strconv.Itoa(count) + " " + singular + "s "
}
return
}
func (solver *Solver) secondsToHuman(input int64) (result string) {
years := math.Floor(float64(input) / 60 / 60 / 24 / 7 / 30 / 12)
seconds := input % (60 * 60 * 24 * 7 * 30 * 12)
months := math.Floor(float64(seconds) / 60 / 60 / 24 / 7 / 30)
seconds = input % (60 * 60 * 24 * 7 * 30)
weeks := math.Floor(float64(seconds) / 60 / 60 / 24 / 7)
seconds = input % (60 * 60 * 24 * 7)
days := math.Floor(float64(seconds) / 60 / 60 / 24)
seconds = input % (60 * 60 * 24)
hours := math.Floor(float64(seconds) / 60 / 60)
seconds = input % (60 * 60)
minutes := math.Floor(float64(seconds) / 60)
seconds = input % 60
if years > 0 {
result = solver.plural(int(years), "year") + solver.plural(int(months), "month") + solver.plural(int(weeks), "week") + solver.plural(int(days), "day") + solver.plural(int(hours), "hour") + solver.plural(int(minutes), "minute") + solver.plural(int(seconds), "second")
} else if months > 0 {
result = solver.plural(int(months), "month") + solver.plural(int(weeks), "week") + solver.plural(int(days), "day") + solver.plural(int(hours), "hour") + solver.plural(int(minutes), "minute") + solver.plural(int(seconds), "second")
} else if weeks > 0 {
result = solver.plural(int(weeks), "week") + solver.plural(int(days), "day") + solver.plural(int(hours), "hour") + solver.plural(int(minutes), "minute") + solver.plural(int(seconds), "second")
} else if days > 0 {
result = solver.plural(int(days), "day") + solver.plural(int(hours), "hour") + solver.plural(int(minutes), "minute") + solver.plural(int(seconds), "second")
} else if hours > 0 {
result = solver.plural(int(hours), "hour") + solver.plural(int(minutes), "minute") + solver.plural(int(seconds), "second")
} else if minutes > 0 {
result = solver.plural(int(minutes), "minute") + solver.plural(int(seconds), "second")
} else {
result = solver.plural(int(seconds), "second")
}
return
}

View File

@ -1,5 +1,9 @@
package solver
import (
"sync/atomic"
)
type Solver struct {
blocks []int
row1 string
@ -21,6 +25,10 @@ type Solver struct {
row8s []int
row9s []int
iter int64
counter int64
counter atomic.Int64
solutions []string
rates []int64
numcpus int
split int
part int
}

94
unused.txt Normal file
View File

@ -0,0 +1,94 @@
// Processing
func (solver *Solver) routine_row1(index1 int) {
for index2 := range solver.row2s {
go solver.routine_row2(index1, index2)
}
}
func (solver *Solver) routine_row2(index1 int, index2 int) {
for index3 := range solver.row3s {
go solver.routine_row3(index1, index2, index3)
}
}
func (solver *Solver) routine_row3(index1 int, index2 int, index3 int) {
for index4 := range solver.row4s {
go solver.routine_row4(index1, index2, index3, index4)
}
}
func (solver *Solver) routine_row4(index1 int, index2 int, index3 int, index4 int) {
for index5 := range solver.row5s {
go solver.routine_row5(index1, index2, index3, index4, index5)
}
}
func (solver *Solver) routine_row5(index1 int, index2 int, index3 int, index4 int, index5 int) {
for index6 := range solver.row6s {
go solver.routine_row6(index1, index2, index3, index4, index5, index6)
}
}
func (solver *Solver) routine_row6(index1 int, index2 int, index3 int, index4 int, index5 int, index6 int) {
for index7 := range solver.row7s {
go solver.routine_row7(index1, index2, index3, index4, index5, index6, index7)
}
}
func (solver *Solver) routine_row7(index1 int, index2 int, index3 int, index4 int, index5 int, index6 int, index7 int) {
for index8 := range solver.row8s {
go solver.routine_row8(index1, index2, index3, index4, index5, index6, index7, index8)
}
}
func (solver *Solver) routine_row8(index1 int, index2 int, index3 int, index4 int, index5 int, index6 int, index7 int, index8 int) {
for index9 := range solver.row9s {
go solver.routine_row9(index1, index2, index3, index4, index5, index6, index7, index8, index9)
}
}
func (solver *Solver) routine_row9(index1 int, index2 int, index3 int, index4 int, index5 int, index6 int, index7 int, index8 int, index9 int) {
go solver.routine_validator(index1, index2, index3, index4, index5, index6, index7, index8, index9)
}
// blocks.go
func (solver *Solver) generate_blocks() []int {
var blocks []int
decvals := [9]int{49, 50, 51, 52, 53, 54, 55, 56, 57}
for counter := 123456789; counter <= 987654321; counter++ {
// Convert number to string ([]byte)
digits := strconv.Itoa(counter)
// Check if every number is only represented only once
var valid bool
valid = true
for decval := range decvals {
var count int
for digit := range digits {
if digits[digit] == byte(decvals[decval]) {
count = count + 1
}
}
if count != 1 {
valid = false
}
}
if valid {
blocks = append(blocks, counter)
}
}
return blocks
}
// stash.go
func (solver *Solver) print_block(block int) {
digits := strconv.Itoa(block)
fmt.Printf("%c %c %c\n%c %c %c\n%c %c %c\n\n", digits[0], digits[1], digits[2], digits[3], digits[4], digits[5], digits[6], digits[7], digits[8])
}