diff --git a/manualsort.go b/manualsort.go new file mode 100644 index 0000000..28f7f63 --- /dev/null +++ b/manualsort.go @@ -0,0 +1,122 @@ +// binary manualsort asks you at the CLI which of two things you like +// better, repeatedly, and pukes up a list sorted accordingly. +package main + +import ( + "bufio" + "fmt" + "log" + "os" + "slices" + "strings" +) + +// handwritten merge sort because it minimizes comparisons, which are +// the expensive part when asking a human for every comparison. +// quicksort's "in-place" behavior isn't necessary. +func merge(a, b []string) []string { + if len(a) == 0 { + return b + } + if len(b) == 0 { + return a + } + ret := make([]string, 0, len(a)+len(b)) + for len(a) > 0 && len(b) > 0 { + if better(a[0], b[0]) { + ret = append(ret, a[0]) + a = a[1:] + } else { + ret = append(ret, b[0]) + b = b[1:] + } + } + if len(a) > 0 { + ret = append(ret, a...) + } + if len(b) > 0 { + ret = append(ret, b...) + } + return ret +} + +var ( + stdinReader *bufio.Reader + truthy = []string{ + "y", "yes", "1", "t", "true", "aye", "left", "up", "top", "first", "p", + } + falsey = []string{ + "n", "no", "0", "2", "f", "false", "nay", "right", "down", "bottom", "second", "nil", + } +) + +func better(first, second string) bool { + for { + fmt.Println() + fmt.Println("Is") + fmt.Println(first) + fmt.Println("better than") + fmt.Println(second) + fmt.Print("? > ") + reply, err := stdinReader.ReadString('\n') + if err != nil && len(reply) == 0 { + log.Fatalf("I/O error: %v", err) + } + reply = strings.TrimSpace(reply) + reply = strings.ToLower(reply) + if slices.Contains(truthy, reply) { + return true + } + if slices.Contains(falsey, reply) { + return false + } + fmt.Printf("Huh? I didn't understand %q. Answer \"y\" or \"n\".\n", reply) + } +} + +func mergeSort(items []string) []string { + if len(items) <= 1 { + return items + } + midpoint := len(items) / 2 + return merge(mergeSort(items[:midpoint]), mergeSort(items[midpoint:])) +} + +func main() { + if len(os.Args) != 3 { + log.Fatal("wrong number of args, expected 2. Usage: manualsort ") + } + ifile, err := os.Open(os.Args[1]) + if err != nil { + log.Fatalf("can't open %q: %v.", os.Args[1], err) + } + ofile, err := os.OpenFile(os.Args[2], os.O_CREATE|os.O_EXCL, 0600) + if err != nil { + log.Fatalf("can't exclusively create %q: %v.", os.Args[2], err) + } + stdinReader = bufio.NewReader(os.Stdin) + + var items []string + scanner := bufio.NewScanner(ifile) + for scanner.Scan() { + if t := strings.TrimSpace(scanner.Text()); len(t) > 0 { + items = append(items, t) + } + } + if err := scanner.Err(); err != nil { + log.Fatalf("can't read %q: %v", os.Args[1], err) + } + + sorted := mergeSort(items) + fmt.Println("Sorted. Saving...") + obuf := bufio.NewWriter(ofile) + for i, s := range sorted { + _, err := obuf.WriteString(fmt.Sprintf("%6d %s\n", i+1, s)) + if err != nil { + log.Printf("error saving %d (%s): %v\n", i+1, s, err) + } + } + obuf.Flush() + ofile.Sync() + ofile.Close() +}