package main
import (
"flag"
"fmt"
"math"
"time"
"algo/metrics"
)
// cmdAlpha is lesson 10's closing question, answered.
//
// The hash index could not produce the library in order at any price. The tree
// produces it by being walked.
func cmdAlpha(args []string) error {
fs := flag.NewFlagSet("alpha", flag.ContinueOnError)
in := fs.String("in", "library.jsonl", "library file")
n := fs.Int("n", 6, "how many titles to show")
from := fs.String("from", "", "range query: lower bound")
to := fs.String("to", "", "range query: upper bound")
if err := fs.Parse(args); err != nil {
return err
}
items, err := load(*in)
if err != nil {
return err
}
tree := NewTreeIndex()
var build metrics.Counter
for _, it := range items {
tree.Put(it, &build)
}
t0 := time.Now()
keys := tree.Keys()
walk := time.Since(t0)
fmt.Printf("the first %d titles, walked out of the tree:\n", *n)
for i := 0; i < *n && i < len(keys); i++ {
fmt.Printf(" %s\n", keys[i])
}
fmt.Printf("\n%d keys in order, 0 comparisons, %s\n", len(keys), walk.Round(time.Microsecond))
lo, _ := tree.Min()
hi, _ := tree.Max()
fmt.Printf("min %q, max %q\n", lo, hi)
if *from != "" || *to != "" {
var rc metrics.Counter
hits := tree.Range(*from, *to, &rc)
fmt.Printf("\nrange [%s .. %s]: %d titles, %d nodes visited out of %d\n",
*from, *to, len(hits), rc.N(), tree.Len())
for i := 0; i < 4 && i < len(hits); i++ {
fmt.Printf(" %s\n", hits[i])
}
if len(hits) > 4 {
fmt.Printf(" ... and %d more\n", len(hits)-4)
}
}
return nil
}
// cmdTree measures the tree against the hash index of lesson 10 and against
// the linear scan of lesson 1 — same library, same questions.
func cmdTree(args []string) error {
fs := flag.NewFlagSet("tree", flag.ContinueOnError)
in := fs.String("in", "library.jsonl", "library file")
if err := fs.Parse(args); err != nil {
return err
}
items, err := load(*in)
if err != nil {
return err
}
tree := NewTreeIndex()
var tb metrics.Counter
t0 := time.Now()
for _, it := range items {
tree.Put(it, &tb)
}
treeBuild := time.Since(t0)
idx := NewHashIndex(16)
var hb metrics.Counter
t0 = time.Now()
for _, it := range items {
idx.Put(it, &hb)
}
hashBuild := time.Since(t0)
var tl, hl, ll metrics.Counter
t0 = time.Now()
for _, it := range items {
tree.Get(it.Title, &tl)
}
treeLook := time.Since(t0)
t0 = time.Now()
for _, it := range items {
idx.Get(it.Title, &hl)
}
hashLook := time.Since(t0)
t0 = time.Now()
for _, it := range items {
LinearFind(items, it.Title, &ll)
}
linLook := time.Since(t0)
n := float64(tree.Len())
fmt.Printf("n = %d distinct titles\n\n", tree.Len())
fmt.Printf(" tree height %d (log2 n = %.1f)\n", tree.Height(), math.Log2(n))
fmt.Printf(" in order? %v\n\n", isSortedStrings(tree.Keys()))
fmt.Printf(" %-16s %14s %14s %12s\n", "", "cmps/lookup", "build cmps", "lookup time")
fmt.Printf(" %-16s %14.3f %14d %12s\n", "tree", float64(tl.N())/n, tb.N(), treeLook.Round(time.Microsecond))
fmt.Printf(" %-16s %14.3f %14d %12s\n", "hash (lesson 10)", float64(hl.N())/n, hb.N(), hashLook.Round(time.Microsecond))
fmt.Printf(" %-16s %14.1f %14d %12s\n", "linear (lesson 1)", float64(ll.N())/n, 0, linLook.Round(time.Millisecond))
fmt.Printf("\n build time: tree %s, hash %s\n", treeBuild.Round(time.Microsecond), hashBuild.Round(time.Microsecond))
// What each one can hand back in order, and what that costs.
fmt.Printf("\n keys in sorted order: tree yes (walk), hash no (must sort all %d)\n", tree.Len())
return nil
}
func isSortedStrings(s []string) bool {
for i := 1; i < len(s); i++ {
if s[i-1] > s[i] {
return false
}
}
return true
}
// cmdShape is the degeneration: the same library, inserted in two orders.
func cmdShape(args []string) error {
fs := flag.NewFlagSet("shape", flag.ContinueOnError)
shuf := fs.String("shuffled", "library.jsonl", "library in random order")
sorted := fs.String("sorted", "sorted.jsonl", "the same library, pre-sorted")
if err := fs.Parse(args); err != nil {
return err
}
a, err := load(*shuf)
if err != nil {
return err
}
b, err := load(*sorted)
if err != nil {
return err
}
fmt.Printf("%-12s %8s %8s %10s %14s %12s\n",
"insert order", "n", "height", "log2 n", "cmps/lookup", "lookup time")
for _, tc := range []struct {
name string
items []Item
}{{"shuffled", a}, {"-sorted", b}} {
tree := NewTreeIndex()
var c metrics.Counter
for _, it := range tc.items {
tree.Put(it, &c)
}
var l metrics.Counter
t0 := time.Now()
for _, it := range tc.items {
tree.Get(it.Title, &l)
}
el := time.Since(t0)
n := float64(tree.Len())
fmt.Printf("%-12s %8d %8d %10.1f %14.1f %12s\n",
tc.name, tree.Len(), tree.Height(), math.Log2(n), float64(l.N())/n, el.Round(time.Microsecond))
}
// Lesson 1's linear scan over the same library, for scale. The degenerate
// tree should be compared against the thing it was supposed to replace.
var ll metrics.Counter
t0 := time.Now()
for _, it := range a {
LinearFind(a, it.Title, &ll)
}
el := time.Since(t0)
fmt.Printf("%-12s %8d %8s %10s %14.1f %12s\n",
"linear (L1)", len(a), "-", "-", float64(ll.N())/float64(len(a)), el.Round(time.Microsecond))
fmt.Println("\nboth trees hold the same keys and both answer correctly.")
return nil
}