From 2399a958334eeeabb00a1573f76f0f3052099667 Mon Sep 17 00:00:00 2001 From: Matthew Borders Date: Wed, 7 Nov 2018 23:28:17 -0600 Subject: [PATCH] Added search functionality to find verses based on a query string; using trie data structure --- bible.go | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++- bible_test.go | 7 ++++++ go.mod | 1 + go.sum | 2 ++ 4 files changed, 72 insertions(+), 1 deletion(-) diff --git a/bible.go b/bible.go index 295a9d1..17cb9d5 100644 --- a/bible.go +++ b/bible.go @@ -5,16 +5,33 @@ import ( "compress/gzip" "encoding/json" "fmt" + "github.com/derekparker/trie" "io" "os" "path" "runtime" + "strings" ) // Bible contains the old testament and new testament type Bible struct { OldTestament Testament NewTestament Testament + searchTree *trie.Trie +} + +type SearchNode struct { + Book *Book + Chapter *Chapter + Verse *Verse +} + +// String creates a string representation for the SearchNode, ex. Genesis 1:1 +func (s *SearchNode) String() string { + return fmt.Sprintf("%s %d:%d", + s.Book.Title, + s.Chapter.ChapterNumber, + s.Verse.VerseNumber) } const bibleTar = "bible.tar.gz" @@ -26,7 +43,9 @@ func NewBible() *Bible { _, currFile, _, _ := runtime.Caller(0) filename := fmt.Sprintf("%s/%s", path.Dir(currFile), bibleTar) - bible := &Bible{} + bible := &Bible{ + searchTree: trie.New(), + } f, _ := os.Open(filename) defer f.Close() @@ -36,6 +55,7 @@ func NewBible() *Bible { tr := tar.NewReader(gzf) + // Build testaments, books, chapters, verses for { h, err := tr.Next() if err == io.EOF { @@ -50,6 +70,28 @@ func NewBible() *Bible { } } + // Build search tree + var books []Book + books = append(books, bible.OldTestament.Books...) + books = append(books, bible.NewTestament.Books...) + + for i := range books { + bk := books[i] + + for j := range bk.Chapters { + ch := bk.Chapters[j] + + for k := range ch.Verses { + vr := strings.ToLower(ch.Verses[k].Text) + bible.searchTree.Add(vr, SearchNode{ + Book: &bk, + Chapter: &ch, + Verse: &ch.Verses[k], + }) + } + } + } + return bible } @@ -58,3 +100,22 @@ func decode(r io.Reader) []Book { json.NewDecoder(r).Decode(&books) return books } + +// Search finds top matching verses based on the given query. +// The number of search results are restricted by maxResults +func (b *Bible) Search(query string, maxResults int) []SearchNode { + t := b.searchTree + keys := t.FuzzySearch(strings.ToLower(query)) + var verses []SearchNode + + for k := range keys { + res, _ := t.Find(keys[k]) + verses = append(verses, res.Meta().(SearchNode)) + + if len(verses) >= maxResults { + break + } + } + + return verses +} diff --git a/bible_test.go b/bible_test.go index 931fe2c..3f180c9 100644 --- a/bible_test.go +++ b/bible_test.go @@ -81,3 +81,10 @@ func TestTestament_GetVerse_InvalidVerse(t *testing.T) { _, err = b.OldTestament.GetVerse(1, 1, 1000) assert.Equal(t, "invalid verse number", err.Error()) } + +func TestBible_Search(t *testing.T) { + v := b.Search("truth make you free", 10) + + assert.NotEmpty(t, v) + assert.Equal(t, v[0].String(), "John 8:32") +} diff --git a/go.mod b/go.mod index 3dd91dc..efde48b 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,7 @@ module github.com/borderstech/vulgata require ( github.com/davecgh/go-spew v1.1.1 // indirect + github.com/derekparker/trie v0.0.0-20180212171413-e608c2733dc7 github.com/pmezard/go-difflib v1.0.0 // indirect github.com/stretchr/testify v1.2.2 ) diff --git a/go.sum b/go.sum index e03ee77..efe0d6b 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/derekparker/trie v0.0.0-20180212171413-e608c2733dc7 h1:Cab9yoTQh1TxObKfis1DzZ6vFLK5kbeenMjRES/UE3o= +github.com/derekparker/trie v0.0.0-20180212171413-e608c2733dc7/go.mod h1:D6ICZm05D9VN1n/8iOtBxLpXtoGp6HDFUJ1RNVieOSE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=