Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify the route data structures. #386

Merged
merged 1 commit into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions LICENCE
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
The MIT License (MIT)
Copyright (c) 2013 Government Digital Service
MIT Licence

Copyright © 2013-2023 Crown Copyright (Government Digital Service)

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
Expand Down
4 changes: 3 additions & 1 deletion trie/LICENSE
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
Copyright (c) 2013 Government Digital Service
MIT Licence

Copyright © 2013, 2023 Crown Copyright (Government Digital Service)

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
Expand Down
6 changes: 3 additions & 3 deletions trie/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ rather than just strings.
This makes it suitable for efficiently storing information about hierarchical
systems in general, rather than being specifically geared towards string lookup.

Read the documentation on [godoc.org][docs] for details of how to use `trie`.
See also the [Godoc documentation for
`trie`](https://pkg.go.dev/github.com/alphagov/router/trie).

[docs]: http://godoc.org/github.com/alphagov/router/trie
[go]: http://golang.org
[go]: https://go.dev/
[trie]: https://en.wikipedia.org/wiki/Trie
141 changes: 63 additions & 78 deletions trie/trie.go
Original file line number Diff line number Diff line change
@@ -1,136 +1,121 @@
// Package trie implements a simple trie data structure that maps "paths" (which
// are slices of strings) to arbitrary data values (type interface{}).
// are slices of strings) to values of some type T.
package trie

type trieChildren map[string]*Trie
type trieChildren[T interface{}] map[string]*Trie[T]

type Trie struct {
Leaf bool
Entry interface{}
Children trieChildren
type Trie[T interface{}] struct {
leaf bool
entry T
children trieChildren[T]
}

// NewTrie makes a new empty Trie
func NewTrie() *Trie {
return &Trie{
Children: make(trieChildren),
}
// NewTrie makes a new, empty Trie.
func NewTrie[T interface{}]() *Trie[T] {
return &Trie[T]{children: make(trieChildren[T])}
}

// Get retrieves an element from the Trie
// Get retrieves an entry from the Trie. If there is no fully-matching entry,
// Get returns `(nil, false)`. `path` can be empty, to denote the root node.
//
// Takes a path (which can be empty, to denote the root element of the Trie),
// and returns the object if the path exists in the Trie, or nil and a status of
// false. Example:
// Example:
//
// if res, ok := trie.Get([]string{"foo", "bar"}); ok {
// fmt.Println("Value at /foo/bar was", res)
// }
func (t *Trie) Get(path []string) (entry interface{}, ok bool) {
// if res, ok := trie.Get([]string{"foo", "bar"}); ok {
// fmt.Println("Value at /foo/bar was", res)
// }
func (t *Trie[T]) Get(path []string) (entry T, ok bool) {
if len(path) == 0 {
return t.getentry()
return t.getEntry()
}

key := path[0]
newpath := path[1:]
key, newPath := path[0], path[1:]

res, ok := t.Children[key]
res, ok := t.children[key]
if !ok {
// Path doesn't exist: shortcut return value
return nil, false
return
}

return res.Get(newpath)
return res.Get(newPath)
}

// GetLongestPrefix retrieves an element from the Trie
// GetLongestPrefix retrieves the longest matching entry from the Trie.
//
// GetLongestPrefix returns a full match if there is one, or the entry with the
// longest matching prefix. If there is no match at all, GetLongestPrefix
// returns `(nil, false)`. `path` can be empty, to denote the root node.
//
// Takes a path (which can be empty, to denote the root element of the Trie).
// If a matching object exists, it is returned. Otherwise the object with the
// longest matching prefix is returned. If nothing matches at all, nil and a
// status of false is returned. Example:
// Example:
//
// if res, ok := trie.GetLongestPrefix([]string{"foo", "bar"}); ok {
// fmt.Println("Value at /foo/bar was", res)
// }
func (t *Trie) GetLongestPrefix(path []string) (entry interface{}, ok bool) {
// if res, ok := trie.GetLongestPrefix([]string{"foo", "bar"}); ok {
// fmt.Println("Value at /foo/bar was", res)
// }
func (t *Trie[T]) GetLongestPrefix(path []string) (entry T, ok bool) {
if len(path) == 0 {
return t.getentry()
return t.getEntry()
}

key := path[0]
newpath := path[1:]
key, newPath := path[0], path[1:]

res, ok := t.Children[key]
res, ok := t.children[key]
if !ok {
// Path doesn't exist: return this node as possible best match
return t.getentry()
return t.getEntry() // Full path not found, but this is the longest match.
}

entry, ok = res.GetLongestPrefix(newpath)
entry, ok = res.GetLongestPrefix(newPath)
if ok {
return entry, ok
}
// We haven't found a match yet, return this node
return t.getentry()
return t.getEntry() // No match yet, so return this node.
}

// Set creates an element in the Trie
//
// Takes a path (which can be empty, to denote the root element of the Trie),
// and an arbitrary value (interface{}) to use as the leaf data.
func (t *Trie) Set(path []string, value interface{}) {
// Set adds an entry to the Trie. `path` can be empty, to denote the root node.
func (t *Trie[T]) Set(path []string, value T) {
if len(path) == 0 {
t.setentry(value)
t.setEntry(value)
return
}

key := path[0]
newpath := path[1:]
key, newPath := path[0], path[1:]

res, ok := t.Children[key]
res, ok := t.children[key]
if !ok {
// Trie node that should hold entry doesn't already exist, so let's create it
res = NewTrie()
t.Children[key] = res
res = NewTrie[T]()
t.children[key] = res
}

res.Set(newpath, value)
res.Set(newPath, value)
}

// Del removes an element from the Trie. Returns a boolean indicating whether an
// element was actually deleted.
func (t *Trie) Del(path []string) bool {
// Del removes an entry from the Trie, returning true if it deleted an entry.
func (t *Trie[T]) Del(path []string) bool {
if len(path) == 0 {
return t.delentry()
return t.delEntry()
}

key := path[0]
newpath := path[1:]
key, newPath := path[0], path[1:]

res, ok := t.Children[key]
res, ok := t.children[key]
if !ok {
return false
}

return res.Del(newpath)
return res.Del(newPath)
}

func (t *Trie) setentry(value interface{}) {
t.Leaf = true
t.Entry = value
func (t *Trie[T]) setEntry(value T) {
t.leaf = true
t.entry = value
}

func (t *Trie) getentry() (entry interface{}, ok bool) {
if t.Leaf {
return t.Entry, true
func (t *Trie[T]) getEntry() (entry T, ok bool) {
if t.leaf {
return t.entry, true
}
return nil, false
return
}

func (t *Trie) delentry() (ok bool) {
ok = t.Leaf
t.Leaf = false
t.Entry = nil
func (t *Trie[T]) delEntry() (ok bool) {
ok = t.leaf
t.leaf = false
var zero T
t.entry = zero
return
}
Loading
Loading