Skip to content

Commit

Permalink
Merge pull request #13 from herzrasen/refactored-searching
Browse files Browse the repository at this point in the history
moved search and sort to record struct
  • Loading branch information
herzrasen authored Mar 20, 2023
2 parents 3c1f964 + 1506252 commit d774a1a
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 142 deletions.
116 changes: 0 additions & 116 deletions fuzzy/fuzzy.go

This file was deleted.

71 changes: 71 additions & 0 deletions record/record.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import (
"github.com/fatih/color"
"strings"
"time"
"unicode"
)

type Record struct {
Id int64
Command string
LastUpdate time.Time
Count uint64
Weight uint64
}

type FormatOptions struct {
Expand All @@ -33,3 +35,72 @@ func (r *Record) Format(options FormatOptions) string {
buf.WriteString(r.Command)
return buf.String()
}

func (r *Record) UpdateWeight(input string) {
inputOccurrences := countOccurrences(input)
inputTuples := splitIntoChunks(input, 2)
inputTriples := splitIntoChunks(input, 3)
compact := strings.Join(strings.Fields(r.Command), "")
occurrences := countOccurrences(compact)
r.Weight = weightOccurrences(occurrences, inputOccurrences)
if r.Weight > 0 {
tuples := splitIntoChunks(compact, 2)
triples := splitIntoChunks(compact, 3)
r.Weight += weightChunks(tuples, inputTuples) +
weightChunks(triples, inputTriples) +
r.Count
}
}

func countOccurrences(input string) map[rune]uint64 {
var occurrences = make(map[rune]uint64)
for _, c := range []byte(input) {
r := unicode.ToLower(rune(c))
if _, ok := occurrences[r]; ok {
occurrences[r]++
} else {
occurrences[r] = 1
}
}
return occurrences
}

func weightOccurrences(occurrences map[rune]uint64, input map[rune]uint64) uint64 {
var weight uint64 = 0
for k, v := range input {
runesInOccurrences := occurrences[k]
switch {
case runesInOccurrences == 0:
return 0
case runesInOccurrences > v:
weight += v
case v >= runesInOccurrences:
weight += runesInOccurrences
}
}
return weight
}

func weightChunks(tuples []string, input []string) uint64 {
var weight uint64 = 0
for _, t := range tuples {
for _, i := range input {
if t == i {
weight += uint64(len(i))
}
}
}
return weight
}

func splitIntoChunks(input string, length int) []string {
var result []string
for i := 0; i < len(input); i += length {
if i+1 < len(input) {
result = append(result, input[i:i+2])
} else {
result = append(result, input[i:])
}
}
return result
}
28 changes: 28 additions & 0 deletions record/records.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package record

import "sort"

type Records []Record

func (r Records) Sort() {
sort.Slice(r, func(i, j int) bool {
left := r[i]
right := r[j]
if left.Weight == right.Weight {
return left.LastUpdate.After(right.LastUpdate)
}
return left.Weight > right.Weight
})
}

func (r Records) Search(input string) []Record {
var newRecs Records
for _, rec := range r {
rec.UpdateWeight(input)
if rec.Weight > 0 || input == "" {
newRecs = append(newRecs, rec)
}
}
newRecs.Sort()
return newRecs
}
41 changes: 30 additions & 11 deletions fuzzy/fuzzy_test.go → record/records_test.go
Original file line number Diff line number Diff line change
@@ -1,27 +1,46 @@
package fuzzy
package record

import (
"github.com/herzrasen/hist/record"
"github.com/stretchr/testify/assert"
"testing"
"time"
)

func TestSearch(t *testing.T) {
func TestRecords_Sort(t *testing.T) {
recs := Records{
{
Weight: 50,
}, {
Weight: 100,
}, {
Weight: 1000,
}, {
Weight: 1,
},
}
recs.Sort()
var weights []uint64
for _, r := range recs {
weights = append(weights, r.Weight)
}
assert.ElementsMatch(t, weights, []uint64{1000, 100, 50, 1})
}

func TestRecords_Search(t *testing.T) {
t.Run("all records should be removed", func(t *testing.T) {
records := []record.Record{{
records := Records{{
Id: 0,
Command: "some command OA",
}, {
Id: 1,
Command: "cw tail /kosmos/fargate/market-status-updater -b 5m -f",
}}
sorted := Search(records, "y")
sorted := records.Search("y")
assert.Empty(t, sorted)
})

t.Run("should prefer the newer record", func(t *testing.T) {
records := []record.Record{{
records := Records{{
Id: 0,
LastUpdate: time.Now().Add(-5 * time.Second),
Command: "some command OA",
Expand All @@ -30,12 +49,12 @@ func TestSearch(t *testing.T) {
LastUpdate: time.Now().Add(-1 * time.Minute),
Command: "cw tail /kosmos/fargate/market-status-updater -b 5m -f",
}}
weighted := Search(records, "s")
weighted := records.Search("s")
assert.True(t, weighted[0].Command == records[0].Command)
})

t.Run("should prefer the record with the highest count", func(t *testing.T) {
records := []record.Record{{
records := Records{{
Id: 0,
Count: 2,
Command: "command 1",
Expand All @@ -48,7 +67,7 @@ func TestSearch(t *testing.T) {
Count: 10,
Command: "command 3",
}}
weighted := Search(records, "command")
weighted := records.Search("command")
var ids []int64
for _, w := range weighted {
ids = append(ids, w.Id)
Expand All @@ -57,11 +76,11 @@ func TestSearch(t *testing.T) {
})

t.Run("should weight 0 if an letter does not occur", func(t *testing.T) {
records := []record.Record{{
records := Records{{
Id: 0,
Command: "git push",
}}
weighted := Search(records, "exp")
weighted := records.Search("exp")
assert.Empty(t, weighted)
})
}
27 changes: 12 additions & 15 deletions search/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"fmt"
"github.com/gdamore/tcell/v2"
"github.com/herzrasen/hist/client"
"github.com/herzrasen/hist/fuzzy"
"github.com/herzrasen/hist/record"
"github.com/rivo/tview"
)
Expand Down Expand Up @@ -78,33 +77,31 @@ func NewSearcher(listClient ListClient) *Searcher {
}

func (s *Searcher) Show(input string, verbose bool) error {
records, err := s.ListClient.List(client.ListOptions{
recs, err := s.ListClient.List(client.ListOptions{
Reverse: false,
})
if err != nil {
return fmt.Errorf("search:Searcher:Show: list: %w", err)
}

for _, rec := range records {
s.List.AddItem(rec.Command, "", 0, nil)
return fmt.Errorf("list: %w", err)
}
records := record.Records(recs)
s.Input.SetChangedFunc(func(text string) {
weightedRecords := fuzzy.Search(records, text)
updatedRecords := records.Search(text)
s.List.Clear()
for _, weightedRecord := range weightedRecords {
item := weightedRecord.Command
for _, updatedRecord := range updatedRecords {
item := updatedRecord.Command
if verbose {
item = fmt.Sprintf("(weight: %d, len: %d) %s", weightedRecord.Weight, len(weightedRecords), weightedRecord.Command)
item = fmt.Sprintf("(weight: %d, len: %d) %s",
updatedRecord.Weight,
len(updatedRecords),
updatedRecord.Command,
)
}
s.List.AddItem(item, "", 0, nil)
}
})
currentItem, _ := s.List.GetItemText(0)
s.List.SetCurrentItem(0)
s.List.SetItemText(0, "> "+currentItem, "")
s.Input.SetText(input)
if err := s.App.Run(); err != nil {
return fmt.Errorf("search:Searcher:Show: run: %w", err)
return fmt.Errorf("run: %w", err)
}
return nil
}

0 comments on commit d774a1a

Please sign in to comment.