diff --git a/main.go b/main.go index c8ba912..30d2454 100644 --- a/main.go +++ b/main.go @@ -1,15 +1,13 @@ package main import ( - "log" - "gitea.ligthert.net/golang/sudoku-funpark/solver" ) func main() { // Set global logger parameters - log.SetFlags(log.LstdFlags | log.Lmicroseconds | log.Lshortfile) + // log.SetFlags(log.LstdFlags | log.Lshortfile) // Run the meat of the program solver.Run() diff --git a/solver/blocks.go b/solver/blocks.go index ca4e4cc..0396da2 100644 --- a/solver/blocks.go +++ b/solver/blocks.go @@ -6,9 +6,14 @@ import ( "log" "os" "strconv" + "time" ) func (solver *Solver) load_blocks() { + + defer solver.timeTrack(time.Now(), "Loaded blocks") + log.Println("Loading blocks") + var blocks []int file, err := os.Open("blocks.csv") @@ -31,4 +36,5 @@ func (solver *Solver) load_blocks() { blocks = append(blocks, block) } solver.blocks = blocks + } diff --git a/solver/printers.go b/solver/printers.go new file mode 100644 index 0000000..b688348 --- /dev/null +++ b/solver/printers.go @@ -0,0 +1,13 @@ +package solver + +import ( + "fmt" + "log" +) + +func (solver *Solver) print_solutions() { + for solutions := range solver.solutions { + log.Printf("\nSolution #%d:", solutions+1) + fmt.Println(solver.solutions[solutions]) + } +} diff --git a/solver/processing.go b/solver/processing.go index c27e815..20a0f94 100644 --- a/solver/processing.go +++ b/solver/processing.go @@ -2,11 +2,15 @@ package solver import ( "log" - "runtime" "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) @@ -19,6 +23,7 @@ func (solver *Solver) populate_blocks() { // 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) { @@ -80,17 +85,84 @@ func (solver *Solver) check_combinations() { func (solver *Solver) routine_validator(rows1_index int, rows2_index int, rows3_index int, rows4_index int, rows5_index int, rows6_index int, rows7_index int, rows8_index int, rows9_index int) { - var percentage float32 + solver.counter = solver.counter + 1 if solver.validate_combination(solver.row1s[rows1_index], solver.row2s[rows2_index], solver.row3s[rows3_index], solver.row4s[rows4_index], solver.row5s[rows5_index], solver.row6s[rows6_index], solver.row7s[rows7_index], solver.row8s[rows8_index], solver.row9s[rows9_index]) { solver.solutions = append(solver.solutions, solver.render_combination(solver.row1s[rows1_index], solver.row2s[rows2_index], solver.row3s[rows3_index], solver.row4s[rows4_index], solver.row5s[rows5_index], solver.row6s[rows6_index], solver.row7s[rows7_index], solver.row8s[rows8_index], solver.row9s[rows9_index])) } - solver.counter = solver.counter + 1 +} - if solver.counter%1000000 == 0 { +func (solver *Solver) tracker() { + + defer solver.timeTrack(time.Now(), "Validated solutions") + log.Println("Validating solutions") + + // Tracking percenting an progress + var percentage float32 + var track int + + // Tracking the rate + var rate_start int + var rate_stop int + var rate_diff int + + // Tracking duration + var timer_start = time.Now() + // Prevent division-by-zero error when establishing `rate` + time.Sleep(time.Second) + + // Estimation how long it will take + var est_fin string + + for solver.iter != solver.counter { + + // Determine how far we are. percentage = (float32(solver.counter) / (float32(solver.iter) / 100)) - log.Println("Processing:", percentage, "%; Procs:", runtime.NumGoroutine()) + if track <= int(percentage) { + // Reset the loop + rate_stop = int(solver.counter) + rate_diff = rate_stop - rate_start + + // Make sure something happened, making rate_start the only reliable variable + if rate_diff == 0 && rate_start > 1 { + percentage = 100 + solver.counter = solver.iter + } + + timer_elapsed := time.Since(timer_start) + rate := int64(rate_diff) / int64(timer_elapsed.Seconds()) + + // Estimate when this is finished: + // TODO: Make this Bayesian + // TODO: Fix + // For short running solutions this is fine + // For long running solutions this breaks + if rate_diff == 0 { + est_fin = "N/A" + } else { + est_fin = solver.secondsToHuman((int(solver.iter) - int(solver.counter)) / int(rate)) + } + + // Printing the meat + log.Println("Processing: " + strconv.Itoa(int(percentage)) + "% (" + strconv.Itoa(int(solver.counter)) + "/" + strconv.Itoa(int(solver.iter)) + "); Rate (avg): " + strconv.Itoa(int(rate)) + "/sec for " + timer_elapsed.String() + "; Time left (est.): " + est_fin) + + // Wrap up the loop or break + if int(percentage) > track { + track = int(percentage) + } else { + track = track + 1 + } + rate_start = rate_stop + timer_start = time.Now() + + if rate_diff == 0 && rate_start > 100 { + break + } + + } + time.Sleep(1 * time.Second) + } } diff --git a/solver/solver.go b/solver/solver.go index c4077df..552a749 100644 --- a/solver/solver.go +++ b/solver/solver.go @@ -19,6 +19,16 @@ func Run() { solver.row8 = "006283090" solver.row9 = "200590006" + solver.row1 = "769104802" + solver.row2 = "154800060" + solver.row3 = "832700154" + solver.row4 = "600900328" + solver.row5 = "045328670" + solver.row6 = "328670945" + solver.row7 = "597410280" + solver.row8 = "006283090" + solver.row9 = "200590006" + // Load blocks from CSV file solver.load_blocks() @@ -26,12 +36,13 @@ func Run() { solver.populate_blocks() // Print the total number of solutions to validate - log.Println("Number of solutions:", solver.iter) + log.Println("Number of (potential) solutions:", solver.iter) // Check the number of solutions - solver.check_combinations() + go solver.check_combinations() + solver.tracker() // Print the valid solutions - log.Println(solver.solutions) + solver.print_solutions() } diff --git a/solver/timers.go b/solver/timers.go new file mode 100644 index 0000000..e454afc --- /dev/null +++ b/solver/timers.go @@ -0,0 +1,56 @@ +package solver + +import ( + "log" + "math" + "strconv" + "time" +) + +func (solver *Solver) timeTrack(start time.Time, msg string) { + elapsed := time.Since(start) + log.Printf("%s (%s)", msg, elapsed) +} + +// Stolen from https://socketloop.com/tutorials/golang-convert-seconds-to-human-readable-time-format-example +func (solver *Solver) plural(count int, singular string) (result string) { + if (count == 1) || (count == 0) { + result = strconv.Itoa(count) + " " + singular + " " + } else { + result = strconv.Itoa(count) + " " + singular + "s " + } + return +} + +func (solver *Solver) secondsToHuman(input int) (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 +}