├── set ├── README.md ├── basic_set_test.go ├── stable_set_test.go ├── sorted_set_test.go ├── format.go ├── hash_set_test.go ├── format_test.go ├── set.go └── benchmark_test.go ├── hash └── README.md ├── errors ├── README.md ├── errors.go ├── errors_test.go ├── group.go ├── example_test.go ├── format.go ├── group_test.go ├── format_test.go └── multi_error.go ├── generic ├── README.md ├── tree.go └── generic.go ├── grammar ├── README.md ├── grammar.go ├── grammar_test.go ├── cfg_follow_test.go ├── errors.go ├── cfg_first_test.go ├── errors_test.go ├── cfg_follow.go └── cfg_first.go ├── radixsort ├── README.md ├── helper_test.go ├── quick_test.go ├── radixsort.go ├── quick.go ├── lsd_test.go ├── msd_test.go ├── benchmark_test.go └── lsd.go ├── range ├── cont │ └── README.md └── disc │ ├── README.md │ └── disc.go ├── spatial ├── point_test.go ├── segment_tree.go ├── interval_tree.go ├── range_tree_test.go ├── segment_tree_test.go ├── interval_tree_test.go ├── range_tree.go ├── spatial.go └── point.go ├── .gitignore ├── sort ├── sort.go ├── shuffle.go ├── insertion.go ├── selection.go ├── helper_test.go ├── shell.go ├── shuffle_test.go ├── heap.go ├── heap_test.go ├── shell_test.go ├── insertion_test.go ├── selection_test.go ├── README.md ├── merge.go ├── benchmark_test.go ├── merge_test.go ├── quick.go └── quick_test.go ├── graph ├── images │ ├── directed.png │ ├── undirected.png │ ├── flow-network.png │ ├── weighted-directed.png │ └── weighted-undirected.png ├── README.md └── graph_test.go ├── renovate.json ├── internal └── parsertest │ ├── parsertest.go │ ├── mock.go │ └── mock_test.go ├── .golangci.yaml ├── .github ├── ISSUE_TEMPLATE │ ├── question.md │ ├── change_request.md │ ├── bug_report.md │ └── feature_request.md ├── CODEOWNERS ├── pull_request_template.md └── workflows │ └── go.yaml ├── dot ├── README.md ├── node.go ├── edge.go ├── util.go ├── node_test.go ├── record.go ├── graph.go ├── subgraph.go └── edge_test.go ├── list ├── README.md ├── list.go ├── list_test.go ├── stack.go ├── queue.go └── soft_queue.go ├── automata ├── README.md └── partition.go ├── Makefile ├── trie ├── README.md ├── bitpattern.go ├── trie.go ├── bitpattern_test.go └── benchmark_test.go ├── CONTRIBUTING.md ├── go.mod ├── LICENSE ├── symboltable ├── hash_table.go ├── README.md ├── symbol_table.go ├── chain_hash_table_test.go ├── double_hash_table_test.go └── linear_hash_table_test.go ├── lexer ├── example_test.go ├── input │ ├── utf8.go │ └── fixture │ │ └── lorem_ipsum └── lexer.go ├── parser ├── combinator │ ├── errors.go │ └── errors_test.go ├── lr │ ├── canonical │ │ ├── canonical.go │ │ ├── parsing_table_test.go │ │ ├── canonical_test.go │ │ └── parsing_table.go │ ├── simple │ │ ├── simple.go │ │ ├── parsing_table_test.go │ │ ├── simple_test.go │ │ └── parsing_table.go │ ├── lookahead │ │ ├── lookahead.go │ │ └── lookahead_test.go │ ├── action.go │ ├── grammar.go │ ├── state.go │ └── grammar_test.go ├── process.go ├── parser_test.go ├── parser.go └── predictive │ └── fixture_test.go ├── go.sum ├── heap ├── heap.go ├── binary_test.go └── indexed_binary_test.go ├── codecov.yaml ├── math └── math.go └── README.md /set/README.md: -------------------------------------------------------------------------------- 1 | # Set 2 | -------------------------------------------------------------------------------- /hash/README.md: -------------------------------------------------------------------------------- 1 | # Hash 2 | -------------------------------------------------------------------------------- /errors/README.md: -------------------------------------------------------------------------------- 1 | # Errors 2 | -------------------------------------------------------------------------------- /generic/README.md: -------------------------------------------------------------------------------- 1 | # Generic 2 | -------------------------------------------------------------------------------- /grammar/README.md: -------------------------------------------------------------------------------- 1 | # Grammar 2 | -------------------------------------------------------------------------------- /radixsort/README.md: -------------------------------------------------------------------------------- 1 | # Radix Sort 2 | -------------------------------------------------------------------------------- /range/cont/README.md: -------------------------------------------------------------------------------- 1 | # Continuous Range 2 | -------------------------------------------------------------------------------- /range/disc/README.md: -------------------------------------------------------------------------------- 1 | # Discrete Range 2 | -------------------------------------------------------------------------------- /spatial/point_test.go: -------------------------------------------------------------------------------- 1 | package spatial 2 | -------------------------------------------------------------------------------- /spatial/segment_tree.go: -------------------------------------------------------------------------------- 1 | package spatial 2 | -------------------------------------------------------------------------------- /spatial/interval_tree.go: -------------------------------------------------------------------------------- 1 | package spatial 2 | -------------------------------------------------------------------------------- /spatial/range_tree_test.go: -------------------------------------------------------------------------------- 1 | package spatial 2 | -------------------------------------------------------------------------------- /spatial/segment_tree_test.go: -------------------------------------------------------------------------------- 1 | package spatial 2 | -------------------------------------------------------------------------------- /spatial/interval_tree_test.go: -------------------------------------------------------------------------------- 1 | package spatial 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Test files 2 | *.out 3 | *.test 4 | 5 | # Misc files 6 | *.html 7 | -------------------------------------------------------------------------------- /sort/sort.go: -------------------------------------------------------------------------------- 1 | // Package sort implements common sorting algorithms. 2 | package sort 3 | -------------------------------------------------------------------------------- /graph/images/directed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moorara/algo/HEAD/graph/images/directed.png -------------------------------------------------------------------------------- /graph/images/undirected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moorara/algo/HEAD/graph/images/undirected.png -------------------------------------------------------------------------------- /graph/images/flow-network.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moorara/algo/HEAD/graph/images/flow-network.png -------------------------------------------------------------------------------- /graph/images/weighted-directed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moorara/algo/HEAD/graph/images/weighted-directed.png -------------------------------------------------------------------------------- /graph/images/weighted-undirected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moorara/algo/HEAD/graph/images/weighted-undirected.png -------------------------------------------------------------------------------- /errors/errors.go: -------------------------------------------------------------------------------- 1 | // Package errors provides custom error types and functionalities to enhance error handling. 2 | package errors 3 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ], 5 | "postUpdateOptions": [ 6 | "gomodTidy" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /spatial/range_tree.go: -------------------------------------------------------------------------------- 1 | package spatial 2 | 3 | // nolint: unused 4 | type rangeTreeNode struct{} 5 | 6 | type RangeTree struct{} 7 | -------------------------------------------------------------------------------- /internal/parsertest/parsertest.go: -------------------------------------------------------------------------------- 1 | // Package parsertest provides reusable test fixtures and utility functions used by other packages. 2 | package parsertest 3 | -------------------------------------------------------------------------------- /.golangci.yaml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | linters: 3 | settings: 4 | staticcheck: 5 | checks: 6 | - all 7 | - '-QF1008' # disable the rule QF1008 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Ask a question about this project 4 | title: "" 5 | labels: question 6 | assignees: '' 7 | --- 8 | 9 | ## Question 10 | -------------------------------------------------------------------------------- /dot/README.md: -------------------------------------------------------------------------------- 1 | # DOT 2 | 3 | This package is for creating [Graphviz](https://www.graphviz.org) graphs and 4 | generating the [DOT language](https://graphviz.gitlab.io/_pages/pdf/dotguide.pdf) code. 5 | -------------------------------------------------------------------------------- /list/README.md: -------------------------------------------------------------------------------- 1 | # List 2 | 3 | | Data Structure | Description | 4 | |----------------|---------------------------| 5 | | Queue | First-in first-out (FIFO) | 6 | | Stack | Last-in first-out (LIFO) | 7 | -------------------------------------------------------------------------------- /automata/README.md: -------------------------------------------------------------------------------- 1 | # Automata 2 | 3 | Finite automata are recognizers; they simply say yes or no for each possible input string. 4 | They come in two flavors: 5 | 6 | - Deterministic finite automata (DFA) 7 | - Non-deterministic finite automata (NFA) 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/change_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Change Request 3 | about: Suggest a change or an improvement for this project 4 | title: "" 5 | labels: enhancement 6 | assignees: '' 7 | --- 8 | 9 | ## Context 10 | 11 | ### Proposed Change 12 | 13 | ### Why Needed? 14 | -------------------------------------------------------------------------------- /radixsort/helper_test.go: -------------------------------------------------------------------------------- 1 | package radixsort 2 | 3 | import "golang.org/x/exp/constraints" 4 | 5 | func isSorted[T constraints.Ordered](a []T) bool { 6 | for i := 0; i < len(a)-1; i++ { 7 | if a[i] > a[i+1] { 8 | return false 9 | } 10 | } 11 | return true 12 | } 13 | -------------------------------------------------------------------------------- /sort/shuffle.go: -------------------------------------------------------------------------------- 1 | package sort 2 | 3 | import "math/rand" 4 | 5 | // Shuffle shuffles a slice in O(n) time. 6 | func Shuffle[T any](a []T, r *rand.Rand) { 7 | n := len(a) 8 | for i := 0; i < n; i++ { 9 | r := i + r.Intn(n-i) 10 | a[i], a[r] = a[r], a[i] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Report a bug for this project 4 | title: "" 5 | labels: bug 6 | assignees: '' 7 | --- 8 | 9 | ## Context 10 | 11 | ### How To Reproduce 12 | 13 | ### Expected Behavior 14 | 15 | ### Proposed Solution 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test 2 | test: 3 | go test -v -race ./... 4 | 5 | .PHONY: benchmark 6 | benchmark: 7 | go test -benchmem -bench=. ./... 8 | 9 | .PHONY: coverage 10 | coverage: 11 | go test -covermode=atomic -coverprofile=c.out ./... 12 | go tool cover -html=c.out -o cover.html 13 | -------------------------------------------------------------------------------- /spatial/spatial.go: -------------------------------------------------------------------------------- 1 | // Package spatial implements spatial data structures. 2 | // 3 | // Spatial data structures are specialized data structures used to efficiently store, organize, and query 4 | // data that represents objects in space, such as points, lines, polygons, or volumes. 5 | package spatial 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an idea for this project 4 | title: "" 5 | labels: feature, needs validation 6 | assignees: '' 7 | --- 8 | 9 | ## Context 10 | 11 | ### Use Case 12 | 13 | ### Proposed Solution 14 | 15 | ### Alternative Solutions 16 | -------------------------------------------------------------------------------- /trie/README.md: -------------------------------------------------------------------------------- 1 | # Trie 2 | 3 | This package provides implementations for **prefix tree** a.k.a. **trie** data structures. 4 | 5 | | Symbol Table | Description | 6 | | -------------|-------------| 7 | | Binary Trie | Binary implementation of Trie tree. | 8 | | Patricia | A space-optimized implementation of Trie tree. | 9 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to algo 2 | 3 | Contributions are through [**issues**](https://github.com/moorara/algo/issues) 4 | and [**pull requests**](https://github.com/moorara/algo/pulls). 5 | 6 | ## Versioning 7 | 8 | **algo** uses [semantic versioning](https://semver.org). 9 | All features and fixes are merged to `main` branch. 10 | -------------------------------------------------------------------------------- /list/list.go: -------------------------------------------------------------------------------- 1 | // Package list implements list data structures. 2 | package list 3 | 4 | type arrayNode[T any] struct { 5 | block []T 6 | next *arrayNode[T] 7 | } 8 | 9 | func newArrayNode[T any](size int, next *arrayNode[T]) *arrayNode[T] { 10 | return &arrayNode[T]{ 11 | block: make([]T, size), 12 | next: next, 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /set/basic_set_test.go: -------------------------------------------------------------------------------- 1 | package set 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/moorara/algo/generic" 7 | ) 8 | 9 | func TestSet(t *testing.T) { 10 | tests := getSetTests() 11 | 12 | for _, tc := range tests { 13 | eqInt := generic.NewEqualFunc[int]() 14 | set := New(eqInt) 15 | 16 | runSetTest(t, set, tc) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Lines starting with # are comments. 2 | # Each line is a file pattern followed by one or more owners 3 | # See https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners 4 | 5 | # These owners will be the default owners for everything in the repo 6 | * @moorara 7 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/moorara/algo 2 | 3 | go 1.25.0 4 | 5 | require ( 6 | github.com/stretchr/testify v1.11.1 7 | golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 8 | ) 9 | 10 | require ( 11 | github.com/davecgh/go-spew v1.1.1 // indirect 12 | github.com/pmezard/go-difflib v1.0.0 // indirect 13 | gopkg.in/yaml.v3 v3.0.1 // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /set/stable_set_test.go: -------------------------------------------------------------------------------- 1 | package set 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/moorara/algo/generic" 7 | ) 8 | 9 | func TestStableSet(t *testing.T) { 10 | tests := getSetTests() 11 | 12 | for _, tc := range tests { 13 | eqInt := generic.NewEqualFunc[int]() 14 | set := NewStableSet(eqInt) 15 | 16 | runSetTest(t, set, tc) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /set/sorted_set_test.go: -------------------------------------------------------------------------------- 1 | package set 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/moorara/algo/generic" 7 | ) 8 | 9 | func TestSortedSet(t *testing.T) { 10 | tests := getSetTests() 11 | 12 | for _, tc := range tests { 13 | cmpInt := generic.NewCompareFunc[int]() 14 | set := NewSortedSet(cmpInt) 15 | 16 | runSetTest(t, set, tc) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /sort/insertion.go: -------------------------------------------------------------------------------- 1 | package sort 2 | 3 | import "github.com/moorara/algo/generic" 4 | 5 | // Insertion implements the insertion sort algorithm. 6 | func Insertion[T any](a []T, cmp generic.CompareFunc[T]) { 7 | n := len(a) 8 | for i := 0; i < n; i++ { 9 | for j := i; j > 0 && cmp(a[j], a[j-1]) < 0; j-- { 10 | a[j], a[j-1] = a[j-1], a[j] 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /errors/errors_test.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | type fooError struct{} 4 | 5 | func (e *fooError) Error() string { 6 | return "error on foo" 7 | } 8 | 9 | type barError struct{} 10 | 11 | func (e *barError) Error() string { 12 | return "error on bar" 13 | } 14 | 15 | type bazError struct{} 16 | 17 | func (e *bazError) Error() string { 18 | return "error on baz\nsomething failed" 19 | } 20 | -------------------------------------------------------------------------------- /sort/selection.go: -------------------------------------------------------------------------------- 1 | package sort 2 | 3 | import "github.com/moorara/algo/generic" 4 | 5 | // Selection implements the selection sort algorithm. 6 | func Selection[T any](a []T, cmp generic.CompareFunc[T]) { 7 | n := len(a) 8 | for i := 0; i < n; i++ { 9 | min := i 10 | for j := i + 1; j < n; j++ { 11 | if cmp(a[j], a[min]) < 0 { 12 | min = j 13 | } 14 | } 15 | a[i], a[min] = a[min], a[i] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /set/format.go: -------------------------------------------------------------------------------- 1 | package set 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // FormatFunc is a function type for formatting a set into a single string representation. 9 | type FormatFunc[T any] func([]T) string 10 | 11 | func defaultFormat[T any](members []T) string { 12 | vals := make([]string, len(members)) 13 | for i, m := range members { 14 | vals[i] = fmt.Sprintf("%v", m) 15 | } 16 | 17 | return fmt.Sprintf("{%s}", strings.Join(vals, ", ")) 18 | } 19 | -------------------------------------------------------------------------------- /grammar/grammar.go: -------------------------------------------------------------------------------- 1 | // Package grammar implements data structures and algorithms for formal grammars. 2 | // 3 | // In the theory of languages, a formal grammar is a set of rules or productions 4 | // that define the structure of strings within a formal language. 5 | // A formal grammar defines which strings in a formal language's alphabet are syntactically valid. 6 | // It specifies the structure of the strings but does not address their meaning or usage in a given context. 7 | package grammar 8 | -------------------------------------------------------------------------------- /sort/helper_test.go: -------------------------------------------------------------------------------- 1 | package sort 2 | 3 | import ( 4 | "math/rand" 5 | 6 | "github.com/moorara/algo/generic" 7 | ) 8 | 9 | func isSorted[T any](items []T, cmp generic.CompareFunc[T]) bool { 10 | for i := 0; i < len(items)-1; i++ { 11 | if cmp(items[i], items[i+1]) > 0 { 12 | return false 13 | } 14 | } 15 | 16 | return true 17 | } 18 | 19 | func randIntSlice(size int) []int { 20 | a := make([]int, size) 21 | for i := range a { 22 | a[i] = rand.Int() 23 | } 24 | 25 | return a 26 | } 27 | -------------------------------------------------------------------------------- /grammar/grammar_test.go: -------------------------------------------------------------------------------- 1 | package grammar 2 | 3 | // MockWriter is an implementation of io.Writer for testing purposes. 4 | type MockWriter struct { 5 | WriteIndex int 6 | WriteMocks []WriteMock 7 | } 8 | 9 | type WriteMock struct { 10 | InBytes []byte 11 | OutN int 12 | OutError error 13 | } 14 | 15 | func (m *MockWriter) Write(b []byte) (int, error) { 16 | i := m.WriteIndex 17 | m.WriteIndex++ 18 | m.WriteMocks[i].InBytes = b 19 | return m.WriteMocks[i].OutN, m.WriteMocks[i].OutError 20 | } 21 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | ## Description 7 | 8 | ### Checklist 9 | 10 | - [ ] PR title is clear and describes the change 11 | - [ ] Commit messages are self-explanatory and summarize the change 12 | - [ ] Tests are provided for the new change 13 | -------------------------------------------------------------------------------- /sort/shell.go: -------------------------------------------------------------------------------- 1 | package sort 2 | 3 | import "github.com/moorara/algo/generic" 4 | 5 | // Shell implements the shell sort algorithm. 6 | func Shell[T any](a []T, cmp generic.CompareFunc[T]) { 7 | n := len(a) 8 | h := 1 9 | for h < n/3 { 10 | h = 3*h + 1 // 1, 4, 13, 40, 121, 364, ... 11 | } 12 | 13 | for ; h >= 1; h /= 3 { 14 | for i := h; i < n; i++ { // h-sort the array 15 | for j := i; j >= h && cmp(a[j], a[j-h]) < 0; j -= h { 16 | a[j], a[j-h] = a[j-h], a[j] 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /spatial/point.go: -------------------------------------------------------------------------------- 1 | package spatial 2 | 3 | // Point1D represents a point in one-dimensional space. 4 | type Point1D[T any] struct { 5 | X T 6 | } 7 | 8 | // Point2D represents a point in two-dimensional space. 9 | type Point2D[T any] struct { 10 | X, Y T 11 | } 12 | 13 | // Point3D represents a point in three-dimensional space. 14 | type Point3D[T any] struct { 15 | X, Y, Z T 16 | } 17 | 18 | // PointND represents a point in n-dimensional space. 19 | type PointND[T any] struct { 20 | Coordinates []T 21 | } 22 | -------------------------------------------------------------------------------- /graph/README.md: -------------------------------------------------------------------------------- 1 | # Graph 2 | 3 | ## Test Cases 4 | 5 | The example graphs used in test files are shown below. 6 | 7 | ### Undirected 8 | 9 | ![undirected graph](./images/undirected.png) 10 | 11 | ### Directed 12 | 13 | ![directed graph](./images/directed.png) 14 | 15 | ### Weighted Undirected 16 | 17 | ![weighted undirected graph](./images/weighted-undirected.png) 18 | 19 | ### Weighted Directed 20 | 21 | ![weighted directed graph](./images/weighted-directed.png) 22 | 23 | ### Flow Network 24 | 25 | ![flow network graph](./images/flow-network.png) 26 | -------------------------------------------------------------------------------- /internal/parsertest/mock.go: -------------------------------------------------------------------------------- 1 | package parsertest 2 | 3 | import ( 4 | "github.com/moorara/algo/lexer" 5 | ) 6 | 7 | // MockLexer is an implementation of lexer.Lexer for testing purposes. 8 | type MockLexer struct { 9 | NextTokenIndex int 10 | NextTokenMocks []NextTokenMock 11 | } 12 | 13 | type NextTokenMock struct { 14 | OutToken lexer.Token 15 | OutError error 16 | } 17 | 18 | func (m *MockLexer) NextToken() (lexer.Token, error) { 19 | i := m.NextTokenIndex 20 | m.NextTokenIndex++ 21 | return m.NextTokenMocks[i].OutToken, m.NextTokenMocks[i].OutError 22 | } 23 | -------------------------------------------------------------------------------- /list/list_test.go: -------------------------------------------------------------------------------- 1 | package list 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | type containsTest[T any] struct { 10 | val T 11 | expected bool 12 | } 13 | 14 | func TestNewArrayNode(t *testing.T) { 15 | tests := []struct { 16 | size int 17 | next *arrayNode[string] 18 | }{ 19 | {64, nil}, 20 | {256, nil}, 21 | {1024, &arrayNode[string]{}}, 22 | {4096, &arrayNode[string]{}}, 23 | } 24 | 25 | for _, tc := range tests { 26 | n := newArrayNode(tc.size, tc.next) 27 | 28 | assert.Equal(t, tc.next, n.next) 29 | assert.Equal(t, tc.size, len(n.block)) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /trie/bitpattern.go: -------------------------------------------------------------------------------- 1 | package trie 2 | 3 | // bitPattern is an extension of bitstring for pattern matching. 4 | type bitPattern struct { 5 | *bitString 6 | } 7 | 8 | func newBitPattern(pattern string) *bitPattern { 9 | return &bitPattern{ 10 | bitString: newBitString(pattern), 11 | } 12 | } 13 | 14 | // Bit returns a given bit by its position (position starts from one). 15 | func (b *bitPattern) Bit(pos int) byte { 16 | if pos > b.len { 17 | return '0' 18 | } 19 | 20 | i := pos - 1 21 | var mask byte = 0x80 >> (i % 8) 22 | 23 | if b.bits[i/8] == '*' { 24 | return '*' 25 | } 26 | 27 | if b.bits[i/8]&mask == 0 { 28 | return '0' 29 | } else { 30 | return '1' 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.github/workflows/go.yaml: -------------------------------------------------------------------------------- 1 | name: Go 2 | on: [push] 3 | jobs: 4 | lint: 5 | name: Lint 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v6 9 | - name: Lint 10 | uses: gardenbed/actions/go-lint@main 11 | test: 12 | name: Test 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v6 16 | - name: Test 17 | id: test 18 | uses: gardenbed/actions/go-cover@main 19 | with: 20 | codecov_token: ${{ secrets.CODECOV_TOKEN }} 21 | - name: Upload Test Report 22 | uses: actions/upload-artifact@v5 23 | with: 24 | name: coverage-report 25 | path: ${{ steps.test.outputs.coverage_report_file }} 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Milad Irannejad 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, 4 | provided that the above copyright notice and this permission notice appear in all copies. 5 | 6 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 7 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. 8 | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 9 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 10 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 11 | -------------------------------------------------------------------------------- /radixsort/quick_test.go: -------------------------------------------------------------------------------- 1 | package radixsort 2 | 3 | import "testing" 4 | 5 | func TestQuick3WayString(t *testing.T) { 6 | tests := []struct { 7 | a []string 8 | w int 9 | }{ 10 | { 11 | a: []string{"Milad", "Mona", "Milad", "Mona"}, 12 | }, 13 | { 14 | a: []string{ 15 | "Docker", "Kubernetes", "Prometheus", "Docker", 16 | "Terraform", "Vault", "Consul", 17 | "Linkerd", "Istio", 18 | "Kafka", "NATS", 19 | "CockroachDB", "ArangoDB", 20 | "Go", "JavaScript", "TypeScript", "Go", 21 | "gRPC", "GraphQL", "gRPC", 22 | "React", "Redux", "Vue", "React", "Redux", 23 | }, 24 | }, 25 | } 26 | 27 | for _, tc := range tests { 28 | Quick3WayString(tc.a) 29 | 30 | if !isSorted[string](tc.a) { 31 | t.Fatalf("%v is not sorted.", tc.a) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /symboltable/hash_table.go: -------------------------------------------------------------------------------- 1 | package symboltable 2 | 3 | // HashOpts represents configuration options for a hash table. 4 | type HashOpts struct { 5 | // The initial capacity of the hash table. 6 | // For chain and linear hash tables, it must be a power of 2 for efficient hashing. 7 | // For quadratic and double hash tables, it must be a prime number for better distribution. 8 | InitialCap int 9 | // The minimum load factor before resizing (shrinking) the hash table. 10 | MinLoadFactor float32 11 | // The maximum load factor before resizing (expanding) the hash table. 12 | MaxLoadFactor float32 13 | } 14 | 15 | // hashTableEntry represents an entry in a non-linear probing hash table that requires soft deletion. 16 | type hashTableEntry[K, V any] struct { 17 | key K 18 | val V 19 | deleted bool 20 | } 21 | -------------------------------------------------------------------------------- /sort/shuffle_test.go: -------------------------------------------------------------------------------- 1 | package sort 2 | 3 | import ( 4 | "math/rand" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestShuffle_int(t *testing.T) { 10 | tests := []struct { 11 | items []int 12 | }{ 13 | {[]int{10, 20, 30, 40, 50, 60, 70, 80, 90}}, 14 | } 15 | 16 | seed := time.Now().UTC().UnixNano() 17 | r := rand.New(rand.NewSource(seed)) 18 | 19 | for _, tc := range tests { 20 | Shuffle[int](tc.items, r) 21 | } 22 | } 23 | 24 | func TestShuffle_string(t *testing.T) { 25 | tests := []struct { 26 | items []string 27 | }{ 28 | {[]string{"Alice", "Bob", "Dan", "Edgar", "Helen", "Karen", "Milad", "Peter", "Sam", "Wesley"}}, 29 | } 30 | 31 | seed := time.Now().UTC().UnixNano() 32 | r := rand.New(rand.NewSource(seed)) 33 | 34 | for _, tc := range tests { 35 | Shuffle[string](tc.items, r) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /set/hash_set_test.go: -------------------------------------------------------------------------------- 1 | package set 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | 8 | "github.com/moorara/algo/generic" 9 | "github.com/moorara/algo/hash" 10 | ) 11 | 12 | func TestHashSet(t *testing.T) { 13 | tests := getSetTests() 14 | 15 | for _, tc := range tests { 16 | hashInt := hash.HashFuncForInt[int](nil) 17 | eqInt := generic.NewEqualFunc[int]() 18 | set := NewHashSet(hashInt, eqInt, HashSetOpts{}) 19 | 20 | runSetTest(t, set, tc) 21 | } 22 | } 23 | 24 | func TestNewHashSet_Panic(t *testing.T) { 25 | hashInt := hash.HashFuncForInt[int](nil) 26 | eqInt := generic.NewEqualFunc[int]() 27 | 28 | assert.PanicsWithValue(t, "The hash set capacity must be a prime number", func() { 29 | NewHashSet(hashInt, eqInt, HashSetOpts{ 30 | InitialCap: 69, 31 | }) 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /lexer/example_test.go: -------------------------------------------------------------------------------- 1 | package lexer_test 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "text/scanner" 7 | 8 | "github.com/moorara/algo/grammar" 9 | "github.com/moorara/algo/lexer" 10 | ) 11 | 12 | func ExampleLexer() { 13 | src := strings.NewReader(`Lorem ipsum dolor sit amet, consectetur adipiscing elit, 14 | sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.`) 15 | 16 | var s scanner.Scanner 17 | s.Init(src) 18 | 19 | for tok := s.Scan(); tok != scanner.EOF; tok = s.Scan() { 20 | token := lexer.Token{ 21 | Terminal: grammar.Terminal("WORD"), 22 | Lexeme: s.TokenText(), 23 | Pos: lexer.Position{ 24 | Filename: "lorem_ipsum", 25 | Offset: s.Position.Offset, 26 | Line: s.Position.Line, 27 | Column: s.Position.Column, 28 | }, 29 | } 30 | 31 | fmt.Println(token) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /radixsort/radixsort.go: -------------------------------------------------------------------------------- 1 | // Package radixsort implements common radix sorting algorithms. 2 | // Radix sorts are key-indexed counting for sorting keys with integer digits between 0 and R-1 (R is a small number). 3 | // Radix sorting algorithms are efficient for a large number of keys with small constant width. 4 | package radixsort 5 | 6 | import ( 7 | "math/rand" 8 | 9 | "golang.org/x/exp/constraints" 10 | ) 11 | 12 | func charAt(s string, d int) int { 13 | if d < len(s) { 14 | return int(s[d]) 15 | } 16 | return -1 17 | } 18 | 19 | func insertion[T constraints.Ordered](a []T, lo, hi int) { 20 | for i := lo; i <= hi; i++ { 21 | for j := i; j > lo && a[j] < a[j-1]; j-- { 22 | a[j], a[j-1] = a[j-1], a[j] 23 | } 24 | } 25 | } 26 | 27 | func shuffle[T any](a []T) { 28 | n := len(a) 29 | for i := 0; i < n; i++ { 30 | r := i + rand.Intn(n-i) 31 | a[i], a[r] = a[r], a[i] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /trie/trie.go: -------------------------------------------------------------------------------- 1 | // Package trie implements prefix tree data structures. 2 | package trie 3 | 4 | import ( 5 | "fmt" 6 | 7 | "github.com/moorara/algo/generic" 8 | ) 9 | 10 | // Trie represents a trie (prefix tree) abstract data type. 11 | type Trie[V any] interface { 12 | fmt.Stringer 13 | generic.Equaler[Trie[V]] 14 | generic.Collection2[string, V] 15 | generic.Tree2[string, V] 16 | 17 | verify() bool 18 | 19 | Height() int 20 | Min() (string, V, bool) 21 | Max() (string, V, bool) 22 | Floor(string) (string, V, bool) 23 | Ceiling(string) (string, V, bool) 24 | DeleteMin() (string, V, bool) 25 | DeleteMax() (string, V, bool) 26 | Select(int) (string, V, bool) 27 | Rank(string) int 28 | Range(string, string) []generic.KeyValue[string, V] 29 | RangeSize(string, string) int 30 | Match(string) []generic.KeyValue[string, V] 31 | WithPrefix(string) []generic.KeyValue[string, V] 32 | LongestPrefixOf(string) (string, V, bool) 33 | } 34 | -------------------------------------------------------------------------------- /parser/combinator/errors.go: -------------------------------------------------------------------------------- 1 | package combinator 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | // errEOF is returned when the end of input is reached unexpectedly. 9 | var errEOF = errors.New("end of input") 10 | 11 | // syntaxError represents a syntactic error encountered during parsing. 12 | type syntaxError struct { 13 | Pos int 14 | Rune rune 15 | } 16 | 17 | // Error implements the error interface. 18 | func (e *syntaxError) Error() string { 19 | return fmt.Sprintf("%d: unexpected rune %q", e.Pos, e.Rune) 20 | } 21 | 22 | // semanticError represents a semantic error encountered during parsing. 23 | type semanticError struct { 24 | Pos int 25 | Err error 26 | } 27 | 28 | // Error implements the error interface. 29 | func (e *semanticError) Error() string { 30 | return fmt.Sprintf("%d: %s", e.Pos, e.Err) 31 | } 32 | 33 | // Unwrap implements the unwrapper interface. 34 | func (e *semanticError) Unwrap() error { 35 | return e.Err 36 | } 37 | -------------------------------------------------------------------------------- /set/format_test.go: -------------------------------------------------------------------------------- 1 | package set 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestDefaultFormat(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | members []int 13 | expectedString string 14 | }{ 15 | { 16 | name: "Nil", 17 | members: nil, 18 | expectedString: "{}", 19 | }, 20 | { 21 | name: "Zero", 22 | members: []int{}, 23 | expectedString: "{}", 24 | }, 25 | { 26 | name: "One", 27 | members: []int{2}, 28 | expectedString: "{2}", 29 | }, 30 | { 31 | name: "Multiple", 32 | members: []int{2, 4, 8, 16, 32, 64}, 33 | expectedString: "{2, 4, 8, 16, 32, 64}", 34 | }, 35 | } 36 | 37 | for _, tc := range tests { 38 | t.Run(tc.name, func(t *testing.T) { 39 | s := defaultFormat(tc.members) 40 | 41 | assert.Equal(t, tc.expectedString, s) 42 | }) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /sort/heap.go: -------------------------------------------------------------------------------- 1 | package sort 2 | 3 | import "github.com/moorara/algo/generic" 4 | 5 | func sink[T any](a []T, k, n int, cmp generic.CompareFunc[T]) { 6 | for 2*k <= n { 7 | j := 2 * k 8 | if j < n && cmp(a[j], a[j+1]) < 0 { 9 | j++ 10 | } 11 | if cmp(a[k], a[j]) >= 0 { 12 | break 13 | } 14 | a[k], a[j] = a[j], a[k] 15 | k = j 16 | } 17 | } 18 | 19 | func heap[T any](a []T, cmp generic.CompareFunc[T]) { 20 | n := len(a) - 1 21 | 22 | // build max-heap bottom-up 23 | for k := n / 2; k >= 1; k-- { 24 | sink[T](a, k, n, cmp) 25 | } 26 | 27 | // remove the maximum, one at a time 28 | for n > 1 { 29 | a[1], a[n] = a[n], a[1] 30 | n-- 31 | sink[T](a, 1, n, cmp) 32 | } 33 | } 34 | 35 | // Heap implements the heap sort algorithm. 36 | func Heap[T any](a []T, cmp generic.CompareFunc[T]) { 37 | // Heap elements need to start from position 1 38 | var zero T 39 | aux := append([]T{zero}, a...) 40 | heap[T](aux, cmp) 41 | copy(a, aux[1:]) 42 | } 43 | -------------------------------------------------------------------------------- /symboltable/README.md: -------------------------------------------------------------------------------- 1 | # Symbol Table 2 | 3 | This package provides implementations for **symbol table** data structures. 4 | 5 | | Symbol Table | Ordered | Self-Balancing | Description | 6 | | -------------------|:-------:|:--------------:|-------------------------------------------| 7 | | ChainHashTable | No | N/A | Separate Chaining Hash Table | 8 | | LinearHashTable | No | N/A | Linear Probing Hash Table | 9 | | QuadraticHashTable | No | N/A | Quadratic Probing Hash Table | 10 | | DoubleHashTable | No | N/A | Double Hashing Hash Table | 11 | | BST | Yes | No | Binary Search Tree | 12 | | AVL | Yes | Yes | AVL Binary Search Tree | 13 | | RedBlack | Yes | Yes | Left-Leaning Red-Black Binary Search Tree | 14 | -------------------------------------------------------------------------------- /graph/graph_test.go: -------------------------------------------------------------------------------- 1 | package graph 2 | 3 | type testVisitors struct { 4 | *Visitors 5 | preOrderVertices []int 6 | postOrderVertices []int 7 | preOrderEdges [][2]int 8 | preOrderWeights []float64 9 | } 10 | 11 | func newTestVisitors() *testVisitors { 12 | tv := &testVisitors{ 13 | preOrderVertices: make([]int, 0), 14 | postOrderVertices: make([]int, 0), 15 | preOrderEdges: make([][2]int, 0), 16 | preOrderWeights: make([]float64, 0), 17 | } 18 | 19 | tv.Visitors = &Visitors{ 20 | VertexPreOrder: func(v int) bool { 21 | tv.preOrderVertices = append(tv.preOrderVertices, v) 22 | return true 23 | }, 24 | VertexPostOrder: func(v int) bool { 25 | tv.postOrderVertices = append(tv.postOrderVertices, v) 26 | return true 27 | }, 28 | EdgePreOrder: func(v, w int, weight float64) bool { 29 | tv.preOrderEdges = append(tv.preOrderEdges, [2]int{v, w}) 30 | tv.preOrderWeights = append(tv.preOrderWeights, weight) 31 | return true 32 | }, 33 | } 34 | 35 | return tv 36 | } 37 | -------------------------------------------------------------------------------- /radixsort/quick.go: -------------------------------------------------------------------------------- 1 | package radixsort 2 | 3 | // Quick3WayString is the 3-Way Radix Quick sorting algorithm for string keys with variable length. 4 | func Quick3WayString(a []string) { 5 | shuffle[string](a) 6 | quick3WayString(a, 0, len(a)-1, 0) 7 | } 8 | 9 | func quick3WayString(a []string, lo, hi, d int) { 10 | const CUTOFF = 15 11 | 12 | // cutoff to insertion sort for small subarrays 13 | if hi <= lo+CUTOFF { 14 | insertion[string](a, lo, hi) 15 | return 16 | } 17 | 18 | // 3-way partitioning on dth char 19 | v := charAt(a[lo], d) 20 | lt, i, gt := lo, lo+1, hi 21 | for i <= gt { 22 | c := charAt(a[i], d) 23 | switch { 24 | case c < v: 25 | a[lt], a[i] = a[i], a[lt] 26 | lt++ 27 | i++ 28 | case c > v: 29 | a[i], a[gt] = a[gt], a[i] 30 | gt-- 31 | default: 32 | i++ 33 | } 34 | } 35 | 36 | // a[lo..lt-1] < v = a[lt..gt] < a[gt+1..hi] 37 | quick3WayString(a, lo, lt-1, d) 38 | if v >= 0 { 39 | quick3WayString(a, lt, gt, d+1) 40 | } 41 | quick3WayString(a, gt+1, hi, d) 42 | } 43 | -------------------------------------------------------------------------------- /errors/group.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import "sync" 4 | 5 | // Group is used for aggregating multiple errors from multiple goroutines. 6 | // It provides thread-safe methods to run functions concurrently and collect any errors they return. 7 | type Group struct { 8 | wg sync.WaitGroup 9 | mu sync.Mutex 10 | err *MultiError 11 | } 12 | 13 | // Go starts a new goroutine to execute the given function f. 14 | // If f returns an error, it will be safely added to the aggregated errors. 15 | func (e *Group) Go(f func() error) { 16 | e.wg.Add(1) 17 | 18 | go func() { 19 | defer e.wg.Done() 20 | 21 | if err := f(); err != nil { 22 | e.mu.Lock() 23 | e.err = Append(e.err, err) 24 | e.mu.Unlock() 25 | } 26 | }() 27 | } 28 | 29 | // Wait blocks until all goroutines started with the Go method have finished. 30 | // It returns the aggregated errors from all goroutines. 31 | // If no errors were returned, it returns nil. 32 | func (e *Group) Wait() *MultiError { 33 | e.wg.Wait() 34 | e.mu.Lock() 35 | defer e.mu.Unlock() 36 | 37 | return e.err 38 | } 39 | -------------------------------------------------------------------------------- /sort/heap_test.go: -------------------------------------------------------------------------------- 1 | package sort 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/moorara/algo/generic" 7 | ) 8 | 9 | func TestHeap_int(t *testing.T) { 10 | tests := []struct { 11 | items []int 12 | }{ 13 | {[]int{}}, 14 | {[]int{20, 10, 30}}, 15 | {[]int{30, 20, 10, 40, 50}}, 16 | {[]int{90, 80, 70, 60, 50, 40, 30, 20, 10}}, 17 | } 18 | 19 | for _, tc := range tests { 20 | cmp := generic.NewCompareFunc[int]() 21 | Heap[int](tc.items, cmp) 22 | 23 | if !isSorted(tc.items, cmp) { 24 | t.Fatalf("%v is not sorted.", tc.items) 25 | } 26 | } 27 | } 28 | 29 | func TestHeap_string(t *testing.T) { 30 | tests := []struct { 31 | items []string 32 | }{ 33 | {[]string{}}, 34 | {[]string{"Milad", "Mona"}}, 35 | {[]string{"Alice", "Bob", "Alex", "Jackie"}}, 36 | {[]string{"Docker", "Kubernetes", "Go", "JavaScript", "Elixir", "React", "Redux", "Vue"}}, 37 | } 38 | 39 | for _, tc := range tests { 40 | cmp := generic.NewCompareFunc[string]() 41 | Heap[string](tc.items, cmp) 42 | 43 | if !isSorted(tc.items, cmp) { 44 | t.Fatalf("%v is not sorted.", tc.items) 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 6 | github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 7 | golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 h1:zfMcR1Cs4KNuomFFgGefv5N0czO2XZpUbxGUy8i8ug0= 8 | golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 10 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 11 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 12 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 13 | -------------------------------------------------------------------------------- /sort/shell_test.go: -------------------------------------------------------------------------------- 1 | package sort 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/moorara/algo/generic" 7 | ) 8 | 9 | func TestShell_int(t *testing.T) { 10 | tests := []struct { 11 | items []int 12 | }{ 13 | {[]int{}}, 14 | {[]int{20, 10, 30}}, 15 | {[]int{30, 20, 10, 40, 50}}, 16 | {[]int{90, 80, 70, 60, 50, 40, 30, 20, 10}}, 17 | } 18 | 19 | for _, tc := range tests { 20 | cmp := generic.NewCompareFunc[int]() 21 | Shell[int](tc.items, cmp) 22 | 23 | if !isSorted(tc.items, cmp) { 24 | t.Fatalf("%v is not sorted.", tc.items) 25 | } 26 | } 27 | } 28 | 29 | func TestShell_string(t *testing.T) { 30 | tests := []struct { 31 | items []string 32 | }{ 33 | {[]string{}}, 34 | {[]string{"Milad", "Mona"}}, 35 | {[]string{"Alice", "Bob", "Alex", "Jackie"}}, 36 | {[]string{"Docker", "Kubernetes", "Go", "JavaScript", "Elixir", "React", "Redux", "Vue"}}, 37 | } 38 | 39 | for _, tc := range tests { 40 | cmp := generic.NewCompareFunc[string]() 41 | Shell[string](tc.items, cmp) 42 | 43 | if !isSorted(tc.items, cmp) { 44 | t.Fatalf("%v is not sorted.", tc.items) 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /sort/insertion_test.go: -------------------------------------------------------------------------------- 1 | package sort 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/moorara/algo/generic" 7 | ) 8 | 9 | func TestInsertion_int(t *testing.T) { 10 | tests := []struct { 11 | items []int 12 | }{ 13 | {[]int{}}, 14 | {[]int{20, 10, 30}}, 15 | {[]int{30, 20, 10, 40, 50}}, 16 | {[]int{90, 80, 70, 60, 50, 40, 30, 20, 10}}, 17 | } 18 | 19 | for _, tc := range tests { 20 | cmp := generic.NewCompareFunc[int]() 21 | Insertion(tc.items, cmp) 22 | 23 | if !isSorted(tc.items, cmp) { 24 | t.Fatalf("%v is not sorted.", tc.items) 25 | } 26 | } 27 | } 28 | 29 | func TestInsertion_string(t *testing.T) { 30 | tests := []struct { 31 | items []string 32 | }{ 33 | {[]string{}}, 34 | {[]string{"Milad", "Mona"}}, 35 | {[]string{"Alice", "Bob", "Alex", "Jackie"}}, 36 | {[]string{"Docker", "Kubernetes", "Go", "JavaScript", "Elixir", "React", "Redux", "Vue"}}, 37 | } 38 | 39 | for _, tc := range tests { 40 | cmp := generic.NewCompareFunc[string]() 41 | Insertion(tc.items, cmp) 42 | 43 | if !isSorted(tc.items, cmp) { 44 | t.Fatalf("%v is not sorted.", tc.items) 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /sort/selection_test.go: -------------------------------------------------------------------------------- 1 | package sort 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/moorara/algo/generic" 7 | ) 8 | 9 | func TestSelection_int(t *testing.T) { 10 | tests := []struct { 11 | items []int 12 | }{ 13 | {[]int{}}, 14 | {[]int{20, 10, 30}}, 15 | {[]int{30, 20, 10, 40, 50}}, 16 | {[]int{90, 80, 70, 60, 50, 40, 30, 20, 10}}, 17 | } 18 | 19 | for _, tc := range tests { 20 | cmp := generic.NewCompareFunc[int]() 21 | Selection[int](tc.items, cmp) 22 | 23 | if !isSorted(tc.items, cmp) { 24 | t.Fatalf("%v is not sorted.", tc.items) 25 | } 26 | } 27 | } 28 | 29 | func TestSelection_string(t *testing.T) { 30 | tests := []struct { 31 | items []string 32 | }{ 33 | {[]string{}}, 34 | {[]string{"Milad", "Mona"}}, 35 | {[]string{"Alice", "Bob", "Alex", "Jackie"}}, 36 | {[]string{"Docker", "Kubernetes", "Go", "JavaScript", "Elixir", "React", "Redux", "Vue"}}, 37 | } 38 | 39 | for _, tc := range tests { 40 | cmp := generic.NewCompareFunc[string]() 41 | Selection[string](tc.items, cmp) 42 | 43 | if !isSorted(tc.items, cmp) { 44 | t.Fatalf("%v is not sorted.", tc.items) 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /symboltable/symbol_table.go: -------------------------------------------------------------------------------- 1 | // Package symboltable implements symbol table data structures. 2 | // 3 | // Symbol tables are also known as maps, dictionaries, etc. 4 | // Symbol tables can be ordered or unordered. 5 | package symboltable 6 | 7 | import ( 8 | "fmt" 9 | "math/rand" 10 | "time" 11 | 12 | "github.com/moorara/algo/generic" 13 | ) 14 | 15 | var r = rand.New(rand.NewSource(time.Now().UnixNano())) 16 | 17 | // SymbolTable represents an unordered symbol table abstract data type. 18 | type SymbolTable[K, V any] interface { 19 | fmt.Stringer 20 | generic.Equaler[SymbolTable[K, V]] 21 | generic.Collection2[K, V] 22 | 23 | verify() bool 24 | } 25 | 26 | // OrderedSymbolTable represents an ordered symbol table abstract data type. 27 | type OrderedSymbolTable[K, V any] interface { 28 | SymbolTable[K, V] 29 | generic.Tree2[K, V] 30 | 31 | Height() int 32 | Min() (K, V, bool) 33 | Max() (K, V, bool) 34 | Floor(K) (K, V, bool) 35 | Ceiling(K) (K, V, bool) 36 | DeleteMin() (K, V, bool) 37 | DeleteMax() (K, V, bool) 38 | Select(int) (K, V, bool) 39 | Rank(K) int 40 | Range(K, K) []generic.KeyValue[K, V] 41 | RangeSize(K, K) int 42 | } 43 | -------------------------------------------------------------------------------- /heap/heap.go: -------------------------------------------------------------------------------- 1 | // Package heap implements heap data structures. 2 | // 3 | // Heaps are also known as priority queues. 4 | package heap 5 | 6 | // Heap represents a heap (priority queue) abstract data type. 7 | type Heap[K, V any] interface { 8 | verify() bool 9 | 10 | Size() int 11 | IsEmpty() bool 12 | Insert(K, V) 13 | Delete() (K, V, bool) 14 | DeleteAll() 15 | Peek() (K, V, bool) 16 | ContainsKey(K) bool 17 | ContainsValue(V) bool 18 | DOT() string 19 | } 20 | 21 | // IndexedHeap represents an indexed heap (priority queue) abstract data type. 22 | type IndexedHeap[K, V any] interface { 23 | verify() bool 24 | 25 | Size() int 26 | IsEmpty() bool 27 | Insert(int, K, V) bool 28 | ChangeKey(int, K) bool 29 | Delete() (int, K, V, bool) 30 | DeleteIndex(int) (K, V, bool) 31 | DeleteAll() 32 | Peek() (int, K, V, bool) 33 | PeekIndex(int) (K, V, bool) 34 | ContainsIndex(int) bool 35 | ContainsKey(K) bool 36 | ContainsValue(V) bool 37 | DOT() string 38 | } 39 | 40 | // MergeableHeap represents a mergeable heap (priority queue) abstract data type. 41 | type MergeableHeap[K, V any] interface { 42 | Heap[K, V] 43 | Merge(MergeableHeap[K, V]) 44 | } 45 | -------------------------------------------------------------------------------- /errors/example_test.go: -------------------------------------------------------------------------------- 1 | package errors_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/moorara/algo/errors" 7 | ) 8 | 9 | func ExampleAppend() { 10 | var err error 11 | err = errors.Append(err, fmt.Errorf("error on foo: %d", 1)) 12 | err = errors.Append(err, fmt.Errorf("error on bar: %d", 2)) 13 | err = errors.Append(err, fmt.Errorf("error on baz: %d", 3)) 14 | 15 | fmt.Println(err) 16 | } 17 | 18 | func ExampleAppend_withCustomFormat() { 19 | err := &errors.MultiError{ 20 | Format: errors.BulletErrorFormat, 21 | } 22 | 23 | err = errors.Append(err, fmt.Errorf("error on foo: %d", 1)) 24 | err = errors.Append(err, fmt.Errorf("error on bar: %d", 2)) 25 | err = errors.Append(err, fmt.Errorf("error on baz: %d", 3)) 26 | 27 | fmt.Println(err) 28 | } 29 | 30 | func ExampleMultiError_ErrorOrNil() { 31 | me := new(errors.MultiError) 32 | err := me.ErrorOrNil() 33 | 34 | fmt.Println(err) 35 | } 36 | 37 | func ExampleMultiError_Unwrap() { 38 | var me *errors.MultiError 39 | me = errors.Append(me, fmt.Errorf("error on foo: %d", 1)) 40 | me = errors.Append(me, fmt.Errorf("error on bar: %d", 2)) 41 | me = errors.Append(me, fmt.Errorf("error on baz: %d", 3)) 42 | 43 | for _, err := range me.Unwrap() { 44 | fmt.Println(err) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /parser/lr/canonical/canonical.go: -------------------------------------------------------------------------------- 1 | // Package canonical provides data structures and algorithms for building Canonical LR parsers. 2 | // A canonical LR parser is a bottom-up parser for the class of LR(1) grammars. 3 | // 4 | // The canonical LR or just LR method makes full use of the lookahead symbols. 5 | // This method uses a large set of items, called the LR(1) items. 6 | // LR method handles a broader class of context-free grammars compared to SLR method. 7 | // 8 | // For more details on parsing theory, 9 | // refer to "Compilers: Principles, Techniques, and Tools (2nd Edition)". 10 | package canonical 11 | 12 | import ( 13 | "github.com/moorara/algo/grammar" 14 | "github.com/moorara/algo/lexer" 15 | "github.com/moorara/algo/parser" 16 | "github.com/moorara/algo/parser/lr" 17 | ) 18 | 19 | // New creates a new Canonical LR parser for a given context-free grammar (CFG). 20 | // It requires a lexer for lexical analysis, which reads the input tokens (terminal symbols). 21 | func New(L lexer.Lexer, G *grammar.CFG, precedences lr.PrecedenceLevels) (*lr.Parser, error) { 22 | T, err := BuildParsingTable(G, precedences) 23 | if err != nil { 24 | return nil, &parser.ParseError{ 25 | Cause: err, 26 | } 27 | } 28 | 29 | return &lr.Parser{ 30 | L: L, 31 | T: T, 32 | }, nil 33 | } 34 | -------------------------------------------------------------------------------- /parser/lr/simple/simple.go: -------------------------------------------------------------------------------- 1 | // Package simple provides data structures and algorithms for building Simple LR (SLR) parsers. 2 | // An SLR parser is a bottom-up parser for the class of LR(1) grammars. 3 | // 4 | // An SLR parser uses the canonical LR(0) items to construct the state machine (DFA). 5 | // An SLR parser is less powerful than a canonical LR(1) parser. 6 | // SLR simplifies the construction process but sacrifices some parsing power compared to canonical LR(1). 7 | // 8 | // For more details on parsing theory, 9 | // refer to "Compilers: Principles, Techniques, and Tools (2nd Edition)". 10 | package simple 11 | 12 | import ( 13 | "github.com/moorara/algo/grammar" 14 | "github.com/moorara/algo/lexer" 15 | "github.com/moorara/algo/parser" 16 | "github.com/moorara/algo/parser/lr" 17 | ) 18 | 19 | // New creates a new SLR parser for a given context-free grammar (CFG). 20 | // It requires a lexer for lexical analysis, which reads the input tokens (terminal symbols). 21 | func New(L lexer.Lexer, G *grammar.CFG, precedences lr.PrecedenceLevels) (*lr.Parser, error) { 22 | T, err := BuildParsingTable(G, precedences) 23 | if err != nil { 24 | return nil, &parser.ParseError{ 25 | Cause: err, 26 | } 27 | } 28 | 29 | return &lr.Parser{ 30 | L: L, 31 | T: T, 32 | }, nil 33 | } 34 | -------------------------------------------------------------------------------- /parser/combinator/errors_test.go: -------------------------------------------------------------------------------- 1 | package combinator 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestSyntaxError(t *testing.T) { 11 | tests := []struct { 12 | name string 13 | e *syntaxError 14 | expectedError string 15 | }{ 16 | { 17 | name: "OK", 18 | e: &syntaxError{ 19 | Pos: 0, 20 | Rune: 'a', 21 | }, 22 | expectedError: "0: unexpected rune 'a'", 23 | }, 24 | } 25 | 26 | for _, tc := range tests { 27 | t.Run(tc.name, func(t *testing.T) { 28 | assert.Equal(t, tc.expectedError, tc.e.Error()) 29 | }) 30 | } 31 | } 32 | 33 | func TestSemanticError(t *testing.T) { 34 | tests := []struct { 35 | name string 36 | e *semanticError 37 | expectedError string 38 | expectedUnwrap error 39 | }{ 40 | { 41 | name: "OK", 42 | e: &semanticError{ 43 | Pos: 0, 44 | Err: errors.New("invalid range"), 45 | }, 46 | expectedError: "0: invalid range", 47 | expectedUnwrap: errors.New("invalid range"), 48 | }, 49 | } 50 | 51 | for _, tc := range tests { 52 | t.Run(tc.name, func(t *testing.T) { 53 | assert.Equal(t, tc.expectedError, tc.e.Error()) 54 | assert.Equal(t, tc.expectedUnwrap, tc.e.Unwrap()) 55 | }) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /trie/bitpattern_test.go: -------------------------------------------------------------------------------- 1 | package trie 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestNewBitPattern(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | pattern string 13 | }{ 14 | { 15 | name: "OK", 16 | pattern: "a**d", 17 | }, 18 | } 19 | 20 | for _, tc := range tests { 21 | t.Run(tc.name, func(t *testing.T) { 22 | b := newBitPattern(tc.pattern) 23 | assert.NotNil(t, b) 24 | }) 25 | } 26 | } 27 | 28 | func TestBitPattern_Bit(t *testing.T) { 29 | tests := []struct { 30 | name string 31 | b *bitPattern 32 | expectedBits []byte 33 | }{ 34 | { 35 | name: "OK", 36 | b: newBitPattern("G*a*i*y"), 37 | expectedBits: []byte{ 38 | '0', '1', '0', '0', '0', '1', '1', '1', 39 | '*', '*', '*', '*', '*', '*', '*', '*', 40 | '0', '1', '1', '0', '0', '0', '0', '1', 41 | '*', '*', '*', '*', '*', '*', '*', '*', 42 | '0', '1', '1', '0', '1', '0', '0', '1', 43 | '*', '*', '*', '*', '*', '*', '*', '*', 44 | '0', '1', '1', '1', '1', '0', '0', '1', 45 | }, 46 | }, 47 | } 48 | 49 | for _, tc := range tests { 50 | t.Run(tc.name, func(t *testing.T) { 51 | for i, expectedBit := range tc.expectedBits { 52 | assert.Equal(t, expectedBit, tc.b.Bit(i+1)) 53 | } 54 | }) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /parser/process.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "github.com/moorara/algo/grammar" 5 | "github.com/moorara/algo/lexer" 6 | ) 7 | 8 | // TokenFunc is a function that is invoked each time a token 9 | // is matched and removed from an input string during parsing. 10 | // 11 | // It executes the actions associated with the matched token, 12 | // such as semantic processing, constructing abstract syntax trees (AST), 13 | // or performing other custom logic required for the parsing process. 14 | // 15 | // The function may return an error, indicating an issue during token processing. 16 | // The parser may stop immediately or continue parsing and accumulate more errors. 17 | type TokenFunc func(*lexer.Token) error 18 | 19 | // ProductionFunc is a function that is invoked each time a production rule 20 | // is matched or applied during the parsing process of an input string. 21 | // 22 | // It executes the actions associated with the matched production rule, 23 | // such as semantic processing, constructing abstract syntax trees (AST), 24 | // or performing other custom logic required for the parsing process. 25 | // 26 | // The function may return an error, indicating an issue during production rule processing. 27 | // The parser may stop immediately or continue parsing and accumulate more errors. 28 | type ProductionFunc func(*grammar.Production) error 29 | -------------------------------------------------------------------------------- /radixsort/lsd_test.go: -------------------------------------------------------------------------------- 1 | package radixsort 2 | 3 | import "testing" 4 | 5 | func TestLSDString(t *testing.T) { 6 | tests := []struct { 7 | a []string 8 | w int 9 | }{ 10 | { 11 | a: []string{"BBB", "BBA", "BAB", "BAA", "ABB", "ABA", "AAB", "AAA"}, 12 | w: 3, 13 | }, 14 | { 15 | a: []string{"4PGC938", "2IYE230", "3CIO720", "1ICK750", "1OHV845", "4JZY524", "1ICK750", "3CIO720", "1OHV845", "1OHV845", "2RLA629", "2RLA629", "3ATW723"}, 16 | w: 7, 17 | }, 18 | } 19 | 20 | for _, tc := range tests { 21 | LSDString(tc.a, tc.w) 22 | 23 | if !isSorted[string](tc.a) { 24 | t.Fatalf("%v is not sorted.", tc.a) 25 | } 26 | } 27 | } 28 | 29 | func TestLSDInt(t *testing.T) { 30 | tests := []struct { 31 | a []int 32 | }{ 33 | {[]int{30, -20, 10, -40, 50}}, 34 | {[]int{90, -80, 70, -60, 50, -40, 30, -20, 10}}, 35 | } 36 | 37 | for _, tc := range tests { 38 | LSDInt(tc.a) 39 | 40 | if !isSorted[int](tc.a) { 41 | t.Fatalf("%v is not sorted.", tc.a) 42 | } 43 | } 44 | } 45 | 46 | func TestLSDUint(t *testing.T) { 47 | tests := []struct { 48 | a []uint 49 | }{ 50 | {[]uint{30, 20, 10, 40, 50}}, 51 | {[]uint{90, 80, 70, 60, 50, 40, 30, 20, 10}}, 52 | } 53 | 54 | for _, tc := range tests { 55 | LSDUint(tc.a) 56 | 57 | if !isSorted[uint](tc.a) { 58 | t.Fatalf("%v is not sorted.", tc.a) 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /internal/parsertest/mock_test.go: -------------------------------------------------------------------------------- 1 | package parsertest 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/moorara/algo/grammar" 8 | "github.com/moorara/algo/lexer" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestMockLexer(t *testing.T) { 13 | t.Run("Error", func(t *testing.T) { 14 | l := &MockLexer{ 15 | NextTokenMocks: []NextTokenMock{ 16 | // First token 17 | {OutError: errors.New("cannot read rune")}, 18 | }, 19 | } 20 | 21 | token, err := l.NextToken() 22 | 23 | assert.Zero(t, token) 24 | assert.EqualError(t, err, "cannot read rune") 25 | }) 26 | 27 | t.Run("OK", func(t *testing.T) { 28 | l := &MockLexer{ 29 | NextTokenMocks: []NextTokenMock{ 30 | // First token 31 | { 32 | OutToken: lexer.Token{ 33 | Terminal: grammar.Terminal("id"), 34 | Lexeme: "a", 35 | Pos: lexer.Position{ 36 | Filename: "test", 37 | Offset: 0, 38 | Line: 1, 39 | Column: 1, 40 | }, 41 | }, 42 | }, 43 | }, 44 | } 45 | 46 | token, err := l.NextToken() 47 | 48 | assert.NoError(t, err) 49 | assert.True(t, token.Equal(lexer.Token{ 50 | Terminal: grammar.Terminal("id"), 51 | Lexeme: "a", 52 | Pos: lexer.Position{ 53 | Filename: "test", 54 | Offset: 0, 55 | Line: 1, 56 | Column: 1, 57 | }, 58 | })) 59 | }) 60 | } 61 | -------------------------------------------------------------------------------- /dot/node.go: -------------------------------------------------------------------------------- 1 | package dot 2 | 3 | import ( 4 | "bytes" 5 | "strconv" 6 | ) 7 | 8 | // Node represents a graph node. 9 | type Node struct { 10 | Name string 11 | Group string 12 | Label string 13 | Color Color 14 | Style Style 15 | Shape Shape 16 | FontColor Color 17 | FontName string 18 | } 19 | 20 | // NewNode creates a new node. 21 | func NewNode(name, group, label string, color Color, style Style, shape Shape, fontcolor Color, fontname string) Node { 22 | return Node{ 23 | Name: name, 24 | Group: group, 25 | Label: label, 26 | Color: color, 27 | Style: style, 28 | Shape: shape, 29 | FontColor: fontcolor, 30 | FontName: fontname, 31 | } 32 | } 33 | 34 | // DOT generates a DOT representation of the Node object. 35 | func (n *Node) DOT() string { 36 | first := true 37 | var b bytes.Buffer 38 | 39 | label := n.Label 40 | label = strconv.Quote(label) 41 | 42 | b.WriteString(n.Name + " [") 43 | first = addListAttr(&b, first, "group", n.Group) 44 | first = addListAttr(&b, first, "label", label) 45 | first = addListAttr(&b, first, "color", string(n.Color)) 46 | first = addListAttr(&b, first, "style", string(n.Style)) 47 | first = addListAttr(&b, first, "shape", string(n.Shape)) 48 | first = addListAttr(&b, first, "fontcolor", string(n.FontColor)) 49 | _ = addListAttr(&b, first, "fontname", `"`+n.FontName+`"`) 50 | b.WriteString("];") 51 | 52 | return b.String() 53 | } 54 | -------------------------------------------------------------------------------- /errors/format.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "strings" 7 | ) 8 | 9 | // ErrorFormat is a function type for formatting a slice of errors into a single string representation. 10 | type ErrorFormat func([]error) string 11 | 12 | var ( 13 | // DefaultErrorFormat formats a slice of errors into a single string, 14 | // with each error on a new line. 15 | DefaultErrorFormat = defaultErrorFormat 16 | 17 | // BulletErrorFormat formats a slice of errors into a single string, 18 | // with each error indented, prefixed by a bullet point, and properly spaced. 19 | BulletErrorFormat = bulletErrorFormat 20 | ) 21 | 22 | func defaultErrorFormat(errs []error) string { 23 | if len(errs) == 0 { 24 | return "" 25 | } 26 | 27 | var b bytes.Buffer 28 | 29 | for _, err := range errs { 30 | fmt.Fprintln(&b, err) 31 | } 32 | 33 | return b.String() 34 | } 35 | 36 | func bulletErrorFormat(errs []error) string { 37 | if len(errs) == 0 { 38 | return "" 39 | } 40 | 41 | var b bytes.Buffer 42 | 43 | if len(errs) == 1 { 44 | b.WriteString("1 error occurred:\n\n") 45 | } else { 46 | fmt.Fprintf(&b, "%d errors occurred:\n\n", len(errs)) 47 | } 48 | 49 | for _, err := range errs { 50 | for i, line := range strings.Split(err.Error(), "\n") { 51 | if i == 0 { 52 | fmt.Fprintf(&b, " • %s\n", line) 53 | } else { 54 | fmt.Fprintf(&b, " %s\n", line) 55 | } 56 | } 57 | } 58 | 59 | return b.String() 60 | } 61 | -------------------------------------------------------------------------------- /grammar/cfg_follow_test.go: -------------------------------------------------------------------------------- 1 | package grammar 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | 8 | "github.com/moorara/algo/set" 9 | ) 10 | 11 | func TestNewTerminalsAndEndmarker(t *testing.T) { 12 | tests := []struct { 13 | name string 14 | terms []Terminal 15 | }{ 16 | { 17 | name: "OK", 18 | terms: []Terminal{"a", "b", "c", "d", "e", "f"}, 19 | }, 20 | } 21 | 22 | for _, tc := range tests { 23 | t.Run(tc.name, func(t *testing.T) { 24 | f := newTerminalsAndEndmarker(tc.terms...) 25 | assert.NotNil(t, f) 26 | assert.True(t, f.Terminals.Contains(tc.terms...)) 27 | assert.False(t, f.IncludesEndmarker) 28 | }) 29 | } 30 | } 31 | 32 | func TestTerminalsAndEndmarker(t *testing.T) { 33 | tests := []struct { 34 | name string 35 | set *TerminalsAndEndmarker 36 | expectedString string 37 | }{ 38 | { 39 | name: "OK", 40 | set: &TerminalsAndEndmarker{ 41 | Terminals: set.New(EqTerminal, "a", "b", "c", "d", "e", "f"), 42 | IncludesEndmarker: true, 43 | }, 44 | expectedString: `{"a", "b", "c", "d", "e", "f", $}`, 45 | }, 46 | } 47 | 48 | for _, tc := range tests { 49 | t.Run(tc.name, func(t *testing.T) { 50 | assert.Equal(t, tc.expectedString, tc.set.String()) 51 | }) 52 | } 53 | } 54 | 55 | func TestNewFollowTable(t *testing.T) { 56 | t.Run("OK", func(t *testing.T) { 57 | follow := newFollowTable() 58 | assert.NotNil(t, follow) 59 | }) 60 | } 61 | -------------------------------------------------------------------------------- /sort/README.md: -------------------------------------------------------------------------------- 1 | # Sort 2 | 3 | | Sort | Best Case | Average Case | Worst Case | Memory | Comment | 4 | |-----------|:-----------------:|:-----------------:|:-----------------:|:------:|-----------------------------------------------| 5 | | Selection | N2 / 2 | N2 / 2 | N2 / 2 | 1 | N exchanges | 6 | | Insertion | N2 / 2 | N2 / 2 | N2 / 2 | 1 | **Stable**, N exchanges, Suitable for small N | 7 | | Shell | N | ? | ? | 1 | Tight code, Subquadratic | 8 | | Merge | NlgN | NlgN | NlgN | N | **Stable**, **NlgN guarantee**, Extra memory | 9 | | MergeRec | NlgN | NlgN | NlgN | N | **Stable**, **NlgN guarantee**, Extra memory | 10 | | Heap | NlgN | NlgN | NlgN | 1 | **NlgN guarantee** | 11 | | Quick | NlgN | 2NlnN | N2 / 2 | clogN | **NlgN probabilistic guarantee** | 12 | | Quick3Way | N | 2NlnN | N2 / 2 | clogN | Faster in presence of duplicate keys | 13 | 14 | By running benchmarks, you can compare the performance of these algorithms with each other and also with built-in sort algorithm of go. 15 | -------------------------------------------------------------------------------- /radixsort/msd_test.go: -------------------------------------------------------------------------------- 1 | package radixsort 2 | 3 | import "testing" 4 | 5 | func TestMSDString(t *testing.T) { 6 | tests := []struct { 7 | a []string 8 | }{ 9 | { 10 | a: []string{"Milad", "Mona", "Milad", "Mona"}, 11 | }, 12 | { 13 | a: []string{ 14 | "Docker", "Kubernetes", "Prometheus", 15 | "Terraform", "Vault", "Consul", 16 | "Linkerd", "Istio", 17 | "Kafka", "NATS", 18 | "CockroachDB", "ArangoDB", 19 | "Go", "JavaScript", "TypeScript", 20 | "gRPC", "GraphQL", 21 | "React", "Redux", "Vue", 22 | }, 23 | }, 24 | } 25 | 26 | for _, tc := range tests { 27 | MSDString(tc.a) 28 | 29 | if !isSorted[string](tc.a) { 30 | t.Fatalf("%v is not sorted.", tc.a) 31 | } 32 | } 33 | } 34 | 35 | func TestMSDInt(t *testing.T) { 36 | tests := []struct { 37 | a []int 38 | }{ 39 | {[]int{30, -20, 10, -40, 50}}, 40 | {[]int{90, -85, 80, -75, 70, -65, 60, -55, 50, -45, 40, -35, 30, -25, 20, -15, 10}}, 41 | } 42 | 43 | for _, tc := range tests { 44 | MSDInt(tc.a) 45 | 46 | if !isSorted[int](tc.a) { 47 | t.Fatalf("%v is not sorted.", tc.a) 48 | } 49 | } 50 | } 51 | 52 | func TestMSDUint(t *testing.T) { 53 | tests := []struct { 54 | a []uint 55 | }{ 56 | {[]uint{30, 20, 10, 40, 50}}, 57 | {[]uint{90, 85, 80, 75, 70, 65, 60, 55, 50, 45, 40, 35, 30, 25, 20, 15, 10}}, 58 | } 59 | 60 | for _, tc := range tests { 61 | MSDUint(tc.a) 62 | 63 | if !isSorted[uint](tc.a) { 64 | t.Fatalf("%v is not sorted.", tc.a) 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /dot/edge.go: -------------------------------------------------------------------------------- 1 | package dot 2 | 3 | import ( 4 | "bytes" 5 | "strconv" 6 | ) 7 | 8 | // Edge represents an edge. 9 | type Edge struct { 10 | From string 11 | To string 12 | EdgeType EdgeType 13 | EdgeDir EdgeDir 14 | Label string 15 | Color Color 16 | Style Style 17 | ArrowHead ArrowType 18 | ArrowTail ArrowType 19 | } 20 | 21 | // NewEdge creates a new edge. 22 | func NewEdge(from, to string, edgeType EdgeType, edgeDir EdgeDir, label string, color Color, style Style, arrowHead, arrowTail ArrowType) Edge { 23 | return Edge{ 24 | From: from, 25 | To: to, 26 | EdgeType: edgeType, 27 | EdgeDir: edgeDir, 28 | Label: label, 29 | Color: color, 30 | Style: style, 31 | ArrowHead: arrowHead, 32 | ArrowTail: arrowTail, 33 | } 34 | } 35 | 36 | // DOT generates a DOT representation of the Edge object. 37 | func (e *Edge) DOT() string { 38 | first := true 39 | var b bytes.Buffer 40 | 41 | label := e.Label 42 | label = strconv.Quote(label) 43 | 44 | b.WriteString(e.From + " " + string(e.EdgeType) + " " + e.To + " [") 45 | first = addListAttr(&b, first, "dirType", string(e.EdgeDir)) 46 | first = addListAttr(&b, first, "label", label) 47 | first = addListAttr(&b, first, "color", string(e.Color)) 48 | first = addListAttr(&b, first, "style", string(e.Style)) 49 | first = addListAttr(&b, first, "arrowhead", string(e.ArrowHead)) 50 | _ = addListAttr(&b, first, "arrowtail", string(e.ArrowTail)) 51 | b.WriteString("];") 52 | 53 | return b.String() 54 | } 55 | -------------------------------------------------------------------------------- /sort/merge.go: -------------------------------------------------------------------------------- 1 | package sort 2 | 3 | import "github.com/moorara/algo/generic" 4 | 5 | func min(a, b int) int { 6 | if a < b { 7 | return a 8 | } 9 | return b 10 | } 11 | 12 | func merge[T any](a, aux []T, lo, mid, hi int, cmp generic.CompareFunc[T]) { 13 | i, j := lo, mid+1 14 | copy(aux[lo:hi+1], a[lo:hi+1]) 15 | for k := lo; k <= hi; k++ { 16 | switch { 17 | case i > mid: 18 | a[k] = aux[j] 19 | j++ 20 | case j > hi: 21 | a[k] = aux[i] 22 | i++ 23 | case cmp(aux[j], aux[i]) < 0: 24 | a[k] = aux[j] 25 | j++ 26 | default: 27 | a[k] = aux[i] 28 | i++ 29 | } 30 | } 31 | } 32 | 33 | // Merge implements the iterative version of merge sort algorithm. 34 | func Merge[T any](a []T, cmp generic.CompareFunc[T]) { 35 | n := len(a) 36 | aux := make([]T, n) 37 | for sz := 1; sz < n; sz += sz { 38 | for lo := 0; lo < n-sz; lo += sz + sz { 39 | merge[T](a, aux, lo, lo+sz-1, min(lo+sz+sz-1, n-1), cmp) 40 | } 41 | } 42 | } 43 | 44 | func mergeRec[T any](a, aux []T, lo, hi int, cmp generic.CompareFunc[T]) { 45 | if hi <= lo { 46 | return 47 | } 48 | 49 | mid := (lo + hi) / 2 50 | mergeRec[T](a, aux, lo, mid, cmp) 51 | mergeRec[T](a, aux, mid+1, hi, cmp) 52 | if cmp(a[mid+1], a[mid]) >= 0 { 53 | return 54 | } 55 | merge[T](a, aux, lo, mid, hi, cmp) 56 | } 57 | 58 | // MergeRec implements the recursive version of merge sort algorithm. 59 | func MergeRec[T any](a []T, cmp generic.CompareFunc[T]) { 60 | n := len(a) 61 | aux := make([]T, n) 62 | 63 | mergeRec[T](a, aux, 0, n-1, cmp) 64 | } 65 | -------------------------------------------------------------------------------- /errors/group_test.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestGroupError(t *testing.T) { 11 | tests := []struct { 12 | name string 13 | funcs []func() error 14 | expectedMultiError *MultiError 15 | }{ 16 | { 17 | name: "WithoutErrors", 18 | funcs: []func() error{ 19 | func() error { 20 | time.Sleep(20 * time.Millisecond) 21 | return nil 22 | }, 23 | func() error { 24 | time.Sleep(10 * time.Millisecond) 25 | return nil 26 | }, 27 | func() error { 28 | time.Sleep(30 * time.Millisecond) 29 | return nil 30 | }, 31 | }, 32 | expectedMultiError: nil, 33 | }, 34 | { 35 | name: "WithErrors", 36 | funcs: []func() error{ 37 | func() error { 38 | time.Sleep(20 * time.Millisecond) 39 | return new(fooError) 40 | }, 41 | func() error { 42 | time.Sleep(10 * time.Millisecond) 43 | return new(barError) 44 | }, 45 | func() error { 46 | time.Sleep(30 * time.Millisecond) 47 | return new(bazError) 48 | }, 49 | }, 50 | expectedMultiError: &MultiError{ 51 | errs: []error{ 52 | new(barError), 53 | new(fooError), 54 | new(bazError), 55 | }, 56 | }, 57 | }, 58 | } 59 | 60 | for _, tc := range tests { 61 | t.Run(tc.name, func(t *testing.T) { 62 | g := new(Group) 63 | 64 | for _, f := range tc.funcs { 65 | g.Go(f) 66 | } 67 | 68 | err := g.Wait() 69 | assert.Equal(t, tc.expectedMultiError, err) 70 | }) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /grammar/errors.go: -------------------------------------------------------------------------------- 1 | package grammar 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | ) 7 | 8 | // CNFError represents an error for a production rule in the form 9 | // A → α that does not conform to Chomsky Normal Form (CNF). 10 | type CNFError struct { 11 | P *Production 12 | } 13 | 14 | // Error implements the error interface. 15 | // It returns a formatted string describing the error in detail. 16 | func (e *CNFError) Error() string { 17 | var b bytes.Buffer 18 | fmt.Fprintf(&b, "production %s is neither a binary rule, a terminal rule, nor S → ε", e.P) 19 | return b.String() 20 | } 21 | 22 | // LL1Error represents an error where two distinct production rules in the form 23 | // A → α | β violate LL(1) parsing requirements for a context-free grammar. 24 | type LL1Error struct { 25 | description string 26 | A NonTerminal 27 | Alpha, Beta String[Symbol] 28 | FOLLOWA *TerminalsAndEndmarker 29 | FIRSTα, FIRSTβ *TerminalsAndEmpty 30 | } 31 | 32 | // Error implements the error interface. 33 | // It returns a formatted string describing the error in detail. 34 | func (e *LL1Error) Error() string { 35 | var b bytes.Buffer 36 | 37 | fmt.Fprintf(&b, "%s:\n", e.description) 38 | fmt.Fprintf(&b, " %s → %s | %s\n", e.A, e.Alpha, e.Beta) 39 | 40 | if e.FOLLOWA != nil { 41 | fmt.Fprintf(&b, " FOLLOW(%s): %s\n", e.A, e.FOLLOWA) 42 | } 43 | 44 | if e.FIRSTα != nil { 45 | fmt.Fprintf(&b, " FIRST(%s): %s\n", e.Alpha, e.FIRSTα) 46 | } 47 | 48 | if e.FIRSTβ != nil { 49 | fmt.Fprintf(&b, " FIRST(%s): %s\n", e.Beta, e.FIRSTβ) 50 | } 51 | 52 | return b.String() 53 | } 54 | -------------------------------------------------------------------------------- /codecov.yaml: -------------------------------------------------------------------------------- 1 | # https://docs.codecov.com/docs/codecov-yaml 2 | # https://docs.codecov.com/docs/codecovyml-reference 3 | # https://docs.codecov.com/docs/coverage-configuration 4 | 5 | codecov: 6 | # Whether or not to wait for all other statuses to pass 7 | # See https://docs.codecov.com/docs/codecovyml-reference#codecovrequire_ci_to_pass 8 | require_ci_to_pass: no 9 | 10 | # See https://docs.codecov.com/docs/codecovyml-reference#coverage 11 | # See https://docs.codecov.com/docs/coverage-configuration 12 | coverage: 13 | range: '75...95' 14 | round: down 15 | precision: 2 16 | 17 | # See https://docs.codecov.com/docs/codecovyml-reference#coveragestatus 18 | # See https://docs.codecov.com/docs/commit-status 19 | status: 20 | 21 | # See https://docs.codecov.com/docs/commit-status#changes-status 22 | # See https://docs.codecov.com/docs/unexpected-coverage-changes 23 | changes: yes 24 | 25 | # Total coverage 26 | # See https://docs.codecov.com/docs/commit-status#project-status 27 | project: 28 | default: 29 | # The minimum coverage ratio for success 30 | # See https://docs.codecov.com/docs/commit-status#target 31 | target: 90% 32 | 33 | # Coverage for lines adjusted in the pull request or single commit 34 | # See https://docs.codecov.com/docs/commit-status#patch-status 35 | patch: 36 | default: 37 | # The minimum coverage ratio for success 38 | target: 90% 39 | 40 | # See https://docs.codecov.com/docs/codecovyml-reference#comment 41 | # See https://docs.codecov.com/docs/pull-request-comments 42 | comment: false 43 | -------------------------------------------------------------------------------- /grammar/cfg_first_test.go: -------------------------------------------------------------------------------- 1 | package grammar 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | 8 | "github.com/moorara/algo/set" 9 | ) 10 | 11 | func TestNewTerminalsAndEmpty(t *testing.T) { 12 | tests := []struct { 13 | name string 14 | terms []Terminal 15 | }{ 16 | { 17 | name: "OK", 18 | terms: []Terminal{"a", "b", "c", "d", "e", "f"}, 19 | }, 20 | } 21 | 22 | for _, tc := range tests { 23 | t.Run(tc.name, func(t *testing.T) { 24 | f := newTerminalsAndEmpty(tc.terms...) 25 | assert.NotNil(t, f) 26 | assert.True(t, f.Terminals.Contains(tc.terms...)) 27 | assert.False(t, f.IncludesEmpty) 28 | }) 29 | } 30 | } 31 | 32 | func TestTerminalsAndEmpty(t *testing.T) { 33 | tests := []struct { 34 | name string 35 | set *TerminalsAndEmpty 36 | expectedString string 37 | }{ 38 | { 39 | name: "OK", 40 | set: &TerminalsAndEmpty{ 41 | Terminals: set.New(EqTerminal, "a", "b", "c", "d", "e", "f"), 42 | IncludesEmpty: true, 43 | }, 44 | expectedString: `{"a", "b", "c", "d", "e", "f", ε}`, 45 | }, 46 | } 47 | 48 | for _, tc := range tests { 49 | t.Run(tc.name, func(t *testing.T) { 50 | assert.Equal(t, tc.expectedString, tc.set.String()) 51 | }) 52 | } 53 | } 54 | 55 | func TestNewFirstBySymbolTable(t *testing.T) { 56 | t.Run("OK", func(t *testing.T) { 57 | firstBySymbol := newFirstBySymbolTable() 58 | assert.NotNil(t, firstBySymbol) 59 | }) 60 | } 61 | 62 | func TestNewFirstByStringTable(t *testing.T) { 63 | t.Run("OK", func(t *testing.T) { 64 | firstByString := newFirstByStringTable() 65 | assert.NotNil(t, firstByString) 66 | }) 67 | } 68 | -------------------------------------------------------------------------------- /parser/lr/lookahead/lookahead.go: -------------------------------------------------------------------------------- 1 | // Package lookahead provides data structures and algorithms for building Look-Ahead LR (LALR) parsers. 2 | // An LALR parser is a bottom-up parser for the class of LR(1) grammars. 3 | // 4 | // An LALR parser, similar to SLR, uses the canonical LR(0) items to construct the state machine (DFA), 5 | // but it refines the states by incorporating lookahead symbols explicitly. 6 | // LALR merges states with identical core LR(0) items but handles lookahead symbols for each merged state separately, 7 | // making it more precise than SLR and avoids many conflicts that SLR might encounter. 8 | // LALR is more powerful than SLR as it can handle a wider range of grammars, including most programming languages. 9 | // However, it is less powerful than canonical LR because state merging can lose distinctions in lookahead contexts, 10 | // potentially leading to conflicts for some grammars. 11 | // 12 | // For more details on parsing theory, 13 | // refer to "Compilers: Principles, Techniques, and Tools (2nd Edition)". 14 | package lookahead 15 | 16 | import ( 17 | "github.com/moorara/algo/grammar" 18 | "github.com/moorara/algo/lexer" 19 | "github.com/moorara/algo/parser" 20 | "github.com/moorara/algo/parser/lr" 21 | ) 22 | 23 | // New creates a new LALR parser for a given context-free grammar (CFG). 24 | // It requires a lexer for lexical analysis, which reads the input tokens (terminal symbols). 25 | func New(L lexer.Lexer, G *grammar.CFG, precedences lr.PrecedenceLevels) (*lr.Parser, error) { 26 | T, err := BuildParsingTable(G, precedences) 27 | if err != nil { 28 | return nil, &parser.ParseError{ 29 | Cause: err, 30 | } 31 | } 32 | 33 | return &lr.Parser{ 34 | L: L, 35 | T: T, 36 | }, nil 37 | } 38 | -------------------------------------------------------------------------------- /parser/parser_test.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | 9 | "github.com/moorara/algo/lexer" 10 | ) 11 | 12 | func TestParseError(t *testing.T) { 13 | tests := []struct { 14 | name string 15 | e *ParseError 16 | expectedError string 17 | }{ 18 | { 19 | name: "WithDescription", 20 | e: &ParseError{ 21 | Description: "error on parsing the input string", 22 | }, 23 | expectedError: "error on parsing the input string", 24 | }, 25 | { 26 | name: "WithDescriptionAndPos", 27 | e: &ParseError{ 28 | Description: "unacceptable input symbol", 29 | Pos: lexer.Position{ 30 | Filename: "test", 31 | Offset: 69, 32 | Line: 8, 33 | Column: 27, 34 | }, 35 | }, 36 | expectedError: "test:8:27: unacceptable input symbol", 37 | }, 38 | { 39 | name: "WithCause", 40 | e: &ParseError{ 41 | Cause: errors.New("grammar is ambiguous"), 42 | Pos: lexer.Position{ 43 | Filename: "test", 44 | Offset: 69, 45 | Line: 8, 46 | Column: 27, 47 | }, 48 | }, 49 | expectedError: "test:8:27: grammar is ambiguous", 50 | }, 51 | { 52 | name: "WithDescriptionAndCauseAndPos", 53 | e: &ParseError{ 54 | Description: "error on parsing the input string", 55 | Cause: errors.New("grammar is ambiguous"), 56 | Pos: lexer.Position{ 57 | Filename: "test", 58 | Offset: 69, 59 | Line: 8, 60 | Column: 27, 61 | }, 62 | }, 63 | expectedError: "test:8:27: error on parsing the input string: grammar is ambiguous", 64 | }, 65 | } 66 | 67 | for _, tc := range tests { 68 | assert.EqualError(t, tc.e, tc.expectedError) 69 | assert.Equal(t, tc.e.Cause, tc.e.Unwrap()) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /grammar/errors_test.go: -------------------------------------------------------------------------------- 1 | package grammar 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | 8 | "github.com/moorara/algo/set" 9 | ) 10 | 11 | func TestCNFError(t *testing.T) { 12 | tests := []struct { 13 | name string 14 | e *CNFError 15 | expectedError string 16 | }{ 17 | { 18 | name: "OK", 19 | e: &CNFError{ 20 | P: &Production{"rule", String[Symbol]{NonTerminal("lhs"), Terminal("="), NonTerminal("rhs")}}, 21 | }, 22 | expectedError: `production rule → lhs "=" rhs is neither a binary rule, a terminal rule, nor S → ε`, 23 | }, 24 | } 25 | 26 | for _, tc := range tests { 27 | assert.EqualError(t, tc.e, tc.expectedError) 28 | } 29 | } 30 | 31 | func TestLL1Error(t *testing.T) { 32 | tests := []struct { 33 | name string 34 | e *LL1Error 35 | expectedError string 36 | }{ 37 | { 38 | name: "OK", 39 | e: &LL1Error{ 40 | description: "ε is in FIRST(β), but FOLLOW(A) and FIRST(α) are not disjoint sets", 41 | A: NonTerminal("decls"), 42 | Alpha: String[Symbol]{NonTerminal("decls"), NonTerminal("decl")}, 43 | Beta: E, 44 | FOLLOWA: &TerminalsAndEndmarker{ 45 | Terminals: set.New(EqTerminal, "IDENT", "TOKEN"), 46 | IncludesEndmarker: true, 47 | }, 48 | FIRSTα: &TerminalsAndEmpty{ 49 | Terminals: set.New(EqTerminal, "IDENT", "TOKEN"), 50 | }, 51 | FIRSTβ: &TerminalsAndEmpty{ 52 | Terminals: set.New(EqTerminal), 53 | IncludesEmpty: true, 54 | }, 55 | }, 56 | expectedError: "ε is in FIRST(β), but FOLLOW(A) and FIRST(α) are not disjoint sets:\n decls → decls decl | ε\n FOLLOW(decls): {\"IDENT\", \"TOKEN\", $}\n FIRST(decls decl): {\"IDENT\", \"TOKEN\"}\n FIRST(ε): {ε}\n", 57 | }, 58 | } 59 | 60 | for _, tc := range tests { 61 | assert.EqualError(t, tc.e, tc.expectedError) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /automata/partition.go: -------------------------------------------------------------------------------- 1 | package automata 2 | 3 | import "github.com/moorara/algo/set" 4 | 5 | // group represents a subset of states within a partition. 6 | // Each group is uniquely identified by a representative state. 7 | type group struct { 8 | States 9 | Rep State 10 | } 11 | 12 | func eqGroup(a, b group) bool { 13 | return a.States.Equal(b.States) 14 | } 15 | 16 | func cmpGroup(a, b group) int { 17 | return CmpStates(a.States, b.States) 18 | } 19 | 20 | func hashGroup(g group) uint64 { 21 | return HashStates(g.States) 22 | } 23 | 24 | // groups represents a set of groups. 25 | type groups set.Set[group] 26 | 27 | func newGroups(gs ...group) groups { 28 | return set.NewSortedSet(cmpGroup, gs...) 29 | } 30 | 31 | // partition groups DFA states into disjoint sets, each tracked by a representative state. 32 | type partition struct { 33 | groups 34 | nextRep State 35 | } 36 | 37 | // newPartition creates a new partition. 38 | func newPartition() *partition { 39 | return &partition{ 40 | groups: newGroups(), 41 | nextRep: 0, 42 | } 43 | } 44 | 45 | // Equal implements the generic.Equaler interface. 46 | func (p *partition) Equal(rhs *partition) bool { 47 | return p.groups.Equal(rhs.groups) && p.nextRep == rhs.nextRep 48 | } 49 | 50 | // Add inserts new groups into the partition set. 51 | // Each group is a set of states and assigned a unique representative state. 52 | func (p *partition) Add(groups ...States) { 53 | for _, states := range groups { 54 | p.groups.Add(group{ 55 | States: states, 56 | Rep: p.nextRep, 57 | }) 58 | 59 | p.nextRep++ 60 | } 61 | } 62 | 63 | // FindRep finds the group containing the given state and returns its representative state. 64 | // If the state is not found in any group, it returns -1. 65 | func (p *partition) FindRep(s State) State { 66 | for G := range p.groups.All() { 67 | if G.Contains(s) { 68 | return G.Rep 69 | } 70 | } 71 | 72 | return -1 73 | } 74 | -------------------------------------------------------------------------------- /dot/util.go: -------------------------------------------------------------------------------- 1 | package dot 2 | 3 | import "bytes" 4 | 5 | func addIndent(buf *bytes.Buffer, indent int) { 6 | for i := 0; i < indent; i++ { 7 | buf.WriteString(" ") 8 | } 9 | } 10 | 11 | func addListAttr(buf *bytes.Buffer, first bool, name, value string) bool { 12 | if value != "" && value != "\"\"" { 13 | if !first { 14 | buf.WriteString(", ") 15 | } 16 | buf.WriteString(name) 17 | buf.WriteString("=") 18 | buf.WriteString(value) 19 | return false 20 | } 21 | 22 | return first 23 | } 24 | 25 | func addAttr(buf *bytes.Buffer, first bool, indent int, name, value string) bool { 26 | if value != "" && value != "\"\"" { 27 | addIndent(buf, indent) 28 | buf.WriteString(name) 29 | buf.WriteString("=") 30 | buf.WriteString(value) 31 | buf.WriteString(";\n") 32 | return false 33 | } 34 | 35 | return first 36 | } 37 | 38 | func addNodes(buf *bytes.Buffer, first bool, indent int, nodes []Node) bool { 39 | added := len(nodes) > 0 40 | if !first && added { 41 | buf.WriteString("\n") 42 | } 43 | 44 | for _, node := range nodes { 45 | addIndent(buf, indent) 46 | buf.WriteString(node.DOT()) 47 | buf.WriteString("\n") 48 | } 49 | 50 | return first && !added 51 | } 52 | 53 | func addEdges(buf *bytes.Buffer, first bool, indent int, edges []Edge) bool { 54 | added := len(edges) > 0 55 | if !first && added { 56 | buf.WriteString("\n") 57 | } 58 | 59 | for _, edge := range edges { 60 | addIndent(buf, indent) 61 | buf.WriteString(edge.DOT()) 62 | buf.WriteString("\n") 63 | } 64 | 65 | return first && !added 66 | } 67 | 68 | func addSubgraphs(buf *bytes.Buffer, first bool, indent int, subgraphs []Subgraph) bool { 69 | added := len(subgraphs) > 0 70 | 71 | for _, subgraph := range subgraphs { 72 | if !first { 73 | buf.WriteString("\n") 74 | } 75 | buf.WriteString(subgraph.DOT(indent)) 76 | buf.WriteString("\n") 77 | first = false 78 | } 79 | 80 | return first && !added 81 | } 82 | -------------------------------------------------------------------------------- /parser/lr/simple/parsing_table_test.go: -------------------------------------------------------------------------------- 1 | package simple 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | 8 | "github.com/moorara/algo/grammar" 9 | "github.com/moorara/algo/internal/parsertest" 10 | "github.com/moorara/algo/parser/lr" 11 | ) 12 | 13 | func TestBuildParsingTable(t *testing.T) { 14 | pt := getTestParsingTables() 15 | 16 | tests := []struct { 17 | name string 18 | G *grammar.CFG 19 | precedences lr.PrecedenceLevels 20 | expectedTable *lr.ParsingTable 21 | expectedError string 22 | }{ 23 | { 24 | name: "E→E+T", 25 | G: parsertest.Grammars[3], 26 | precedences: lr.PrecedenceLevels{}, 27 | expectedTable: pt[0], 28 | }, 29 | { 30 | name: "E→E+E", 31 | G: parsertest.Grammars[4], 32 | precedences: lr.PrecedenceLevels{}, 33 | expectedError: `Error: Ambiguous Grammar 34 | Cause: Multiple conflicts in the parsing table: 35 | 1. Shift/Reduce conflict in ACTION[2, "*"] 36 | 2. Shift/Reduce conflict in ACTION[2, "+"] 37 | 3. Shift/Reduce conflict in ACTION[3, "*"] 38 | 4. Shift/Reduce conflict in ACTION[3, "+"] 39 | Resolution: Specify associativity and precedence for these Terminals/Productions: 40 | • "*" vs. "*", "+" 41 | • "+" vs. "*", "+" 42 | Terminals/Productions listed earlier will have higher precedence. 43 | Terminals/Productions in the same line will have the same precedence. 44 | `, 45 | }, 46 | } 47 | 48 | for _, tc := range tests { 49 | t.Run(tc.name, func(t *testing.T) { 50 | assert.NoError(t, tc.G.Verify()) 51 | table, err := BuildParsingTable(tc.G, tc.precedences) 52 | 53 | if len(tc.expectedError) == 0 { 54 | assert.NoError(t, err) 55 | assert.True(t, table.Equal(tc.expectedTable)) 56 | } else { 57 | assert.EqualError(t, err, tc.expectedError) 58 | } 59 | }) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /parser/lr/simple/simple_test.go: -------------------------------------------------------------------------------- 1 | package simple 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | 8 | "github.com/moorara/algo/grammar" 9 | "github.com/moorara/algo/internal/parsertest" 10 | "github.com/moorara/algo/lexer" 11 | "github.com/moorara/algo/parser/lr" 12 | ) 13 | 14 | func TestNew(t *testing.T) { 15 | tests := []struct { 16 | name string 17 | L lexer.Lexer 18 | G *grammar.CFG 19 | precedences lr.PrecedenceLevels 20 | expectedError string 21 | }{ 22 | { 23 | name: "E→E+T", 24 | L: nil, 25 | G: parsertest.Grammars[3], 26 | precedences: lr.PrecedenceLevels{}, 27 | expectedError: ``, 28 | }, 29 | { 30 | name: "E→E+E", 31 | L: nil, 32 | G: parsertest.Grammars[4], 33 | precedences: lr.PrecedenceLevels{}, 34 | expectedError: `Error: Ambiguous Grammar 35 | Cause: Multiple conflicts in the parsing table: 36 | 1. Shift/Reduce conflict in ACTION[2, "*"] 37 | 2. Shift/Reduce conflict in ACTION[2, "+"] 38 | 3. Shift/Reduce conflict in ACTION[3, "*"] 39 | 4. Shift/Reduce conflict in ACTION[3, "+"] 40 | Resolution: Specify associativity and precedence for these Terminals/Productions: 41 | • "*" vs. "*", "+" 42 | • "+" vs. "*", "+" 43 | Terminals/Productions listed earlier will have higher precedence. 44 | Terminals/Productions in the same line will have the same precedence. 45 | `, 46 | }, 47 | } 48 | 49 | for _, tc := range tests { 50 | t.Run(tc.name, func(t *testing.T) { 51 | assert.NoError(t, tc.G.Verify()) 52 | p, err := New(tc.L, tc.G, tc.precedences) 53 | 54 | if len(tc.expectedError) == 0 { 55 | assert.NotNil(t, p) 56 | assert.NoError(t, err) 57 | } else { 58 | assert.Nil(t, p) 59 | assert.EqualError(t, err, tc.expectedError) 60 | } 61 | }) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /errors/format_test.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestDefaultErrorFormat(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | errs []error 13 | expectedString string 14 | }{ 15 | { 16 | name: "Nil", 17 | errs: nil, 18 | expectedString: "", 19 | }, 20 | { 21 | name: "Zero", 22 | errs: []error{}, 23 | expectedString: "", 24 | }, 25 | { 26 | name: "One", 27 | errs: []error{ 28 | new(fooError), 29 | }, 30 | expectedString: "error on foo\n", 31 | }, 32 | { 33 | name: "Multiple", 34 | errs: []error{ 35 | new(fooError), 36 | new(barError), 37 | new(bazError), 38 | }, 39 | expectedString: "error on foo\nerror on bar\nerror on baz\nsomething failed\n", 40 | }, 41 | } 42 | 43 | for _, tc := range tests { 44 | t.Run(tc.name, func(t *testing.T) { 45 | s := DefaultErrorFormat(tc.errs) 46 | 47 | assert.Equal(t, tc.expectedString, s) 48 | }) 49 | } 50 | } 51 | 52 | func TestBulletErrorFormat(t *testing.T) { 53 | tests := []struct { 54 | name string 55 | errs []error 56 | expectedString string 57 | }{ 58 | { 59 | name: "Nil", 60 | errs: nil, 61 | expectedString: "", 62 | }, 63 | { 64 | name: "Zero", 65 | errs: []error{}, 66 | expectedString: "", 67 | }, 68 | { 69 | name: "One", 70 | errs: []error{ 71 | new(fooError), 72 | }, 73 | expectedString: "1 error occurred:\n\n • error on foo\n", 74 | }, 75 | { 76 | name: "Multiple", 77 | errs: []error{ 78 | new(fooError), 79 | new(barError), 80 | new(bazError), 81 | }, 82 | expectedString: "3 errors occurred:\n\n • error on foo\n • error on bar\n • error on baz\n something failed\n", 83 | }, 84 | } 85 | 86 | for _, tc := range tests { 87 | t.Run(tc.name, func(t *testing.T) { 88 | s := BulletErrorFormat(tc.errs) 89 | 90 | assert.Equal(t, tc.expectedString, s) 91 | }) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /sort/benchmark_test.go: -------------------------------------------------------------------------------- 1 | package sort 2 | 3 | import ( 4 | "math/rand" 5 | "sort" 6 | "testing" 7 | "time" 8 | 9 | "github.com/moorara/algo/generic" 10 | ) 11 | 12 | const size = 100000 13 | 14 | func BenchmarkSort(b *testing.B) { 15 | seed := time.Now().UTC().UnixNano() 16 | r := rand.New(rand.NewSource(seed)) 17 | 18 | nums := randIntSlice(size) 19 | cmp := generic.NewCompareFunc[int]() 20 | 21 | b.Run("sort.Sort", func(b *testing.B) { 22 | for n := 0; n < b.N; n++ { 23 | a := make([]int, len(nums)) 24 | copy(a, nums) 25 | Shuffle[int](a, r) 26 | sort.Sort(sort.IntSlice(a)) // nolint directives: SA1032 27 | } 28 | }) 29 | 30 | b.Run("Heap", func(b *testing.B) { 31 | for n := 0; n < b.N; n++ { 32 | a := make([]int, len(nums)) 33 | copy(a, nums) 34 | Shuffle[int](a, r) 35 | Heap[int](a, cmp) 36 | } 37 | }) 38 | 39 | b.Run("Insertion", func(b *testing.B) { 40 | for n := 0; n < b.N; n++ { 41 | a := make([]int, len(nums)) 42 | copy(a, nums) 43 | Shuffle[int](a, r) 44 | Insertion[int](a, cmp) 45 | } 46 | }) 47 | 48 | b.Run("Merge", func(b *testing.B) { 49 | for n := 0; n < b.N; n++ { 50 | a := make([]int, len(nums)) 51 | copy(a, nums) 52 | Shuffle[int](a, r) 53 | Merge[int](a, cmp) 54 | } 55 | }) 56 | 57 | b.Run("MergeRec", func(b *testing.B) { 58 | for n := 0; n < b.N; n++ { 59 | a := make([]int, len(nums)) 60 | copy(a, nums) 61 | Shuffle[int](a, r) 62 | MergeRec[int](a, cmp) 63 | } 64 | }) 65 | 66 | b.Run("Quick", func(b *testing.B) { 67 | for n := 0; n < b.N; n++ { 68 | a := make([]int, len(nums)) 69 | copy(a, nums) 70 | Shuffle[int](a, r) 71 | Quick[int](a, cmp) 72 | } 73 | }) 74 | 75 | b.Run("Quick3Way", func(b *testing.B) { 76 | for n := 0; n < b.N; n++ { 77 | a := make([]int, len(nums)) 78 | copy(a, nums) 79 | Shuffle[int](a, r) 80 | Quick3Way[int](a, cmp) 81 | } 82 | }) 83 | 84 | b.Run("Shell", func(b *testing.B) { 85 | for n := 0; n < b.N; n++ { 86 | a := make([]int, len(nums)) 87 | copy(a, nums) 88 | Shuffle[int](a, r) 89 | Shell[int](a, cmp) 90 | } 91 | }) 92 | } 93 | -------------------------------------------------------------------------------- /sort/merge_test.go: -------------------------------------------------------------------------------- 1 | package sort 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/moorara/algo/generic" 7 | ) 8 | 9 | func TestMerge_int(t *testing.T) { 10 | tests := []struct { 11 | items []int 12 | }{ 13 | {[]int{}}, 14 | {[]int{20, 10, 30}}, 15 | {[]int{30, 20, 10, 40, 50}}, 16 | {[]int{90, 80, 70, 60, 50, 40, 30, 20, 10}}, 17 | } 18 | 19 | for _, tc := range tests { 20 | cmp := generic.NewCompareFunc[int]() 21 | Merge[int](tc.items, cmp) 22 | 23 | if !isSorted(tc.items, cmp) { 24 | t.Fatalf("%v is not sorted.", tc.items) 25 | } 26 | } 27 | } 28 | 29 | func TestMerge_string(t *testing.T) { 30 | tests := []struct { 31 | items []string 32 | }{ 33 | {[]string{}}, 34 | {[]string{"Milad", "Mona"}}, 35 | {[]string{"Alice", "Bob", "Alex", "Jackie"}}, 36 | {[]string{"Docker", "Kubernetes", "Go", "JavaScript", "Elixir", "React", "Redux", "Vue"}}, 37 | } 38 | 39 | for _, tc := range tests { 40 | cmp := generic.NewCompareFunc[string]() 41 | Merge[string](tc.items, cmp) 42 | 43 | if !isSorted(tc.items, cmp) { 44 | t.Fatalf("%v is not sorted.", tc.items) 45 | } 46 | } 47 | } 48 | 49 | func TestMergeRec_int(t *testing.T) { 50 | tests := []struct { 51 | items []int 52 | }{ 53 | {[]int{}}, 54 | {[]int{20, 10, 30}}, 55 | {[]int{30, 20, 10, 40, 50}}, 56 | {[]int{90, 80, 70, 60, 50, 40, 30, 20, 10}}, 57 | } 58 | 59 | for _, tc := range tests { 60 | cmp := generic.NewCompareFunc[int]() 61 | MergeRec[int](tc.items, cmp) 62 | 63 | if !isSorted(tc.items, cmp) { 64 | t.Fatalf("%v is not sorted.", tc.items) 65 | } 66 | } 67 | } 68 | 69 | func TestMergeRec_string(t *testing.T) { 70 | tests := []struct { 71 | items []string 72 | }{ 73 | {[]string{}}, 74 | {[]string{"Milad", "Mona"}}, 75 | {[]string{"Alice", "Bob", "Alex", "Jackie"}}, 76 | {[]string{"Docker", "Kubernetes", "Go", "JavaScript", "Elixir", "React", "Redux", "Vue"}}, 77 | } 78 | 79 | for _, tc := range tests { 80 | cmp := generic.NewCompareFunc[string]() 81 | MergeRec[string](tc.items, cmp) 82 | 83 | if !isSorted(tc.items, cmp) { 84 | t.Fatalf("%v is not sorted.", tc.items) 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /parser/lr/lookahead/lookahead_test.go: -------------------------------------------------------------------------------- 1 | package lookahead 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | 8 | "github.com/moorara/algo/grammar" 9 | "github.com/moorara/algo/internal/parsertest" 10 | "github.com/moorara/algo/lexer" 11 | "github.com/moorara/algo/parser/lr" 12 | ) 13 | 14 | func TestNew(t *testing.T) { 15 | tests := []struct { 16 | name string 17 | L lexer.Lexer 18 | G *grammar.CFG 19 | precedences lr.PrecedenceLevels 20 | expectedError string 21 | }{ 22 | { 23 | name: "S→L=R", 24 | L: nil, 25 | G: parsertest.Grammars[2], 26 | precedences: lr.PrecedenceLevels{}, 27 | expectedError: ``, 28 | }, 29 | { 30 | name: "E→E+T", 31 | L: nil, 32 | G: parsertest.Grammars[3], 33 | precedences: lr.PrecedenceLevels{}, 34 | expectedError: ``, 35 | }, 36 | { 37 | name: "E→E+E", 38 | L: nil, 39 | G: parsertest.Grammars[4], 40 | precedences: lr.PrecedenceLevels{}, 41 | expectedError: `Error: Ambiguous Grammar 42 | Cause: Multiple conflicts in the parsing table: 43 | 1. Shift/Reduce conflict in ACTION[2, "*"] 44 | 2. Shift/Reduce conflict in ACTION[2, "+"] 45 | 3. Shift/Reduce conflict in ACTION[3, "*"] 46 | 4. Shift/Reduce conflict in ACTION[3, "+"] 47 | Resolution: Specify associativity and precedence for these Terminals/Productions: 48 | • "*" vs. "*", "+" 49 | • "+" vs. "*", "+" 50 | Terminals/Productions listed earlier will have higher precedence. 51 | Terminals/Productions in the same line will have the same precedence. 52 | `, 53 | }, 54 | } 55 | 56 | for _, tc := range tests { 57 | t.Run(tc.name, func(t *testing.T) { 58 | assert.NoError(t, tc.G.Verify()) 59 | p, err := New(tc.L, tc.G, tc.precedences) 60 | 61 | if len(tc.expectedError) == 0 { 62 | assert.NotNil(t, p) 63 | assert.NoError(t, err) 64 | } else { 65 | assert.Nil(t, p) 66 | assert.EqualError(t, err, tc.expectedError) 67 | } 68 | }) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /parser/lr/canonical/parsing_table_test.go: -------------------------------------------------------------------------------- 1 | package canonical 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | 8 | "github.com/moorara/algo/grammar" 9 | "github.com/moorara/algo/internal/parsertest" 10 | "github.com/moorara/algo/parser/lr" 11 | ) 12 | 13 | func TestBuildParsingTable(t *testing.T) { 14 | pt := getTestParsingTables() 15 | 16 | tests := []struct { 17 | name string 18 | G *grammar.CFG 19 | precedences lr.PrecedenceLevels 20 | expectedTable *lr.ParsingTable 21 | expectedError string 22 | }{ 23 | { 24 | name: "S→CC", 25 | G: parsertest.Grammars[1], 26 | precedences: lr.PrecedenceLevels{}, 27 | expectedTable: pt[0], 28 | }, 29 | { 30 | name: "E→E+E", 31 | G: parsertest.Grammars[4], 32 | precedences: lr.PrecedenceLevels{}, 33 | expectedError: `Error: Ambiguous Grammar 34 | Cause: Multiple conflicts in the parsing table: 35 | 1. Shift/Reduce conflict in ACTION[2, "*"] 36 | 2. Shift/Reduce conflict in ACTION[2, "+"] 37 | 3. Shift/Reduce conflict in ACTION[3, "*"] 38 | 4. Shift/Reduce conflict in ACTION[3, "+"] 39 | 5. Shift/Reduce conflict in ACTION[4, "*"] 40 | 6. Shift/Reduce conflict in ACTION[4, "+"] 41 | 7. Shift/Reduce conflict in ACTION[5, "*"] 42 | 8. Shift/Reduce conflict in ACTION[5, "+"] 43 | Resolution: Specify associativity and precedence for these Terminals/Productions: 44 | • "*" vs. "*", "+" 45 | • "+" vs. "*", "+" 46 | Terminals/Productions listed earlier will have higher precedence. 47 | Terminals/Productions in the same line will have the same precedence. 48 | `, 49 | }, 50 | } 51 | 52 | for _, tc := range tests { 53 | t.Run(tc.name, func(t *testing.T) { 54 | assert.NoError(t, tc.G.Verify()) 55 | table, err := BuildParsingTable(tc.G, tc.precedences) 56 | 57 | if len(tc.expectedError) == 0 { 58 | assert.NoError(t, err) 59 | assert.True(t, table.Equal(tc.expectedTable)) 60 | } else { 61 | assert.EqualError(t, err, tc.expectedError) 62 | } 63 | }) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /parser/lr/canonical/canonical_test.go: -------------------------------------------------------------------------------- 1 | package canonical 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | 8 | "github.com/moorara/algo/grammar" 9 | "github.com/moorara/algo/internal/parsertest" 10 | "github.com/moorara/algo/lexer" 11 | "github.com/moorara/algo/parser/lr" 12 | ) 13 | 14 | func TestNew(t *testing.T) { 15 | tests := []struct { 16 | name string 17 | L lexer.Lexer 18 | G *grammar.CFG 19 | precedences lr.PrecedenceLevels 20 | expectedError string 21 | }{ 22 | { 23 | name: "S→CC", 24 | L: nil, 25 | G: parsertest.Grammars[1], 26 | precedences: lr.PrecedenceLevels{}, 27 | expectedError: ``, 28 | }, 29 | { 30 | name: "E→E+E", 31 | L: nil, 32 | G: parsertest.Grammars[4], 33 | precedences: lr.PrecedenceLevels{}, 34 | expectedError: `Error: Ambiguous Grammar 35 | Cause: Multiple conflicts in the parsing table: 36 | 1. Shift/Reduce conflict in ACTION[2, "*"] 37 | 2. Shift/Reduce conflict in ACTION[2, "+"] 38 | 3. Shift/Reduce conflict in ACTION[3, "*"] 39 | 4. Shift/Reduce conflict in ACTION[3, "+"] 40 | 5. Shift/Reduce conflict in ACTION[4, "*"] 41 | 6. Shift/Reduce conflict in ACTION[4, "+"] 42 | 7. Shift/Reduce conflict in ACTION[5, "*"] 43 | 8. Shift/Reduce conflict in ACTION[5, "+"] 44 | Resolution: Specify associativity and precedence for these Terminals/Productions: 45 | • "*" vs. "*", "+" 46 | • "+" vs. "*", "+" 47 | Terminals/Productions listed earlier will have higher precedence. 48 | Terminals/Productions in the same line will have the same precedence. 49 | `, 50 | }, 51 | } 52 | 53 | for _, tc := range tests { 54 | t.Run(tc.name, func(t *testing.T) { 55 | assert.NoError(t, tc.G.Verify()) 56 | p, err := New(tc.L, tc.G, tc.precedences) 57 | 58 | if len(tc.expectedError) == 0 { 59 | assert.NotNil(t, p) 60 | assert.NoError(t, err) 61 | } else { 62 | assert.Nil(t, p) 63 | assert.EqualError(t, err, tc.expectedError) 64 | } 65 | }) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /generic/tree.go: -------------------------------------------------------------------------------- 1 | package generic 2 | 3 | // TraverseOrder represents the order in which nodes are traversed in a tree. 4 | type TraverseOrder int 5 | 6 | const ( 7 | // VLR is a pre-order traversal from left to right. 8 | VLR TraverseOrder = iota 9 | // VRL is a pre-order traversal from right to left. 10 | VRL 11 | // LVR is an in-order traversal from left to right. 12 | LVR 13 | // RVL is an in-order traversal from right to left. 14 | RVL 15 | // LRV is a post-order traversal from left to right. 16 | LRV 17 | // RLV is a post-order traversal from right to left. 18 | RLV 19 | // Ascending is an ascending traversal. 20 | Ascending 21 | // Descending is a descending traversal. 22 | Descending 23 | ) 24 | 25 | type ( 26 | // VisitFunc1 is a generic function type used during tree traversal for processing nodes with a single value. 27 | VisitFunc1[T any] func(T) bool 28 | 29 | // VisitFunc2 is a generic function type used during tree traversal for processing nodes with key-value pairs. 30 | VisitFunc2[K, V any] func(K, V) bool 31 | ) 32 | 33 | type ( 34 | // Tree1 represents a generic tree structure where nodes contain a single value. 35 | Tree1[T any] interface { 36 | // Traverse performs a traversal of the tree using the specified traversal order 37 | // and yields the value of each node to the provided VisitFunc1 function. 38 | // If the function returns false, the traversal is halted. 39 | Traverse(TraverseOrder, VisitFunc1[T]) 40 | 41 | // DOT generates and returns a representation of the tree in DOT format. 42 | // This format is commonly used for visualizing graphs with Graphviz tools. 43 | DOT() string 44 | } 45 | 46 | // Tree2 represents a generic tree structure where nodes contain key-value pairs. 47 | Tree2[K, V any] interface { 48 | // Traverse performs a traversal of the tree using the specified traversal order 49 | // and yields the key-value pair of each node to the provided VisitFunc2 function. 50 | // If the function returns false, the traversal is halted. 51 | Traverse(TraverseOrder, VisitFunc2[K, V]) 52 | 53 | // DOT generates and returns a representation of the tree in DOT format. 54 | // This format is commonly used for visualizing graphs with Graphviz tools. 55 | DOT() string 56 | } 57 | ) 58 | -------------------------------------------------------------------------------- /dot/node_test.go: -------------------------------------------------------------------------------- 1 | package dot 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestNode(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | nodeName string 13 | group string 14 | label string 15 | color Color 16 | style Style 17 | shape Shape 18 | fontcolor Color 19 | fontname string 20 | expectedDOT string 21 | }{ 22 | { 23 | name: "EmptyNode", 24 | nodeName: "root", 25 | group: "", 26 | label: "", 27 | color: "", 28 | style: "", 29 | shape: "", 30 | fontcolor: "", 31 | fontname: "", 32 | expectedDOT: `root [];`, 33 | }, 34 | { 35 | name: "NodeWithLabel", 36 | nodeName: "root", 37 | group: "", 38 | label: `"id"`, 39 | color: "", 40 | style: "", 41 | shape: "", 42 | fontcolor: "", 43 | fontname: "", 44 | expectedDOT: `root [label="\"id\""];`, 45 | }, 46 | { 47 | name: "NodeWithGroup", 48 | nodeName: "struct0", 49 | group: "group0", 50 | label: " left| middle| right", 51 | color: ColorBlue, 52 | style: StyleBold, 53 | shape: ShapeBox, 54 | fontcolor: ColorGray, 55 | fontname: "", 56 | expectedDOT: `struct0 [group=group0, label=" left| middle| right", color=blue, style=bold, shape=box, fontcolor=gray];`, 57 | }, 58 | { 59 | name: "ComplexNode", 60 | nodeName: "struct1", 61 | group: "group1", 62 | label: "a | { b | { c | d | e } | f } | g | h", 63 | color: ColorNavy, 64 | style: StyleDashed, 65 | shape: ShapeOval, 66 | fontcolor: ColorBlack, 67 | fontname: "Arial", 68 | expectedDOT: `struct1 [group=group1, label="a | { b | { c | d | e } | f } | g | h", color=navy, style=dashed, shape=oval, fontcolor=black, fontname="Arial"];`, 69 | }, 70 | } 71 | 72 | for _, tc := range tests { 73 | t.Run(tc.name, func(t *testing.T) { 74 | n := NewNode(tc.nodeName, tc.group, tc.label, tc.color, tc.style, tc.shape, tc.fontcolor, tc.fontname) 75 | assert.Equal(t, tc.expectedDOT, n.DOT()) 76 | }) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /sort/quick.go: -------------------------------------------------------------------------------- 1 | package sort 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | 7 | "github.com/moorara/algo/generic" 8 | ) 9 | 10 | func partition[T any](a []T, lo, hi int, cmp generic.CompareFunc[T]) int { 11 | v := a[lo] 12 | i, j := lo, hi+1 13 | 14 | for { 15 | for i++; i < hi && cmp(a[i], v) < 0; i++ { 16 | } 17 | for j--; j > lo && cmp(a[j], v) > 0; j-- { 18 | } 19 | if i >= j { 20 | break 21 | } 22 | a[i], a[j] = a[j], a[i] 23 | } 24 | a[lo], a[j] = a[j], a[lo] 25 | 26 | return j 27 | } 28 | 29 | // Select finds the kth smallest item of an array in O(n) time on average. 30 | func Select[T any](a []T, k int, cmp generic.CompareFunc[T]) T { 31 | seed := time.Now().UTC().UnixNano() 32 | r := rand.New(rand.NewSource(seed)) 33 | Shuffle[T](a, r) 34 | 35 | lo, hi := 0, len(a)-1 36 | for lo < hi { 37 | j := partition[T](a, lo, hi, cmp) 38 | switch { 39 | case j < k: 40 | lo = j + 1 41 | case j > k: 42 | hi = j - 1 43 | default: 44 | return a[k] 45 | } 46 | } 47 | 48 | return a[k] 49 | } 50 | 51 | func quick[T any](a []T, lo, hi int, cmp generic.CompareFunc[T]) { 52 | if lo >= hi { 53 | return 54 | } 55 | 56 | j := partition[T](a, lo, hi, cmp) 57 | quick[T](a, lo, j-1, cmp) 58 | quick[T](a, j+1, hi, cmp) 59 | } 60 | 61 | // Quick implements the quick sort algorithm. 62 | func Quick[T any](a []T, cmp generic.CompareFunc[T]) { 63 | seed := time.Now().UTC().UnixNano() 64 | r := rand.New(rand.NewSource(seed)) 65 | Shuffle[T](a, r) 66 | 67 | quick[T](a, 0, len(a)-1, cmp) 68 | } 69 | 70 | func quick3Way[T any](a []T, lo, hi int, cmp generic.CompareFunc[T]) { 71 | if lo >= hi { 72 | return 73 | } 74 | 75 | v := a[lo] 76 | lt, i, gt := lo, lo+1, hi 77 | 78 | for i <= gt { 79 | c := cmp(a[i], v) 80 | switch { 81 | case c < 0: 82 | a[lt], a[i] = a[i], a[lt] 83 | lt++ 84 | i++ 85 | case c > 0: 86 | a[i], a[gt] = a[gt], a[i] 87 | gt-- 88 | default: 89 | i++ 90 | } 91 | } 92 | 93 | quick3Way[T](a, lo, lt-1, cmp) 94 | quick3Way[T](a, gt+1, hi, cmp) 95 | } 96 | 97 | // Quick3Way implements the 3-way version of quick sort algorithm. 98 | func Quick3Way[T any](a []T, cmp generic.CompareFunc[T]) { 99 | quick3Way[T](a, 0, len(a)-1, cmp) 100 | } 101 | -------------------------------------------------------------------------------- /dot/record.go: -------------------------------------------------------------------------------- 1 | package dot 2 | 3 | import ( 4 | "bytes" 5 | "strings" 6 | ) 7 | 8 | var replacer = strings.NewReplacer( 9 | `|`, `\|`, 10 | `{`, `\{`, 11 | `}`, `\}`, 12 | `<`, `\<`, 13 | `>`, `\>`, 14 | ) 15 | 16 | // Record represents a record. 17 | type Record struct { 18 | Fields []Field 19 | } 20 | 21 | // NewRecord creates a new record. 22 | func NewRecord(fields ...Field) Record { 23 | return Record{ 24 | Fields: fields, 25 | } 26 | } 27 | 28 | // AddField adds a new field to the record. 29 | func (r *Record) AddField(f Field) { 30 | r.Fields = append(r.Fields, f) 31 | } 32 | 33 | // Label returns the label for a node with record or Mrecord shape. 34 | func (r *Record) Label() string { 35 | fields := []string{} 36 | for _, f := range r.Fields { 37 | fields = append(fields, f.DOT()) 38 | } 39 | 40 | return strings.Join(fields, " | ") 41 | } 42 | 43 | // Field represents a field. 44 | type Field interface { 45 | DOT() string 46 | } 47 | 48 | // simpleField represents a simple field. 49 | type simpleField struct { 50 | Name string 51 | Label string 52 | } 53 | 54 | // NewSimpleField creates a new simple field. 55 | func NewSimpleField(name, label string) Field { 56 | return &simpleField{ 57 | Name: name, 58 | Label: label, 59 | } 60 | } 61 | 62 | // DOT generates a DOT representation of the simpleField object. 63 | func (f *simpleField) DOT() string { 64 | var b bytes.Buffer 65 | 66 | if f.Name != "" { 67 | b.WriteRune('<') 68 | b.WriteString(f.Name) 69 | b.WriteRune('>') 70 | } 71 | 72 | if f.Label != "" { 73 | label := f.Label 74 | label = replacer.Replace(label) 75 | 76 | b.WriteString(label) 77 | } 78 | 79 | return b.String() 80 | } 81 | 82 | // complexField represents a complex field. 83 | type complexField struct { 84 | Record Record 85 | } 86 | 87 | // NewComplexField creates a new complex field. 88 | func NewComplexField(record Record) Field { 89 | return &complexField{ 90 | Record: record, 91 | } 92 | } 93 | 94 | // DOT generates a DOT representation of the complexField object. 95 | func (f *complexField) DOT() string { 96 | var b bytes.Buffer 97 | 98 | b.WriteString("{ ") 99 | b.WriteString(f.Record.Label()) 100 | b.WriteString(" }") 101 | 102 | return b.String() 103 | } 104 | -------------------------------------------------------------------------------- /math/math.go: -------------------------------------------------------------------------------- 1 | // Package math provides utility mathematical functions. 2 | package math 3 | 4 | // GCD computes the greatest common divisor of two numbers. 5 | // It implements the Euclidean algorithm. 6 | func GCD(a, b uint64) uint64 { 7 | // Ensure a ≥ b 8 | a, b = max(a, b), min(a, b) 9 | 10 | /* 11 | * Let the quotient be q and the remainder be r, so that a = b × q + r 12 | * Replace a with b and b with r 13 | * Repeat this until the remainder r becomes 0 14 | * The GCD is the last non-zero remainder 15 | */ 16 | 17 | for b != 0 { 18 | a, b = b, a%b 19 | } 20 | 21 | return a 22 | } 23 | 24 | // Power2 computes 2 raised to the power of n. 25 | func Power2(n int) int { 26 | return 1 << n 27 | } 28 | 29 | // IsPowerOf2 determines whether or not a given integer n is a power of 2. 30 | func IsPowerOf2(n int) bool { 31 | if n <= 0 { 32 | return false 33 | } 34 | 35 | return n&(n-1) == 0 36 | } 37 | 38 | // IsPrime determines whether or not a given integer n is a prime number. 39 | func IsPrime(n int) bool { 40 | if n <= 1 { 41 | return false 42 | } 43 | 44 | // Check for prime numbers less than 100 directly 45 | if n == 2 || n == 3 || n == 5 || n == 7 || n == 11 || n == 13 || n == 17 || n == 19 || n == 23 || n == 29 || n == 31 || n == 37 || 46 | n == 41 || n == 43 || n == 47 || n == 53 || n == 59 || n == 61 || n == 67 || n == 71 || n == 73 || n == 79 || n == 83 || n == 89 || n == 97 { 47 | return true 48 | } else if n <= 100 { 49 | return false 50 | } 51 | 52 | // Check if n is prime using trial division 53 | for i := 2; i*i <= n; i++ { 54 | if n%i == 0 { 55 | return false 56 | } 57 | } 58 | 59 | return true 60 | } 61 | 62 | // LargestPrimeSmallerThan finds the largest prime number equal to or smaller than a given arbitrary number. 63 | // If n is less than 2, the function returns -1 (2 is the first prime number). 64 | // If n is prime, the function returns n. 65 | func LargestPrimeSmallerThan(n int) int { 66 | if n < 2 { 67 | return -1 // No prime number smaller than 2 68 | } 69 | 70 | for p := n; p >= 2; p-- { 71 | if IsPrime(p) { 72 | return p 73 | } 74 | } 75 | 76 | return -1 77 | } 78 | 79 | // SmallestPrimeLargerThan finds the smallest prime number equal to or larger than a given arbitrary number. 80 | // If n is prime, the function returns n. 81 | func SmallestPrimeLargerThan(n int) int { 82 | for p := n; ; p++ { 83 | if IsPrime(p) { 84 | return p 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /set/set.go: -------------------------------------------------------------------------------- 1 | // Package set implements a set data structure. 2 | // A set is a collection of objects without any particular order. 3 | package set 4 | 5 | import ( 6 | "fmt" 7 | "math/rand" 8 | "time" 9 | 10 | "github.com/moorara/algo/generic" 11 | ) 12 | 13 | var r = rand.New(rand.NewSource(time.Now().UnixNano())) 14 | 15 | // Set represents a set abstract data type. 16 | type Set[T any] interface { 17 | fmt.Stringer 18 | generic.Equaler[Set[T]] 19 | generic.Cloner[Set[T]] 20 | generic.Collection1[T] 21 | 22 | CloneEmpty() Set[T] 23 | IsSubset(Set[T]) bool 24 | IsSuperset(Set[T]) bool 25 | Union(...Set[T]) Set[T] 26 | Intersection(...Set[T]) Set[T] 27 | Difference(...Set[T]) Set[T] 28 | } 29 | 30 | // Powerset creates and returns the power set of a given set. 31 | // The power set of a set is the set of all subsets, including the empty set and the set itself. 32 | // 33 | // - Set[T]: A set 34 | // - Set[Set[T]]: The power set (the set of all subsets) 35 | // 36 | // The power set implementation is chosen based on the concrete type of the input set. 37 | // For custom Set implementations, the basic set is used to construct the power set. 38 | func Powerset[T any](s Set[T]) Set[Set[T]] { 39 | switch s.(type) { 40 | case *set[T]: 41 | return powerset(s) 42 | case *stableSet[T]: 43 | return stablePowerset(s) 44 | case *sortedSet[T]: 45 | return sortedPowerset(s) 46 | case *hashSet[T]: 47 | return hashPowerset(s) 48 | default: 49 | return powerset(s) 50 | } 51 | } 52 | 53 | // Partitions creates and returns the set of all partitions for a given set. 54 | // A partition of a set is a grouping of its elements into non-empty subsets, 55 | // in such a way that every element is included in exactly one subset. 56 | // 57 | // - Set[T]: A set 58 | // - Set[Set[T]]: A partition (a set of non-empty disjoint subsets with every element included) 59 | // - Set[Set[Set[T]]]: The set of all partitions 60 | // 61 | // The partition implementation is chosen based on the concrete type of the input set. 62 | // For custom Set implementations, the basic set is used to construct partitions and the set of all partitions. 63 | func Partitions[T any](s Set[T]) Set[Set[Set[T]]] { 64 | switch s.(type) { 65 | case *set[T]: 66 | return partitions(s) 67 | case *stableSet[T]: 68 | return stablePartitions(s) 69 | case *sortedSet[T]: 70 | return sortedPartitions(s) 71 | case *hashSet[T]: 72 | return hashPartitions(s) 73 | default: 74 | return partitions(s) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /grammar/cfg_follow.go: -------------------------------------------------------------------------------- 1 | package grammar 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/moorara/algo/generic" 8 | "github.com/moorara/algo/set" 9 | "github.com/moorara/algo/sort" 10 | "github.com/moorara/algo/symboltable" 11 | ) 12 | 13 | // FOLLOW is the FIRST function associated with a context-free grammar. 14 | // 15 | // FOLLOW(A), for non-terminal A, is the set of terminals 𝑎 16 | // that can appear immediately to the right of A in some sentential form; 17 | // that is; the set of terminals 𝑎 such that there exists a derivation of the form S ⇒* αAaβ 18 | // for some α and β strings of grammar symbols (terminals and non-terminals). 19 | type FOLLOW func(NonTerminal) *TerminalsAndEndmarker 20 | 21 | // TerminalsAndEndmarker is the return type for the FOLLOW function. 22 | // 23 | // It contains: 24 | // 25 | // - A set of terminals that can appear immediately after the given non-terminal. 26 | // - A flag indicating whether the special endmarker symbol is included in the FOLLOW set. 27 | type TerminalsAndEndmarker struct { 28 | Terminals set.Set[Terminal] 29 | IncludesEndmarker bool 30 | } 31 | 32 | // newTerminalsAndEndmarker creates a new TerminalsAndEndmarker instance with the given set of terminals. 33 | func newTerminalsAndEndmarker(terms ...Terminal) *TerminalsAndEndmarker { 34 | return &TerminalsAndEndmarker{ 35 | Terminals: set.New(EqTerminal, terms...), 36 | IncludesEndmarker: false, 37 | } 38 | } 39 | 40 | // String returns a string representation of the FOLLOW set. 41 | func (s *TerminalsAndEndmarker) String() string { 42 | members := []string{} 43 | 44 | for term := range s.Terminals.All() { 45 | members = append(members, term.String()) 46 | } 47 | 48 | if s.IncludesEndmarker { 49 | members = append(members, Endmarker.String()) 50 | } 51 | 52 | sort.Quick(members, generic.NewCompareFunc[string]()) 53 | 54 | return fmt.Sprintf("{%s}", strings.Join(members, ", ")) 55 | } 56 | 57 | func eqTerminalsAndEndmarker(lhs, rhs *TerminalsAndEndmarker) bool { 58 | return lhs.Terminals.Equal(rhs.Terminals) && lhs.IncludesEndmarker == rhs.IncludesEndmarker 59 | } 60 | 61 | // followTable is the type for a table that stores the FOLLOW set for each non-terminal. 62 | type followTable symboltable.SymbolTable[NonTerminal, *TerminalsAndEndmarker] 63 | 64 | func newFollowTable() followTable { 65 | return symboltable.NewQuadraticHashTable( 66 | HashNonTerminal, 67 | EqNonTerminal, 68 | eqTerminalsAndEndmarker, 69 | symboltable.HashOpts{}, 70 | ) 71 | } 72 | -------------------------------------------------------------------------------- /list/stack.go: -------------------------------------------------------------------------------- 1 | package list 2 | 3 | import "github.com/moorara/algo/generic" 4 | 5 | // Stack represents a stack abstract data type. 6 | type Stack[T any] interface { 7 | // Size returns the number of values on the stack. 8 | Size() int 9 | // IsEmpty returns true if the stack is empty. 10 | IsEmpty() bool 11 | // Enqueue adds a new value to the stack. 12 | Push(T) 13 | // Dequeue removes a value from the stack. 14 | Pop() (T, bool) 15 | // Peek returns the next value on stack without removing it from the stack. 16 | Peek() (T, bool) 17 | // Contains returns true if a given value is already on the stack. 18 | Contains(T) bool 19 | } 20 | 21 | type arrayStack[T any] struct { 22 | nodeSize int 23 | equal generic.EqualFunc[T] 24 | 25 | listSize int 26 | topIndex int 27 | topNode *arrayNode[T] 28 | } 29 | 30 | // NewStack creates a new array-list stack. 31 | func NewStack[T any](nodeSize int, equal generic.EqualFunc[T]) Stack[T] { 32 | return &arrayStack[T]{ 33 | nodeSize: nodeSize, 34 | equal: equal, 35 | 36 | listSize: 0, 37 | topIndex: -1, 38 | topNode: nil, 39 | } 40 | } 41 | 42 | func (s *arrayStack[T]) Size() int { 43 | return s.listSize 44 | } 45 | 46 | func (s *arrayStack[T]) IsEmpty() bool { 47 | return s.listSize == 0 48 | } 49 | 50 | func (s *arrayStack[T]) Push(val T) { 51 | s.listSize++ 52 | s.topIndex++ 53 | 54 | if s.topNode == nil { 55 | s.topNode = newArrayNode[T](s.nodeSize, nil) 56 | } else if s.topIndex == s.nodeSize { 57 | s.topNode = newArrayNode[T](s.nodeSize, s.topNode) 58 | s.topIndex = 0 59 | } 60 | 61 | s.topNode.block[s.topIndex] = val 62 | } 63 | 64 | func (s *arrayStack[T]) Pop() (T, bool) { 65 | if s.IsEmpty() { 66 | var zero T 67 | return zero, false 68 | } 69 | 70 | val := s.topNode.block[s.topIndex] 71 | s.topIndex-- 72 | s.listSize-- 73 | 74 | if s.topIndex == -1 { 75 | s.topNode = s.topNode.next 76 | if s.topNode != nil { 77 | s.topIndex = s.nodeSize - 1 78 | } 79 | } 80 | 81 | return val, true 82 | } 83 | 84 | func (s *arrayStack[T]) Peek() (T, bool) { 85 | if s.IsEmpty() { 86 | var zero T 87 | return zero, false 88 | } 89 | 90 | return s.topNode.block[s.topIndex], true 91 | } 92 | 93 | func (s *arrayStack[T]) Contains(val T) bool { 94 | n := s.topNode 95 | i := s.topIndex 96 | 97 | for n != nil { 98 | if s.equal(n.block[i], val) { 99 | return true 100 | } 101 | 102 | if i--; i < 0 { 103 | n = n.next 104 | i = s.nodeSize - 1 105 | } 106 | } 107 | 108 | return false 109 | } 110 | -------------------------------------------------------------------------------- /dot/graph.go: -------------------------------------------------------------------------------- 1 | package dot 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | ) 7 | 8 | // Graph represents a graph. 9 | type Graph struct { 10 | Strict bool 11 | Digraph bool 12 | Concentrate bool 13 | Name string 14 | RankDir RankDir 15 | NodeColor Color 16 | NodeStyle Style 17 | NodeShape Shape 18 | Nodes []Node 19 | Edges []Edge 20 | Subgraphs []Subgraph 21 | } 22 | 23 | // NewGraph creates a new graph. 24 | func NewGraph(strict, digraph, concentrate bool, name string, rankDir RankDir, nodeColor Color, nodeStyle Style, nodeShape Shape) Graph { 25 | if name != "" { 26 | name = fmt.Sprintf("%q", name) 27 | } 28 | 29 | return Graph{ 30 | Strict: strict, 31 | Digraph: digraph, 32 | Concentrate: concentrate, 33 | Name: name, 34 | RankDir: rankDir, 35 | NodeColor: nodeColor, 36 | NodeStyle: nodeStyle, 37 | NodeShape: nodeShape, 38 | Nodes: make([]Node, 0), 39 | Edges: make([]Edge, 0), 40 | Subgraphs: make([]Subgraph, 0), 41 | } 42 | } 43 | 44 | // AddNode adds a new node to this graph. 45 | func (g *Graph) AddNode(nodes ...Node) { 46 | g.Nodes = append(g.Nodes, nodes...) 47 | } 48 | 49 | // AddEdge adds a new edge to this graph. 50 | func (g *Graph) AddEdge(edges ...Edge) { 51 | g.Edges = append(g.Edges, edges...) 52 | } 53 | 54 | // AddSubgraph adds a new subgraph to this graph. 55 | func (g *Graph) AddSubgraph(subgraphs ...Subgraph) { 56 | g.Subgraphs = append(g.Subgraphs, subgraphs...) 57 | } 58 | 59 | // DOT generates a DOT representation of the Graph object. 60 | func (g *Graph) DOT() string { 61 | first := true 62 | var b bytes.Buffer 63 | 64 | if g.Strict { 65 | b.WriteString("strict ") 66 | } 67 | 68 | if g.Digraph { 69 | b.WriteString("digraph ") 70 | } else { 71 | b.WriteString("graph ") 72 | } 73 | 74 | if g.Name != "" { 75 | b.WriteString(g.Name) 76 | b.WriteString(" ") 77 | } 78 | 79 | b.WriteString("{\n") 80 | 81 | first = addAttr(&b, first, 2, "rankdir", string(g.RankDir)) 82 | _ = addAttr(&b, first, 2, "concentrate", fmt.Sprintf("%t", g.Concentrate)) 83 | 84 | first = true 85 | addIndent(&b, 2) 86 | b.WriteString("node [") 87 | first = addListAttr(&b, first, "color", string(g.NodeColor)) 88 | first = addListAttr(&b, first, "style", string(g.NodeStyle)) 89 | _ = addListAttr(&b, first, "shape", string(g.NodeShape)) 90 | b.WriteString("];\n") 91 | first = false 92 | 93 | first = addSubgraphs(&b, first, 2, g.Subgraphs) 94 | first = addNodes(&b, first, 2, g.Nodes) 95 | addEdges(&b, first, 2, g.Edges) 96 | b.WriteString("}") 97 | 98 | return b.String() 99 | } 100 | -------------------------------------------------------------------------------- /grammar/cfg_first.go: -------------------------------------------------------------------------------- 1 | package grammar 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/moorara/algo/generic" 8 | "github.com/moorara/algo/set" 9 | "github.com/moorara/algo/sort" 10 | "github.com/moorara/algo/symboltable" 11 | ) 12 | 13 | // FIRST is the FIRST function associated with a context-free grammar. 14 | // 15 | // FIRST(α), where α is any string of grammar symbols (terminals and non-terminals), 16 | // is the set of terminals that begin strings derived from α. 17 | // If α ⇒* ε, then ε is also in FIRST(α). 18 | type FIRST func(String[Symbol]) *TerminalsAndEmpty 19 | 20 | // TerminalsAndEmpty is the return type for the FIRST function. 21 | // 22 | // It contains: 23 | // 24 | // - A set of terminals that may appear at the beginning of strings derived from α. 25 | // - A flag indicating whether the empty string ε is included in the FIRST set.. 26 | type TerminalsAndEmpty struct { 27 | Terminals set.Set[Terminal] 28 | IncludesEmpty bool 29 | } 30 | 31 | // newTerminalsAndEmpty creates a new TerminalsAndEmpty instance with the given set of terminals. 32 | func newTerminalsAndEmpty(terms ...Terminal) *TerminalsAndEmpty { 33 | return &TerminalsAndEmpty{ 34 | Terminals: set.New(EqTerminal, terms...), 35 | IncludesEmpty: false, 36 | } 37 | } 38 | 39 | // String returns a string representation of the FIRST set. 40 | func (s *TerminalsAndEmpty) String() string { 41 | members := []string{} 42 | 43 | for term := range s.Terminals.All() { 44 | members = append(members, term.String()) 45 | } 46 | 47 | if s.IncludesEmpty { 48 | members = append(members, "ε") 49 | } 50 | 51 | sort.Quick(members, generic.NewCompareFunc[string]()) 52 | 53 | return fmt.Sprintf("{%s}", strings.Join(members, ", ")) 54 | } 55 | 56 | func eqTerminalsAndEmpty(lhs, rhs *TerminalsAndEmpty) bool { 57 | return lhs.Terminals.Equal(rhs.Terminals) && lhs.IncludesEmpty == rhs.IncludesEmpty 58 | } 59 | 60 | // firstBySymbolTable is the type for a table that stores the FIRST set for each grammar symbol. 61 | type firstBySymbolTable symboltable.SymbolTable[Symbol, *TerminalsAndEmpty] 62 | 63 | func newFirstBySymbolTable() firstBySymbolTable { 64 | return symboltable.NewQuadraticHashTable( 65 | HashSymbol, 66 | EqSymbol, 67 | eqTerminalsAndEmpty, 68 | symboltable.HashOpts{}, 69 | ) 70 | } 71 | 72 | // firstByStringTable is the type for a table that stores the FIRST set for strings of grammar symbols. 73 | type firstByStringTable symboltable.SymbolTable[String[Symbol], *TerminalsAndEmpty] 74 | 75 | func newFirstByStringTable() firstByStringTable { 76 | return symboltable.NewQuadraticHashTable( 77 | HashString, 78 | EqString, 79 | eqTerminalsAndEmpty, 80 | symboltable.HashOpts{}, 81 | ) 82 | } 83 | -------------------------------------------------------------------------------- /dot/subgraph.go: -------------------------------------------------------------------------------- 1 | package dot 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | ) 7 | 8 | // Subgraph represents a subgraph. 9 | type Subgraph struct { 10 | Name string 11 | Label string 12 | Color Color 13 | Style Style 14 | Rank Rank 15 | RankDir RankDir 16 | NodeColor Color 17 | NodeStyle Style 18 | NodeShape Shape 19 | Nodes []Node 20 | Edges []Edge 21 | Subgraphs []Subgraph 22 | } 23 | 24 | // NewSubgraph creates a new subgraph. 25 | func NewSubgraph(name, label string, color Color, style Style, rank Rank, rankDir RankDir, nodeColor Color, nodeStyle Style, nodeShape Shape) Subgraph { 26 | return Subgraph{ 27 | Name: name, 28 | Label: label, 29 | Color: color, 30 | Style: style, 31 | Rank: rank, 32 | RankDir: rankDir, 33 | NodeColor: nodeColor, 34 | NodeStyle: nodeStyle, 35 | NodeShape: nodeShape, 36 | Nodes: make([]Node, 0), 37 | Edges: make([]Edge, 0), 38 | Subgraphs: make([]Subgraph, 0), 39 | } 40 | } 41 | 42 | // AddNode adds a new node to this subgraph. 43 | func (s *Subgraph) AddNode(nodes ...Node) { 44 | s.Nodes = append(s.Nodes, nodes...) 45 | } 46 | 47 | // AddEdge adds a new edge to this subgraph. 48 | func (s *Subgraph) AddEdge(edges ...Edge) { 49 | s.Edges = append(s.Edges, edges...) 50 | } 51 | 52 | // AddSubgraph adds a new subgraph to this subgraph. 53 | func (s *Subgraph) AddSubgraph(subgraphs ...Subgraph) { 54 | s.Subgraphs = append(s.Subgraphs, subgraphs...) 55 | } 56 | 57 | // DOT generates a DOT representation of the Subgraph object. 58 | func (s *Subgraph) DOT(indent int) string { 59 | first := true 60 | var b bytes.Buffer 61 | 62 | addIndent(&b, indent) 63 | b.WriteString("subgraph ") 64 | if s.Name != "" { 65 | b.WriteString(s.Name) 66 | b.WriteString(" ") 67 | } 68 | b.WriteString("{\n") 69 | 70 | first = addAttr(&b, first, indent+2, "label", fmt.Sprintf("%q", s.Label)) 71 | first = addAttr(&b, first, indent+2, "color", string(s.Color)) 72 | first = addAttr(&b, first, indent+2, "style", string(s.Style)) 73 | first = addAttr(&b, first, indent+2, "rank", string(s.Rank)) 74 | _ = addAttr(&b, first, indent+2, "rankdir", string(s.RankDir)) 75 | 76 | first = true 77 | addIndent(&b, indent+2) 78 | b.WriteString("node [") 79 | first = addListAttr(&b, first, "color", string(s.NodeColor)) 80 | first = addListAttr(&b, first, "style", string(s.NodeStyle)) 81 | _ = addListAttr(&b, first, "shape", string(s.NodeShape)) 82 | b.WriteString("];\n") 83 | first = false 84 | 85 | first = addSubgraphs(&b, first, indent+2, s.Subgraphs) 86 | first = addNodes(&b, first, indent+2, s.Nodes) 87 | _ = addEdges(&b, first, indent+2, s.Edges) 88 | 89 | addIndent(&b, indent) 90 | b.WriteString("}") 91 | 92 | return b.String() 93 | } 94 | -------------------------------------------------------------------------------- /dot/edge_test.go: -------------------------------------------------------------------------------- 1 | package dot 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestEdge(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | from string 13 | to string 14 | edgeType EdgeType 15 | edgeDir EdgeDir 16 | label string 17 | color Color 18 | style Style 19 | arrowHead ArrowType 20 | arrowTail ArrowType 21 | expectedDOT string 22 | }{ 23 | { 24 | name: "DirectedEdge", 25 | from: "root", 26 | to: "left", 27 | edgeType: EdgeTypeDirected, 28 | edgeDir: "", 29 | label: "", 30 | color: "", 31 | style: "", 32 | arrowHead: "", 33 | arrowTail: "", 34 | expectedDOT: `root -> left [];`, 35 | }, 36 | { 37 | name: "UndirectedEdge", 38 | from: "root", 39 | to: "right", 40 | edgeType: EdgeTypeUndirected, 41 | edgeDir: "", 42 | label: "", 43 | color: "", 44 | style: "", 45 | arrowHead: "", 46 | arrowTail: "", 47 | expectedDOT: `root -- right [];`, 48 | }, 49 | { 50 | name: "EdgeWithLabel", 51 | from: "root", 52 | to: "right", 53 | edgeType: EdgeTypeUndirected, 54 | edgeDir: "", 55 | label: `"id"`, 56 | color: "", 57 | style: "", 58 | arrowHead: "", 59 | arrowTail: "", 60 | expectedDOT: `root -- right [label="\"id\""];`, 61 | }, 62 | { 63 | name: "DirectedEdgeWithProperties", 64 | from: "parent", 65 | to: "child", 66 | edgeType: EdgeTypeDirected, 67 | edgeDir: EdgeDirNone, 68 | label: "red", 69 | color: ColorGold, 70 | style: StyleDashed, 71 | arrowHead: ArrowTypeDot, 72 | arrowTail: ArrowTypeODot, 73 | expectedDOT: `parent -> child [dirType=none, label="red", color=gold, style=dashed, arrowhead=dot, arrowtail=odot];`, 74 | }, 75 | { 76 | name: "UndirectedEdgeWithProperties", 77 | from: "parent", 78 | to: "child", 79 | edgeType: EdgeTypeUndirected, 80 | edgeDir: EdgeDirBoth, 81 | label: "black", 82 | color: ColorOrchid, 83 | style: StyleDotted, 84 | arrowHead: ArrowTypeBox, 85 | arrowTail: ArrowTypeOBox, 86 | expectedDOT: `parent -- child [dirType=both, label="black", color=orchid, style=dotted, arrowhead=box, arrowtail=obox];`, 87 | }, 88 | } 89 | 90 | for _, tc := range tests { 91 | t.Run(tc.name, func(t *testing.T) { 92 | e := NewEdge(tc.from, tc.to, tc.edgeType, tc.edgeDir, tc.label, tc.color, tc.style, tc.arrowHead, tc.arrowTail) 93 | assert.Equal(t, tc.expectedDOT, e.DOT()) 94 | }) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /parser/lr/action.go: -------------------------------------------------------------------------------- 1 | package lr 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/moorara/algo/grammar" 7 | "github.com/moorara/algo/hash" 8 | "github.com/moorara/algo/set" 9 | ) 10 | 11 | var ( 12 | EqAction = eqAction 13 | CmpAction = cmpAction 14 | HashAction = hashAction 15 | 16 | hashActionType = hash.HashFuncForInt[ActionType](nil) 17 | ) 18 | 19 | // ActionType enumerates the possible types of actions in an LR parser. 20 | type ActionType int 21 | 22 | const ( 23 | SHIFT ActionType = 1 + iota // Advance to the next state by consuming input. 24 | REDUCE // Apply a production to reduce symbols on the stack. 25 | ACCEPT // Accept the input as successfully parsed. 26 | ERROR // Signal an error in parsing. 27 | ) 28 | 29 | // Action represents an action in the LR parsing table or automaton. 30 | type Action struct { 31 | Type ActionType 32 | State State // Only set for SHIFT actions 33 | Production *grammar.Production // Only set for REDUCE actions 34 | } 35 | 36 | // String returns a string representation of an action. 37 | func (a *Action) String() string { 38 | switch a.Type { 39 | case SHIFT: 40 | return fmt.Sprintf("SHIFT %d", a.State) 41 | case REDUCE: 42 | return fmt.Sprintf("REDUCE %s", a.Production) 43 | case ACCEPT: 44 | return "ACCEPT" 45 | case ERROR: 46 | return "ERROR" 47 | } 48 | 49 | return fmt.Sprintf("INVALID ACTION(%d)", a.Type) 50 | } 51 | 52 | // Equal determines whether or not two actions are the same. 53 | func (a *Action) Equal(rhs *Action) bool { 54 | return a.Type == rhs.Type && 55 | a.State == rhs.State && 56 | equalProductions(a.Production, rhs.Production) 57 | } 58 | 59 | func equalProductions(lhs, rhs *grammar.Production) bool { 60 | if lhs == nil || rhs == nil { 61 | return lhs == rhs 62 | } 63 | return lhs.Equal(rhs) 64 | } 65 | 66 | func eqAction(lhs, rhs *Action) bool { 67 | return lhs.Equal(rhs) 68 | } 69 | 70 | func eqActionSet(lhs, rhs set.Set[*Action]) bool { 71 | return lhs.Equal(rhs) 72 | } 73 | 74 | func cmpAction(lhs, rhs *Action) int { 75 | if lhs.Type == SHIFT && rhs.Type == SHIFT { 76 | return int(lhs.State) - int(rhs.State) 77 | } else if lhs.Type == REDUCE && rhs.Type == REDUCE { 78 | return grammar.CmpProduction(lhs.Production, rhs.Production) 79 | } 80 | 81 | return int(lhs.Type) - int(rhs.Type) 82 | } 83 | 84 | func hashAction(a *Action) uint64 { 85 | var hash uint64 86 | 87 | // Use a polynomial rolling hash to combine the individual hashes. 88 | const B = 0x9E3779B185EBCA87 89 | 90 | hash = hash*B + hashActionType(a.Type) 91 | hash = hash*B + HashState(a.State) 92 | 93 | if a.Production != nil { 94 | hash = hash*B + grammar.HashProduction(a.Production) 95 | } 96 | 97 | return hash 98 | } 99 | -------------------------------------------------------------------------------- /list/queue.go: -------------------------------------------------------------------------------- 1 | package list 2 | 3 | import "github.com/moorara/algo/generic" 4 | 5 | // Queue represents a queue abstract data type. 6 | type Queue[T any] interface { 7 | // Size returns the number of values in the queue. 8 | Size() int 9 | // IsEmpty returns true if the queue is empty. 10 | IsEmpty() bool 11 | // Enqueue adds a new value to the queue. 12 | Enqueue(T) 13 | // Dequeue removes a value from the queue. 14 | Dequeue() (T, bool) 15 | // Peek returns the next value in queue without removing it from the queue. 16 | Peek() (T, bool) 17 | // Contains returns true if a given value is already in the queue. 18 | Contains(T) bool 19 | } 20 | 21 | type arrayQueue[T any] struct { 22 | nodeSize int 23 | equal generic.EqualFunc[T] 24 | 25 | listSize int 26 | frontIndex int 27 | rearIndex int 28 | frontNode *arrayNode[T] 29 | rearNode *arrayNode[T] 30 | } 31 | 32 | // NewQueue creates a new array-list queue. 33 | func NewQueue[T any](nodeSize int, equal generic.EqualFunc[T]) Queue[T] { 34 | return &arrayQueue[T]{ 35 | nodeSize: nodeSize, 36 | equal: equal, 37 | 38 | listSize: 0, 39 | frontIndex: -1, 40 | rearIndex: -1, 41 | frontNode: nil, 42 | rearNode: nil, 43 | } 44 | } 45 | 46 | func (q *arrayQueue[T]) Size() int { 47 | return q.listSize 48 | } 49 | 50 | func (q *arrayQueue[T]) IsEmpty() bool { 51 | return q.listSize == 0 52 | } 53 | 54 | func (q *arrayQueue[T]) Enqueue(val T) { 55 | q.listSize++ 56 | q.rearIndex++ 57 | 58 | if q.frontNode == nil { 59 | q.frontNode, q.frontIndex = newArrayNode[T](q.nodeSize, nil), 0 60 | q.rearNode = q.frontNode 61 | } else if q.rearIndex == q.nodeSize { 62 | q.rearNode.next = newArrayNode[T](q.nodeSize, nil) 63 | q.rearNode, q.rearIndex = q.rearNode.next, 0 64 | } 65 | 66 | q.rearNode.block[q.rearIndex] = val 67 | } 68 | 69 | func (q *arrayQueue[T]) Dequeue() (T, bool) { 70 | if q.IsEmpty() { 71 | var zero T 72 | return zero, false 73 | } 74 | 75 | val := q.frontNode.block[q.frontIndex] 76 | q.frontIndex++ 77 | q.listSize-- 78 | 79 | if q.frontIndex == q.nodeSize { 80 | q.frontNode, q.frontIndex = q.frontNode.next, 0 81 | } 82 | 83 | return val, true 84 | } 85 | 86 | func (q *arrayQueue[T]) Peek() (T, bool) { 87 | if q.IsEmpty() { 88 | var zero T 89 | return zero, false 90 | } 91 | 92 | return q.frontNode.block[q.frontIndex], true 93 | } 94 | 95 | func (q *arrayQueue[T]) Contains(val T) bool { 96 | n, i := q.frontNode, q.frontIndex 97 | 98 | for n != nil && (n != q.rearNode || i <= q.rearIndex) { 99 | if q.equal(n.block[i], val) { 100 | return true 101 | } 102 | 103 | if i++; i == q.nodeSize { 104 | n = n.next 105 | i = 0 106 | } 107 | } 108 | 109 | return false 110 | } 111 | -------------------------------------------------------------------------------- /lexer/input/utf8.go: -------------------------------------------------------------------------------- 1 | package input 2 | 3 | const ( 4 | // The default lowest and highest continuation byte. 5 | locb = 0b10000000 6 | hicb = 0b10111111 7 | ) 8 | 9 | const ( 10 | maskx = 0b00111111 11 | mask2 = 0b00011111 12 | mask3 = 0b00001111 13 | mask4 = 0b00000111 14 | ) 15 | 16 | const ( 17 | // The names of these constants are chosen to give nice alignment in the table below. 18 | // The first nibble is an index into acceptRanges or F for special one-byte cases. 19 | // The second nibble is the Rune length or the status for the special one-byte case. 20 | xx = 0xF1 // Invalid: size 1 21 | as = 0xF0 // ASCII: size 1 22 | s1 = 0x02 // accept 0, size 2 23 | s2 = 0x13 // accept 1, size 3 24 | s3 = 0x03 // accept 0, size 3 25 | s4 = 0x23 // accept 2, size 3 26 | s5 = 0x34 // accept 3, size 4 27 | s6 = 0x04 // accept 0, size 4 28 | s7 = 0x44 // accept 4, size 4 29 | ) 30 | 31 | // first is information about the first byte in a UTF-8 sequence. 32 | var first = [256]uint8{ 33 | // 1 2 3 4 5 6 7 8 9 A B C D E F 34 | as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x00-0x0F 35 | as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x10-0x1F 36 | as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x20-0x2F 37 | as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x30-0x3F 38 | as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x40-0x4F 39 | as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x50-0x5F 40 | as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x60-0x6F 41 | as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x70-0x7F 42 | // 1 2 3 4 5 6 7 8 9 A B C D E F 43 | xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, // 0x80-0x8F 44 | xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, // 0x90-0x9F 45 | xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, // 0xA0-0xAF 46 | xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, // 0xB0-0xBF 47 | xx, xx, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, // 0xC0-0xCF 48 | s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, // 0xD0-0xDF 49 | s2, s3, s3, s3, s3, s3, s3, s3, s3, s3, s3, s3, s3, s4, s3, s3, // 0xE0-0xEF 50 | s5, s6, s6, s6, s7, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, // 0xF0-0xFF 51 | } 52 | 53 | // acceptRange gives the range of valid values for the second byte in a UTF-8 sequence. 54 | type acceptRange struct { 55 | lo uint8 // lowest value for second byte. 56 | hi uint8 // highest value for second byte. 57 | } 58 | 59 | // acceptRanges has size 16 to avoid bounds checks in the code that uses it. 60 | var acceptRanges = [16]acceptRange{ 61 | 0: {locb, hicb}, 62 | 1: {0xA0, hicb}, 63 | 2: {locb, 0x9F}, 64 | 3: {0x90, hicb}, 65 | 4: {locb, 0x8F}, 66 | } 67 | -------------------------------------------------------------------------------- /radixsort/benchmark_test.go: -------------------------------------------------------------------------------- 1 | package radixsort 2 | 3 | import ( 4 | "math/rand" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | const ( 10 | slen = 128 11 | size = 100000 12 | chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 13 | ) 14 | 15 | var r *rand.Rand 16 | 17 | func randString(l int) string { 18 | n := len(chars) 19 | b := make([]byte, l) 20 | 21 | for i := range b { 22 | b[i] = chars[r.Intn(n)] 23 | } 24 | 25 | return string(b) 26 | } 27 | 28 | func BenchmarkString(b *testing.B) { 29 | seed := time.Now().UTC().UnixNano() 30 | r = rand.New(rand.NewSource(seed)) 31 | 32 | // generate a sequence of random strings 33 | vals := make([]string, size) 34 | for i := range vals { 35 | vals[i] = randString(slen) 36 | } 37 | 38 | b.Run("LSDString", func(b *testing.B) { 39 | for n := 0; n < b.N; n++ { 40 | a := make([]string, len(vals)) 41 | copy(a, vals) 42 | shuffle[string](a) 43 | LSDString(a, slen) 44 | } 45 | }) 46 | 47 | b.Run("MSDString", func(b *testing.B) { 48 | for n := 0; n < b.N; n++ { 49 | a := make([]string, len(vals)) 50 | copy(a, vals) 51 | shuffle[string](a) 52 | MSDString(a) 53 | } 54 | }) 55 | 56 | b.Run("Quick3WayString", func(b *testing.B) { 57 | for n := 0; n < b.N; n++ { 58 | a := make([]string, len(vals)) 59 | copy(a, vals) 60 | shuffle[string](a) 61 | Quick3WayString(a) 62 | } 63 | }) 64 | } 65 | 66 | func BenchmarkInt(b *testing.B) { 67 | seed := time.Now().UTC().UnixNano() 68 | r = rand.New(rand.NewSource(seed)) 69 | 70 | // generate a sequence of random integers (signed). 71 | nums := make([]int, size) 72 | for i := range nums { 73 | nums[i] = r.Int() 74 | } 75 | 76 | b.Run("LSDInt", func(b *testing.B) { 77 | for n := 0; n < b.N; n++ { 78 | a := make([]int, len(nums)) 79 | copy(a, nums) 80 | shuffle[int](a) 81 | LSDInt(a) 82 | } 83 | }) 84 | 85 | b.Run("MSDInt", func(b *testing.B) { 86 | for n := 0; n < b.N; n++ { 87 | a := make([]int, len(nums)) 88 | copy(a, nums) 89 | shuffle[int](a) 90 | MSDInt(a) 91 | } 92 | }) 93 | } 94 | 95 | func BenchmarkUint(b *testing.B) { 96 | seed := time.Now().UTC().UnixNano() 97 | r = rand.New(rand.NewSource(seed)) 98 | 99 | // generate a sequence of random integers (unsigned). 100 | nums := make([]uint, size) 101 | for i := range nums { 102 | nums[i] = (uint(r.Uint32()) << 32) + uint(r.Uint32()) 103 | } 104 | 105 | b.Run("LSDUint", func(b *testing.B) { 106 | for n := 0; n < b.N; n++ { 107 | a := make([]uint, len(nums)) 108 | copy(a, nums) 109 | shuffle[uint](a) 110 | LSDUint(a) 111 | } 112 | }) 113 | 114 | b.Run("MSDUint", func(b *testing.B) { 115 | for n := 0; n < b.N; n++ { 116 | a := make([]uint, len(nums)) 117 | copy(a, nums) 118 | shuffle[uint](a) 119 | MSDUint(a) 120 | } 121 | }) 122 | } 123 | -------------------------------------------------------------------------------- /parser/lr/canonical/parsing_table.go: -------------------------------------------------------------------------------- 1 | package canonical 2 | 3 | import ( 4 | "github.com/moorara/algo/grammar" 5 | "github.com/moorara/algo/parser/lr" 6 | ) 7 | 8 | // BuildParsingTable constructs a parsing table for an SLR parser. 9 | func BuildParsingTable(G *grammar.CFG, precedences lr.PrecedenceLevels) (*lr.ParsingTable, error) { 10 | /* 11 | * INPUT: An augmented grammar G′. 12 | * OUTPUT: The canonical LR parsing table functions ACTION and GOTO for G′. 13 | */ 14 | 15 | G1 := lr.NewGrammarWithLR1(G) 16 | 17 | C := G1.Canonical() // 1. Construct C = {I₀, I₁, ..., Iₙ}, the collection of sets of LR(1) items for G′. 18 | S := lr.BuildStateMap(C) // Map sets of LR(1) items to state numbers. 19 | 20 | terminals := G1.OrderTerminals() 21 | _, _, nonTerminals := G1.OrderNonTerminals() 22 | table := lr.NewParsingTable(S.States(), terminals, nonTerminals, precedences) 23 | 24 | // 2. State i is constructed from I. 25 | for i, I := range S.All() { 26 | // The parsing action for state i is determined as follows: 27 | 28 | for item := range I.All() { 29 | item := item.(*lr.Item1) 30 | 31 | // If "A → α•aβ, b" is in Iᵢ and GOTO(Iᵢ,a) = Iⱼ (a must be a terminal) 32 | if X, ok := item.DotSymbol(); ok { 33 | if a, ok := X.(grammar.Terminal); ok { 34 | J := G1.GOTO(I, a) 35 | j := S.FindItemSet(J) 36 | 37 | // Set ACTION[i,a] to SHIFT j 38 | table.AddACTION(i, a, &lr.Action{ 39 | Type: lr.SHIFT, 40 | State: j, 41 | }) 42 | } 43 | } 44 | 45 | // If "A → α•, a" is in Iᵢ (A ≠ S′) 46 | if item.IsComplete() && !item.IsFinal() { 47 | a := item.Lookahead 48 | 49 | // Set ACTION[i,a] to REDUCE A → α 50 | table.AddACTION(i, a, &lr.Action{ 51 | Type: lr.REDUCE, 52 | Production: item.Production, 53 | }) 54 | } 55 | 56 | // If "S′ → S•, $" is in Iᵢ 57 | if item.IsFinal() { 58 | // Set ACTION[i,$] to ACCEPT 59 | table.AddACTION(i, grammar.Endmarker, &lr.Action{ 60 | Type: lr.ACCEPT, 61 | }) 62 | } 63 | 64 | // If any conflicting actions result from the above rules, the grammar is not LR(1). 65 | // The table.Error() method will list all conflicts, if any exist. 66 | } 67 | 68 | // 3. The goto transitions for state i are constructed for all non-terminals A using the rule: 69 | // If GOTO(Iᵢ,A) = Iⱼ 70 | for A := range G1.NonTerminals.All() { 71 | if !A.Equal(G1.Start) { 72 | J := G1.GOTO(I, A) 73 | j := S.FindItemSet(J) 74 | 75 | // Set GOTO[i,A] = j 76 | table.SetGOTO(i, A, j) 77 | } 78 | } 79 | 80 | // 4. All entries not defined by rules (2) and (3) are made ERROR. 81 | } 82 | 83 | // 5. The initial state of the parser is the one constructed from the set of items containing "S′ → •S, $". 84 | 85 | // Try resolving any conflicts in the ACTION parsing table. 86 | if err := table.ResolveConflicts(); err != nil { 87 | return table, err 88 | } 89 | 90 | return table, nil 91 | } 92 | -------------------------------------------------------------------------------- /parser/parser.go: -------------------------------------------------------------------------------- 1 | // Package parser provides data types and algorithms for building parsers. 2 | // 3 | // A parser generally relies on a lexer (also known as a lexical analyzer or scanner) 4 | // to process a stream of tokens. 5 | // Lexical analysis (scanning) is distinct from syntax analysis (parsing): 6 | // lexical analysis deals with regular languages and grammars (Type 3), 7 | // while syntax analysis deals with context-free languages and grammars (Type 2). 8 | package parser 9 | 10 | import ( 11 | "bytes" 12 | "fmt" 13 | 14 | "github.com/moorara/algo/lexer" 15 | ) 16 | 17 | // Parser defines the interface for a syntax analyzer that processes input tokens. 18 | type Parser interface { 19 | // Parse analyzes a sequence of input tokens (terminal symbols) provided by a lexical analyzer. 20 | // It attempts to parse the input according to the production rules of a context-free grammar, 21 | // determining whether the input string belongs to the language defined by the grammar. 22 | // 23 | // The Parse method invokes the provided functions each time a token or a production rule is matched. 24 | // This allows the caller to process or react to each step of the parsing process. 25 | // 26 | // An error is returned if the input fails to conform to the grammar rules, indicating a syntax issue, 27 | // or if any of the provided functions return an error, indicating a semantic issue. 28 | Parse(TokenFunc, ProductionFunc) error 29 | 30 | // ParseAndBuildAST analyzes a sequence of input tokens (terminal symbols) provided by a lexical analyzer. 31 | // It attempts to parse the input according to the production rules of a context-free grammar, 32 | // constructing an abstract syntax tree (AST) that reflects the structure of the input. 33 | // 34 | // If the input string is valid, the root node of the AST is returned, 35 | // representing the syntactic structure of the input string. 36 | // 37 | // An error is returned if the input fails to conform to the grammar rules, indicating a syntax issue. 38 | ParseAndBuildAST() (Node, error) 39 | } 40 | 41 | // ParseError represents an error encountered when parsing an input string. 42 | type ParseError struct { 43 | Description string 44 | Cause error 45 | Pos lexer.Position 46 | } 47 | 48 | // Error implements the error interface. 49 | // It returns a formatted string describing the error in detail. 50 | func (e *ParseError) Error() string { 51 | var b bytes.Buffer 52 | 53 | if !e.Pos.IsZero() { 54 | fmt.Fprintf(&b, "%s", e.Pos) 55 | } 56 | 57 | if len(e.Description) != 0 { 58 | if b.Len() > 0 { 59 | fmt.Fprint(&b, ": ") 60 | } 61 | fmt.Fprintf(&b, "%s", e.Description) 62 | } 63 | 64 | if e.Cause != nil { 65 | if b.Len() > 0 { 66 | fmt.Fprint(&b, ": ") 67 | } 68 | fmt.Fprintf(&b, "%s", e.Cause) 69 | } 70 | 71 | return b.String() 72 | } 73 | 74 | // Unwrap implements the unwrap interface. 75 | func (e *ParseError) Unwrap() error { 76 | return e.Cause 77 | } 78 | -------------------------------------------------------------------------------- /parser/predictive/fixture_test.go: -------------------------------------------------------------------------------- 1 | package predictive 2 | 3 | import ( 4 | "github.com/moorara/algo/grammar" 5 | "github.com/moorara/algo/internal/parsertest" 6 | ) 7 | 8 | func getTestParsingTables() []*ParsingTable { 9 | pt0 := NewParsingTable( 10 | []grammar.Terminal{"+", "*", "(", ")", "id", grammar.Endmarker}, 11 | []grammar.NonTerminal{"E", "E′", "T", "T′", "F"}, 12 | ) 13 | 14 | pt0.addProduction("E", "(", parsertest.Prods[0][0]) // E → T E′ 15 | pt0.addProduction("E", "id", parsertest.Prods[0][0]) // E → T E′ 16 | pt0.addProduction("E′", ")", parsertest.Prods[0][2]) // E′ → ε 17 | pt0.addProduction("E′", "+", parsertest.Prods[0][1]) // E′ → + T E′ 18 | pt0.addProduction("E′", grammar.Endmarker, parsertest.Prods[0][2]) // E′ → ε 19 | pt0.addProduction("T", "(", parsertest.Prods[0][3]) // T → F T′ 20 | pt0.addProduction("T", "id", parsertest.Prods[0][3]) // T → F T′ 21 | pt0.addProduction("T′", ")", parsertest.Prods[0][5]) // T′ → ε 22 | pt0.addProduction("T′", "*", parsertest.Prods[0][4]) // T′ → * F T′ 23 | pt0.addProduction("T′", "+", parsertest.Prods[0][5]) // T′ → ε 24 | pt0.addProduction("T′", grammar.Endmarker, parsertest.Prods[0][5]) // T′ → ε 25 | pt0.addProduction("F", "(", parsertest.Prods[0][6]) // F → ( E ) 26 | pt0.addProduction("F", "id", parsertest.Prods[0][7]) // F → id 27 | 28 | pt0.setSync("E", ")", true) 29 | pt0.setSync("E", grammar.Endmarker, true) 30 | pt0.setSync("T", "+", true) 31 | pt0.setSync("T", ")", true) 32 | pt0.setSync("T", grammar.Endmarker, true) 33 | pt0.setSync("F", "+", true) 34 | pt0.setSync("F", "*", true) 35 | pt0.setSync("F", ")", true) 36 | pt0.setSync("F", grammar.Endmarker, true) 37 | 38 | pt1 := NewParsingTable( 39 | []grammar.Terminal{"a", "b", "e", "i", "t"}, 40 | []grammar.NonTerminal{"S", "S′", "E"}, 41 | ) 42 | 43 | pt1.addProduction("S", "a", &grammar.Production{Head: "S", Body: grammar.String[grammar.Symbol]{grammar.Terminal("a")}}) 44 | pt1.addProduction("S", "i", &grammar.Production{Head: "S", Body: grammar.String[grammar.Symbol]{grammar.Terminal("i"), grammar.NonTerminal("E"), grammar.Terminal("t"), grammar.NonTerminal("S"), grammar.NonTerminal("S′")}}) 45 | pt1.addProduction("S′", "e", &grammar.Production{Head: "S′", Body: grammar.E}) 46 | pt1.addProduction("S′", "e", &grammar.Production{Head: "S′", Body: grammar.String[grammar.Symbol]{grammar.Terminal("e"), grammar.NonTerminal("S")}}) 47 | pt1.addProduction("S′", grammar.Endmarker, &grammar.Production{Head: "S′", Body: grammar.E}) 48 | pt1.addProduction("E", "b", &grammar.Production{Head: "E", Body: grammar.String[grammar.Symbol]{grammar.Terminal("b")}}) 49 | 50 | pt2 := NewParsingTable( 51 | []grammar.Terminal{"+", "*", "(", ")", "id", grammar.Endmarker}, 52 | []grammar.NonTerminal{"E", "T", "F"}, 53 | ) 54 | 55 | return []*ParsingTable{pt0, pt1, pt2} 56 | } 57 | -------------------------------------------------------------------------------- /list/soft_queue.go: -------------------------------------------------------------------------------- 1 | package list 2 | 3 | import "github.com/moorara/algo/generic" 4 | 5 | // SoftQueue represents the abstract data type for a queue with soft deletion. 6 | type SoftQueue[T any] interface { 7 | // Size returns the number of values in the queue. 8 | Size() int 9 | 10 | // IsEmpty returns true if the queue is empty. 11 | IsEmpty() bool 12 | 13 | // Enqueue inserts a new value to the queue. 14 | // It returns the index of the newly enqueued value in the queue. 15 | Enqueue(T) int 16 | 17 | // Dequeue deletes a value from the queue. 18 | // The deletion is soft and the entries remain in the queue. 19 | // Deleted entries are searchable using the Contains method. 20 | // The second return value is the index of the dequeued value in the queue. 21 | Dequeue() (T, int) 22 | 23 | // Peek returns the next value in queue without deleting it from the queue. 24 | // The second return value is the index of the peeked value in the queue. 25 | Peek() (T, int) 26 | 27 | // Contains returns true if a given value is either in the queue or deleted in the past. 28 | // If the value is found, its index in the queue is returned; otherwise, -1 is returned. 29 | Contains(T) int 30 | 31 | // Values returns the list of all values in the queue including the deleted ones. 32 | Values() []T 33 | } 34 | 35 | type softQueue[T any] struct { 36 | equal generic.EqualFunc[T] 37 | 38 | front int 39 | rear int 40 | list []T 41 | } 42 | 43 | // NewSoftQueue creates a new array-list queue with soft deletion. 44 | // Deleted entries remain in the queue and are searchable. 45 | func NewSoftQueue[T any](equal generic.EqualFunc[T]) SoftQueue[T] { 46 | return &softQueue[T]{ 47 | equal: equal, 48 | 49 | front: 0, 50 | rear: -1, 51 | list: make([]T, 0), 52 | } 53 | } 54 | 55 | func (q *softQueue[T]) Size() int { 56 | return q.rear - q.front + 1 57 | } 58 | 59 | func (q *softQueue[T]) IsEmpty() bool { 60 | return q.front > q.rear 61 | } 62 | 63 | func (q *softQueue[T]) Enqueue(val T) int { 64 | q.list = append(q.list, val) 65 | 66 | if len(q.list) == 1 { 67 | q.front, q.rear = 0, 0 68 | } else { 69 | q.rear++ 70 | } 71 | 72 | return q.rear 73 | } 74 | 75 | func (q *softQueue[T]) Dequeue() (T, int) { 76 | if q.IsEmpty() { 77 | var zero T 78 | return zero, -1 79 | } 80 | 81 | val := q.list[q.front] 82 | i := q.front 83 | q.front++ 84 | 85 | return val, i 86 | } 87 | 88 | func (q *softQueue[T]) Peek() (T, int) { 89 | if q.IsEmpty() { 90 | var zero T 91 | return zero, -1 92 | } 93 | 94 | return q.list[q.front], q.front 95 | } 96 | 97 | func (q *softQueue[T]) Contains(val T) int { 98 | for i, v := range q.list { 99 | if q.equal(v, val) { 100 | return i 101 | } 102 | } 103 | 104 | return -1 105 | } 106 | 107 | func (q *softQueue[T]) Values() []T { 108 | vals := make([]T, len(q.list)) 109 | copy(vals, q.list) 110 | 111 | return vals 112 | } 113 | -------------------------------------------------------------------------------- /sort/quick_test.go: -------------------------------------------------------------------------------- 1 | package sort 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/moorara/algo/generic" 7 | ) 8 | 9 | func TestSelect_int(t *testing.T) { 10 | tests := []struct { 11 | items []int 12 | expectedItems []int 13 | }{ 14 | {[]int{}, nil}, 15 | {[]int{20, 10, 30}, []int{10, 20, 30}}, 16 | {[]int{20, 10, 30, 40, 50}, []int{10, 20, 30, 40, 50}}, 17 | {[]int{20, 10, 30, 40, 50, 80, 60, 70, 90}, []int{10, 20, 30, 40, 50, 60, 70, 80, 90}}, 18 | } 19 | 20 | for _, tc := range tests { 21 | for k := 0; k < len(tc.items); k++ { 22 | cmp := generic.NewCompareFunc[int]() 23 | item := Select[int](tc.items, k, cmp) 24 | 25 | if item != tc.expectedItems[k] { 26 | t.Fatalf("expected selection: %d, actual selection: %d", tc.expectedItems[k], item) 27 | } 28 | } 29 | } 30 | } 31 | 32 | func TestQuick_int(t *testing.T) { 33 | tests := []struct { 34 | items []int 35 | }{ 36 | {[]int{}}, 37 | {[]int{20, 10, 30}}, 38 | {[]int{30, 20, 10, 40, 50}}, 39 | {[]int{90, 80, 70, 60, 50, 40, 30, 20, 10}}, 40 | } 41 | 42 | for _, tc := range tests { 43 | cmp := generic.NewCompareFunc[int]() 44 | Quick[int](tc.items, cmp) 45 | 46 | if !isSorted(tc.items, cmp) { 47 | t.Fatalf("%v is not sorted.", tc.items) 48 | } 49 | } 50 | } 51 | 52 | func TestQuick_string(t *testing.T) { 53 | tests := []struct { 54 | items []string 55 | }{ 56 | {[]string{}}, 57 | {[]string{"Milad", "Mona"}}, 58 | {[]string{"Alice", "Bob", "Alex", "Jackie"}}, 59 | {[]string{"Docker", "Kubernetes", "Go", "JavaScript", "Elixir", "React", "Redux", "Vue"}}, 60 | } 61 | 62 | for _, tc := range tests { 63 | cmp := generic.NewCompareFunc[string]() 64 | Quick[string](tc.items, cmp) 65 | 66 | if !isSorted(tc.items, cmp) { 67 | t.Fatalf("%v is not sorted.", tc.items) 68 | } 69 | } 70 | } 71 | 72 | func TestQuick3Way_int(t *testing.T) { 73 | tests := []struct { 74 | items []int 75 | }{ 76 | {[]int{}}, 77 | {[]int{20, 10, 10, 20, 30, 30, 30}}, 78 | {[]int{30, 20, 30, 20, 10, 40, 40, 40, 50, 50}}, 79 | {[]int{90, 10, 80, 20, 70, 30, 60, 40, 50, 50, 40, 60, 30, 70, 20, 80, 10, 90}}, 80 | } 81 | 82 | for _, tc := range tests { 83 | cmp := generic.NewCompareFunc[int]() 84 | Quick3Way[int](tc.items, cmp) 85 | 86 | if !isSorted(tc.items, cmp) { 87 | t.Fatalf("%v is not sorted.", tc.items) 88 | } 89 | } 90 | } 91 | 92 | func TestQuick3Way_string(t *testing.T) { 93 | tests := []struct { 94 | items []string 95 | }{ 96 | {[]string{}}, 97 | {[]string{"Milad", "Mona", "Milad", "Mona"}}, 98 | {[]string{"Alice", "Bob", "Alex", "Jackie", "Jackie", "Alex", "Bob", "Alice"}}, 99 | {[]string{"Docker", "Kubernetes", "Docker", "Go", "JavaScript", "Go", "React", "Redux", "Vue", "Redux", "React"}}, 100 | } 101 | 102 | for _, tc := range tests { 103 | cmp := generic.NewCompareFunc[string]() 104 | Quick3Way[string](tc.items, cmp) 105 | 106 | if !isSorted(tc.items, cmp) { 107 | t.Fatalf("%v is not sorted.", tc.items) 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /generic/generic.go: -------------------------------------------------------------------------------- 1 | // Package generic provides types, interfaces, and functions to support generic programming use cases. 2 | package generic 3 | 4 | import "golang.org/x/exp/constraints" 5 | 6 | // Cloner is an interface that defines a method for cloning an object (the prototype pattern). 7 | type Cloner[T any] interface { 8 | Clone() T 9 | } 10 | 11 | // Equaler is a generic interface for determining equality two objects of the same type. 12 | type Equaler[T any] interface { 13 | Equal(T) bool 14 | } 15 | 16 | // EqualFunc defines a generic function type for checking equality between two values of the same type. 17 | // The function takes two arguments of type T and returns true if they are considered equal, or false otherwise. 18 | type EqualFunc[T any] func(T, T) bool 19 | 20 | // NewEqualFunc returns a generic equality function for any type that satisfies the comparable constraint. 21 | func NewEqualFunc[T comparable]() EqualFunc[T] { 22 | return func(lhs, rhs T) bool { 23 | return lhs == rhs 24 | } 25 | } 26 | 27 | // Comparer is a generic interface for comparing two objects of the same type 28 | // and establishing an order between them. 29 | // The Compare method returns a negative value if the current object is less than the given object, 30 | // zero if they are equal, and a positive value if the current object is greater. 31 | type Comparer[T any] interface { 32 | Compare(T) int 33 | } 34 | 35 | // CompareFunc defines a generic function type for comparing two values of the same type. 36 | // The function takes two arguments of type T and returns: 37 | // - A negative integer if the first value is less than the second, 38 | // - Zero if the two values are equal, 39 | // - A positive integer if the first value is greater than the second. 40 | type CompareFunc[T any] func(T, T) int 41 | 42 | // NewCompareFunc returns a generic comparison function 43 | // for any type that satisfies the constraints.Ordered interface. 44 | // The returned function compares two values of type T and returns: 45 | // - -1 if lhs is less than rhs, 46 | // - 1 if lhs is greater than rhs, 47 | // - 0 if lhs is equal to rhs. 48 | // 49 | // This is useful for implementing custom sorting or comparison logic. 50 | func NewCompareFunc[T constraints.Ordered]() CompareFunc[T] { 51 | return func(lhs, rhs T) int { 52 | switch { 53 | case lhs < rhs: 54 | return -1 55 | case lhs > rhs: 56 | return 1 57 | default: 58 | return 0 59 | } 60 | } 61 | } 62 | 63 | // NewReverseCompareFunc returns a generic reverse comparison function 64 | // for any type that satisfies the constraints.Ordered interface. 65 | // The returned function compares two values of type T and returns: 66 | // - 1 if lhs is less than rhs, 67 | // - -1 if lhs is greater than rhs, 68 | // - 0 if lhs is equal to rhs. 69 | // 70 | // This is useful for implementing reverse sorting or inverted comparison logic. 71 | func NewReverseCompareFunc[T constraints.Ordered]() CompareFunc[T] { 72 | return func(lhs, rhs T) int { 73 | switch { 74 | case lhs < rhs: 75 | return 1 76 | case lhs > rhs: 77 | return -1 78 | default: 79 | return 0 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Go Doc][godoc-image]][godoc-url] 2 | [![Build Status][workflow-image]][workflow-url] 3 | [![Go Report Card][goreport-image]][goreport-url] 4 | [![Test Coverage][codecov-image]][codecov-url] 5 | 6 | # algo 7 | 8 | A collection of common data structures and algorithms for Go applications. 9 | 10 | ## Summary 11 | 12 | - **Algorithms** 13 | - Comparative Sorts 14 | - Selection Sort 15 | - Insertion Sort 16 | - Shell Sort 17 | - Merge Sort 18 | - Quick Sort 19 | - 3-Way Quick Sort 20 | - Heap Sort 21 | - Non-Comparative Sorts 22 | - Least Significant Digit 23 | - Most Significant Digit 24 | - 3-Way Quick Sort 25 | - Misc 26 | - Shuffle 27 | - Quick Select 28 | - **Data Structures** 29 | - Lists 30 | - Queue 31 | - Stack 32 | - Heaps 33 | - Binary Heaps 34 | - Binomial Heaps 35 | - Fibonacci Heaps 36 | - Sets 37 | - Union 38 | - Intersection 39 | - Difference 40 | - Powerset 41 | - Partitions 42 | - Range 43 | - Discrete 44 | - RangeList 45 | - RangeMap 46 | - Continuous 47 | - RangeList 48 | - RangeMap 49 | - Symbol Tables 50 | - Unordered 51 | - Separate Chaining Hash Table 52 | - Linear Probing Hash Table 53 | - Quadratic Probing Hash Table 54 | - Double Hashing Hash Table 55 | - Ordered 56 | - BST 57 | - AVL Tree 58 | - Red-Black Tree 59 | - Tries 60 | - Binary Trie 61 | - Patricia Trie 62 | - Graphs 63 | - Undirected Graph 64 | - Directed Graph 65 | - Weighted Undirected Graph 66 | - Weighted Directed Graph 67 | - Automata 68 | - DFA 69 | - NFA 70 | - Grammars 71 | - Context-Free Grammar 72 | - Chomsky Normal Form 73 | - Left Recursion Elimination 74 | - Left Factoring 75 | - FIRST and FOLLOW 76 | - **Lexers** 77 | - Two-Buffer Input Reader 78 | - **Parsers** 79 | - Parser Combinators 80 | - Predictive Parser 81 | - LR Parsers (SLR, LALR, Canonical LR) 82 | - Conflict Resolution 83 | 84 | ## Development 85 | 86 | | Command | Purpose | 87 | |------------------|---------------------------------------------| 88 | | `make test` | Run unit tests | 89 | | `make benchmark` | Run benchmarks | 90 | | `make coverage` | Run unit tests and generate coverage report | 91 | 92 | 93 | [godoc-url]: https://pkg.go.dev/github.com/moorara/algo 94 | [godoc-image]: https://pkg.go.dev/badge/github.com/moorara/algo 95 | [workflow-url]: https://github.com/moorara/algo/actions 96 | [workflow-image]: https://github.com/moorara/algo/workflows/Go/badge.svg 97 | [goreport-url]: https://goreportcard.com/report/github.com/moorara/algo 98 | [goreport-image]: https://goreportcard.com/badge/github.com/moorara/algo 99 | [codecov-url]: https://codecov.io/gh/moorara/algo 100 | [codecov-image]: https://codecov.io/gh/moorara/algo/branch/main/graph/badge.svg 101 | -------------------------------------------------------------------------------- /trie/benchmark_test.go: -------------------------------------------------------------------------------- 1 | package trie 2 | 3 | import ( 4 | "math/rand" 5 | "testing" 6 | "time" 7 | 8 | . "github.com/moorara/algo/generic" 9 | ) 10 | 11 | const ( 12 | minLen = 10 13 | maxLen = 100 14 | chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 15 | ) 16 | 17 | var r *rand.Rand 18 | 19 | func randIntSlice(size int) []int { 20 | vals := make([]int, size) 21 | for i := 0; i < len(vals); i++ { 22 | vals[i] = r.Int() 23 | } 24 | 25 | return vals 26 | } 27 | 28 | func randStringKey(minLen, maxLen int) string { 29 | n := len(chars) 30 | l := minLen + r.Intn(maxLen-minLen+1) 31 | b := make([]byte, l) 32 | 33 | for i := range b { 34 | b[i] = chars[r.Intn(n)] 35 | } 36 | 37 | return string(b) 38 | } 39 | 40 | func randStringSlice(size int) []string { 41 | s := make([]string, size) 42 | for i := range s { 43 | s[i] = randStringKey(minLen, maxLen) 44 | } 45 | 46 | return s 47 | } 48 | 49 | func runPutBenchmark(b *testing.B, trie Trie[int]) { 50 | keys := randStringSlice(b.N) 51 | vals := randIntSlice(b.N) 52 | 53 | b.ResetTimer() 54 | 55 | for n := 0; n < b.N; n++ { 56 | trie.Put(keys[n], vals[n]) 57 | } 58 | } 59 | 60 | func runGetBenchmark(b *testing.B, trie Trie[int]) { 61 | keys := randStringSlice(b.N) 62 | vals := randIntSlice(b.N) 63 | 64 | for n := 0; n < b.N; n++ { 65 | trie.Put(keys[n], vals[n]) 66 | } 67 | 68 | b.ResetTimer() 69 | 70 | for n := 0; n < b.N; n++ { 71 | trie.Get(keys[n]) 72 | } 73 | } 74 | 75 | func runDeleteBenchmark(b *testing.B, trie Trie[int]) { 76 | keys := randStringSlice(b.N) 77 | vals := randIntSlice(b.N) 78 | 79 | for n := 0; n < b.N; n++ { 80 | trie.Put(keys[n], vals[n]) 81 | } 82 | 83 | b.ResetTimer() 84 | 85 | for n := 0; n < b.N; n++ { 86 | trie.Delete(keys[n]) 87 | } 88 | } 89 | 90 | func BenchmarkTrie_Put(b *testing.B) { 91 | seed := time.Now().UTC().UnixNano() 92 | r = rand.New(rand.NewSource(seed)) 93 | 94 | eqVal := NewEqualFunc[int]() 95 | 96 | b.Run("BinaryTrie.Put", func(b *testing.B) { 97 | trie := NewBinary[int](eqVal) 98 | runPutBenchmark(b, trie) 99 | }) 100 | 101 | b.Run("Patricia.Put", func(b *testing.B) { 102 | trie := NewPatricia[int](eqVal) 103 | runPutBenchmark(b, trie) 104 | }) 105 | } 106 | 107 | func BenchmarkTrie_Get(b *testing.B) { 108 | seed := time.Now().UTC().UnixNano() 109 | r = rand.New(rand.NewSource(seed)) 110 | 111 | eqVal := NewEqualFunc[int]() 112 | 113 | b.Run("BinaryTrie.Get", func(b *testing.B) { 114 | trie := NewBinary[int](eqVal) 115 | runGetBenchmark(b, trie) 116 | }) 117 | 118 | b.Run("Patricia.Get", func(b *testing.B) { 119 | trie := NewPatricia[int](eqVal) 120 | runGetBenchmark(b, trie) 121 | }) 122 | } 123 | 124 | func BenchmarkTrie_Delete(b *testing.B) { 125 | seed := time.Now().UTC().UnixNano() 126 | r = rand.New(rand.NewSource(seed)) 127 | 128 | eqVal := NewEqualFunc[int]() 129 | 130 | b.Run("BinaryTrie.Delete", func(b *testing.B) { 131 | trie := NewBinary[int](eqVal) 132 | runDeleteBenchmark(b, trie) 133 | }) 134 | 135 | b.Run("Patricia.Delete", func(b *testing.B) { 136 | trie := NewPatricia[int](eqVal) 137 | runDeleteBenchmark(b, trie) 138 | }) 139 | } 140 | -------------------------------------------------------------------------------- /radixsort/lsd.go: -------------------------------------------------------------------------------- 1 | package radixsort 2 | 3 | import "math/bits" 4 | 5 | // LSDString is the LSD (least significant digit) sorting algorithm for string keys with fixed length w. 6 | func LSDString(a []string, w int) { 7 | const R = 256 8 | 9 | n := len(a) 10 | aux := make([]string, n) 11 | 12 | // sort by key-indexed counting on dth char (stable) 13 | for d := w - 1; d >= 0; d-- { 14 | count := make([]int, R+1) 15 | 16 | // compute frequency counts 17 | for _, s := range a { 18 | count[s[d]+1]++ 19 | } 20 | 21 | // compute cumulative counts 22 | for r := 0; r < R; r++ { 23 | count[r+1] += count[r] 24 | } 25 | 26 | // distribute keys to aux 27 | for _, s := range a { 28 | aux[count[s[d]]] = s 29 | count[s[d]]++ 30 | } 31 | 32 | // copy back aux to a 33 | for i := 0; i < n; i++ { 34 | a[i] = aux[i] 35 | } 36 | } 37 | } 38 | 39 | // LSDInt is the LSD (least significant digit) sorting algorithm for integer numbers (signed). 40 | func LSDInt(a []int) { 41 | const ( 42 | ByteSize = 8 43 | IntSize = bits.UintSize 44 | W = IntSize / ByteSize 45 | R = 1 << ByteSize 46 | Mask = R - 1 47 | ) 48 | 49 | n := len(a) 50 | aux := make([]int, n) 51 | 52 | // sort by key-indexed counting on dth char (stable) 53 | for d := 0; d < W; d++ { 54 | count := make([]int, R+1) 55 | shift := ByteSize * d 56 | 57 | // compute frequency counts 58 | for _, v := range a { 59 | c := (v >> shift) & Mask 60 | count[c+1]++ 61 | } 62 | 63 | // compute cumulative counts 64 | for r := 0; r < R; r++ { 65 | count[r+1] += count[r] 66 | } 67 | 68 | // for most significant byte, 0x80-0xFF comes before 0x00-0x7F 69 | if d == W-1 { 70 | shift1 := count[R] - count[R/2] 71 | shift2 := count[R/2] 72 | 73 | for r := 0; r < R/2; r++ { 74 | count[r] += shift1 75 | } 76 | 77 | for r := R / 2; r < R; r++ { 78 | count[r] -= shift2 79 | } 80 | } 81 | 82 | // distribute keys to aux 83 | for _, v := range a { 84 | c := (v >> shift) & Mask 85 | aux[count[c]] = v 86 | count[c]++ 87 | } 88 | 89 | // copy back aux to a 90 | for i := 0; i < n; i++ { 91 | a[i] = aux[i] 92 | } 93 | } 94 | } 95 | 96 | // LSDUint is the LSD (least significant digit) sorting algorithm for integer numbers (unsigned). 97 | func LSDUint(a []uint) { 98 | const ( 99 | ByteSize = 8 100 | UintSize = bits.UintSize 101 | W = UintSize / ByteSize 102 | R = 1 << ByteSize 103 | Mask = R - 1 104 | ) 105 | 106 | n := len(a) 107 | aux := make([]uint, n) 108 | 109 | // sort by key-indexed counting on dth char (stable) 110 | for d := 0; d < W; d++ { 111 | count := make([]int, R+1) 112 | shift := ByteSize * d 113 | 114 | // compute frequency counts 115 | for _, v := range a { 116 | c := (v >> shift) & Mask 117 | count[c+1]++ 118 | } 119 | 120 | // compute cumulative counts 121 | for r := 0; r < R; r++ { 122 | count[r+1] += count[r] 123 | } 124 | 125 | // distribute keys to aux 126 | for _, v := range a { 127 | c := (v >> shift) & Mask 128 | aux[count[c]] = v 129 | count[c]++ 130 | } 131 | 132 | // copy back aux to a 133 | for i := 0; i < n; i++ { 134 | a[i] = aux[i] 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /lexer/input/fixture/lorem_ipsum: -------------------------------------------------------------------------------- 1 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Tincidunt eget nullam non nisi. Elementum pulvinar etiam non quam lacus suspendisse faucibus. Viverra accumsan in nisl nisi scelerisque. Ultricies tristique nulla aliquet enim tortor at auctor. Est ante in nibh mauris cursus mattis. Sed arcu non odio euismod lacinia at quis risus. Vestibulum lectus mauris ultrices eros. Amet consectetur adipiscing elit duis tristique sollicitudin nibh sit amet. Netus et malesuada fames ac turpis egestas. Arcu risus quis varius quam quisque id diam vel. 2 | 3 | Donec pretium vulputate sapien nec. At urna condimentum mattis pellentesque id nibh. Aliquet lectus proin nibh nisl condimentum id venenatis a. Faucibus et molestie ac feugiat sed lectus vestibulum. Erat nam at lectus urna duis. Cursus mattis molestie a iaculis. Nam at lectus urna duis convallis convallis tellus id interdum. Natoque penatibus et magnis dis parturient montes. Fringilla phasellus faucibus scelerisque eleifend donec pretium vulputate sapien nec. Mattis nunc sed blandit libero volutpat sed cras. 4 | 5 | Pulvinar etiam non quam lacus. Condimentum lacinia quis vel eros donec. Ut aliquam purus sit amet. Nisi porta lorem mollis aliquam ut porttitor. Eget nunc lobortis mattis aliquam faucibus purus in massa tempor. Dignissim diam quis enim lobortis scelerisque fermentum. Turpis nunc eget lorem dolor sed viverra ipsum. Adipiscing bibendum est ultricies integer quis auctor. Neque laoreet suspendisse interdum consectetur libero. Hac habitasse platea dictumst vestibulum rhoncus est pellentesque. Integer quis auctor elit sed vulputate mi. Eget egestas purus viverra accumsan in nisl. Et netus et malesuada fames ac. Lectus quam id leo in vitae. Imperdiet proin fermentum leo vel orci porta non. Ac placerat vestibulum lectus mauris. Morbi enim nunc faucibus a pellentesque sit amet porttitor. Tortor posuere ac ut consequat semper viverra nam libero. Lectus mauris ultrices eros in cursus turpis massa. 6 | 7 | Mattis ullamcorper velit sed ullamcorper morbi tincidunt ornare massa. Non curabitur gravida arcu ac tortor dignissim. Scelerisque fermentum dui faucibus in ornare quam viverra. Varius morbi enim nunc faucibus a. Non consectetur a erat nam at lectus. Quis blandit turpis cursus in hac habitasse platea. Posuere lorem ipsum dolor sit amet consectetur adipiscing elit duis. Tortor condimentum lacinia quis vel eros donec ac odio. Eu augue ut lectus arcu bibendum. Dolor magna eget est lorem ipsum dolor sit amet consectetur. Duis at consectetur lorem donec massa sapien faucibus. Porta nibh venenatis cras sed felis eget velit. 8 | 9 | Nullam non nisi est sit amet facilisis magna etiam tempor. Fermentum dui faucibus in ornare quam viverra orci. Elit pellentesque habitant morbi tristique senectus et netus et. Eu nisl nunc mi ipsum faucibus vitae aliquet nec ullamcorper. Ut tristique et egestas quis ipsum suspendisse. Purus gravida quis blandit turpis cursus. Aliquam faucibus purus in massa. Nec feugiat in fermentum posuere urna nec tincidunt praesent semper. Porttitor massa id neque aliquam. Fringilla phasellus faucibus scelerisque eleifend donec pretium. Eget magna fermentum iaculis eu non diam. Venenatis tellus in metus vulputate eu scelerisque felis imperdiet proin. Varius duis at consectetur lorem donec massa sapien faucibus et. Nisl vel pretium lectus quam id leo in vitae turpis. 10 | -------------------------------------------------------------------------------- /parser/lr/simple/parsing_table.go: -------------------------------------------------------------------------------- 1 | package simple 2 | 3 | import ( 4 | "github.com/moorara/algo/grammar" 5 | "github.com/moorara/algo/parser/lr" 6 | ) 7 | 8 | // BuildParsingTable constructs a parsing table for an SLR parser. 9 | func BuildParsingTable(G *grammar.CFG, precedences lr.PrecedenceLevels) (*lr.ParsingTable, error) { 10 | /* 11 | * INPUT: An augmented grammar G′. 12 | * OUTPUT: The SLR parsing table functions ACTION and GOTO for G′. 13 | */ 14 | 15 | G0 := lr.NewGrammarWithLR0(G) 16 | FIRST := G0.ComputeFIRST() 17 | FOLLOW := G0.ComputeFOLLOW(FIRST) 18 | 19 | C := G0.Canonical() // 1. Construct C = {I₀, I₁, ..., Iₙ}, the collection of sets of LR(0) items for G′. 20 | S := lr.BuildStateMap(C) // Map sets of LR(0) items to state numbers. 21 | 22 | terminals := G0.OrderTerminals() 23 | _, _, nonTerminals := G0.OrderNonTerminals() 24 | table := lr.NewParsingTable(S.States(), terminals, nonTerminals, precedences) 25 | 26 | // 2. State i is constructed from I. 27 | for i, I := range S.All() { 28 | // The parsing action for state i is determined as follows: 29 | 30 | for item := range I.All() { 31 | item := item.(*lr.Item0) 32 | 33 | // If "A → α•aβ" is in Iᵢ and GOTO(Iᵢ,a) = Iⱼ (a must be a terminal) 34 | if X, ok := item.DotSymbol(); ok { 35 | if a, ok := X.(grammar.Terminal); ok { 36 | J := G0.GOTO(I, a) 37 | j := S.FindItemSet(J) 38 | 39 | // Set ACTION[i,a] to SHIFT j 40 | table.AddACTION(i, a, &lr.Action{ 41 | Type: lr.SHIFT, 42 | State: j, 43 | }) 44 | } 45 | } 46 | 47 | // If "A → α•" is in Iᵢ (A ≠ S′) 48 | if item.IsComplete() && !item.IsFinal() { 49 | FOLLOWA := FOLLOW(item.Head) 50 | 51 | // For all a in FOLLOW(A) 52 | for a := range FOLLOWA.Terminals.All() { 53 | // Set ACTION[i,a] to REDUCE A → α 54 | table.AddACTION(i, a, &lr.Action{ 55 | Type: lr.REDUCE, 56 | Production: item.Production, 57 | }) 58 | } 59 | 60 | if FOLLOWA.IncludesEndmarker { 61 | // Set ACTION[i,$] to REDUCE A → α 62 | table.AddACTION(i, grammar.Endmarker, &lr.Action{ 63 | Type: lr.REDUCE, 64 | Production: item.Production, 65 | }) 66 | } 67 | } 68 | 69 | // If "S′ → S•" is in Iᵢ 70 | if item.IsFinal() { 71 | // Set ACTION[i,$] to ACCEPT 72 | table.AddACTION(i, grammar.Endmarker, &lr.Action{ 73 | Type: lr.ACCEPT, 74 | }) 75 | } 76 | 77 | // If any conflicting actions result from the above rules, the grammar is not SLR(1). 78 | // The table.Error() method will list all conflicts, if any exist. 79 | } 80 | 81 | // 3. The goto transitions for state i are constructed for all non-terminals A using the rule: 82 | // If GOTO(Iᵢ,A) = Iⱼ 83 | for A := range G0.NonTerminals.All() { 84 | if !A.Equal(G0.Start) { 85 | J := G0.GOTO(I, A) 86 | j := S.FindItemSet(J) 87 | 88 | // Set GOTO[i,A] = j 89 | table.SetGOTO(i, A, j) 90 | } 91 | } 92 | 93 | // 4. All entries not defined by rules (2) and (3) are made ERROR. 94 | } 95 | 96 | // 5. The initial state of the parser is the one constructed from the set of items containing "S′ → •S". 97 | 98 | // Try resolving any conflicts in the ACTION parsing table. 99 | if err := table.ResolveConflicts(); err != nil { 100 | return table, err 101 | } 102 | 103 | return table, nil 104 | } 105 | -------------------------------------------------------------------------------- /parser/lr/grammar.go: -------------------------------------------------------------------------------- 1 | package lr 2 | 3 | import "github.com/moorara/algo/grammar" 4 | 5 | var primeSuffixes = []string{ 6 | "′", // Prime (U+2032) 7 | "″", // Double Prime (U+2033) 8 | "‴", // Triple Prime (U+2034) 9 | "⁗", // Quadruple Prime (U+2057) 10 | } 11 | 12 | // augment augments a context-free grammar G by creating a new start symbol S′ 13 | // and adding a production "S′ → S", where S is the original start symbol of G. 14 | // This transformation is used to prepare grammars for LR parsing. 15 | // The function clones the input grammar G, ensuring the original grammar remains unmodified. 16 | func augment(G *grammar.CFG) *grammar.CFG { 17 | augG := G.Clone() 18 | 19 | // A special symbol used to indicate the end of a string. 20 | augG.Terminals.Add(grammar.Endmarker) 21 | 22 | newS := augG.AddNewNonTerminal(G.Start, primeSuffixes...) 23 | augG.Start = newS 24 | augG.Productions.Add(&grammar.Production{ 25 | Head: newS, 26 | Body: grammar.String[grammar.Symbol]{G.Start}, 27 | }) 28 | 29 | return augG 30 | } 31 | 32 | // Grammar represents a context-free grammar with additional features 33 | // tailored for LR parsing and constructing an LR parser. 34 | // The grammar is augmented with a new start symbol S′ and a new production "S′ → S" 35 | // to facilitate the construction of the LR parser. 36 | // It extends a regular context-free grammar by incorporating precedence and associativity information 37 | // to handle ambiguities in the grammar and resolve conflicts. 38 | // It also provides essential functionalities for building an LR automaton and the corresponding LR parsing table. 39 | // 40 | // Always use one of the provided functions to create a new instance of this type. 41 | // Direct instantiation of the struct is discouraged. 42 | type Grammar struct { 43 | *grammar.CFG 44 | Automaton 45 | } 46 | 47 | // NewGrammarWithLR0 creates a new augmented grammar based on the complete sets of LR(0) items. 48 | func NewGrammarWithLR0(G *grammar.CFG) *Grammar { 49 | G = augment(G) 50 | 51 | return &Grammar{ 52 | CFG: G, 53 | Automaton: &automaton{ 54 | G: G, 55 | calculator: &calculator0{ 56 | G: G, 57 | }, 58 | }, 59 | } 60 | } 61 | 62 | // NewGrammarWithLR1 creates a new augmented grammar based on the complete sets of LR(1) items. 63 | func NewGrammarWithLR1(G *grammar.CFG) *Grammar { 64 | G = augment(G) 65 | FIRST := G.ComputeFIRST() 66 | 67 | return &Grammar{ 68 | CFG: G, 69 | Automaton: &automaton{ 70 | G: G, 71 | calculator: &calculator1{ 72 | G: G, 73 | FIRST: FIRST, 74 | }, 75 | }, 76 | } 77 | } 78 | 79 | // NewGrammarWithLR0Kernel creates a new augmented grammar based on the kernel sets of LR(0) items. 80 | func NewGrammarWithLR0Kernel(G *grammar.CFG) *Grammar { 81 | G = augment(G) 82 | 83 | return &Grammar{ 84 | CFG: G, 85 | Automaton: &kernelAutomaton{ 86 | G: G, 87 | calculator: &calculator0{ 88 | G: G, 89 | }, 90 | }, 91 | } 92 | } 93 | 94 | // NewGrammarWithLR1Kernel creates a new augmented grammar based on the kernel sets of LR(1) items. 95 | func NewGrammarWithLR1Kernel(G *grammar.CFG) *Grammar { 96 | G = augment(G) 97 | FIRST := G.ComputeFIRST() 98 | 99 | return &Grammar{ 100 | CFG: G, 101 | Automaton: &kernelAutomaton{ 102 | G: G, 103 | calculator: &calculator1{ 104 | G: G, 105 | FIRST: FIRST, 106 | }, 107 | }, 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /heap/binary_test.go: -------------------------------------------------------------------------------- 1 | package heap 2 | 3 | import "testing" 4 | 5 | func getBinaryTests() []heapTest[int, string] { 6 | tests := getHeapTests() 7 | 8 | tests[0].heap = "Binary Min Heap" 9 | tests[0].expectedDOT = `strict digraph "Binary Heap" { 10 | concentrate=false; 11 | node [shape=Mrecord]; 12 | }` 13 | 14 | tests[1].heap = "Binary Max Heap" 15 | tests[1].expectedDOT = `strict digraph "Binary Heap" { 16 | concentrate=false; 17 | node [shape=Mrecord]; 18 | }` 19 | 20 | tests[2].heap = "Binary Min Heap" 21 | tests[2].expectedDOT = `strict digraph "Binary Heap" { 22 | concentrate=false; 23 | node [shape=Mrecord]; 24 | 25 | 1 [label="10 | Task#1"]; 26 | 2 [label="30 | Task#3"]; 27 | 3 [label="20 | Task#2"]; 28 | 29 | 1 -> 2 []; 30 | 1 -> 3 []; 31 | }` 32 | 33 | tests[3].heap = "Binary Max Heap" 34 | tests[3].expectedDOT = `strict digraph "Binary Heap" { 35 | concentrate=false; 36 | node [shape=Mrecord]; 37 | 38 | 1 [label="30 | Task#3"]; 39 | 2 [label="10 | Task#1"]; 40 | 3 [label="20 | Task#2"]; 41 | 42 | 1 -> 2 []; 43 | 1 -> 3 []; 44 | }` 45 | 46 | tests[4].heap = "Binary Min Heap" 47 | tests[4].expectedDOT = `strict digraph "Binary Heap" { 48 | concentrate=false; 49 | node [shape=Mrecord]; 50 | 51 | 1 [label="10 | Task#1"]; 52 | 2 [label="20 | Task#2"]; 53 | 3 [label="40 | Task#4"]; 54 | 4 [label="50 | Task#5"]; 55 | 5 [label="30 | Task#3"]; 56 | 57 | 1 -> 2 []; 58 | 1 -> 3 []; 59 | 2 -> 4 []; 60 | 2 -> 5 []; 61 | }` 62 | 63 | tests[5].heap = "Binary Max Heap" 64 | tests[5].expectedDOT = `strict digraph "Binary Heap" { 65 | concentrate=false; 66 | node [shape=Mrecord]; 67 | 68 | 1 [label="50 | Task#5"]; 69 | 2 [label="40 | Task#4"]; 70 | 3 [label="20 | Task#2"]; 71 | 4 [label="10 | Task#1"]; 72 | 5 [label="30 | Task#3"]; 73 | 74 | 1 -> 2 []; 75 | 1 -> 3 []; 76 | 2 -> 4 []; 77 | 2 -> 5 []; 78 | }` 79 | 80 | tests[6].heap = "Binary Min Heap" 81 | tests[6].expectedDOT = `strict digraph "Binary Heap" { 82 | concentrate=false; 83 | node [shape=Mrecord]; 84 | 85 | 1 [label="10 | Task#1"]; 86 | 2 [label="20 | Task#2"]; 87 | 3 [label="40 | Task#4"]; 88 | 4 [label="30 | Task#3"]; 89 | 5 [label="70 | Task#7"]; 90 | 6 [label="80 | Task#8"]; 91 | 7 [label="60 | Task#6"]; 92 | 8 [label="90 | Task#9"]; 93 | 9 [label="50 | Task#5"]; 94 | 95 | 1 -> 2 []; 96 | 1 -> 3 []; 97 | 2 -> 4 []; 98 | 2 -> 5 []; 99 | 3 -> 6 []; 100 | 3 -> 7 []; 101 | 4 -> 8 []; 102 | 4 -> 9 []; 103 | }` 104 | 105 | tests[7].heap = "Binary Max Heap" 106 | tests[7].expectedDOT = `strict digraph "Binary Heap" { 107 | concentrate=false; 108 | node [shape=Mrecord]; 109 | 110 | 1 [label="90 | Task#9"]; 111 | 2 [label="80 | Task#8"]; 112 | 3 [label="60 | Task#6"]; 113 | 4 [label="70 | Task#7"]; 114 | 5 [label="30 | Task#3"]; 115 | 6 [label="20 | Task#2"]; 116 | 7 [label="50 | Task#5"]; 117 | 8 [label="10 | Task#1"]; 118 | 9 [label="40 | Task#4"]; 119 | 120 | 1 -> 2 []; 121 | 1 -> 3 []; 122 | 2 -> 4 []; 123 | 2 -> 5 []; 124 | 3 -> 6 []; 125 | 3 -> 7 []; 126 | 4 -> 8 []; 127 | 4 -> 9 []; 128 | }` 129 | 130 | return tests 131 | } 132 | 133 | func TestBinaryHeap(t *testing.T) { 134 | tests := getBinaryTests() 135 | 136 | for _, tc := range tests { 137 | heap := NewBinary(tc.size, tc.cmpKey, tc.eqVal) 138 | runHeapTest(t, heap, tc) 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /range/disc/disc.go: -------------------------------------------------------------------------------- 1 | // Package disc provides algorithms and data structures for discrete ranges. 2 | package disc 3 | 4 | import "fmt" 5 | 6 | // Discrete represents discrete numerical types. 7 | type Discrete interface { 8 | ~int | ~int8 | ~int16 | ~int32 | ~int64 | 9 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr 10 | } 11 | 12 | // Range represents a range of discrete values. 13 | // Discrete range bounds are always inclusive. 14 | type Range[T Discrete] struct { 15 | Lo T 16 | Hi T 17 | } 18 | 19 | // Valid determines if the range is valid. 20 | func (r Range[T]) Valid() bool { 21 | return r.Lo <= r.Hi 22 | } 23 | 24 | // String implements the fmt.Stringer interface. 25 | func (r Range[T]) String() string { 26 | return fmt.Sprintf("[%v, %v]", r.Lo, r.Hi) 27 | } 28 | 29 | // Equal implements the generic.Equaler interface. 30 | func (r Range[T]) Equal(rhs Range[T]) bool { 31 | if !rhs.Valid() { 32 | panic(fmt.Sprintf("invalid range: %s", rhs)) 33 | } 34 | 35 | return r.Lo == rhs.Lo && r.Hi == rhs.Hi 36 | } 37 | 38 | // Includes checks if the discrete range includes the given value. 39 | func (r Range[T]) Includes(v T) bool { 40 | return r.Lo <= v && v <= r.Hi 41 | } 42 | 43 | // Adjacent checks if two discrete ranges are adjacent. 44 | // The first return value indicates if r is immediately before rr. 45 | // The second return value indicates if r is immediately after rr. 46 | func (r Range[T]) Adjacent(rr Range[T]) (bool, bool) { 47 | if !rr.Valid() { 48 | panic(fmt.Sprintf("invalid range: %s", rr)) 49 | } 50 | 51 | return r.Hi+1 == rr.Lo, rr.Hi+1 == r.Lo 52 | } 53 | 54 | // Intersect returns the intersection of two discrete ranges. 55 | func (r Range[T]) Intersect(rr Range[T]) RangeOrEmpty[T] { 56 | if !rr.Valid() { 57 | panic(fmt.Sprintf("invalid range: %s", rr)) 58 | } 59 | 60 | res := Range[T]{ 61 | Lo: max(r.Lo, rr.Lo), 62 | Hi: min(r.Hi, rr.Hi), 63 | } 64 | 65 | if res.Valid() { 66 | return RangeOrEmpty[T]{Range: res} 67 | } 68 | 69 | return RangeOrEmpty[T]{Empty: true} 70 | } 71 | 72 | // Subtract returns the subtraction of two discrete ranges. 73 | // It returns two ranges representing the left and right parts of the subtraction. 74 | func (r Range[T]) Subtract(rr Range[T]) (RangeOrEmpty[T], RangeOrEmpty[T]) { 75 | if !rr.Valid() { 76 | panic(fmt.Sprintf("invalid range: %s", rr)) 77 | } 78 | 79 | left := Range[T]{ 80 | Lo: r.Lo, 81 | Hi: min(r.Hi, rr.Lo-1), 82 | } 83 | 84 | right := Range[T]{ 85 | Lo: max(r.Lo, rr.Hi+1), 86 | Hi: r.Hi, 87 | } 88 | 89 | if left.Valid() && right.Valid() { 90 | return RangeOrEmpty[T]{Range: left}, RangeOrEmpty[T]{Range: right} 91 | } else if left.Valid() { 92 | return RangeOrEmpty[T]{Range: left}, RangeOrEmpty[T]{Empty: true} 93 | } else if right.Valid() { 94 | return RangeOrEmpty[T]{Empty: true}, RangeOrEmpty[T]{Range: right} 95 | } 96 | 97 | return RangeOrEmpty[T]{Empty: true}, RangeOrEmpty[T]{Empty: true} 98 | } 99 | 100 | // RangeOrEmpty represents a discrete range that can be empty. 101 | type RangeOrEmpty[T Discrete] struct { 102 | Range[T] 103 | Empty bool 104 | } 105 | 106 | // EqRange compares two discrete ranges for equality. 107 | func EqRange[T Discrete](lhs, rhs Range[T]) bool { 108 | return lhs.Equal(rhs) 109 | } 110 | 111 | // CmpRange compares two discrete ranges by their low bounds. 112 | func CmpRange[T Discrete](lhs, rhs Range[T]) int { 113 | if lhs.Lo < rhs.Lo { 114 | return -1 115 | } else if lhs.Lo > rhs.Lo { 116 | return 1 117 | } else { 118 | return 0 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /parser/lr/state.go: -------------------------------------------------------------------------------- 1 | package lr 2 | 3 | import ( 4 | "iter" 5 | 6 | "github.com/moorara/algo/generic" 7 | "github.com/moorara/algo/hash" 8 | "github.com/moorara/algo/sort" 9 | ) 10 | 11 | const ErrState = State(-1) 12 | 13 | var ( 14 | EqState = generic.NewEqualFunc[State]() 15 | CmpState = generic.NewCompareFunc[State]() 16 | HashState = hash.HashFuncForInt[State](nil) 17 | ) 18 | 19 | // State represents a state in the LR parsing table or automaton. 20 | type State int 21 | 22 | // StateMap represents a two-leveled indexed map where the first index (state) maps to an item set, 23 | // and within each item set, the second index maps to individual items. 24 | type StateMap [][]Item 25 | 26 | // BuildStateMap constructs a deterministic mapping of item sets and individual items. 27 | // It creates a StateMap where the first index (state) corresponds to an item set, 28 | // and the second index maps to individual items within each set, sorted in a consistent order. 29 | func BuildStateMap(C ItemSetCollection) StateMap { 30 | sets := generic.Collect1(C.All()) 31 | sort.Quick(sets, cmpItemSet) 32 | 33 | m := make([][]Item, len(sets)) 34 | 35 | for i, set := range sets { 36 | m[i] = generic.Collect1(set.All()) 37 | sort.Quick(m[i], CmpItem) 38 | } 39 | 40 | return m 41 | } 42 | 43 | // Item returns the item at the specified index i within the item set indexed by state s. 44 | func (m StateMap) Item(s State, i int) Item { 45 | return m[s][i] 46 | } 47 | 48 | // ItemSet returns the item set associated with the specified state s. 49 | // It creates a new ItemSet from the items corresponding to the given state. 50 | func (m StateMap) ItemSet(s State) ItemSet { 51 | return NewItemSet(m[s]...) 52 | } 53 | 54 | // FindItem finds the state corresponding to a given item set. 55 | // It returns the state if found, or ErrState if no match exists. 56 | func (m StateMap) FindItem(s State, item Item) int { 57 | for i, it := range m[s] { 58 | if it.Equal(item) { 59 | return i 60 | } 61 | } 62 | 63 | return -1 64 | } 65 | 66 | // FindItemSet finds the state corresponding to a given item set. 67 | // It returns the state if found, or ErrState if no match exists. 68 | func (m StateMap) FindItemSet(I ItemSet) State { 69 | for i := range m { 70 | if s := State(i); m.equalToItemSet(s, I) { 71 | return s 72 | } 73 | } 74 | 75 | return ErrState 76 | } 77 | 78 | func (m StateMap) equalToItemSet(s State, I ItemSet) bool { 79 | for _, it := range m[s] { 80 | if !I.Contains(it) { 81 | return false 82 | } 83 | } 84 | 85 | for it := range I.All() { 86 | if m.FindItem(s, it) == -1 { 87 | return false 88 | } 89 | } 90 | 91 | return true 92 | } 93 | 94 | // States returns all states in the map. 95 | func (m StateMap) States() []State { 96 | states := make([]State, len(m)) 97 | for i := range m { 98 | states[i] = State(i) 99 | } 100 | 101 | return states 102 | } 103 | 104 | // All returns an iterator sequence that contains all state-ItemSet pairs in the StateMap. 105 | func (m StateMap) All() iter.Seq2[State, ItemSet] { 106 | return func(yield func(State, ItemSet) bool) { 107 | for i := range m { 108 | if s := State(i); !yield(s, m.ItemSet(s)) { 109 | return 110 | } 111 | } 112 | } 113 | } 114 | 115 | // String returns a string representation of all states in the map. 116 | func (m StateMap) String() string { 117 | cs := &itemSetCollectionStringer{ 118 | sets: make([]ItemSet, len(m)), 119 | } 120 | 121 | for i, items := range m { 122 | cs.sets[i] = NewItemSet(items...) 123 | } 124 | 125 | return cs.String() 126 | } 127 | -------------------------------------------------------------------------------- /lexer/lexer.go: -------------------------------------------------------------------------------- 1 | // Package lexer defines abstractions and data types for constructing lexers. 2 | // 3 | // A lexer, also known as a lexical analyzer or scanner, is responsible for tokenizing input source code. 4 | // It processes a stream of characters and converts them into a stream of tokens, 5 | // which represent meaningful units of the language. 6 | // These tokens are subsequently passed to a parser for syntax analysis and the construction of parse trees. 7 | // 8 | // Lexical analysis (scanning) belongs to a different domain than syntax analysis (parsing). 9 | // Lexical analysis deals with regular languages and grammars (Type 3), 10 | // while syntax analysis deals with context-free languages and grammars (Type 2). 11 | // A lexical analyzer is, in principle, a deterministic finite automaton (DFA) 12 | // with additional functionality built on top of it. 13 | // Lexers can be implemented either by hand or auto-generated. 14 | package lexer 15 | 16 | import ( 17 | "bytes" 18 | "fmt" 19 | 20 | "github.com/moorara/algo/grammar" 21 | ) 22 | 23 | // Lexer defines the interface for a lexical analyzer. 24 | type Lexer interface { 25 | // NextToken reads characters from the input source and returns the next token. 26 | // It may also return an error if there is an issue during tokenization. 27 | NextToken() (Token, error) 28 | } 29 | 30 | // Token represents a unit of the input language. 31 | // 32 | // A token consists of a terminal symbol, along with additional information such as 33 | // the lexeme (the actual value of the token in the input) and its position in the input stream. 34 | // 35 | // For example, identifiers in a programming language may have different names, 36 | // but their token type (terminal symbol) is typically "ID". 37 | // Similarly, the token "NUM" can have various lexeme values, 38 | // representing different numerical values in the input. 39 | type Token struct { 40 | grammar.Terminal 41 | Lexeme string 42 | Pos Position 43 | } 44 | 45 | // String implements the fmt.Stringer interface. 46 | // 47 | // It returns a formatted string representation of the token. 48 | func (t Token) String() string { 49 | return fmt.Sprintf("%s <%s, %s>", t.Terminal, t.Lexeme, t.Pos) 50 | } 51 | 52 | // Equal determines whether or not two tokens are the same. 53 | func (t Token) Equal(rhs Token) bool { 54 | return t.Terminal.Equal(rhs.Terminal) && 55 | t.Lexeme == rhs.Lexeme && 56 | t.Pos.Equal(rhs.Pos) 57 | } 58 | 59 | // Position represents a specific location in an input source. 60 | type Position struct { 61 | Filename string // The name of the input source file (optional). 62 | Offset int // The byte offset from the beginning of the file. 63 | Line int // The line number (1-based). 64 | Column int // The column number on the line (1-based). 65 | } 66 | 67 | // String implements the fmt.Stringer interface. 68 | // 69 | // It returns a formatted string representation of the position. 70 | func (p Position) String() string { 71 | var b bytes.Buffer 72 | 73 | if len(p.Filename) > 0 { 74 | fmt.Fprintf(&b, "%s:", p.Filename) 75 | } 76 | 77 | if p.Line > 0 && p.Column > 0 { 78 | fmt.Fprintf(&b, "%d:%d", p.Line, p.Column) 79 | } else { 80 | fmt.Fprintf(&b, "%d", p.Offset) 81 | } 82 | 83 | return b.String() 84 | } 85 | 86 | // Equal determines whether or not two positions are the same. 87 | func (p Position) Equal(rhs Position) bool { 88 | return p.Filename == rhs.Filename && 89 | p.Offset == rhs.Offset && 90 | p.Line == rhs.Line && 91 | p.Column == rhs.Column 92 | } 93 | 94 | // IsZero checks if a position is a zero (empty) value. 95 | func (p Position) IsZero() bool { 96 | var zero Position 97 | return p == zero 98 | } 99 | -------------------------------------------------------------------------------- /set/benchmark_test.go: -------------------------------------------------------------------------------- 1 | package set 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/moorara/algo/generic" 7 | "github.com/moorara/algo/hash" 8 | ) 9 | 10 | const ( 11 | minLen = 10 12 | maxLen = 100 13 | chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 14 | ) 15 | 16 | func randString(minLen, maxLen int) string { 17 | n := len(chars) 18 | l := minLen + r.Intn(maxLen-minLen+1) 19 | b := make([]byte, l) 20 | 21 | for i := range b { 22 | b[i] = chars[r.Intn(n)] 23 | } 24 | 25 | return string(b) 26 | } 27 | 28 | func randStringSlice(size int) []string { 29 | s := make([]string, size) 30 | for i := range s { 31 | s[i] = randString(minLen, maxLen) 32 | } 33 | 34 | return s 35 | } 36 | 37 | func runAddBenchmark(b *testing.B, set Set[string]) { 38 | vals := randStringSlice(b.N) 39 | 40 | b.ResetTimer() 41 | 42 | set.Add(vals...) 43 | } 44 | 45 | func runRemoveBenchmark(b *testing.B, set Set[string]) { 46 | vals := randStringSlice(b.N) 47 | set.Add(vals...) 48 | 49 | b.ResetTimer() 50 | 51 | set.Remove(vals...) 52 | } 53 | 54 | func runContainsBenchmark(b *testing.B, set Set[string]) { 55 | vals := randStringSlice(b.N) 56 | set.Add(vals...) 57 | 58 | b.ResetTimer() 59 | 60 | set.Contains(vals...) 61 | } 62 | 63 | func BenchmarkSet_Add(b *testing.B) { 64 | hashString := hash.HashFuncForString[string](nil) 65 | eqString := generic.NewEqualFunc[string]() 66 | cmpString := generic.NewCompareFunc[string]() 67 | opts := HashSetOpts{} 68 | 69 | b.Run("Set.Add", func(b *testing.B) { 70 | ht := New(eqString) 71 | runAddBenchmark(b, ht) 72 | }) 73 | 74 | b.Run("StableSet.Add", func(b *testing.B) { 75 | ht := NewStableSet(eqString) 76 | runAddBenchmark(b, ht) 77 | }) 78 | 79 | b.Run("SortedSet.Add", func(b *testing.B) { 80 | ht := NewSortedSet(cmpString) 81 | runAddBenchmark(b, ht) 82 | }) 83 | 84 | b.Run("HashSet.Add", func(b *testing.B) { 85 | ht := NewHashSet(hashString, eqString, opts) 86 | runAddBenchmark(b, ht) 87 | }) 88 | } 89 | 90 | func BenchmarkSet_Remove(b *testing.B) { 91 | hashString := hash.HashFuncForString[string](nil) 92 | eqString := generic.NewEqualFunc[string]() 93 | cmpString := generic.NewCompareFunc[string]() 94 | opts := HashSetOpts{} 95 | 96 | b.Run("Set.Remove", func(b *testing.B) { 97 | ht := New(eqString) 98 | runRemoveBenchmark(b, ht) 99 | }) 100 | 101 | b.Run("StableSet.Remove", func(b *testing.B) { 102 | ht := NewStableSet(eqString) 103 | runRemoveBenchmark(b, ht) 104 | }) 105 | 106 | b.Run("SortedSet.Remove", func(b *testing.B) { 107 | ht := NewSortedSet(cmpString) 108 | runRemoveBenchmark(b, ht) 109 | }) 110 | 111 | b.Run("HashSet.Remove", func(b *testing.B) { 112 | ht := NewHashSet(hashString, eqString, opts) 113 | runRemoveBenchmark(b, ht) 114 | }) 115 | } 116 | 117 | func BenchmarkSet_Contains(b *testing.B) { 118 | hashString := hash.HashFuncForString[string](nil) 119 | eqString := generic.NewEqualFunc[string]() 120 | cmpString := generic.NewCompareFunc[string]() 121 | opts := HashSetOpts{} 122 | 123 | b.Run("Set.Contains", func(b *testing.B) { 124 | ht := New(eqString) 125 | runContainsBenchmark(b, ht) 126 | }) 127 | 128 | b.Run("StableSet.Contains", func(b *testing.B) { 129 | ht := NewStableSet(eqString) 130 | runContainsBenchmark(b, ht) 131 | }) 132 | 133 | b.Run("SortedSet.Contains", func(b *testing.B) { 134 | ht := NewSortedSet(cmpString) 135 | runContainsBenchmark(b, ht) 136 | }) 137 | 138 | b.Run("HashSet.Contains", func(b *testing.B) { 139 | ht := NewHashSet(hashString, eqString, opts) 140 | runContainsBenchmark(b, ht) 141 | }) 142 | } 143 | -------------------------------------------------------------------------------- /errors/multi_error.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import "errors" 4 | 5 | // Append adds one or more errors to an existing error and returns a MultiError instance. 6 | // 7 | // - If the input error e is nil, a new instance of MultiError is created. 8 | // - If e is not already an instance of MultiError, it will be wrapped into a new MultiError instance. 9 | // - If any of the provided errors (errs) are instances of MultiError, they are flattened into the result. 10 | func Append(e error, errs ...error) *MultiError { 11 | var me *MultiError 12 | 13 | if e == nil || e == me { 14 | me = new(MultiError) 15 | } else { 16 | switch e := e.(type) { 17 | case *MultiError: 18 | me = e 19 | default: 20 | // Wrap the existing error e into a new MultiError instance. 21 | me = new(MultiError) 22 | me.errs = make([]error, 0, len(errs)+1) 23 | me.errs = append(me.errs, e) 24 | } 25 | } 26 | 27 | // Iterate through the provided errors and flatten any instances of MultiError. 28 | for _, err := range errs { 29 | if err != nil { 30 | switch err := err.(type) { 31 | case *MultiError: 32 | me.errs = append(me.errs, err.errs...) 33 | default: 34 | me.errs = append(me.errs, err) 35 | } 36 | } 37 | } 38 | 39 | // If no errors were accumulated, return nil. 40 | // This ensures the behavior of MultiError stays consistent with a single error when no errors are present. 41 | if len(me.errs) == 0 { 42 | return nil 43 | } 44 | 45 | return me 46 | } 47 | 48 | // MultiError represents an error type that aggregates multiple errors. 49 | // It is used to accumulate and manage multiple errors, wrapping them into a single error instance. 50 | type MultiError struct { 51 | errs []error 52 | Format ErrorFormat 53 | } 54 | 55 | // ErrorOrNil returns an error if the MultiError instance contains any errors, or nil if it has none. 56 | // This method is useful for ensuring that a valid error value is returned after accumulating errors, 57 | // indicating whether errors are present or not. 58 | func (e *MultiError) ErrorOrNil() error { 59 | if e == nil || len(e.errs) == 0 { 60 | return nil 61 | } 62 | 63 | return e 64 | } 65 | 66 | // Error implements the error interface for MultiError. 67 | // It formats the accumulated errors into a single string representation. 68 | // If a custom ErrorFormat function is provided, it will be used; 69 | // otherwise, the default ErrorFormat (DefaultErrorFormat) will be applied. 70 | func (e *MultiError) Error() string { 71 | if e.Format == nil { 72 | e.Format = DefaultErrorFormat 73 | } 74 | 75 | return e.Format(e.errs) 76 | } 77 | 78 | // Is checks if any of the errors in the MultiError matches the target error. 79 | func (e *MultiError) Is(target error) bool { 80 | for _, err := range e.errs { 81 | if errors.Is(err, target) { 82 | return true 83 | } 84 | } 85 | 86 | return false 87 | } 88 | 89 | // As finds the first error in the MultiError's errors that matches target, 90 | // and if one is found, sets target to that error value and returns true. 91 | // Otherwise, it returns false. 92 | func (e *MultiError) As(target any) bool { 93 | for _, err := range e.errs { 94 | if errors.As(err, target) { 95 | return true 96 | } 97 | } 98 | 99 | return false 100 | } 101 | 102 | // Unwrap implements the unwrap interface for MultiError. 103 | // It returns the slice of accumulated errors wrapped in the MultiError instance. 104 | // If there are no errors, it returns nil, indicating that e does not wrap any error. 105 | func (e *MultiError) Unwrap() []error { 106 | if len(e.errs) == 0 { 107 | return nil 108 | } 109 | 110 | return e.errs 111 | } 112 | -------------------------------------------------------------------------------- /parser/lr/grammar_test.go: -------------------------------------------------------------------------------- 1 | package lr 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | 8 | "github.com/moorara/algo/grammar" 9 | "github.com/moorara/algo/internal/parsertest" 10 | ) 11 | 12 | func TestAugment(t *testing.T) { 13 | tests := []struct { 14 | name string 15 | G *grammar.CFG 16 | expectedCFG *grammar.CFG 17 | }{ 18 | { 19 | name: "OK", 20 | G: parsertest.Grammars[3], 21 | expectedCFG: grammar.NewCFG( 22 | []grammar.Terminal{"+", "*", "(", ")", "id", grammar.Endmarker}, 23 | []grammar.NonTerminal{"E′", "E", "T", "F"}, 24 | parsertest.Prods[3], 25 | "E′", 26 | ), 27 | }, 28 | } 29 | 30 | for _, tc := range tests { 31 | t.Run(tc.name, func(t *testing.T) { 32 | assert.NoError(t, tc.G.Verify()) 33 | augG := augment(tc.G) 34 | assert.True(t, augG.Equal(tc.expectedCFG)) 35 | }) 36 | } 37 | } 38 | 39 | func TestNewGrammarWithLR0(t *testing.T) { 40 | tests := []struct { 41 | name string 42 | G *grammar.CFG 43 | }{ 44 | { 45 | name: "OK", 46 | G: parsertest.Grammars[3], 47 | }, 48 | } 49 | 50 | for _, tc := range tests { 51 | t.Run(tc.name, func(t *testing.T) { 52 | assert.NoError(t, tc.G.Verify()) 53 | G := NewGrammarWithLR0(tc.G) 54 | 55 | assert.NotNil(t, G) 56 | assert.NotNil(t, G.CFG) 57 | assert.NotNil(t, G.Automaton) 58 | assert.IsType(t, G.Automaton, &automaton{}) 59 | assert.IsType(t, G.Automaton.(*automaton).calculator, &calculator0{}) 60 | }) 61 | } 62 | } 63 | 64 | func TestNewGrammarWithLR1(t *testing.T) { 65 | tests := []struct { 66 | name string 67 | G *grammar.CFG 68 | precedences PrecedenceLevels 69 | }{ 70 | { 71 | name: "OK", 72 | G: parsertest.Grammars[3], 73 | }, 74 | } 75 | 76 | for _, tc := range tests { 77 | t.Run(tc.name, func(t *testing.T) { 78 | assert.NoError(t, tc.G.Verify()) 79 | G := NewGrammarWithLR1(tc.G) 80 | 81 | assert.NotNil(t, G) 82 | assert.NotNil(t, G.CFG) 83 | assert.NotNil(t, G.Automaton) 84 | assert.IsType(t, G.Automaton, &automaton{}) 85 | assert.IsType(t, G.Automaton.(*automaton).calculator, &calculator1{}) 86 | }) 87 | } 88 | } 89 | 90 | func TestNewGrammarWithLR0Kernel(t *testing.T) { 91 | tests := []struct { 92 | name string 93 | G *grammar.CFG 94 | precedences PrecedenceLevels 95 | }{ 96 | { 97 | name: "OK", 98 | G: parsertest.Grammars[3], 99 | precedences: PrecedenceLevels{}, 100 | }, 101 | } 102 | 103 | for _, tc := range tests { 104 | t.Run(tc.name, func(t *testing.T) { 105 | assert.NoError(t, tc.G.Verify()) 106 | G := NewGrammarWithLR0Kernel(tc.G) 107 | 108 | assert.NotNil(t, G) 109 | assert.NotNil(t, G.CFG) 110 | assert.NotNil(t, G.Automaton) 111 | assert.IsType(t, G.Automaton, &kernelAutomaton{}) 112 | assert.IsType(t, G.Automaton.(*kernelAutomaton).calculator, &calculator0{}) 113 | }) 114 | } 115 | } 116 | 117 | func TestNewGrammarWithLR1Kernel(t *testing.T) { 118 | tests := []struct { 119 | name string 120 | G *grammar.CFG 121 | precedences PrecedenceLevels 122 | }{ 123 | { 124 | name: "OK", 125 | G: parsertest.Grammars[3], 126 | precedences: PrecedenceLevels{}, 127 | }, 128 | } 129 | 130 | for _, tc := range tests { 131 | t.Run(tc.name, func(t *testing.T) { 132 | assert.NoError(t, tc.G.Verify()) 133 | G := NewGrammarWithLR1Kernel(tc.G) 134 | 135 | assert.NotNil(t, G) 136 | assert.NotNil(t, G.CFG) 137 | assert.NotNil(t, G.Automaton) 138 | assert.IsType(t, G.Automaton, &kernelAutomaton{}) 139 | assert.IsType(t, G.Automaton.(*kernelAutomaton).calculator, &calculator1{}) 140 | }) 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /heap/indexed_binary_test.go: -------------------------------------------------------------------------------- 1 | package heap 2 | 3 | import "testing" 4 | 5 | func getIndexedBinaryTests() []indexedHeapTest[int, string] { 6 | tests := getIndexedHeapTests() 7 | 8 | tests[0].heap = "Indexed Binary Min Heap" 9 | tests[0].expectedDOT = `strict digraph "Indexed Binary Heap" { 10 | concentrate=false; 11 | node [shape=Mrecord]; 12 | }` 13 | 14 | tests[1].heap = "Indexed Binary Max Heap" 15 | tests[1].expectedDOT = `strict digraph "Indexed Binary Heap" { 16 | concentrate=false; 17 | node [shape=Mrecord]; 18 | }` 19 | 20 | tests[2].heap = "Indexed Binary Min Heap" 21 | tests[2].expectedDOT = `strict digraph "Indexed Binary Heap" { 22 | concentrate=false; 23 | node [shape=Mrecord]; 24 | 25 | 1 [label="{ 1 | { 10 | Task#1 } }"]; 26 | 0 [label="{ 0 | { 30 | Task#3 } }"]; 27 | 2 [label="{ 2 | { 20 | Task#2 } }"]; 28 | 29 | 1 -> 0 []; 30 | 1 -> 2 []; 31 | }` 32 | 33 | tests[3].heap = "Indexed Binary Max Heap" 34 | tests[3].expectedDOT = `strict digraph "Indexed Binary Heap" { 35 | concentrate=false; 36 | node [shape=Mrecord]; 37 | 38 | 1 [label="{ 1 | { 30 | Task#3 } }"]; 39 | 2 [label="{ 2 | { 20 | Task#2 } }"]; 40 | 0 [label="{ 0 | { 10 | Task#1 } }"]; 41 | 42 | 1 -> 2 []; 43 | 1 -> 0 []; 44 | }` 45 | 46 | tests[4].heap = "Indexed Binary Min Heap" 47 | tests[4].expectedDOT = `strict digraph "Indexed Binary Heap" { 48 | concentrate=false; 49 | node [shape=Mrecord]; 50 | 51 | 3 [label="{ 3 | { 10 | Task#1 } }"]; 52 | 4 [label="{ 4 | { 20 | Task#2 } }"]; 53 | 1 [label="{ 1 | { 30 | Task#3 } }"]; 54 | 0 [label="{ 0 | { 50 | Task#5 } }"]; 55 | 2 [label="{ 2 | { 40 | Task#4 } }"]; 56 | 57 | 3 -> 4 []; 58 | 3 -> 1 []; 59 | 4 -> 0 []; 60 | 4 -> 2 []; 61 | }` 62 | 63 | tests[5].heap = "Indexed Binary Max Heap" 64 | tests[5].expectedDOT = `strict digraph "Indexed Binary Heap" { 65 | concentrate=false; 66 | node [shape=Mrecord]; 67 | 68 | 3 [label="{ 3 | { 50 | Task#5 } }"]; 69 | 4 [label="{ 4 | { 40 | Task#4 } }"]; 70 | 2 [label="{ 2 | { 20 | Task#2 } }"]; 71 | 0 [label="{ 0 | { 10 | Task#1 } }"]; 72 | 1 [label="{ 1 | { 30 | Task#3 } }"]; 73 | 74 | 3 -> 4 []; 75 | 3 -> 2 []; 76 | 4 -> 0 []; 77 | 4 -> 1 []; 78 | }` 79 | 80 | tests[6].heap = "Indexed Binary Min Heap" 81 | tests[6].expectedDOT = `strict digraph "Indexed Binary Heap" { 82 | concentrate=false; 83 | node [shape=Mrecord]; 84 | 85 | 7 [label="{ 7 | { 10 | Task#1 } }"]; 86 | 8 [label="{ 8 | { 20 | Task#2 } }"]; 87 | 4 [label="{ 4 | { 50 | Task#5 } }"]; 88 | 6 [label="{ 6 | { 30 | Task#3 } }"]; 89 | 2 [label="{ 2 | { 70 | Task#7 } }"]; 90 | 1 [label="{ 1 | { 80 | Task#8 } }"]; 91 | 5 [label="{ 5 | { 60 | Task#6 } }"]; 92 | 0 [label="{ 0 | { 90 | Task#9 } }"]; 93 | 3 [label="{ 3 | { 40 | Task#4 } }"]; 94 | 95 | 7 -> 8 []; 96 | 7 -> 4 []; 97 | 8 -> 6 []; 98 | 8 -> 2 []; 99 | 4 -> 1 []; 100 | 4 -> 5 []; 101 | 6 -> 0 []; 102 | 6 -> 3 []; 103 | }` 104 | 105 | tests[7].heap = "Indexed Binary Max Heap" 106 | tests[7].expectedDOT = `strict digraph "Indexed Binary Heap" { 107 | concentrate=false; 108 | node [shape=Mrecord]; 109 | 110 | 7 [label="{ 7 | { 90 | Task#9 } }"]; 111 | 8 [label="{ 8 | { 80 | Task#8 } }"]; 112 | 5 [label="{ 5 | { 60 | Task#6 } }"]; 113 | 6 [label="{ 6 | { 70 | Task#7 } }"]; 114 | 4 [label="{ 4 | { 40 | Task#4 } }"]; 115 | 3 [label="{ 3 | { 50 | Task#5 } }"]; 116 | 2 [label="{ 2 | { 20 | Task#2 } }"]; 117 | 0 [label="{ 0 | { 10 | Task#1 } }"]; 118 | 1 [label="{ 1 | { 30 | Task#3 } }"]; 119 | 120 | 7 -> 8 []; 121 | 7 -> 5 []; 122 | 8 -> 6 []; 123 | 8 -> 4 []; 124 | 5 -> 3 []; 125 | 5 -> 2 []; 126 | 6 -> 0 []; 127 | 6 -> 1 []; 128 | }` 129 | 130 | return tests 131 | } 132 | 133 | func TestIndexedBinaryHeap(t *testing.T) { 134 | tests := getIndexedBinaryTests() 135 | 136 | for _, tc := range tests { 137 | heap := NewIndexedBinary(tc.cap, tc.cmpKey, tc.eqVal) 138 | runIndexedHeapTest(t, heap, tc) 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /symboltable/chain_hash_table_test.go: -------------------------------------------------------------------------------- 1 | package symboltable 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | 8 | "github.com/moorara/algo/generic" 9 | "github.com/moorara/algo/hash" 10 | ) 11 | 12 | func getChainHashTableTests() []symbolTableTest[string, int] { 13 | hashFunc := hash.HashFuncForString[string](nil) 14 | eqKey := generic.NewEqualFunc[string]() 15 | eqVal := generic.NewEqualFunc[int]() 16 | opts := HashOpts{} 17 | 18 | tests := getSymbolTableTests() 19 | 20 | tests[0].symbolTable = "Separate Chaining Hash Table" 21 | tests[0].equal = NewChainHashTable(hashFunc, eqKey, eqVal, opts) 22 | tests[0].equal.Put("Apple", 182) 23 | tests[0].equal.Put("Avocado", 200) 24 | tests[0].equal.Put("Banana", 120) 25 | tests[0].equal.Put("Coconut", 1500) 26 | tests[0].equal.Put("Dragon Fruit", 600) 27 | tests[0].equal.Put("Durian", 1500) 28 | tests[0].equal.Put("Guava", 180) 29 | tests[0].equal.Put("Kiwi", 75) 30 | tests[0].equal.Put("Lychee", 20) 31 | tests[0].equal.Put("Mango", 200) 32 | tests[0].equal.Put("Orange", 130) 33 | tests[0].equal.Put("Papaya", 1000) 34 | tests[0].equal.Put("Passion Fruit", 40) 35 | tests[0].equal.Put("Pineapple", 1200) 36 | tests[0].equal.Put("Watermelon", 9000) 37 | tests[0].expectedEqual = true 38 | 39 | tests[1].symbolTable = "Separate Chaining Hash Table" 40 | tests[1].equal = NewChainHashTable(hashFunc, eqKey, eqVal, opts) 41 | tests[1].equal.Put("Golden Pheasant", 15) 42 | tests[1].equal.Put("Harpy Eagle", 35) 43 | tests[1].equal.Put("Kingfisher", 15) 44 | tests[1].equal.Put("Mandarin Duck", 10) 45 | tests[1].equal.Put("Peacock", 20) 46 | tests[1].equal.Put("Quetzal", 25) 47 | tests[1].equal.Put("Scarlet Macaw", 50) 48 | tests[1].equal.Put("Snowy Owl", 10) 49 | tests[1].expectedEqual = true 50 | 51 | tests[2].symbolTable = "Separate Chaining Hash Table" 52 | tests[2].equal = NewChainHashTable(hashFunc, eqKey, eqVal, opts) 53 | tests[2].equal.Put("Accordion", 50) 54 | tests[2].equal.Put("Bassoon", 140) 55 | tests[2].equal.Put("Cello", 120) 56 | tests[2].equal.Put("Clarinet", 66) 57 | tests[2].equal.Put("Double Bass", 180) 58 | tests[2].equal.Put("Drum Set", 200) 59 | tests[2].equal.Put("Flute", 67) 60 | tests[2].equal.Put("Guitar", 100) 61 | tests[2].equal.Put("Harp", 170) 62 | tests[2].equal.Put("Organ", 300) // Extra 63 | tests[2].equal.Put("Piano", 150) 64 | tests[2].equal.Put("Saxophone", 80) 65 | tests[2].equal.Put("Trombone", 120) 66 | tests[2].equal.Put("Trumpet", 48) 67 | tests[2].equal.Put("Ukulele", 60) 68 | tests[2].equal.Put("Violin", 60) 69 | tests[2].expectedEqual = false 70 | 71 | tests[3].symbolTable = "Separate Chaining Hash Table" 72 | tests[3].equal = NewChainHashTable(hashFunc, eqKey, eqVal, opts) 73 | tests[3].equal.Put("Berlin", 10) 74 | // tests[3].equal.Put("London", 11) 75 | tests[3].equal.Put("Montreal", 6) 76 | tests[3].equal.Put("New York", 13) 77 | tests[3].equal.Put("Paris", 12) 78 | tests[3].equal.Put("Rome", 16) 79 | tests[3].equal.Put("Tehran", 17) 80 | tests[3].equal.Put("Tokyo", 16) 81 | tests[3].equal.Put("Toronto", 8) 82 | tests[3].equal.Put("Vancouver", 10) 83 | tests[3].expectedEqual = false 84 | 85 | tests[4].symbolTable = "Separate Chaining Hash Table" 86 | tests[4].equal = nil 87 | tests[4].expectedEqual = false 88 | 89 | return tests 90 | } 91 | 92 | func TestChainHashTable(t *testing.T) { 93 | tests := getChainHashTableTests() 94 | 95 | for _, tc := range tests { 96 | ht := NewChainHashTable(tc.hashKey, tc.eqKey, tc.eqVal, tc.opts) 97 | runSymbolTableTest(t, ht, tc) 98 | } 99 | } 100 | 101 | func TestNewChainHashTable_Panic(t *testing.T) { 102 | hashString := hash.HashFuncForString[string](nil) 103 | eqString := generic.NewEqualFunc[string]() 104 | eqInt := generic.NewEqualFunc[int]() 105 | 106 | assert.PanicsWithValue(t, "The hash table capacity must be a power of 2", func() { 107 | NewChainHashTable(hashString, eqString, eqInt, HashOpts{ 108 | InitialCap: 69, 109 | }) 110 | }) 111 | } 112 | -------------------------------------------------------------------------------- /symboltable/double_hash_table_test.go: -------------------------------------------------------------------------------- 1 | package symboltable 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | 8 | "github.com/moorara/algo/generic" 9 | "github.com/moorara/algo/hash" 10 | ) 11 | 12 | func getDoubleHashTableTests() []symbolTableTest[string, int] { 13 | hashFunc := hash.HashFuncForString[string](nil) 14 | eqKey := generic.NewEqualFunc[string]() 15 | eqVal := generic.NewEqualFunc[int]() 16 | opts := HashOpts{} 17 | 18 | tests := getSymbolTableTests() 19 | 20 | tests[0].symbolTable = "Double Hashing Hash Table" 21 | tests[0].equal = NewDoubleHashTable(hashFunc, eqKey, eqVal, opts) 22 | tests[0].equal.Put("Apple", 182) 23 | tests[0].equal.Put("Avocado", 200) 24 | tests[0].equal.Put("Banana", 120) 25 | tests[0].equal.Put("Coconut", 1500) 26 | tests[0].equal.Put("Dragon Fruit", 600) 27 | tests[0].equal.Put("Durian", 1500) 28 | tests[0].equal.Put("Guava", 180) 29 | tests[0].equal.Put("Kiwi", 75) 30 | tests[0].equal.Put("Lychee", 20) 31 | tests[0].equal.Put("Mango", 200) 32 | tests[0].equal.Put("Orange", 130) 33 | tests[0].equal.Put("Papaya", 1000) 34 | tests[0].equal.Put("Passion Fruit", 40) 35 | tests[0].equal.Put("Pineapple", 1200) 36 | tests[0].equal.Put("Watermelon", 9000) 37 | tests[0].expectedEqual = true 38 | 39 | tests[1].symbolTable = "Double Hashing Hash Table" 40 | tests[1].equal = NewDoubleHashTable(hashFunc, eqKey, eqVal, opts) 41 | tests[1].equal.Put("Golden Pheasant", 15) 42 | tests[1].equal.Put("Harpy Eagle", 35) 43 | tests[1].equal.Put("Kingfisher", 15) 44 | tests[1].equal.Put("Mandarin Duck", 10) 45 | tests[1].equal.Put("Peacock", 20) 46 | tests[1].equal.Put("Quetzal", 25) 47 | tests[1].equal.Put("Scarlet Macaw", 50) 48 | tests[1].equal.Put("Snowy Owl", 10) 49 | tests[1].expectedEqual = true 50 | 51 | tests[2].symbolTable = "Double Hashing Hash Table" 52 | tests[2].equal = NewDoubleHashTable(hashFunc, eqKey, eqVal, opts) 53 | tests[2].equal.Put("Accordion", 50) 54 | tests[2].equal.Put("Bassoon", 140) 55 | tests[2].equal.Put("Cello", 120) 56 | tests[2].equal.Put("Clarinet", 66) 57 | tests[2].equal.Put("Double Bass", 180) 58 | tests[2].equal.Put("Drum Set", 200) 59 | tests[2].equal.Put("Flute", 67) 60 | tests[2].equal.Put("Guitar", 100) 61 | tests[2].equal.Put("Harp", 170) 62 | tests[2].equal.Put("Organ", 300) // Extra 63 | tests[2].equal.Put("Piano", 150) 64 | tests[2].equal.Put("Saxophone", 80) 65 | tests[2].equal.Put("Trombone", 120) 66 | tests[2].equal.Put("Trumpet", 48) 67 | tests[2].equal.Put("Ukulele", 60) 68 | tests[2].equal.Put("Violin", 60) 69 | tests[2].expectedEqual = false 70 | 71 | tests[3].symbolTable = "Double Hashing Hash Table" 72 | tests[3].equal = NewDoubleHashTable(hashFunc, eqKey, eqVal, opts) 73 | tests[3].equal.Put("Berlin", 10) 74 | // tests[3].equal.Put("London", 11) 75 | tests[3].equal.Put("Montreal", 6) 76 | tests[3].equal.Put("New York", 13) 77 | tests[3].equal.Put("Paris", 12) 78 | tests[3].equal.Put("Rome", 16) 79 | tests[3].equal.Put("Tehran", 17) 80 | tests[3].equal.Put("Tokyo", 16) 81 | tests[3].equal.Put("Toronto", 8) 82 | tests[3].equal.Put("Vancouver", 10) 83 | tests[3].expectedEqual = false 84 | 85 | tests[4].symbolTable = "Separate Chaining Hash Table" 86 | tests[4].equal = nil 87 | tests[4].expectedEqual = false 88 | 89 | return tests 90 | } 91 | 92 | func TestDoubleHashTable(t *testing.T) { 93 | tests := getDoubleHashTableTests() 94 | 95 | for _, tc := range tests { 96 | ht := NewDoubleHashTable(tc.hashKey, tc.eqKey, tc.eqVal, tc.opts) 97 | runSymbolTableTest(t, ht, tc) 98 | } 99 | } 100 | 101 | func TestNewDoubleHashTable_Panic(t *testing.T) { 102 | hashString := hash.HashFuncForString[string](nil) 103 | eqString := generic.NewEqualFunc[string]() 104 | eqInt := generic.NewEqualFunc[int]() 105 | 106 | assert.PanicsWithValue(t, "The hash table capacity must be a prime number", func() { 107 | NewDoubleHashTable(hashString, eqString, eqInt, HashOpts{ 108 | InitialCap: 69, 109 | }) 110 | }) 111 | } 112 | -------------------------------------------------------------------------------- /symboltable/linear_hash_table_test.go: -------------------------------------------------------------------------------- 1 | package symboltable 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | 8 | "github.com/moorara/algo/generic" 9 | "github.com/moorara/algo/hash" 10 | ) 11 | 12 | func getLinearHashTableTests() []symbolTableTest[string, int] { 13 | hashFunc := hash.HashFuncForString[string](nil) 14 | eqKey := generic.NewEqualFunc[string]() 15 | eqVal := generic.NewEqualFunc[int]() 16 | opts := HashOpts{} 17 | 18 | tests := getSymbolTableTests() 19 | 20 | tests[0].symbolTable = "Linear Probing Hash Table" 21 | tests[0].equal = NewLinearHashTable(hashFunc, eqKey, eqVal, opts) 22 | tests[0].equal.Put("Apple", 182) 23 | tests[0].equal.Put("Avocado", 200) 24 | tests[0].equal.Put("Banana", 120) 25 | tests[0].equal.Put("Coconut", 1500) 26 | tests[0].equal.Put("Dragon Fruit", 600) 27 | tests[0].equal.Put("Durian", 1500) 28 | tests[0].equal.Put("Guava", 180) 29 | tests[0].equal.Put("Kiwi", 75) 30 | tests[0].equal.Put("Lychee", 20) 31 | tests[0].equal.Put("Mango", 200) 32 | tests[0].equal.Put("Orange", 130) 33 | tests[0].equal.Put("Papaya", 1000) 34 | tests[0].equal.Put("Passion Fruit", 40) 35 | tests[0].equal.Put("Pineapple", 1200) 36 | tests[0].equal.Put("Watermelon", 9000) 37 | tests[0].expectedEqual = true 38 | 39 | tests[1].symbolTable = "Linear Probing Hash Table" 40 | tests[1].equal = NewLinearHashTable(hashFunc, eqKey, eqVal, opts) 41 | tests[1].equal.Put("Golden Pheasant", 15) 42 | tests[1].equal.Put("Harpy Eagle", 35) 43 | tests[1].equal.Put("Kingfisher", 15) 44 | tests[1].equal.Put("Mandarin Duck", 10) 45 | tests[1].equal.Put("Peacock", 20) 46 | tests[1].equal.Put("Quetzal", 25) 47 | tests[1].equal.Put("Scarlet Macaw", 50) 48 | tests[1].equal.Put("Snowy Owl", 10) 49 | tests[1].expectedEqual = true 50 | 51 | tests[2].symbolTable = "Linear Probing Hash Table" 52 | tests[2].equal = NewLinearHashTable(hashFunc, eqKey, eqVal, opts) 53 | tests[2].equal.Put("Accordion", 50) 54 | tests[2].equal.Put("Bassoon", 140) 55 | tests[2].equal.Put("Cello", 120) 56 | tests[2].equal.Put("Clarinet", 66) 57 | tests[2].equal.Put("Double Bass", 180) 58 | tests[2].equal.Put("Drum Set", 200) 59 | tests[2].equal.Put("Flute", 67) 60 | tests[2].equal.Put("Guitar", 100) 61 | tests[2].equal.Put("Harp", 170) 62 | tests[2].equal.Put("Organ", 300) // Extra 63 | tests[2].equal.Put("Piano", 150) 64 | tests[2].equal.Put("Saxophone", 80) 65 | tests[2].equal.Put("Trombone", 120) 66 | tests[2].equal.Put("Trumpet", 48) 67 | tests[2].equal.Put("Ukulele", 60) 68 | tests[2].equal.Put("Violin", 60) 69 | tests[2].expectedEqual = false 70 | 71 | tests[3].symbolTable = "Linear Probing Hash Table" 72 | tests[3].equal = NewLinearHashTable(hashFunc, eqKey, eqVal, opts) 73 | tests[3].equal.Put("Berlin", 10) 74 | // tests[3].equal.Put("London", 11) 75 | tests[3].equal.Put("Montreal", 6) 76 | tests[3].equal.Put("New York", 13) 77 | tests[3].equal.Put("Paris", 12) 78 | tests[3].equal.Put("Rome", 16) 79 | tests[3].equal.Put("Tehran", 17) 80 | tests[3].equal.Put("Tokyo", 16) 81 | tests[3].equal.Put("Toronto", 8) 82 | tests[3].equal.Put("Vancouver", 10) 83 | tests[3].expectedEqual = false 84 | 85 | tests[4].symbolTable = "Separate Chaining Hash Table" 86 | tests[4].equal = nil 87 | tests[4].expectedEqual = false 88 | 89 | return tests 90 | } 91 | 92 | func TestLinearHashTable(t *testing.T) { 93 | tests := getLinearHashTableTests() 94 | 95 | for _, tc := range tests { 96 | ht := NewLinearHashTable(tc.hashKey, tc.eqKey, tc.eqVal, tc.opts) 97 | runSymbolTableTest(t, ht, tc) 98 | } 99 | } 100 | 101 | func TestNewLinearHashTable_Panic(t *testing.T) { 102 | hashString := hash.HashFuncForString[string](nil) 103 | eqString := generic.NewEqualFunc[string]() 104 | eqInt := generic.NewEqualFunc[int]() 105 | 106 | assert.PanicsWithValue(t, "The hash table capacity must be a power of 2", func() { 107 | NewLinearHashTable(hashString, eqString, eqInt, HashOpts{ 108 | InitialCap: 69, 109 | }) 110 | }) 111 | } 112 | --------------------------------------------------------------------------------