package main
import (
	"bufio"
	"fmt"
	"log"
	"math/rand"
	"os"
	"strconv"
	"time"
)
// getCommaPositions returns the slice of indices where commas occur in the line
func getCommaPositions(line string) []int {
	var commas []int
		for i := 0; i < len(line); i++ {
			if line[i] == ',' {
				commas = append(commas, i)
			}
		}
		return commas
	}
// substringBetweenCommasFast extracts substring between nth and mth commas (1-based), using cached comma positions
// Returns empty string if indices invalid.
func substringBetweenCommasFast(line string, commas []int, nth, mth int) (string, error) {
	if nth >= mth {
		return "", fmt.Errorf("invalid indices: nth >= mth")
	}
	if mth > len(commas) {
		return "", fmt.Errorf("not enough commas: needed %d got %d", mth, len(commas))
	}
	start := 0
	if nth > 0 {
		start = commas[nth-1] + 1
	}
	end := commas[mth-1]
	if start > end || start > len(line) || end > len(line) || start < 0 || end < 0 {
		return "", fmt.Errorf("invalid substring indices")
	}
	return line[start:end], nil
}
// extractPartsFast extracts multiple parts given pairs of (nth, mth) comma indices, using cached comma positions
func extractPartsFast(line string, commas []int, pairs [][2]int) ([]string, error) {
	parts := make([]string, len(pairs))
	for i, pair := range pairs {
		part, err := substringBetweenCommasFast(line, commas, pair[0], pair[1])
		if err != nil {
			return nil, err
		}
		parts[i] = part
	}
	return parts, nil
}
// pickOffspringParts1 picks offspring parts from parents with ensured swapping
func pickOffspringParts1(p1G, p1g, p2G, p2g string) (string, string) {
	if rand.Intn(2) == 0 {
		if rand.Intn(2) == 0 {
			return p1G, p2G
		}
		return p1G, p2g
	}
	if rand.Intn(2) == 0 {
		return p2G, p1G
	}
	return p2G, p1g
}
// pickOffspringParts2 picks offspring parts by selecting each part from either parent1 or parent2
func pickOffspringParts2(parent1Parts, parent2Parts []string) ([]string, error) {
	if len(parent1Parts) != len(parent2Parts) {
		return nil, fmt.Errorf("parent parts slices must have the same length")
	}
	offspringParts := make([]string, len(parent1Parts))
	for i := range parent1Parts {
		if rand.Intn(2) == 0 {
			offspringParts[i] = parent1Parts[i]
		} else {
			offspringParts[i] = parent2Parts[i]
		}
	}
	return offspringParts, nil
}
// produce offspring parts, 2 parts replaced by random int strings between 1-10 excluding parents' values
func offspringWithRandomExclusion(parent1Parts, parent2Parts []string) ([]string, error) {
	if len(parent1Parts) != len(parent2Parts) {
		return nil, fmt.Errorf("parent parts slices must have the same length")
	}
	n := len(parent1Parts)
	if n < 2 {
		return nil, fmt.Errorf("need at least two parts for replacement")
	}
	offspring := make([]string, n)
	chosenRandomIdx := make(map[int]bool)
	// randomly pick 2 distinct indexes to replace with random ints
	perm := rand.Perm(n)
	chosenRandomIdx[perm[0]] = true
	chosenRandomIdx[perm[1]] = true
	for i := 0; i < n; i++ {
		if chosenRandomIdx[i] {
			// parse parent parts to integers
			p1v, err1 := strconv.Atoi(parent1Parts[i])
			p2v, err2 := strconv.Atoi(parent2Parts[i])
			// collect forbidden values (only if both parses succeeded)
			forbidden := map[int]bool{}
			if err1 == nil {
				forbidden[p1v] = true
			}
			if err2 == nil {
				forbidden[p2v] = true
			}
			var val int
			tries := 0
			// randomly select int 1-10 excluding forbidden
			for {
				val = rand.Intn(10) + 1
				if !forbidden[val] {
					break
				}
				tries++
				if tries > 100 {
					return nil, fmt.Errorf("cannot find suitable random integer excluding parents at index %d", i)
				}
			}
			offspring[i] = strconv.Itoa(val)
			} else {
			// pick randomly parent1 or parent2 part for this index
			if rand.Intn(2) == 0 {
				offspring[i] = parent1Parts[i]
			} else {
				offspring[i] = parent2Parts[i]
			}
		}
	}
	return offspring, nil
}
// countLines quickly counts lines in file; only scanning once
func countLines(filepath string) (int, error) {
	f, err := os.Open(filepath)
	if err != nil {
		return 0, err
	}
	defer f.Close()
	scanner := bufio.NewScanner(f)
	count := 0
	for scanner.Scan() {
		count++
	}
	return count, scanner.Err()
}
// getLineAt reads only the specified line (0-based) from the file
func getLineAt(filepath string, lineNum int) (string, error) {
	f, err := os.Open(filepath)
	if err != nil {
		return "", err
	}
	defer f.Close()
	scanner := bufio.NewScanner(f)
	current := 0
	for scanner.Scan() {
		if current == lineNum {
			return scanner.Text(), nil
		}
		current++
	}
	if err := scanner.Err(); err != nil {
		return "", err
	}
	return "", fmt.Errorf("line %d not found", lineNum)
}
func main() {
	rand.Seed(time.Now().UnixNano())
	filePath := "CharacterAppearanceData.txt"
	// Count total lines first to pick random indices
	totalLines, err := countLines(filePath)
	if err != nil {
		log.Fatalf("Error counting lines: %v", err)
	}
	if totalLines < 2 {
		log.Fatal("File must have at least two lines")
	}
	// Pick two distinct random line indices
	i1 := rand.Intn(totalLines)
	i2 := rand.Intn(totalLines - 1)
	if i2 >= i1 {
		i2++
	}
	// Fetch lines without loading entire file into memory
	line1, err := getLineAt(filePath, i1)
	if err != nil {
		log.Fatalf("Error reading line %d: %v", i1+1, err)
	}
	line2, err := getLineAt(filePath, i2)
	if err != nil {
		log.Fatalf("Error reading line %d: %v", i2+1, err)
	}
	fmt.Printf("Selected lines: %d and %d\n\n", i1+1, i2+1)
	fmt.Printf("Line %d: %s\nLine %d: %s\n\n", i1+1, line1, i2+1, line2)
	// Cache commas
	c1 := getCommaPositions(line1)
	c2 := getCommaPositions(line2)
	// Comma index pairs to extract
	groups := [][2]int{{1, 2}, {2, 3}, {3, 4}, {4, 5}}
	// Example: extract parts between 1th-2th commas and 2th-3th commas
	parts1, err := extractPartsFast(line1, c1, groups)
	if err != nil {
		log.Fatalf("Error extracting parts from line %d: %v", i1+1, err)
	}
	// Example: extract parts between 3th-4th commas and 4th-5th commas
	parts2, err := extractPartsFast(line2, c2, groups)
	if err != nil {
		log.Fatalf("Error extracting parts from line %d: %v", i2+1, err)
	}
	// Use pairs of parts from parents:
	// Example: select parts for offspring between 1th-2th commas and 2th-3th commas
	offspringPart1, offspringPart2 := pickOffspringParts1(parts1[0], parts1[1], parts2[0], parts2[1])
	// Example: select parts for offspring between 3th-4th commas and 4th-5th commas
	offspringPart3, offspringPart4 := pickOffspringParts1(parts1[2], parts1[3], parts2[2], parts2[3])
	fmt.Printf("Offspring parts between 1-2 commas, 2-3 commas: %s, %s\n", offspringPart1, offspringPart2)
	fmt.Printf("Offspring parts between 3-4 commas, 4-5 commas: %s, %s\n", offspringPart3, offspringPart4)
	// Next group (33 to 41)
	groups = [][2]int{{33, 34}, {34, 35}, {35, 36}, {36, 37},{37, 38}, {38, 39}, {39, 40}, {40, 41}}
	// Example: extract parts between 33th-34th commas to 40th-41th commas
	parts1, err = extractPartsFast(line1, c1, groups)
	if err != nil {
		log.Fatalf("Error extracting parts from parent1 line: %v", err)
	}
	parts2, err = extractPartsFast(line2, c2, groups)
	if err != nil {
		log.Fatalf("Error extracting parts from parent2 line: %v", err)
	}
	offspringParts, err := offspringWithRandomExclusion(parts1, parts2)
	if err != nil {
		log.Fatalf("Error generating offspring parts: %v", err)
	}
	for i, part := range offspringParts {
		fmt.Printf("Between commas %d-%d: %s\n", groups[i][0], groups[i][1], part)
	}
}
If the txt file is like this:
[ca/1,2,2,1,2,253,120,105,255,142,98,198,255,142,98,198,255,255,255,255,0,255,255,255,0,255,255,255,0,145,167,141,255,2,8,4,2,9,3,7,9,8,101,101,101,1,1,1.846,2,0.742,3,1.325,4,1.537,5,0.329,6,0.677,7,0.764,8,0.568,9,1.03,10,0.564,11,1.462,12,1.805,13,0.89,14,1.737,15,1.441,16,0.98,17,0.795,18,0.898,19,0.407,20,0.706,21,1.308,22,0.03,23,0.3,24,1.753,25,0.925,26,1.585,27,0.681,28,0.882,29,1.376,30,1.229,31,0.658,32,0.845,33,0.759,34,0.851,35,1.379,36,1.521,37,1.846,38,1.481,39,0.642,40,0.182,41,0.442,42,1.135,43,1.63,44,0.476,45,1.523,46,0.525,47,0.966,48,0.772,49,0.015,50,0.119,51,0.574,52,1.252,53,0.578,54,1.347,55,1.622,56,1.675,57,0.432,58,1.546,59,1.825,60,1.745,61,1.704,62,1.221,63,0.982,64,0.995,65,1.177,66,0.342,67,1.179,68,0.688,69,1.232,70,1.134,71,0.343,72,1.797,73,1.011,74,0.178,75,0.216,76,0.118,77,1.202,78,0.768,79,1.485,80,0.629,81,0.087,82,0.165,83,0.216,84,1.002,85,0.186,86,1.487,87,0.696,88,0.742,89,1.799,90,1.191,91,1.24,92,1.572,93,1.44,94,1.135,95,0.819,96,1.627,97,1.648,98,0.515,99,1.333,100,1.765,1]
[ca/2,2,1,3,2,217,109,109,255,145,167,141,255,145,167,141,255,255,255,255,0,145,167,141,255,145,167,141,255,145,167,141,255,6,10,1,2,1,5,9,4,5,101,101,4,4,1,1.027,2,1.721,3,1.833,4,1.934,5,1.593,6,0.4,7,1.818,8,0.051,9,0.547,10,1.806,11,1.132,12,1.067,13,1.874,14,0.616,15,0.88,16,0.98,17,0.701,18,1.471,19,1.008,20,0.765,21,0.65,22,0.956,23,1.203,24,1.12,25,1.639,26,0.87,27,1.623,28,0.725,29,1.011,30,1.117,31,0.661,32,0.274,33,0.43,34,0.552,35,1.42,36,0.462,37,1.475,38,0.728,39,1.762,40,0.311,41,0.331,42,0.705,43,0.646,44,1.842,45,1.325,46,1.102,47,0.41,48,1.091,49,1.797,50,0.601,51,0.97,52,0.486,53,0.692,54,1.511,55,1.569,56,0.714,57,0.792,58,0.278,59,0.056,60,0.64,61,1.013,62,0.219,63,1.314,64,0.997,65,0.004,66,1.847,67,0.38,68,1.011,69,1.175,70,0.105,71,1.846,72,1.829,73,1.737,74,0.217,75,1.173,76,0.789,77,1.704,78,0.686,79,0.047,80,0.727,81,0.14,82,0.654,83,0.242,84,1.55,85,0.016,86,1.155,87,0.885,88,1.114,89,0.176,90,1.901,91,1.038,92,1.934,93,1.573,94,1.359,95,1.349,96,1.612,97,1.257,98,1.691,99,0.362,100,1.4,4]
I want to make the one line has (the number between 1-2 commas, the number between 2 -3 commas)=(1,1) picked out as parent1, and the one line which has (the number between 1-2 commas, the number between 2 -3 commas) != (1,1) picked out as parent2, before starting generating an offspring.