Compare commits
	
		
			27 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| cd54fdf345 | |||
| cced4f04b2 | |||
| 2bdcdcb1dd | |||
| 71fefd760e | |||
| 2461f5b417 | |||
| b83d6bdde4 | |||
| a314e945ed | |||
| 2735074515 | |||
| 647476f6d8 | |||
| 515034a64e | |||
| a7c5845b7a | |||
| 6dd5d2f232 | |||
| 6b0e55e3f9 | |||
| 719dd43307 | |||
| 6241c0b720 | |||
| 3d4729bdb6 | |||
| 7084a62962 | |||
| 8d2cca657b | |||
| 56ebe616ab | |||
| fc555fa581 | |||
| 0e3aa42b78 | |||
| c72bc6b0e5 | |||
| c73c88679a | |||
| 81b44e1702 | |||
| c8f897cf8d | |||
| c0525c2bc8 | |||
| 3f6ba6e9dc | 
							
								
								
									
										19
									
								
								.gitea/workflows/demo.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								.gitea/workflows/demo.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
			
		||||
name: Gitea Actions Demo
 | 
			
		||||
run-name: ${{ gitea.actor }} is testing out Gitea Actions 🚀
 | 
			
		||||
on: [push]
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  Explore-Gitea-Actions:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - run: echo "🎉 The job was automatically triggered by a ${{ gitea.event_name }} event."
 | 
			
		||||
      - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by Gitea!"
 | 
			
		||||
      - run: echo "🔎 The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}."
 | 
			
		||||
      - name: Check out repository code
 | 
			
		||||
        uses: actions/checkout@v4
 | 
			
		||||
      - run: echo "💡 The ${{ gitea.repository }} repository has been cloned to the runner."
 | 
			
		||||
      - run: echo "🖥️ The workflow is now ready to test your code on the runner."
 | 
			
		||||
      - name: List files in the repository
 | 
			
		||||
        run: |
 | 
			
		||||
          ls ${{ gitea.workspace }}
 | 
			
		||||
      - run: echo "🍏 This job's status is ${{ job.status }}."
 | 
			
		||||
@@ -2,9 +2,17 @@
 | 
			
		||||
# See https://pre-commit.com/hooks.html for more hooks
 | 
			
		||||
repos:
 | 
			
		||||
-   repo: https://github.com/pre-commit/pre-commit-hooks
 | 
			
		||||
    rev: v3.2.0
 | 
			
		||||
    rev: v5.0.0
 | 
			
		||||
    hooks:
 | 
			
		||||
    -   id: trailing-whitespace
 | 
			
		||||
    -   id: end-of-file-fixer
 | 
			
		||||
    -   id: check-yaml
 | 
			
		||||
    -   id: check-added-large-files
 | 
			
		||||
-   repo: https://github.com/dnephin/pre-commit-golang
 | 
			
		||||
    rev: v0.5.1
 | 
			
		||||
    hooks:
 | 
			
		||||
      - id: go-fmt
 | 
			
		||||
      - id: go-imports
 | 
			
		||||
      - id: no-go-testing
 | 
			
		||||
      - id: golangci-lint
 | 
			
		||||
      - id: go-unit-tests
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										126
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										126
									
								
								README.md
									
									
									
									
									
								
							@@ -9,10 +9,23 @@ _(This was a learning project to get a better grasp of Golang. But more importan
 | 
			
		||||
 | 
			
		||||
I wrote [a blog post](https://blog.ligthert.net/posts/exploration-fun-and-process-cycles-of-sudoku/) about this.
 | 
			
		||||
 | 
			
		||||
## Features
 | 
			
		||||
* Worlds least efficient Sudoku solver
 | 
			
		||||
* Ability to assign a number of CPU cores to this task
 | 
			
		||||
* Split workloads among several computers
 | 
			
		||||
 | 
			
		||||
## Usage
 | 
			
		||||
To use the sudoku solver, run the binary with all the parameters available:
 | 
			
		||||
To use the sudoku solver, run the binary with all the `-row` parameters available. Use other parameters to tune our output or CPU usage.
 | 
			
		||||
```
 | 
			
		||||
Usage of ./sudoku-funpark:
 | 
			
		||||
  -numcpu int
 | 
			
		||||
    	Number of CPU cores to assign to this task. (default 12)
 | 
			
		||||
  -output string
 | 
			
		||||
    	Type of output. 'human' for human readable. 'flat' for flat as stored in memory output. 'json' for JSON output. (default "human")
 | 
			
		||||
  -part int
 | 
			
		||||
    	Process part x in n parts. Cannot be lower than 1, or higher than specified in split. (default 1)
 | 
			
		||||
  -print string
 | 
			
		||||
    	'short': normal output;'long': normal output with timestamps; 'silent': Only print the results; (default "short")
 | 
			
		||||
  -row1 string
 | 
			
		||||
    	1st row of the sudoku puzzle. (default "000000000")
 | 
			
		||||
  -row2 string
 | 
			
		||||
@@ -31,6 +44,8 @@ Usage of ./sudoku-funpark:
 | 
			
		||||
    	8th row of the sudoku puzzle. (default "000000000")
 | 
			
		||||
  -row9 string
 | 
			
		||||
    	9th row of the sudoku puzzle. (default "000000000")
 | 
			
		||||
  -split int
 | 
			
		||||
    	Split the tasks in n parts. This depends on the availability of the first row. (default 1)
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Instead of using the 3x3 blocks with 3x3 digits, it uses horizontal rows from top to bottom.
 | 
			
		||||
@@ -39,7 +54,7 @@ Instead of using the 3x3 blocks with 3x3 digits, it uses horizontal rows from to
 | 
			
		||||
## Example
 | 
			
		||||
To see the solver in action, run the tool with the following parameters.
 | 
			
		||||
 | 
			
		||||
For a short running (~15 seconds) example:
 | 
			
		||||
For a short running (~14 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:
 | 
			
		||||
@@ -47,41 +62,80 @@ For a long running (~1 hours 15 minutes) example:
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
./sudoku-funpark -row1 769104802 -row2 154800060 -row3 832700154 -row4 600900328 -row5 045328670 -row6 328670945 -row7 597410280 -row8 006283090 -row9 200590006 
 | 
			
		||||
Loading blocks... Done! (38.957376ms)
 | 
			
		||||
Populating blocks... Done! (92.087174ms)
 | 
			
		||||
Number of (potential) solutions: 26542080
 | 
			
		||||
Validating solutions
 | 
			
		||||
Processing: 8% (2131893/26542080); Rate: 2131884/sec for 1.000028115s; Time left (est.): 11s
 | 
			
		||||
Processing: 16% (4292163/26542080); Rate: 2160219/sec for 1.000087826s; Time left (est.): 10s
 | 
			
		||||
Processing: 24% (6438334/26542080); Rate: 2146157/sec for 1.000017364s; Time left (est.): 9s
 | 
			
		||||
Processing: 32% (8529362/26542080); Rate: 2090965/sec for 1.000367121s; Time left (est.): 8s
 | 
			
		||||
Processing: 40% (10737065/26542080); Rate: 2207530/sec for 1.000072427s; Time left (est.): 7s
 | 
			
		||||
Processing: 48% (12958905/26542080); Rate: 2221755/sec for 1.000003187s; Time left (est.): 6s
 | 
			
		||||
Processing: 57% (15163877/26542080); Rate: 2204929/sec for 1.000002717s; Time left (est.): 5s
 | 
			
		||||
Processing: 65% (17254760/26542080); Rate: 2090742/sec for 1.00008452s; Time left (est.): 4s
 | 
			
		||||
Processing: 73% (19513142/26542080); Rate: 2258348/sec for 1.000071076s; Time left (est.): 3s
 | 
			
		||||
Processing: 82% (21795213/26542080); Rate: 2282028/sec for 1.000076024s; Time left (est.): 2s
 | 
			
		||||
Processing: 90% (24048891/26542080); Rate: 2253645/sec for 1.000146957s; Time left (est.): 1s
 | 
			
		||||
Processing: 98% (26226252/26542080); Rate: 2177215/sec for 1.000129955s; Time left (est.): 0s
 | 
			
		||||
Processing: 100% (26542080/26542080); Rate: 315792/sec for 1.000105149s; Time left (est.): 0s
 | 
			
		||||
Validated solutions (13.001683829s)
 | 
			
		||||
 | 
			
		||||
Solution #1:
 | 
			
		||||
769154832
 | 
			
		||||
154832769
 | 
			
		||||
832769154
 | 
			
		||||
671945328
 | 
			
		||||
945328671
 | 
			
		||||
328671945
 | 
			
		||||
597416283
 | 
			
		||||
416283597
 | 
			
		||||
283597416
 | 
			
		||||
╔═══════════╗
 | 
			
		||||
║769│154│832╢
 | 
			
		||||
║154│832│769╢
 | 
			
		||||
║832│769│154╢
 | 
			
		||||
╟───┼───┼───╢
 | 
			
		||||
║671│945│328╢
 | 
			
		||||
║945│328│671╢
 | 
			
		||||
║328│671│945╢
 | 
			
		||||
╟───┼───┼───╢
 | 
			
		||||
║597│416│283╢
 | 
			
		||||
║416│283│597╢
 | 
			
		||||
║283│597│416╢
 | 
			
		||||
╚═══════════╝
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
# Caveats
 | 
			
		||||
## 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.
 | 
			
		||||
 | 
			
		||||
## Generating your own blocks
 | 
			
		||||
To generate your own blocks, you could use the following code:
 | 
			
		||||
```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
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,7 @@ tasks:
 | 
			
		||||
    silent: true
 | 
			
		||||
  precommit:
 | 
			
		||||
    cmds:
 | 
			
		||||
      - pre-commit autoupdate
 | 
			
		||||
      - pre-commit run --all
 | 
			
		||||
    silent: true
 | 
			
		||||
  lint:
 | 
			
		||||
@@ -33,3 +34,11 @@ tasks:
 | 
			
		||||
      - 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
 | 
			
		||||
  gource:
 | 
			
		||||
    cmds:
 | 
			
		||||
      - gource --auto-skip-seconds 1 --key -r 60
 | 
			
		||||
    silent: true
 | 
			
		||||
  godoc:
 | 
			
		||||
    cmds:
 | 
			
		||||
      - godoc -http=:6060
 | 
			
		||||
    silent: true
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										41
									
								
								controller/types.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								controller/types.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
			
		||||
package controller
 | 
			
		||||
 | 
			
		||||
import "gitea.ligthert.net/golang/sudoku-funpark/outputter"
 | 
			
		||||
 | 
			
		||||
// Simple interface to store values shared amongst packages.
 | 
			
		||||
type Controller struct {
 | 
			
		||||
	// All possible blocks/rows available
 | 
			
		||||
	Blocks []string
 | 
			
		||||
	// 1st row of the Sudoku puzzle.
 | 
			
		||||
	Row1 string
 | 
			
		||||
	// 2nd row of the Sudoku puzzle.
 | 
			
		||||
	Row2 string
 | 
			
		||||
	// 3rd row of the Sudoku puzzle.
 | 
			
		||||
	Row3 string
 | 
			
		||||
	// 4th row of the Sudoku puzzle.
 | 
			
		||||
	Row4 string
 | 
			
		||||
	// 5th row of the Sudoku puzzle.
 | 
			
		||||
	Row5 string
 | 
			
		||||
	// 6th row of the Sudoku puzzle.
 | 
			
		||||
	Row6 string
 | 
			
		||||
	// 7th row of the Sudoku puzzle.
 | 
			
		||||
	Row7 string
 | 
			
		||||
	// 8th row of the Sudoku puzzle.
 | 
			
		||||
	Row8 string
 | 
			
		||||
	// 9th row of the Sudoku puzzle.
 | 
			
		||||
	Row9 string
 | 
			
		||||
	// Slice with all found solutions.
 | 
			
		||||
	Solutions [][]string
 | 
			
		||||
	// Number of CPUs Go routines are allowed to use.
 | 
			
		||||
	NumCPUs int
 | 
			
		||||
	// Number of parts the search should be split into.
 | 
			
		||||
	Split int
 | 
			
		||||
	// Which part of the search should the solver focus on.
 | 
			
		||||
	Part int
 | 
			
		||||
	// Type of output requested
 | 
			
		||||
	Output string
 | 
			
		||||
	// Select printing method
 | 
			
		||||
	Print string
 | 
			
		||||
	// Outputter package
 | 
			
		||||
	Outputter *outputter.Outputter
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										14
									
								
								export/Export.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								export/Export.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
package export
 | 
			
		||||
 | 
			
		||||
func (export *Export) Export() (render string) {
 | 
			
		||||
	// Print the valid solutions
 | 
			
		||||
	switch export.Controller.Output {
 | 
			
		||||
	case "human":
 | 
			
		||||
		render = export.renderHumanReadable()
 | 
			
		||||
	case "flat":
 | 
			
		||||
		render = export.renderFlat()
 | 
			
		||||
	case "json":
 | 
			
		||||
		render = export.renderJSON()
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										15
									
								
								export/renderFlat.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								export/renderFlat.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
package export
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strconv"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Render output as stored internally.
 | 
			
		||||
func (export *Export) renderFlat() (render string) {
 | 
			
		||||
	for solutionIndex, solution := range export.Controller.Solutions {
 | 
			
		||||
		render += fmt.Sprintln("\nSolution #" + strconv.Itoa(solutionIndex+1) + ":")
 | 
			
		||||
		render += fmt.Sprintln(solution)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										22
									
								
								export/renderHumanReadable.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								export/renderHumanReadable.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
package export
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strconv"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Render solutions in a human friendly format.
 | 
			
		||||
func (export *Export) renderHumanReadable() (render string) {
 | 
			
		||||
	for solutionIndex, solution := range export.Controller.Solutions {
 | 
			
		||||
		render += fmt.Sprintln("\nSolution #" + strconv.Itoa(solutionIndex+1) + ":")
 | 
			
		||||
		render += fmt.Sprintln("╔═══════════╗")
 | 
			
		||||
		for rowIndex, row := range solution {
 | 
			
		||||
			if rowIndex == 3 || rowIndex == 6 {
 | 
			
		||||
				render += fmt.Sprintln("╟───┼───┼───╢")
 | 
			
		||||
			}
 | 
			
		||||
			render += fmt.Sprintln("║" + row[0:3] + "│" + row[3:6] + "│" + row[6:9] + "╢")
 | 
			
		||||
		}
 | 
			
		||||
		render += fmt.Sprintln("╚═══════════╝")
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										35
									
								
								export/renderJSON.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								export/renderJSON.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
			
		||||
package export
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Render JSON output.
 | 
			
		||||
func (export *Export) renderJSON() (render string) {
 | 
			
		||||
	type solution_type map[string]any
 | 
			
		||||
	solutions := make([]solution_type, 0)
 | 
			
		||||
 | 
			
		||||
	for solutionIndex, solution := range export.Controller.Solutions {
 | 
			
		||||
		solutionMap := map[string]any{
 | 
			
		||||
			"order": solutionIndex,
 | 
			
		||||
			"row1":  solution[0],
 | 
			
		||||
			"row2":  solution[1],
 | 
			
		||||
			"row3":  solution[2],
 | 
			
		||||
			"row4":  solution[3],
 | 
			
		||||
			"row5":  solution[4],
 | 
			
		||||
			"row6":  solution[5],
 | 
			
		||||
			"row7":  solution[6],
 | 
			
		||||
			"row8":  solution[7],
 | 
			
		||||
			"row9":  solution[8],
 | 
			
		||||
		}
 | 
			
		||||
		solutions = append(solutions, solutionMap)
 | 
			
		||||
	}
 | 
			
		||||
	renderBytes, err := json.Marshal(solutions)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		export.Controller.Outputter.Println("ERROR: json.Marshal error:", err)
 | 
			
		||||
		export.Controller.Outputter.Println("Printing solution as-is:", solutions)
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
	render = string(renderBytes)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										8
									
								
								export/types.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								export/types.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
package export
 | 
			
		||||
 | 
			
		||||
import "gitea.ligthert.net/golang/sudoku-funpark/controller"
 | 
			
		||||
 | 
			
		||||
// A simple inteface to export found solutions in different formats.
 | 
			
		||||
type Export struct {
 | 
			
		||||
	Controller *controller.Controller
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										93
									
								
								flags/ParseFlags.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								flags/ParseFlags.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,93 @@
 | 
			
		||||
package flags
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"flag"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Parse command-line parameters, test input, store them in the Controller.
 | 
			
		||||
func (flags *Flags) ParseFlags() {
 | 
			
		||||
 | 
			
		||||
	// Define parameters
 | 
			
		||||
	flag.StringVar(&flags.Controller.Row1, "row1", "000000000", "1st row of the sudoku puzzle.")
 | 
			
		||||
	flag.StringVar(&flags.Controller.Row2, "row2", "000000000", "2nd row of the sudoku puzzle.")
 | 
			
		||||
	flag.StringVar(&flags.Controller.Row3, "row3", "000000000", "4rd row of the sudoku puzzle.")
 | 
			
		||||
	flag.StringVar(&flags.Controller.Row4, "row4", "000000000", "4th row of the sudoku puzzle.")
 | 
			
		||||
	flag.StringVar(&flags.Controller.Row5, "row5", "000000000", "5th row of the sudoku puzzle.")
 | 
			
		||||
	flag.StringVar(&flags.Controller.Row6, "row6", "000000000", "6th row of the sudoku puzzle.")
 | 
			
		||||
	flag.StringVar(&flags.Controller.Row7, "row7", "000000000", "7th row of the sudoku puzzle.")
 | 
			
		||||
	flag.StringVar(&flags.Controller.Row8, "row8", "000000000", "8th row of the sudoku puzzle.")
 | 
			
		||||
	flag.StringVar(&flags.Controller.Row9, "row9", "000000000", "9th row of the sudoku puzzle.")
 | 
			
		||||
	flag.IntVar(&flags.Controller.NumCPUs, "numcpu", runtime.NumCPU(), "Number of CPU cores to assign to this task.")
 | 
			
		||||
	flag.IntVar(&flags.Controller.Split, "split", 1, "Split the tasks in n parts. This depends on the availability of the first row.")
 | 
			
		||||
	flag.IntVar(&flags.Controller.Part, "part", 1, "Process part x in n parts. Cannot be lower than 1, or higher than specified in split.")
 | 
			
		||||
	flag.StringVar(&flags.Controller.Output, "output", "human", "Type of output. 'human' for human readable. 'flat' for flat as stored in memory output. 'json' for JSON output.")
 | 
			
		||||
	flag.StringVar(&flags.Controller.Print, "print", "short", "'short': normal output;'long': normal output with timestamps; 'silent': Only print the results;")
 | 
			
		||||
 | 
			
		||||
	// Parse the flags
 | 
			
		||||
	flag.Parse()
 | 
			
		||||
 | 
			
		||||
	// Process any changes to the CPU usage.
 | 
			
		||||
	if flags.Controller.NumCPUs <= 0 {
 | 
			
		||||
		fmt.Printf("ERROR: Number of CPU cores must be 1 or higher.\n\n")
 | 
			
		||||
		flags.printUsage()
 | 
			
		||||
		os.Exit(1)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if flags.Controller.NumCPUs != runtime.NumCPU() {
 | 
			
		||||
		runtime.GOMAXPROCS(flags.Controller.NumCPUs)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Process rows
 | 
			
		||||
	if flags.Controller.Row1 == "000000000" || flags.Controller.Row2 == "000000000" || flags.Controller.Row3 == "000000000" || flags.Controller.Row4 == "000000000" || flags.Controller.Row5 == "000000000" || flags.Controller.Row6 == "000000000" || flags.Controller.Row7 == "000000000" || flags.Controller.Row8 == "000000000" || flags.Controller.Row9 == "000000000" {
 | 
			
		||||
		fmt.Printf("ERROR: All parameters must be entered.\n\n")
 | 
			
		||||
		flags.printUsage()
 | 
			
		||||
		os.Exit(1)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Validate the row (never trust user input)
 | 
			
		||||
	flags.validateRow("row1", flags.Controller.Row1)
 | 
			
		||||
	flags.validateRow("row2", flags.Controller.Row2)
 | 
			
		||||
	flags.validateRow("row3", flags.Controller.Row3)
 | 
			
		||||
	flags.validateRow("row4", flags.Controller.Row4)
 | 
			
		||||
	flags.validateRow("row5", flags.Controller.Row5)
 | 
			
		||||
	flags.validateRow("row6", flags.Controller.Row6)
 | 
			
		||||
	flags.validateRow("row7", flags.Controller.Row7)
 | 
			
		||||
	flags.validateRow("row8", flags.Controller.Row8)
 | 
			
		||||
	flags.validateRow("row9", flags.Controller.Row9)
 | 
			
		||||
 | 
			
		||||
	// Process workload splitting
 | 
			
		||||
	// Ensure split and part are 1 or higher
 | 
			
		||||
	if flags.Controller.Split <= 0 || flags.Controller.Part <= 0 {
 | 
			
		||||
		fmt.Printf("ERROR: '-split' and '-part' need to be 1 or higher.\n")
 | 
			
		||||
		flags.printUsage()
 | 
			
		||||
		os.Exit(1)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Ensure part is between 1 and split
 | 
			
		||||
	if flags.Controller.Part > flags.Controller.Split {
 | 
			
		||||
		fmt.Printf("ERROR: '-part' cannot be bigger than `-split`.\n")
 | 
			
		||||
		flags.printUsage()
 | 
			
		||||
		os.Exit(1)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Process output selection
 | 
			
		||||
	flags.Controller.Output = strings.ToLower(flags.Controller.Output)
 | 
			
		||||
	if flags.Controller.Output != "human" && flags.Controller.Output != "flat" && flags.Controller.Output != "json" {
 | 
			
		||||
		fmt.Printf("ERROR: Invalid output, can only be 'human' or 'flat' or 'json'.\n")
 | 
			
		||||
		flags.printUsage()
 | 
			
		||||
		os.Exit(1)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Process print selection
 | 
			
		||||
	flags.Controller.Print = strings.ToLower(flags.Controller.Print)
 | 
			
		||||
	if flags.Controller.Print != "short" && flags.Controller.Print != "long" && flags.Controller.Print != "silent" {
 | 
			
		||||
		fmt.Printf("ERROR: Invalid Print, can only be 'short' or 'long' or 'silent'.\n")
 | 
			
		||||
		flags.printUsage()
 | 
			
		||||
		os.Exit(1)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										15
									
								
								flags/printUsage.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								flags/printUsage.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
package flags
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"flag"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Print help information for the end-user
 | 
			
		||||
func (flags *Flags) printUsage() {
 | 
			
		||||
	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()
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										8
									
								
								flags/types.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								flags/types.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
package flags
 | 
			
		||||
 | 
			
		||||
import "gitea.ligthert.net/golang/sudoku-funpark/controller"
 | 
			
		||||
 | 
			
		||||
// Interface to parse command-line parameters
 | 
			
		||||
type Flags struct {
 | 
			
		||||
	Controller *controller.Controller
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										14
									
								
								flags/validChar.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								flags/validChar.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
package flags
 | 
			
		||||
 | 
			
		||||
// Validate if the char provided is 0-9
 | 
			
		||||
func (flags *Flags) validChar(char rune) (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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										54
									
								
								flags/validateRow.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								flags/validateRow.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,54 @@
 | 
			
		||||
package flags
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Validate if a row is properly set.
 | 
			
		||||
// This check for:
 | 
			
		||||
// - Correct length
 | 
			
		||||
// - Correct numbers
 | 
			
		||||
// - Numbers only present once
 | 
			
		||||
func (flags *Flags) validateRow(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 {
 | 
			
		||||
		fmt.Printf("ERROR: Invalid length of %s (%s), must be 9 numbers\n\n", name, row)
 | 
			
		||||
		flags.printUsage()
 | 
			
		||||
		os.Exit(1)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 2. Ensure all digits are numbers
 | 
			
		||||
	for _, value := range row {
 | 
			
		||||
		found = flags.validChar(value)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !found {
 | 
			
		||||
		fmt.Printf("ERROR: Invalid character of %s (%s), must be 9 numbers\n\n", name, row)
 | 
			
		||||
		flags.printUsage()
 | 
			
		||||
		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 {
 | 
			
		||||
		fmt.Printf("ERROR: Double character of %s (%s), numbers between 1 and 9 may only be entered once\n\n", name, row)
 | 
			
		||||
		flags.printUsage()
 | 
			
		||||
		os.Exit(1)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										49
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										49
									
								
								main.go
									
									
									
									
									
								
							@@ -1,10 +1,55 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"strconv"
 | 
			
		||||
 | 
			
		||||
	"gitea.ligthert.net/golang/sudoku-funpark/controller"
 | 
			
		||||
	"gitea.ligthert.net/golang/sudoku-funpark/export"
 | 
			
		||||
	"gitea.ligthert.net/golang/sudoku-funpark/flags"
 | 
			
		||||
	"gitea.ligthert.net/golang/sudoku-funpark/outputter"
 | 
			
		||||
	"gitea.ligthert.net/golang/sudoku-funpark/solver"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	// Run the meat of the program
 | 
			
		||||
	solver.Run()
 | 
			
		||||
	// Instantiate the interfaces
 | 
			
		||||
	controller := controller.Controller{}
 | 
			
		||||
	outp := outputter.Outputter{}
 | 
			
		||||
	export := export.Export{Controller: &controller}
 | 
			
		||||
	flags := flags.Flags{Controller: &controller}
 | 
			
		||||
	solver := solver.Solver{Controller: &controller, Outp: &outp}
 | 
			
		||||
 | 
			
		||||
	// Parse and handle flags
 | 
			
		||||
	flags.ParseFlags()
 | 
			
		||||
 | 
			
		||||
	// Tell outp what kind of output is expected.
 | 
			
		||||
	outp.OutputType = controller.Print
 | 
			
		||||
 | 
			
		||||
	// Report number of CPUs being used, if set.
 | 
			
		||||
	if runtime.NumCPU() != controller.NumCPUs {
 | 
			
		||||
		outp.Println("Using " + strconv.Itoa(controller.NumCPUs) + " CPUs, (was " + strconv.Itoa(runtime.NumCPU()) + ")")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Load blocks from CSV file
 | 
			
		||||
	solver.LoadBlocks()
 | 
			
		||||
 | 
			
		||||
	// Find rows that fit with the entered rows
 | 
			
		||||
	solver.PopulateBlocks()
 | 
			
		||||
 | 
			
		||||
	// If needed, split the workload
 | 
			
		||||
	// May exit and throw an error if the work load isn't viable
 | 
			
		||||
	if controller.Split != 1 {
 | 
			
		||||
		solver.SelectWorkload()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Print the total number of solutions to validate
 | 
			
		||||
	outp.Println("Number of (potential) solutions:", solver.Iter)
 | 
			
		||||
 | 
			
		||||
	// Check the number of solutions
 | 
			
		||||
	go solver.CheckCombinations()
 | 
			
		||||
	solver.Tracker()
 | 
			
		||||
 | 
			
		||||
	fmt.Println(export.Export())
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										17
									
								
								outputter/Printf.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								outputter/Printf.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
package outputter
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (outputter *Outputter) Printf(format string, args ...any) {
 | 
			
		||||
	switch outputter.OutputType {
 | 
			
		||||
	case "short":
 | 
			
		||||
		fmt.Printf(format, args...)
 | 
			
		||||
	case "long":
 | 
			
		||||
		log.Printf(format, args...)
 | 
			
		||||
	case "silent":
 | 
			
		||||
		// Do nothing
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										17
									
								
								outputter/Println.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								outputter/Println.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
package outputter
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (outputter *Outputter) Println(msg ...any) {
 | 
			
		||||
	switch outputter.OutputType {
 | 
			
		||||
	case "short":
 | 
			
		||||
		fmt.Println(msg...)
 | 
			
		||||
	case "long":
 | 
			
		||||
		log.Println(msg...)
 | 
			
		||||
	case "silent":
 | 
			
		||||
		// Do nothing
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										5
									
								
								outputter/types.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								outputter/types.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
package outputter
 | 
			
		||||
 | 
			
		||||
type Outputter struct {
 | 
			
		||||
	OutputType string
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										24
									
								
								solver/CheckCombinations.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								solver/CheckCombinations.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
package solver
 | 
			
		||||
 | 
			
		||||
// Iterate through all combination of blocks and validate them.
 | 
			
		||||
func (solver *Solver) CheckCombinations() {
 | 
			
		||||
	for rows1Index := range solver.row1s {
 | 
			
		||||
		for rows2Index := range solver.row2s {
 | 
			
		||||
			for rows3Index := range solver.row3s {
 | 
			
		||||
				for rows4Index := range solver.row4s {
 | 
			
		||||
					for rows5Index := range solver.row5s {
 | 
			
		||||
						for rows6Index := range solver.row6s {
 | 
			
		||||
							for rows7Index := range solver.row7s {
 | 
			
		||||
								for rows8Index := range solver.row8s {
 | 
			
		||||
									for rows9Index := range solver.row9s {
 | 
			
		||||
										go solver.validator(rows1Index, rows2Index, rows3Index, rows4Index, rows5Index, rows6Index, rows7Index, rows8Index, rows9Index)
 | 
			
		||||
									}
 | 
			
		||||
								}
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -2,8 +2,6 @@ package solver
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"embed"
 | 
			
		||||
	"log"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
@@ -13,12 +11,13 @@ import (
 | 
			
		||||
//go:embed blocks.csv
 | 
			
		||||
var f embed.FS
 | 
			
		||||
 | 
			
		||||
func (solver *Solver) load_blocks() {
 | 
			
		||||
// Load all possible blocks from CSV in to memory
 | 
			
		||||
func (solver *Solver) LoadBlocks() {
 | 
			
		||||
 | 
			
		||||
	defer solver.timeTrack(time.Now(), "Loaded blocks")
 | 
			
		||||
	log.Println("Loading blocks")
 | 
			
		||||
	defer solver.timeTrack(time.Now(), "Done!")
 | 
			
		||||
	solver.Outp.Printf("Loading blocks... ")
 | 
			
		||||
 | 
			
		||||
	var blocks []int
 | 
			
		||||
	var blocks []string
 | 
			
		||||
 | 
			
		||||
	file, err := f.ReadFile("blocks.csv")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -28,13 +27,13 @@ func (solver *Solver) load_blocks() {
 | 
			
		||||
	temp := strings.Split(string(file), "\n")
 | 
			
		||||
 | 
			
		||||
	for _, line := range temp {
 | 
			
		||||
		block, _ := strconv.Atoi(string(line))
 | 
			
		||||
		if block == 0 { // Ignore new-line at the end of the file.
 | 
			
		||||
		block := string(line)
 | 
			
		||||
		if block == "\n" || block == "" { // Ignore new-line at the end of the file.
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		blocks = append(blocks, block)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	solver.blocks = blocks
 | 
			
		||||
	solver.Controller.Blocks = blocks
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										26
									
								
								solver/PopulateBlocks.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								solver/PopulateBlocks.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
			
		||||
package solver
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Find all possible blocks that can be used to find a solution.
 | 
			
		||||
func (solver *Solver) PopulateBlocks() {
 | 
			
		||||
 | 
			
		||||
	defer solver.timeTrack(time.Now(), "Done!")
 | 
			
		||||
	solver.Outp.Printf("Populating blocks... ")
 | 
			
		||||
 | 
			
		||||
	solver.findBlocks(&solver.Controller.Row1, &solver.row1s)
 | 
			
		||||
	solver.findBlocks(&solver.Controller.Row2, &solver.row2s)
 | 
			
		||||
	solver.findBlocks(&solver.Controller.Row3, &solver.row3s)
 | 
			
		||||
	solver.findBlocks(&solver.Controller.Row4, &solver.row4s)
 | 
			
		||||
	solver.findBlocks(&solver.Controller.Row5, &solver.row5s)
 | 
			
		||||
	solver.findBlocks(&solver.Controller.Row6, &solver.row6s)
 | 
			
		||||
	solver.findBlocks(&solver.Controller.Row7, &solver.row7s)
 | 
			
		||||
	solver.findBlocks(&solver.Controller.Row8, &solver.row8s)
 | 
			
		||||
	solver.findBlocks(&solver.Controller.Row9, &solver.row9s)
 | 
			
		||||
 | 
			
		||||
	// This calculates and stores the total number of solutions to validate.
 | 
			
		||||
	solver.Iter = uint64(len(solver.row1s)) * uint64(len(solver.row2s)) * uint64(len(solver.row3s)) * uint64(len(solver.row4s)) * uint64(len(solver.row5s)) * uint64(len(solver.row6s)) * uint64(len(solver.row7s)) * uint64(len(solver.row8s)) * uint64(len(solver.row9s))
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										22
									
								
								solver/SelectWorkload.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								solver/SelectWorkload.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
package solver
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Renders workload for an agent.
 | 
			
		||||
// Checks if this feature can be used, otherwise exits.
 | 
			
		||||
// Modify solver.row1s so it limits the workload to what is only desired
 | 
			
		||||
func (solver *Solver) SelectWorkload() {
 | 
			
		||||
	if solver.Controller.Split > len(solver.row1s) {
 | 
			
		||||
		solver.Outp.Println("ERROR: Unable to divide the workload in " + strconv.Itoa(solver.Controller.Split) + " parts, when only " + strconv.Itoa(len(solver.row1s)) + " are available.\n\n")
 | 
			
		||||
		os.Exit(1)
 | 
			
		||||
	}
 | 
			
		||||
	defer solver.timeTrack(time.Now(), "Workload set")
 | 
			
		||||
	solver.Outp.Println("Setting workload")
 | 
			
		||||
	solver.Outp.Println("We are agent " + strconv.Itoa(solver.Controller.Part) + " of " + strconv.Itoa(solver.Controller.Split))
 | 
			
		||||
	workloads := solver.splitWorkload()
 | 
			
		||||
	solver.setWorkload(workloads)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										102
									
								
								solver/Tracker.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								solver/Tracker.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,102 @@
 | 
			
		||||
package solver
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Keep track and output progress.
 | 
			
		||||
// Calculate rates, display percentages, estimate the ETA till completion.
 | 
			
		||||
func (solver *Solver) Tracker() {
 | 
			
		||||
 | 
			
		||||
	// Add time tracking
 | 
			
		||||
	defer solver.timeTrack(time.Now(), "Validated solutions")
 | 
			
		||||
	solver.Outp.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 rateStart uint64
 | 
			
		||||
	// Tracking the rate, difference between previous iterations
 | 
			
		||||
	var rateDiff uint64
 | 
			
		||||
 | 
			
		||||
	// Tracking duration
 | 
			
		||||
	var timerStart = time.Now()
 | 
			
		||||
 | 
			
		||||
	// Estimation how long it will take
 | 
			
		||||
	var est_fin string
 | 
			
		||||
 | 
			
		||||
	// While not needed for rateDiff anymore, it makes estimation calculations more accurate. ☹️
 | 
			
		||||
	time.Sleep(time.Second)
 | 
			
		||||
 | 
			
		||||
	// 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
 | 
			
		||||
		rateDiff = solver.counter.Load() - rateStart
 | 
			
		||||
 | 
			
		||||
		if track <= int(percentage) || rateDiff == 0 { // Start if-statement
 | 
			
		||||
 | 
			
		||||
			// Make sure something happened, making rateStart the only reliable variable
 | 
			
		||||
			if solver.Iter == solver.counter.Load() {
 | 
			
		||||
				percentage = 100
 | 
			
		||||
				solver.counter.Store(solver.Iter)
 | 
			
		||||
				done = true
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			timer_elapsed := time.Since(timerStart)
 | 
			
		||||
			solver.rates = append(solver.rates, rateDiff)
 | 
			
		||||
			rate_avg := solver.calcAVG()
 | 
			
		||||
 | 
			
		||||
			// Estimate when this is finished
 | 
			
		||||
			if rateDiff == 0 {
 | 
			
		||||
				est_fin = "N/A"
 | 
			
		||||
			} else {
 | 
			
		||||
				duration_int := (solver.Iter - solver.counter.Load()) / rate_avg
 | 
			
		||||
				duration_string := strconv.Itoa(int(duration_int)) + "s"
 | 
			
		||||
				est, err := time.ParseDuration(duration_string)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					est_fin = "parse error"
 | 
			
		||||
				} else {
 | 
			
		||||
					est_fin = est.String()
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Printing the progress
 | 
			
		||||
			solver.Outp.Println("Processing: " + strconv.Itoa(int(percentage)) + "% (" + strconv.FormatUint(solver.counter.Load(), 10) + "/" + strconv.Itoa(int(solver.Iter)) + "); Rate: " + strconv.FormatUint(rateDiff, 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
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			timerStart = time.Now()
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Resert the rate counter
 | 
			
		||||
		rateStart = solver.counter.Load()
 | 
			
		||||
 | 
			
		||||
		// Sleep for a second
 | 
			
		||||
		if solver.Iter != solver.counter.Load() {
 | 
			
		||||
			time.Sleep(1 * time.Second)
 | 
			
		||||
		}
 | 
			
		||||
	} // End for-loop
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										14
									
								
								solver/calcAVG.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								solver/calcAVG.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
package solver
 | 
			
		||||
 | 
			
		||||
// Calculate the average rate in a stored slice of rates.
 | 
			
		||||
func (solver *Solver) calcAVG() (avg uint64) {
 | 
			
		||||
	var avgSum uint64
 | 
			
		||||
 | 
			
		||||
	for _, value := range solver.rates {
 | 
			
		||||
		avgSum += uint64(value)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	avg = avgSum / uint64(len(solver.rates))
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										37
									
								
								solver/findBlocks.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								solver/findBlocks.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
			
		||||
package solver
 | 
			
		||||
 | 
			
		||||
// The actual function that finds the blocks matching the partial blocks.
 | 
			
		||||
func (solver *Solver) findBlocks(row *string, rows *[]string) {
 | 
			
		||||
	// Declare selection
 | 
			
		||||
	var selection []string
 | 
			
		||||
	var currBlocks []string
 | 
			
		||||
	funcRow := *row
 | 
			
		||||
 | 
			
		||||
	for letter := range funcRow {
 | 
			
		||||
 | 
			
		||||
		if len(selection) == 0 {
 | 
			
		||||
			currBlocks = solver.Controller.Blocks
 | 
			
		||||
		} else {
 | 
			
		||||
			currBlocks = selection
 | 
			
		||||
			selection = nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, block := range currBlocks {
 | 
			
		||||
 | 
			
		||||
			currRow := block
 | 
			
		||||
 | 
			
		||||
			if funcRow[letter] == currRow[letter] {
 | 
			
		||||
				foundRow := currRow
 | 
			
		||||
				selection = append(selection, foundRow)
 | 
			
		||||
			}
 | 
			
		||||
			if funcRow[letter] == '0' {
 | 
			
		||||
				foundRow := currRow
 | 
			
		||||
				selection = append(selection, foundRow)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		} // End for-loop
 | 
			
		||||
 | 
			
		||||
	} // End for-loop
 | 
			
		||||
 | 
			
		||||
	*rows = selection
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										164
									
								
								solver/flags.go
									
									
									
									
									
								
							
							
						
						
									
										164
									
								
								solver/flags.go
									
									
									
									
									
								
							@@ -1,164 +0,0 @@
 | 
			
		||||
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()
 | 
			
		||||
}
 | 
			
		||||
@@ -1,13 +0,0 @@
 | 
			
		||||
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)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,247 +0,0 @@
 | 
			
		||||
package solver
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"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
 | 
			
		||||
 | 
			
		||||
	for letter := range func_row {
 | 
			
		||||
 | 
			
		||||
		if len(selection) == 0 {
 | 
			
		||||
			curr_blocks = solver.blocks
 | 
			
		||||
		} else {
 | 
			
		||||
			curr_blocks = selection
 | 
			
		||||
			selection = nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, block := range curr_blocks {
 | 
			
		||||
 | 
			
		||||
			curr_row := strconv.Itoa(block)
 | 
			
		||||
 | 
			
		||||
			if func_row[letter] == curr_row[letter] {
 | 
			
		||||
				found_row, _ := strconv.Atoi(curr_row)
 | 
			
		||||
				selection = append(selection, found_row)
 | 
			
		||||
			}
 | 
			
		||||
			if func_row[letter] == '0' {
 | 
			
		||||
				found_row, _ := strconv.Atoi(curr_row)
 | 
			
		||||
				selection = append(selection, found_row)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		} // End for-loop
 | 
			
		||||
 | 
			
		||||
	} // End for-loop
 | 
			
		||||
 | 
			
		||||
	*rows = selection
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (solver *Solver) check_combinations() {
 | 
			
		||||
	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)
 | 
			
		||||
									}
 | 
			
		||||
								}
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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) {
 | 
			
		||||
 | 
			
		||||
	// 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]))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 {
 | 
			
		||||
	var retval bool
 | 
			
		||||
	retval = true
 | 
			
		||||
 | 
			
		||||
	row1s := strconv.Itoa(row1)
 | 
			
		||||
	row2s := strconv.Itoa(row2)
 | 
			
		||||
	row3s := strconv.Itoa(row3)
 | 
			
		||||
	row4s := strconv.Itoa(row4)
 | 
			
		||||
	row5s := strconv.Itoa(row5)
 | 
			
		||||
	row6s := strconv.Itoa(row6)
 | 
			
		||||
	row7s := strconv.Itoa(row7)
 | 
			
		||||
	row8s := strconv.Itoa(row8)
 | 
			
		||||
	row9s := strconv.Itoa(row9)
 | 
			
		||||
 | 
			
		||||
	for index := range 9 {
 | 
			
		||||
		if row1s[index] == row2s[index] || row1s[index] == row3s[index] || row1s[index] == row4s[index] || row1s[index] == row5s[index] || row1s[index] == row6s[index] || row1s[index] == row7s[index] || row1s[index] == row8s[index] || row1s[index] == row9s[index] {
 | 
			
		||||
			retval = false
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if row2s[index] == row1s[index] || row2s[index] == row3s[index] || row2s[index] == row4s[index] || row2s[index] == row5s[index] || row2s[index] == row6s[index] || row2s[index] == row7s[index] || row2s[index] == row8s[index] || row2s[index] == row9s[index] {
 | 
			
		||||
			retval = false
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if row3s[index] == row1s[index] || row3s[index] == row2s[index] || row3s[index] == row4s[index] || row3s[index] == row5s[index] || row3s[index] == row6s[index] || row3s[index] == row7s[index] || row3s[index] == row8s[index] || row3s[index] == row9s[index] {
 | 
			
		||||
			retval = false
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if row4s[index] == row1s[index] || row4s[index] == row2s[index] || row4s[index] == row3s[index] || row4s[index] == row5s[index] || row4s[index] == row6s[index] || row4s[index] == row7s[index] || row4s[index] == row8s[index] || row4s[index] == row9s[index] {
 | 
			
		||||
			retval = false
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if row5s[index] == row1s[index] || row5s[index] == row2s[index] || row5s[index] == row3s[index] || row5s[index] == row4s[index] || row5s[index] == row6s[index] || row5s[index] == row7s[index] || row5s[index] == row8s[index] || row5s[index] == row9s[index] {
 | 
			
		||||
			retval = false
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if row6s[index] == row1s[index] || row6s[index] == row2s[index] || row6s[index] == row3s[index] || row6s[index] == row4s[index] || row6s[index] == row5s[index] || row6s[index] == row7s[index] || row6s[index] == row8s[index] || row6s[index] == row9s[index] {
 | 
			
		||||
			retval = false
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if row7s[index] == row1s[index] || row7s[index] == row2s[index] || row7s[index] == row3s[index] || row7s[index] == row4s[index] || row5s[index] == row6s[index] || row7s[index] == row6s[index] || row7s[index] == row8s[index] || row7s[index] == row9s[index] {
 | 
			
		||||
			retval = false
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if row8s[index] == row1s[index] || row8s[index] == row2s[index] || row8s[index] == row3s[index] || row8s[index] == row4s[index] || row8s[index] == row5s[index] || row8s[index] == row6s[index] || row8s[index] == row7s[index] || row8s[index] == row9s[index] {
 | 
			
		||||
			retval = false
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if row9s[index] == row1s[index] || row9s[index] == row2s[index] || row9s[index] == row3s[index] || row9s[index] == row4s[index] || row9s[index] == row5s[index] || row9s[index] == row6s[index] || row9s[index] == row7s[index] || row9s[index] == row8s[index] {
 | 
			
		||||
			retval = false
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										21
									
								
								solver/setWorkload.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								solver/setWorkload.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
package solver
 | 
			
		||||
 | 
			
		||||
// Set the workload by setting solver.row1s
 | 
			
		||||
func (solver *Solver) setWorkload(agents []int) {
 | 
			
		||||
	var start int = 0
 | 
			
		||||
	var finish int = 0
 | 
			
		||||
	for key, value := range agents {
 | 
			
		||||
		if key == solver.Controller.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 = uint64(len(solver.row1s)) * uint64(len(solver.row2s)) * uint64(len(solver.row3s)) * uint64(len(solver.row4s)) * uint64(len(solver.row5s)) * uint64(len(solver.row6s)) * uint64(len(solver.row7s)) * uint64(len(solver.row8s)) * uint64(len(solver.row9s))
 | 
			
		||||
}
 | 
			
		||||
@@ -1,43 +0,0 @@
 | 
			
		||||
package solver
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"log"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"strconv"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func Run() {
 | 
			
		||||
	// Instantiate the Solver interface
 | 
			
		||||
	solver := Solver{}
 | 
			
		||||
 | 
			
		||||
	// 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()
 | 
			
		||||
 | 
			
		||||
	// Find rows that fit with the entered rows
 | 
			
		||||
	solver.populate_blocks()
 | 
			
		||||
 | 
			
		||||
	// 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()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Print the total number of solutions to validate
 | 
			
		||||
	log.Println("Number of (potential) solutions:", solver.iter)
 | 
			
		||||
 | 
			
		||||
	// Check the number of solutions
 | 
			
		||||
	go solver.check_combinations()
 | 
			
		||||
	solver.tracker()
 | 
			
		||||
 | 
			
		||||
	// Print the valid solutions
 | 
			
		||||
	solver.print_solutions()
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -1,61 +0,0 @@
 | 
			
		||||
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))
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										19
									
								
								solver/splitWorkload.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								solver/splitWorkload.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
			
		||||
package solver
 | 
			
		||||
 | 
			
		||||
// Determine how workload should be split among the agents
 | 
			
		||||
func (solver *Solver) splitWorkload() []int {
 | 
			
		||||
	agents := make([]int, solver.Controller.Split)
 | 
			
		||||
	var tracker int
 | 
			
		||||
	var tasks int = len(solver.row1s)
 | 
			
		||||
 | 
			
		||||
	for tasks != 0 {
 | 
			
		||||
		agents[tracker] += 1
 | 
			
		||||
		tasks -= 1
 | 
			
		||||
		tracker += 1
 | 
			
		||||
		if tracker == solver.Controller.Split {
 | 
			
		||||
			tracker = 0
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return agents
 | 
			
		||||
}
 | 
			
		||||
@@ -1,20 +0,0 @@
 | 
			
		||||
package solver
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"strconv"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (solver *Solver) render_combination(row1 int, row2 int, row3 int, row4 int, row5 int, row6 int, row7 int, row8 int, row9 int) string {
 | 
			
		||||
 | 
			
		||||
	row1s := strconv.Itoa(row1)
 | 
			
		||||
	row2s := strconv.Itoa(row2)
 | 
			
		||||
	row3s := strconv.Itoa(row3)
 | 
			
		||||
	row4s := strconv.Itoa(row4)
 | 
			
		||||
	row5s := strconv.Itoa(row5)
 | 
			
		||||
	row6s := strconv.Itoa(row6)
 | 
			
		||||
	row7s := strconv.Itoa(row7)
 | 
			
		||||
	row8s := strconv.Itoa(row8)
 | 
			
		||||
	row9s := strconv.Itoa(row9)
 | 
			
		||||
 | 
			
		||||
	return row1s + "\n" + row2s + "\n" + row3s + "\n" + row4s + "\n" + row5s + "\n" + row6s + "\n" + row7s + "\n" + row8s + "\n" + row9s + "\n"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								solver/timeTrack.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								solver/timeTrack.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
package solver
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// A simple function to track time
 | 
			
		||||
// Use with `defer`
 | 
			
		||||
func (solver *Solver) timeTrack(start time.Time, msg string) {
 | 
			
		||||
	elapsed := time.Since(start)
 | 
			
		||||
	solver.Outp.Printf("%s (%s)\n", msg, elapsed)
 | 
			
		||||
}
 | 
			
		||||
@@ -1,56 +0,0 @@
 | 
			
		||||
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
 | 
			
		||||
}
 | 
			
		||||
@@ -2,33 +2,38 @@ package solver
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"sync/atomic"
 | 
			
		||||
 | 
			
		||||
	"gitea.ligthert.net/golang/sudoku-funpark/controller"
 | 
			
		||||
	"gitea.ligthert.net/golang/sudoku-funpark/outputter"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Solve a given Sudoku puzzle by iterating through all possible solutions.
 | 
			
		||||
type Solver struct {
 | 
			
		||||
	blocks    []int
 | 
			
		||||
	row1      string
 | 
			
		||||
	row2      string
 | 
			
		||||
	row3      string
 | 
			
		||||
	row4      string
 | 
			
		||||
	row5      string
 | 
			
		||||
	row6      string
 | 
			
		||||
	row7      string
 | 
			
		||||
	row8      string
 | 
			
		||||
	row9      string
 | 
			
		||||
	row1s     []int
 | 
			
		||||
	row2s     []int
 | 
			
		||||
	row3s     []int
 | 
			
		||||
	row4s     []int
 | 
			
		||||
	row5s     []int
 | 
			
		||||
	row6s     []int
 | 
			
		||||
	row7s     []int
 | 
			
		||||
	row8s     []int
 | 
			
		||||
	row9s     []int
 | 
			
		||||
	iter      int64
 | 
			
		||||
	counter   atomic.Int64
 | 
			
		||||
	solutions []string
 | 
			
		||||
	rates     []int64
 | 
			
		||||
	numcpus   int
 | 
			
		||||
	split     int
 | 
			
		||||
	part      int
 | 
			
		||||
	Controller *controller.Controller
 | 
			
		||||
	// Slice of possible blocks for the 1st row.
 | 
			
		||||
	row1s []string
 | 
			
		||||
	// Slice of possible blocks for the 2nd row.
 | 
			
		||||
	row2s []string
 | 
			
		||||
	// Slice of possible blocks for the 3rd row.
 | 
			
		||||
	row3s []string
 | 
			
		||||
	// Slice of possible blocks for the 4th row.
 | 
			
		||||
	row4s []string
 | 
			
		||||
	// Slice of possible blocks for the 5th row.
 | 
			
		||||
	row5s []string
 | 
			
		||||
	// Slice of possible blocks for the 6th row.
 | 
			
		||||
	row6s []string
 | 
			
		||||
	// Slice of possible blocks for the 7th row.
 | 
			
		||||
	row7s []string
 | 
			
		||||
	// Slice of possible blocks for the 8th row.
 | 
			
		||||
	row8s []string
 | 
			
		||||
	// Slice of possible blocks for the 9th row.
 | 
			
		||||
	row9s []string
 | 
			
		||||
	// Maximum number of possible solutions with the current set of rows.
 | 
			
		||||
	Iter uint64
 | 
			
		||||
	// Progress counter, needs atomic due to the number of updates.
 | 
			
		||||
	counter atomic.Uint64
 | 
			
		||||
	// Slice of rates for accurate duration estimation.
 | 
			
		||||
	rates []uint64
 | 
			
		||||
	// Reference to Outputter interface
 | 
			
		||||
	Outp *outputter.Outputter
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										47
									
								
								solver/validateCombination.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								solver/validateCombination.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
			
		||||
package solver
 | 
			
		||||
 | 
			
		||||
// Validate combination
 | 
			
		||||
func (solver *Solver) validateCombination(row1 string, row2 string, row3 string, row4 string, row5 string, row6 string, row7 string, row8 string, row9 string) (retval bool) {
 | 
			
		||||
	retval = true
 | 
			
		||||
 | 
			
		||||
	for index := range 9 {
 | 
			
		||||
		if row1[index] == row2[index] || row1[index] == row3[index] || row1[index] == row4[index] || row1[index] == row5[index] || row1[index] == row6[index] || row1[index] == row7[index] || row1[index] == row8[index] || row1[index] == row9[index] {
 | 
			
		||||
			retval = false
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if row2[index] == row1[index] || row2[index] == row3[index] || row2[index] == row4[index] || row2[index] == row5[index] || row2[index] == row6[index] || row2[index] == row7[index] || row2[index] == row8[index] || row2[index] == row9[index] {
 | 
			
		||||
			retval = false
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if row3[index] == row1[index] || row3[index] == row2[index] || row3[index] == row4[index] || row3[index] == row5[index] || row3[index] == row6[index] || row3[index] == row7[index] || row3[index] == row8[index] || row3[index] == row9[index] {
 | 
			
		||||
			retval = false
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if row4[index] == row1[index] || row4[index] == row2[index] || row4[index] == row3[index] || row4[index] == row5[index] || row4[index] == row6[index] || row4[index] == row7[index] || row4[index] == row8[index] || row4[index] == row9[index] {
 | 
			
		||||
			retval = false
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if row5[index] == row1[index] || row5[index] == row2[index] || row5[index] == row3[index] || row5[index] == row4[index] || row5[index] == row6[index] || row5[index] == row7[index] || row5[index] == row8[index] || row5[index] == row9[index] {
 | 
			
		||||
			retval = false
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if row6[index] == row1[index] || row6[index] == row2[index] || row6[index] == row3[index] || row6[index] == row4[index] || row6[index] == row5[index] || row6[index] == row7[index] || row6[index] == row8[index] || row6[index] == row9[index] {
 | 
			
		||||
			retval = false
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if row7[index] == row1[index] || row7[index] == row2[index] || row7[index] == row3[index] || row7[index] == row4[index] || row5[index] == row6[index] || row7[index] == row6[index] || row7[index] == row8[index] || row7[index] == row9[index] {
 | 
			
		||||
			retval = false
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if row8[index] == row1[index] || row8[index] == row2[index] || row8[index] == row3[index] || row8[index] == row4[index] || row8[index] == row5[index] || row8[index] == row6[index] || row8[index] == row7[index] || row8[index] == row9[index] {
 | 
			
		||||
			retval = false
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if row9[index] == row1[index] || row9[index] == row2[index] || row9[index] == row3[index] || row9[index] == row4[index] || row9[index] == row5[index] || row9[index] == row6[index] || row9[index] == row7[index] || row9[index] == row8[index] {
 | 
			
		||||
			retval = false
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return retval
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								solver/validator.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								solver/validator.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
package solver
 | 
			
		||||
 | 
			
		||||
// Validate the provided rows and verify it is a valid solution.
 | 
			
		||||
func (solver *Solver) validator(rows1Index int, rows2Index int, rows3Index int, rows4Index int, rows5Index int, rows6Index int, rows7Index int, rows8Index int, rows9Index int) {
 | 
			
		||||
 | 
			
		||||
	solver.counter.Add(1)
 | 
			
		||||
 | 
			
		||||
	if solver.validateCombination(solver.row1s[rows1Index], solver.row2s[rows2Index], solver.row3s[rows3Index], solver.row4s[rows4Index], solver.row5s[rows5Index], solver.row6s[rows6Index], solver.row7s[rows7Index], solver.row8s[rows8Index], solver.row9s[rows9Index]) {
 | 
			
		||||
		solver.Controller.Solutions = append(solver.Controller.Solutions, []string{solver.row1s[rows1Index], solver.row2s[rows2Index], solver.row3s[rows3Index], solver.row4s[rows4Index], solver.row5s[rows5Index], solver.row6s[rows6Index], solver.row7s[rows7Index], solver.row8s[rows8Index], solver.row9s[rows9Index]})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										94
									
								
								unused.txt
									
									
									
									
									
								
							
							
						
						
									
										94
									
								
								unused.txt
									
									
									
									
									
								
							@@ -1,94 +0,0 @@
 | 
			
		||||
// 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])
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user