├── 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 | 
10 |
11 | ### Directed
12 |
13 | 
14 |
15 | ### Weighted Undirected
16 |
17 | 
18 |
19 | ### Weighted Directed
20 |
21 | 
22 |
23 | ### Flow Network
24 |
25 | 
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 |
--------------------------------------------------------------------------------