manual-sort/manualsort.go

123 lines
2.8 KiB
Go

// 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 <item list file> <output file path>")
}
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()
}