Give each function its own file.
This commit is contained in:
		| @@ -6,7 +6,7 @@ import ( | |||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // Print solutions into a human friendly format for in the console. | // Print solutions into a human friendly format for in the console. | ||||||
| func (export *Export) PrintHumanSolutions() { | func (export *Export) PrintHumanReadableSolutions() { | ||||||
| 	for solutionIndex, solution := range export.Controller.Solutions { | 	for solutionIndex, solution := range export.Controller.Solutions { | ||||||
| 		log.Printf("\nSolution #%d:", solutionIndex+1) | 		log.Printf("\nSolution #%d:", solutionIndex+1) | ||||||
| 		fmt.Println("╔═══════════╗") | 		fmt.Println("╔═══════════╗") | ||||||
| @@ -2,7 +2,6 @@ package flags | |||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"flag" | 	"flag" | ||||||
| 	"fmt" |  | ||||||
| 	"log" | 	"log" | ||||||
| 	"os" | 	"os" | ||||||
| 	"runtime" | 	"runtime" | ||||||
| @@ -83,72 +82,3 @@ func (flags *Flags) ParseFlags() { | |||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
| 
 |  | ||||||
| // 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 { |  | ||||||
| 		log.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 { |  | ||||||
| 		log.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 { |  | ||||||
| 		log.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) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // 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 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // 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() |  | ||||||
| } |  | ||||||
							
								
								
									
										28
									
								
								flags/printUsage.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								flags/printUsage.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | |||||||
|  | package flags | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"flag" | ||||||
|  | 	"fmt" | ||||||
|  | 	"os" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // 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 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 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() | ||||||
|  | } | ||||||
							
								
								
									
										54
									
								
								flags/validateRow.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								flags/validateRow.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | |||||||
|  | package flags | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"log" | ||||||
|  | 	"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 { | ||||||
|  | 		log.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 { | ||||||
|  | 		log.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 { | ||||||
|  | 		log.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) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										2
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								main.go
									
									
									
									
									
								
							| @@ -48,7 +48,7 @@ func main() { | |||||||
| 	// Print the valid solutions | 	// Print the valid solutions | ||||||
| 	switch controller.Output { | 	switch controller.Output { | ||||||
| 	case "human": | 	case "human": | ||||||
| 		export.PrintHumanSolutions() | 		export.PrintHumanReadableSolutions() | ||||||
| 	case "flat": | 	case "flat": | ||||||
| 		export.PrintFlatSolutions() | 		export.PrintFlatSolutions() | ||||||
| 	} | 	} | ||||||
|   | |||||||
							
								
								
									
										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) | ||||||
|  | 									} | ||||||
|  | 								} | ||||||
|  | 							} | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										27
									
								
								solver/PopulateBlocks.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								solver/PopulateBlocks.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | package solver | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"log" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Find all possible blocks that can be used to find a solution. | ||||||
|  | func (solver *Solver) PopulateBlocks() { | ||||||
|  |  | ||||||
|  | 	defer solver.timeTrack(time.Now(), "Populated blocks") | ||||||
|  | 	log.Println("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)) | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										23
									
								
								solver/SelectWorkload.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								solver/SelectWorkload.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | package solver | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"log" | ||||||
|  | 	"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) { | ||||||
|  | 		log.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") | ||||||
|  | 	log.Println("Setting workload") | ||||||
|  | 	log.Println("We are agent " + strconv.Itoa(solver.Controller.Part) + " of " + strconv.Itoa(solver.Controller.Split)) | ||||||
|  | 	workloads := solver.splitWorkload() | ||||||
|  | 	solver.setWorkload(workloads) | ||||||
|  | } | ||||||
							
								
								
									
										103
									
								
								solver/Tracker.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								solver/Tracker.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,103 @@ | |||||||
|  | package solver | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"log" | ||||||
|  | 	"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") | ||||||
|  | 	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 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 | ||||||
|  | 			log.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 | ||||||
|  | } | ||||||
| @@ -1,253 +0,0 @@ | |||||||
| package solver |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"log" |  | ||||||
| 	"strconv" |  | ||||||
| 	"time" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // Find all possible blocks that can be used to find a solution. |  | ||||||
| func (solver *Solver) PopulateBlocks() { |  | ||||||
|  |  | ||||||
| 	defer solver.timeTrack(time.Now(), "Populated blocks") |  | ||||||
| 	log.Println("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)) |  | ||||||
|  |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // 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 |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // 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) |  | ||||||
| 									} |  | ||||||
| 								} |  | ||||||
| 							} |  | ||||||
| 						} |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // 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]}) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // 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") |  | ||||||
| 	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 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 |  | ||||||
| 			log.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 |  | ||||||
|  |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // 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 |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // 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 |  | ||||||
| } |  | ||||||
							
								
								
									
										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,61 +0,0 @@ | |||||||
| package solver |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"log" |  | ||||||
| 	"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) { |  | ||||||
| 		log.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") |  | ||||||
| 	log.Println("Setting workload") |  | ||||||
| 	log.Println("We are agent " + strconv.Itoa(solver.Controller.Part) + " of " + strconv.Itoa(solver.Controller.Split)) |  | ||||||
| 	workloads := solver.splitWorkload() |  | ||||||
| 	solver.setWorkload(workloads) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // 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 |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // 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)) |  | ||||||
| } |  | ||||||
							
								
								
									
										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 | ||||||
|  | } | ||||||
							
								
								
									
										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]}) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user