Skip to content

Commit b2faf77

Browse files
committed
Initial implementation of sets library
1 parent 0f86551 commit b2faf77

59 files changed

Lines changed: 5099 additions & 2 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/go.yml

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
name: Go
2+
3+
on:
4+
push:
5+
branches: [ 'main' ]
6+
tags: [ 'v*' ]
7+
pull_request:
8+
branches: [ '*' ]
9+
10+
permissions:
11+
contents: read
12+
13+
jobs:
14+
build:
15+
name: Build and test
16+
runs-on: ubuntu-latest
17+
strategy:
18+
fail-fast: false
19+
matrix:
20+
go: [ 1.23, stable ]
21+
22+
steps:
23+
- name: Checkout code
24+
uses: actions/checkout@v6
25+
26+
- name: Set up Go
27+
uses: actions/setup-go@v6
28+
with:
29+
go-version: ${{ matrix.go }}
30+
31+
- name: Check License
32+
if: matrix.go == 'stable'
33+
run: ./checklicense.sh
34+
35+
- name: Check No External Dependencies
36+
if: matrix.go == 'stable'
37+
run: |
38+
if [ "$(go list -m all | wc -l | xargs)" -ne 1 ]; then
39+
echo "External dependencies found:"
40+
go list -m all
41+
exit 1
42+
fi
43+
echo "No external dependencies verified."
44+
45+
- name: Build
46+
run: go build -v ./...
47+
48+
- name: Test
49+
run: |
50+
if [ "${{ matrix.go }}" = "stable" ]; then
51+
go test -v -race -coverprofile=coverage.out ./...
52+
else
53+
go test -v -race ./...
54+
fi
55+
56+
- name: Upload coverage reports to Codecov
57+
if: matrix.go == 'stable'
58+
uses: codecov/codecov-action@v5
59+
with:
60+
token: ${{ secrets.CODECOV_TOKEN }}
61+
files: ./coverage.out
62+
63+
- name: Check Coverage
64+
if: matrix.go == 'stable'
65+
run: |
66+
total=$(go tool cover -func=coverage.out | grep total | grep -oE "[0-9]+\.[0-9]+")
67+
if [ "$total" != "100.0" ]; then
68+
echo "Coverage is ${total}%, but 100.0% is required."
69+
exit 1
70+
fi
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
name: golangci-lint
2+
on:
3+
push:
4+
branches: [ "main" ]
5+
tags: [ "v*" ]
6+
pull_request:
7+
branches: [ "*" ]
8+
9+
permissions:
10+
contents: read
11+
12+
jobs:
13+
golangci:
14+
name: lint
15+
runs-on: ubuntu-latest
16+
steps:
17+
- name: Checkout code
18+
uses: actions/checkout@v6
19+
20+
- name: Setup Go
21+
uses: actions/setup-go@v6
22+
with:
23+
go-version: 'stable'
24+
25+
- name: Install golangci-lint
26+
uses: golangci/golangci-lint-action@v9
27+
with:
28+
version: latest
29+
args: --help

.gitignore

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Editors
2+
.idea
3+
.vscode
4+
5+
# Test files
6+
*.test
7+
*.out
8+
coverage.txt
9+
10+
# Other
11+
.DS_Store

.golangci.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
version: "2"
2+
3+
issues:
4+
# Print all issues reported by all linters.
5+
max-issues-per-linter: 0
6+
max-same-issues: 0
7+
8+
linters:
9+
enable:
10+
# correctness: find real bugs
11+
- govet
12+
- staticcheck
13+
14+
# error handling
15+
- errcheck
16+
- errorlint
17+
18+
# simplicity: remove unnecessary code
19+
- unconvert
20+
- unparam
21+
- unused
22+
- ineffassign
23+
24+
# style
25+
- revive
26+
- misspell
27+
28+
# performance
29+
- prealloc
30+
31+
formatters:
32+
enable: [gofumpt]

CONTRIBUTING.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Contributing
2+
3+
Contributions are welcome!
4+
5+
If you'd like to add new exported APIs, please [open an issue][open-issue] describing your proposal first. Discussing API changes ahead of time makes pull request review much smoother.
6+
7+
## Setup
8+
9+
1. [Fork][fork] the repository
10+
2. Clone your fork:
11+
12+
```bash
13+
mkdir -p $GOPATH/src/github.com/kkhmel
14+
cd $GOPATH/src/github.com/kkhmel
15+
git clone git@github.com:your_github_username/sets.git
16+
cd sets
17+
git remote add upstream https://github.com/kkhmel/sets.git
18+
git fetch upstream
19+
```
20+
21+
## Making Changes
22+
23+
1. Create a new branch for your changes:
24+
25+
```bash
26+
git checkout main
27+
git fetch upstream
28+
git rebase upstream/main
29+
git checkout -b feature/amazing-feature
30+
```
31+
32+
2. Make your changes.
33+
34+
3. Run tests and linters locally:
35+
36+
```bash
37+
make lint
38+
make test
39+
make cover # 100% code coverage is required
40+
```
41+
42+
4. Commit your changes with a [descriptive message][commit-message]:
43+
44+
```bash
45+
git commit -m 'Add amazing feature'
46+
```
47+
48+
5. Push to your fork:
49+
50+
```bash
51+
git push origin feature/amazing-feature
52+
```
53+
54+
6. Open a Pull Request via the GitHub UI.
55+
56+
## Quality Requirements
57+
58+
Your pull request will be automatically tested by GitHub Actions, which verify:
59+
60+
- **Compatibility**: Code must work on Go 1.23 and latest stable version
61+
- **Dependencies**: No external dependencies allowed (stdlib only)
62+
- **Linting**: Code must pass golangci-lint checks
63+
- **Tests**: All tests must pass (including race detector)
64+
- **Coverage**: 100% code coverage is required
65+
66+
[open-issue]: https://github.com/kkhmel/sets/issues/new
67+
[fork]: https://github.com/kkhmel/sets/fork
68+
[commit-message]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html

LICENSE

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
MIT License
2-
31
Copyright (c) 2026 kkhmel
42

53
Permission is hereby granted, free of charge, to any person obtaining a copy

Makefile

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
.PHONY: help test bench lint all coverage
2+
3+
.DEFAULT_GOAL := help
4+
5+
.PHONY: help
6+
help:
7+
@echo "Available targets:"
8+
@sed -n 's/^##//p' $(MAKEFILE_LIST) | column -t -s ':' | sed -e 's/^/ /'
9+
10+
.PHONY: lint
11+
lint:
12+
@echo "Running golangci-lint..."
13+
@if command -v golangci-lint >/dev/null 2>&1; then \
14+
golangci-lint run ./...; \
15+
else \
16+
echo "golangci-lint not installed. Visit github.com/golangci/golangci-lint to install."; \
17+
fi
18+
19+
.PHONY: test
20+
test:
21+
@echo "Running tests..."
22+
@go test ./...
23+
24+
.PHONY: cover
25+
cover:
26+
@echo "Generating HTML coverage report..."
27+
@go test -coverprofile=coverage.out ./...
28+
@go tool cover -html=coverage.out -o coverage.html
29+
@echo "Coverage report generated: coverage.html"
30+
31+
.PHONY: bench
32+
bench:
33+
@echo "Running benchmarks..."
34+
@cd benchmark && go test -bench=. -benchmem
35+
36+
.PHONY: all
37+
all: lint test

README.md

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# sets
2+
3+
[![Go Reference](https://pkg.go.dev/badge/github.com/kkhmel/sets.svg)](https://pkg.go.dev/github.com/kkhmel/sets)
4+
[![Github release](https://img.shields.io/github/release/kkhmel/sets.svg)](https://github.com/kkhmel/sets/releases)
5+
[![Go Report Card](https://goreportcard.com/badge/github.com/kkhmel/sets)](https://goreportcard.com/report/github.com/kkhmel/sets)
6+
[![Lint Status](https://img.shields.io/github/actions/workflow/status/kkhmel/sets/golangci-lint.yml?branch=main&label=lint)](https://github.com/kkhmel/sets/actions)
7+
[![Coverage Status](https://codecov.io/gh/kkhmel/sets/branch/main/graph/badge.svg)](https://codecov.io/gh/kkhmel/sets/branch/main)
8+
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
9+
10+
A high-performance, idiomatic Go library for set operations that follows the same design principles as the standard library's [`maps`](https://pkg.go.dev/maps) and [`slices`](https://pkg.go.dev/slices) packages.
11+
12+
## Design Philosophy: Idiomatic Go
13+
14+
This library combines **idiomatic Go design** with **practical functionality** that developers need in real-world projects.
15+
16+
**Native map at the core.** `Set[T]` is a **type alias** for `map[T]struct{}`, not a wrapper type. All native Go map operations work out of the box: `len()`, `clear()`, `delete()`, `for...range`.
17+
18+
**Follows standard library conventions.** The API mirrors the design of [`maps`](https://pkg.go.dev/maps) and [`slices`](https://pkg.go.dev/slices) — composable functions that each do one thing well, consistent naming, variadic parameters where they make sense. No method chaining or fluent APIs.
19+
20+
**Comprehensive.** ~30 functions covering set theory, functional programming, and iterators.
21+
22+
**Robust.** Read-only functions gracefully handle nil sets, consistent with how the standard library's `maps` and `slices` packages treat nil inputs. Pre-allocated capacity for optimal performance.
23+
24+
**Zero dependencies.** Pure Go standard library only — no external dependencies to manage or security vulnerabilities to track.
25+
26+
---
27+
28+
## Installation
29+
30+
```bash
31+
go get github.com/kkhmel/sets
32+
```
33+
34+
Requires Go 1.23+.
35+
36+
---
37+
38+
## Usage
39+
40+
### Quick Start
41+
42+
```go
43+
package main
44+
45+
import (
46+
"fmt"
47+
"github.com/kkhmel/sets"
48+
)
49+
50+
func main() {
51+
// Create sets of user permissions
52+
admin := sets.From("read", "write", "delete", "admin")
53+
editor := sets.From("read", "write")
54+
viewer := sets.From("read")
55+
56+
// Check permissions
57+
if sets.ContainsAll(admin, "delete", "admin") {
58+
fmt.Println("Admin has delete and admin privileges") // Output: Admin has delete and admin privileges
59+
}
60+
61+
// Combine all unique permissions
62+
allPerms := sets.Union(admin, editor, viewer)
63+
fmt.Println("All permissions:", allPerms) // Output: All permissions: {admin, delete, read, write}
64+
65+
// Find admin-exclusive permissions
66+
exclusive := sets.Difference(admin, editor, viewer)
67+
fmt.Println("Admin-only:", exclusive) // Output: Admin-only: {admin, delete}
68+
69+
// Check subset relationships
70+
if sets.Subset(viewer, editor) {
71+
fmt.Println("Viewers are a subset of editors") // Output: Viewers are a subset of editors
72+
}
73+
}
74+
```
75+
76+
### Guideline
77+
78+
Use builtin `len()` for size, `clear()` for clearing, and `range` for iteration. For all other operations, use `sets` package functions for consistency and cleaner code.
79+
80+
```go
81+
s := sets.From("a", "b", "c")
82+
83+
// Use native operations for these common cases
84+
count := len(s) // Get size
85+
clear(s) // Clear all elements
86+
for e := range s { // Iterate over elements
87+
...
88+
}
89+
90+
// Use sets package functions for everything else -- cleaner and more consistent
91+
items := []string{"d", "e"}
92+
sets.Insert(s, items...) // Instead of: for _, e := range items { s[e] = struct{}{} }
93+
sets.Delete(s, items...) // Instead of: for _, e := range items { delete(s, e) }
94+
if sets.Contains(s, "x") { ... } // Instead of: if _, ok := s["x"]; ok { ... }
95+
if sets.ContainsAny(s, items...) { ... } // Instead of: manual loop with checks
96+
```
97+
98+
But since `Set[T]` is a type alias for `map[T]struct{}`, you have full access to native Go map operations and the [`maps`](https://pkg.go.dev/maps) package.
99+
100+
---
101+
102+
## Performance
103+
104+
The library is designed for maximum performance through several key principles:
105+
106+
**Core Performance Principles:**
107+
108+
- **Zero-cost abstraction:** Leverages Go's highly-optimized map implementation directly
109+
- **Zero-byte values:** Uses `struct{}` as map value (0 bytes per element) for minimal memory footprint
110+
- **Smart pre-allocation:** Set operations pre-allocate capacity based on input sizes to minimize reallocation
111+
- **Inlining-friendly:** Simple functions are designed to be inlined by the compiler
112+
113+
**Complexity Information:**
114+
Detailed time and space complexity for each function is documented in the [API documentation](https://pkg.go.dev/github.com/kkhmel/sets).
115+
116+
**Note:** Call `sets.Clone()` on results to reclaim excess memory if needed.
117+
118+
---
119+
120+
## Thread Safety
121+
122+
- **Safe for concurrent reads** — Multiple goroutines can safely read from a set simultaneously
123+
- **Requires synchronization for writes** — Use `sync.RWMutex` or `sync.Mutex` when modifying sets concurrently
124+
125+
---
126+
127+
## Contributing
128+
129+
Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to submit pull requests, report issues, and contribute to the code.

0 commit comments

Comments
 (0)