From 195924fb9e7cc0badc1e7a121f76b4daceb6c8e7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 3 Nov 2025 15:37:45 +0000 Subject: [PATCH] Bootstrap Go library with EUPL-1.2, goreleaser, mkdocs, and sorting functions Co-authored-by: Snider <631881+Snider@users.noreply.github.com> --- .github/workflows/docs.yml | 30 +++ .github/workflows/release.yml | 32 ++++ .github/workflows/test.yml | 30 +++ .gitignore | 58 ++++++ .goreleaser.yml | 71 +++++++ LICENSE | 190 +++++++++++++++++++ README.md | 65 ++++++- docs/api.md | 318 +++++++++++++++++++++++++++++++ docs/getting-started.md | 125 ++++++++++++ docs/index.md | 49 +++++ docs/license.md | 32 ++++ go.mod | 3 + mkdocs.yml | 61 ++++++ poindexter.go | 15 ++ poindexter_test.go | 34 ++++ sort.go | 89 +++++++++ sort_test.go | 347 ++++++++++++++++++++++++++++++++++ 17 files changed, 1548 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/docs.yml create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/test.yml create mode 100644 .gitignore create mode 100644 .goreleaser.yml create mode 100644 LICENSE create mode 100644 docs/api.md create mode 100644 docs/getting-started.md create mode 100644 docs/index.md create mode 100644 docs/license.md create mode 100644 go.mod create mode 100644 mkdocs.yml create mode 100644 poindexter.go create mode 100644 poindexter_test.go create mode 100644 sort.go create mode 100644 sort_test.go diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..f9800ea --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,30 @@ +name: Deploy Documentation + +on: + push: + branches: + - main + workflow_dispatch: + +permissions: + contents: write + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.x + + - name: Install dependencies + run: | + pip install mkdocs-material + + - name: Deploy to GitHub Pages + run: | + mkdocs gh-deploy --force diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..0148662 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,32 @@ +name: Release + +on: + push: + tags: + - 'v*' + +permissions: + contents: write + +jobs: + goreleaser: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: stable + + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v6 + with: + distribution: goreleaser + version: latest + args: release --clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..5fe7293 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,30 @@ +name: Tests + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: stable + + - name: Run tests + run: go test -v -race -coverprofile=coverage.txt -covermode=atomic ./... + + - name: Upload coverage + uses: codecov/codecov-action@v4 + with: + files: ./coverage.txt + fail_ci_if_error: false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1921ae5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,58 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +vendor/ + +# Go workspace file +go.work + +# Build output +dist/ +build/ + +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +env/ +venv/ +ENV/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# MkDocs +site/ + +# IDEs +.vscode/ +.idea/ +*.swp +*.swo +*~ +.DS_Store diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 0000000..7dd07b9 --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,71 @@ +# This is an example .goreleaser.yml file with some sensible defaults. +# Make sure to check the documentation at https://goreleaser.com + +# The lines below are called `modelines`. See `:help modeline` +# Feel free to remove those if you don't want/need to use them. +# yaml-language-server: $schema=https://goreleaser.com/static/schema.json +# vim: set ts=2 sw=2 tw=0 fo=cnqoj + +version: 2 + +before: + hooks: + # You may remove this if you don't use go modules. + - go mod tidy + # you may remove this if you don't need go generate + - go generate ./... + +builds: + - env: + - CGO_ENABLED=0 + goos: + - linux + - windows + - darwin + goarch: + - amd64 + - arm64 + # Optional: Exclude specific combinations + ignore: + - goos: windows + goarch: arm64 + +archives: + - format: tar.gz + # this name template makes the OS and Arch compatible with the results of `uname`. + name_template: >- + {{ .ProjectName }}_ + {{- title .Os }}_ + {{- if eq .Arch "amd64" }}x86_64 + {{- else if eq .Arch "386" }}i386 + {{- else }}{{ .Arch }}{{ end }} + {{- if .Arm }}v{{ .Arm }}{{ end }} + # use zip for windows archives + format_overrides: + - goos: windows + format: zip + +changelog: + sort: asc + filters: + exclude: + - "^docs:" + - "^test:" + - "^ci:" + - "^chore:" + - "^build:" + - Merge pull request + - Merge branch + +checksum: + name_template: 'checksums.txt' + +snapshot: + version_template: "{{ incpatch .Version }}-next" + +release: + github: + owner: Snider + name: Poindexter + draft: false + prerelease: auto diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6d8cea4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,190 @@ +EUROPEAN UNION PUBLIC LICENCE v. 1.2 +EUPL © the European Union 2007, 2016 + +This European Union Public Licence (the ‘EUPL’) applies to the Work (as defined below) which is provided under the +terms of this Licence. Any use of the Work, other than as authorised under this Licence is prohibited (to the extent such +use is covered by a right of the copyright holder of the Work). +The Work is provided under the terms of this Licence when the Licensor (as defined below) has placed the following +notice immediately following the copyright notice for the Work: + Licensed under the EUPL +or has expressed by any other means his willingness to license under the EUPL. + +1.Definitions +In this Licence, the following terms have the following meaning: +— ‘The Licence’:this Licence. +— ‘The Original Work’:the work or software distributed or communicated by the Licensor under this Licence, available +as Source Code and also as Executable Code as the case may be. +— ‘Derivative Works’:the works or software that could be created by the Licensee, based upon the Original Work or +modifications thereof. This Licence does not define the extent of modification or dependence on the Original Work +required in order to classify a work as a Derivative Work; this extent is determined by copyright law applicable in +the country mentioned in Article 15. +— ‘The Work’:the Original Work or its Derivative Works. +— ‘The Source Code’:the human-readable form of the Work which is the most convenient for people to study and +modify. +— ‘The Executable Code’:any code which has generally been compiled and which is meant to be interpreted by +a computer as a program. +— ‘The Licensor’:the natural or legal person that distributes or communicates the Work under the Licence. +— ‘Contributor(s)’:any natural or legal person who modifies the Work under the Licence, or otherwise contributes to +the creation of a Derivative Work. +— ‘The Licensee’ or ‘You’:any natural or legal person who makes any usage of the Work under the terms of the +Licence. +— ‘Distribution’ or ‘Communication’:any act of selling, giving, lending, renting, distributing, communicating, +transmitting, or otherwise making available, online or offline, copies of the Work or providing access to its essential +functionalities at the disposal of any other natural or legal person. + +2.Scope of the rights granted by the Licence +The Licensor hereby grants You a worldwide, royalty-free, non-exclusive, sublicensable licence to do the following, for +the duration of copyright vested in the Original Work: +— use the Work in any circumstance and for all usage, +— reproduce the Work, +— modify the Work, and make Derivative Works based upon the Work, +— communicate to the public, including the right to make available or display the Work or copies thereof to the public +and perform publicly, as the case may be, the Work, +— distribute the Work or copies thereof, +— lend and rent the Work or copies thereof, +— sublicense rights in the Work or copies thereof. +Those rights can be exercised on any media, supports and formats, whether now known or later invented, as far as the +applicable law permits so. +In the countries where moral rights apply, the Licensor waives his right to exercise his moral right to the extent allowed +by law in order to make effective the licence of the economic rights here above listed. +The Licensor grants to the Licensee royalty-free, non-exclusive usage rights to any patents held by the Licensor, to the +extent necessary to make use of the rights granted on the Work under this Licence. + +3.Communication of the Source Code +The Licensor may provide the Work either in its Source Code form, or as Executable Code. If the Work is provided as +Executable Code, the Licensor provides in addition a machine-readable copy of the Source Code of the Work along with +each copy of the Work that the Licensor distributes or indicates, in a notice following the copyright notice attached to +the Work, a repository where the Source Code is easily and freely accessible for as long as the Licensor continues to +distribute or communicate the Work. + +4.Limitations on copyright +Nothing in this Licence is intended to deprive the Licensee of the benefits from any exception or limitation to the +exclusive rights of the rights owners in the Work, of the exhaustion of those rights or of other applicable limitations +thereto. + +5.Obligations of the Licensee +The grant of the rights mentioned above is subject to some restrictions and obligations imposed on the Licensee. Those +obligations are the following: + +Attribution right: The Licensee shall keep intact all copyright, patent or trademarks notices and all notices that refer to +the Licence and to the disclaimer of warranties. The Licensee must include a copy of such notices and a copy of the +Licence with every copy of the Work he/she distributes or communicates. The Licensee must cause any Derivative Work +to carry prominent notices stating that the Work has been modified and the date of modification. + +Copyleft clause: If the Licensee distributes or communicates copies of the Original Works or Derivative Works, this +Distribution or Communication will be done under the terms of this Licence or of a later version of this Licence unless +the Original Work is expressly distributed only under this version of the Licence — for example by communicating +‘EUPL v. 1.2 only’. The Licensee (becoming Licensor) cannot offer or impose any additional terms or conditions on the +Work or Derivative Work that alter or restrict the terms of the Licence. + +Compatibility clause: If the Licensee Distributes or Communicates Derivative Works or copies thereof based upon both +the Work and another work licensed under a Compatible Licence, this Distribution or Communication can be done +under the terms of this Compatible Licence. For the sake of this clause, ‘Compatible Licence’ refers to the licences listed +in the appendix attached to this Licence. Should the Licensee's obligations under the Compatible Licence conflict with +his/her obligations under this Licence, the obligations of the Compatible Licence shall prevail. + +Provision of Source Code: When distributing or communicating copies of the Work, the Licensee will provide +a machine-readable copy of the Source Code or indicate a repository where this Source will be easily and freely available +for as long as the Licensee continues to distribute or communicate the Work. +Legal Protection: This Licence does not grant permission to use the trade names, trademarks, service marks, or names +of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and +reproducing the content of the copyright notice. + +6.Chain of Authorship +The original Licensor warrants that the copyright in the Original Work granted hereunder is owned by him/her or +licensed to him/her and that he/she has the power and authority to grant the Licence. +Each Contributor warrants that the copyright in the modifications he/she brings to the Work are owned by him/her or +licensed to him/her and that he/she has the power and authority to grant the Licence. +Each time You accept the Licence, the original Licensor and subsequent Contributors grant You a licence to their contributions +to the Work, under the terms of this Licence. + +7.Disclaimer of Warranty +The Work is a work in progress, which is continuously improved by numerous Contributors. It is not a finished work +and may therefore contain defects or ‘bugs’ inherent to this type of development. +For the above reason, the Work is provided under the Licence on an ‘as is’ basis and without warranties of any kind +concerning the Work, including without limitation merchantability, fitness for a particular purpose, absence of defects or +errors, accuracy, non-infringement of intellectual property rights other than copyright as stated in Article 6 of this +Licence. +This disclaimer of warranty is an essential part of the Licence and a condition for the grant of any rights to the Work. + +8.Disclaimer of Liability +Except in the cases of wilful misconduct or damages directly caused to natural persons, the Licensor will in no event be +liable for any direct or indirect, material or moral, damages of any kind, arising out of the Licence or of the use of the +Work, including without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, loss +of data or any commercial damage, even if the Licensor has been advised of the possibility of such damage. However, +the Licensor will be liable under statutory product liability laws as far such laws apply to the Work. + +9.Additional agreements +While distributing the Work, You may choose to conclude an additional agreement, defining obligations or services +consistent with this Licence. However, if accepting obligations, You may act only on your own behalf and on your sole +responsibility, not on behalf of the original Licensor or any other Contributor, and only if You agree to indemnify, +defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against such Contributor by +the fact You have accepted any warranty or additional liability. + +10.Acceptance of the Licence +The provisions of this Licence can be accepted by clicking on an icon ‘I agree’ placed under the bottom of a window +displaying the text of this Licence or by affirming consent in any other similar way, in accordance with the rules of +applicable law. Clicking on that icon indicates your clear and irrevocable acceptance of this Licence and all of its terms +and conditions. +Similarly, you irrevocably accept this Licence and all of its terms and conditions by exercising any rights granted to You +by Article 2 of this Licence, such as the use of the Work, the creation by You of a Derivative Work or the Distribution +or Communication by You of the Work or copies thereof. + +11.Information to the public +In case of any Distribution or Communication of the Work by means of electronic communication by You (for example, +by offering to download the Work from a remote location) the distribution channel or media (for example, a website) +must at least provide to the public the information requested by the applicable law regarding the Licensor, the Licence +and the way it may be accessible, concluded, stored and reproduced by the Licensee. + +12.Termination of the Licence +The Licence and the rights granted hereunder will terminate automatically upon any breach by the Licensee of the terms +of the Licence. +Such a termination will not terminate the licences of any person who has received the Work from the Licensee under +the Licence, provided such persons remain in full compliance with the Licence. + +13.Miscellaneous +Without prejudice of Article 9 above, the Licence represents the complete agreement between the Parties as to the +Work. +If any provision of the Licence is invalid or unenforceable under applicable law, this will not affect the validity or +enforceability of the Licence as a whole. Such provision will be construed or reformed so as necessary to make it valid +and enforceable. +The European Commission may publish other linguistic versions or new versions of this Licence or updated versions of +the Appendix, so far this is required and reasonable, without reducing the scope of the rights granted by the Licence. +New versions of the Licence will be published with a unique version number. +All linguistic versions of this Licence, approved by the European Commission, have identical value. Parties can take +advantage of the linguistic version of their choice. + +14.Jurisdiction +Without prejudice to specific agreement between parties, +— any litigation resulting from the interpretation of this License, arising between the European Union institutions, +bodies, offices or agencies, as a Licensor, and any Licensee, will be subject to the jurisdiction of the Court of Justice +of the European Union, as laid down in article 272 of the Treaty on the Functioning of the European Union, +— any litigation arising between other parties and resulting from the interpretation of this License, will be subject to +the exclusive jurisdiction of the competent court where the Licensor resides or conducts its primary business. + +15.Applicable Law +Without prejudice to specific agreement between parties, +— this Licence shall be governed by the law of the European Union Member State where the Licensor has his seat, +resides or has his registered office, +— this licence shall be governed by Belgian law if the Licensor has no seat, residence or registered office inside +a European Union Member State. + + + Appendix + +‘Compatible Licences’ according to Article 5 EUPL are: +— GNU General Public License (GPL) v. 2, v. 3 +— GNU Affero General Public License (AGPL) v. 3 +— Open Software License (OSL) v. 2.1, v. 3.0 +— Eclipse Public License (EPL) v. 1.0 +— CeCILL v. 2.0, v. 2.1 +— Mozilla Public Licence (MPL) v. 2 +— GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3 +— Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for works other than software +— European Union Public Licence (EUPL) v. 1.1, v. 1.2 +— Québec Free and Open-Source Licence — Reciprocity (LiLiQ-R) or Strong Reciprocity (LiLiQ-R+). + +The European Commission may update this Appendix to later versions of the above licences without producing +a new version of the EUPL, as long as they provide the rights granted in Article 2 of this Licence and protect the +covered Source Code from exclusive appropriation. +All other changes or additions to this Appendix require the production of a new EUPL version. diff --git a/README.md b/README.md index 617f492..f5b9b50 100644 --- a/README.md +++ b/README.md @@ -1 +1,64 @@ -# Poindexter \ No newline at end of file +# Poindexter + +A Go library package providing utility functions including sorting algorithms with custom comparators. + +## Features + +- 🔢 **Sorting Utilities**: Sort integers, strings, and floats in ascending or descending order +- 🎯 **Custom Sorting**: Sort any type with custom comparison functions or key extractors +- 🔍 **Binary Search**: Fast search on sorted data +- 📦 **Generic Functions**: Type-safe operations using Go generics +- ✅ **Well-Tested**: Comprehensive test coverage +- 📖 **Documentation**: Full documentation available at GitHub Pages + +## Installation + +```bash +go get github.com/Snider/Poindexter +``` + +## Quick Start + +```go +package main + +import ( + "fmt" + "github.com/Snider/Poindexter" +) + +func main() { + // Basic sorting + numbers := []int{3, 1, 4, 1, 5, 9} + poindexter.SortInts(numbers) + fmt.Println(numbers) // [1 1 3 4 5 9] + + // Custom sorting with key function + type Product struct { + Name string + Price float64 + } + + products := []Product{ + {"Apple", 1.50}, + {"Banana", 0.75}, + {"Cherry", 3.00}, + } + + poindexter.SortByKey(products, func(p Product) float64 { + return p.Price + }) +} +``` + +## Documentation + +Full documentation is available at [https://snider.github.io/Poindexter/](https://snider.github.io/Poindexter/) + +## License + +This project is licensed under the European Union Public Licence v1.2 (EUPL-1.2). See [LICENSE](LICENSE) for details. + +## Contributing + +Contributions are welcome! Please feel free to submit a Pull Request. \ No newline at end of file diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 0000000..ad5439b --- /dev/null +++ b/docs/api.md @@ -0,0 +1,318 @@ +# API Reference + +Complete API documentation for the Poindexter library. + +## Core Functions + +### Version + +```go +func Version() string +``` + +Returns the current version of the library. + +**Returns:** +- `string`: The version string (e.g., "0.1.0") + +**Example:** + +```go +version := poindexter.Version() +fmt.Println(version) // Output: 0.1.0 +``` + +--- + +### Hello + +```go +func Hello(name string) string +``` + +Returns a greeting message. + +**Parameters:** +- `name` (string): The name to greet. If empty, defaults to "World" + +**Returns:** +- `string`: A greeting message + +**Examples:** + +```go +// Greet the world +message := poindexter.Hello("") +fmt.Println(message) // Output: Hello, World! + +// Greet a specific person +message = poindexter.Hello("Alice") +fmt.Println(message) // Output: Hello, Alice! +``` + +--- + +## Sorting Functions + +### Basic Sorting + +#### SortInts + +```go +func SortInts(data []int) +``` + +Sorts a slice of integers in ascending order in place. + +**Example:** + +```go +numbers := []int{3, 1, 4, 1, 5, 9} +poindexter.SortInts(numbers) +fmt.Println(numbers) // Output: [1 1 3 4 5 9] +``` + +--- + +#### SortIntsDescending + +```go +func SortIntsDescending(data []int) +``` + +Sorts a slice of integers in descending order in place. + +**Example:** + +```go +numbers := []int{3, 1, 4, 1, 5, 9} +poindexter.SortIntsDescending(numbers) +fmt.Println(numbers) // Output: [9 5 4 3 1 1] +``` + +--- + +#### SortStrings + +```go +func SortStrings(data []string) +``` + +Sorts a slice of strings in ascending order in place. + +**Example:** + +```go +words := []string{"banana", "apple", "cherry"} +poindexter.SortStrings(words) +fmt.Println(words) // Output: [apple banana cherry] +``` + +--- + +#### SortStringsDescending + +```go +func SortStringsDescending(data []string) +``` + +Sorts a slice of strings in descending order in place. + +--- + +#### SortFloat64s + +```go +func SortFloat64s(data []float64) +``` + +Sorts a slice of float64 values in ascending order in place. + +--- + +#### SortFloat64sDescending + +```go +func SortFloat64sDescending(data []float64) +``` + +Sorts a slice of float64 values in descending order in place. + +--- + +### Advanced Sorting + +#### SortBy + +```go +func SortBy[T any](data []T, less func(i, j int) bool) +``` + +Sorts a slice using a custom comparison function. + +**Parameters:** +- `data`: The slice to sort +- `less`: A function that returns true if data[i] should come before data[j] + +**Example:** + +```go +type Person struct { + Name string + Age int +} + +people := []Person{ + {"Alice", 30}, + {"Bob", 25}, + {"Charlie", 35}, +} + +// Sort by age +poindexter.SortBy(people, func(i, j int) bool { + return people[i].Age < people[j].Age +}) +// Result: [Bob(25) Alice(30) Charlie(35)] +``` + +--- + +#### SortByKey + +```go +func SortByKey[T any, K int | float64 | string](data []T, key func(T) K) +``` + +Sorts a slice by extracting a comparable key from each element in ascending order. + +**Parameters:** +- `data`: The slice to sort +- `key`: A function that extracts a sortable key from each element + +**Example:** + +```go +type Product struct { + Name string + Price float64 +} + +products := []Product{ + {"Apple", 1.50}, + {"Banana", 0.75}, + {"Cherry", 3.00}, +} + +// Sort by price +poindexter.SortByKey(products, func(p Product) float64 { + return p.Price +}) +// Result: [Banana(0.75) Apple(1.50) Cherry(3.00)] +``` + +--- + +#### SortByKeyDescending + +```go +func SortByKeyDescending[T any, K int | float64 | string](data []T, key func(T) K) +``` + +Sorts a slice by extracting a comparable key from each element in descending order. + +**Example:** + +```go +type Student struct { + Name string + Score int +} + +students := []Student{ + {"Alice", 85}, + {"Bob", 92}, + {"Charlie", 78}, +} + +// Sort by score descending +poindexter.SortByKeyDescending(students, func(s Student) int { + return s.Score +}) +// Result: [Bob(92) Alice(85) Charlie(78)] +``` + +--- + +### Checking if Sorted + +#### IsSorted + +```go +func IsSorted(data []int) bool +``` + +Checks if a slice of integers is sorted in ascending order. + +--- + +#### IsSortedStrings + +```go +func IsSortedStrings(data []string) bool +``` + +Checks if a slice of strings is sorted in ascending order. + +--- + +#### IsSortedFloat64s + +```go +func IsSortedFloat64s(data []float64) bool +``` + +Checks if a slice of float64 values is sorted in ascending order. + +--- + +### Binary Search + +#### BinarySearch + +```go +func BinarySearch(data []int, target int) int +``` + +Performs a binary search on a sorted slice of integers. + +**Parameters:** +- `data`: A sorted slice of integers +- `target`: The value to search for + +**Returns:** +- `int`: The index where target is found, or -1 if not found + +**Example:** + +```go +numbers := []int{1, 3, 5, 7, 9, 11} +index := poindexter.BinarySearch(numbers, 7) +fmt.Println(index) // Output: 3 +``` + +--- + +#### BinarySearchStrings + +```go +func BinarySearchStrings(data []string, target string) int +``` + +Performs a binary search on a sorted slice of strings. + +**Parameters:** +- `data`: A sorted slice of strings +- `target`: The value to search for + +**Returns:** +- `int`: The index where target is found, or -1 if not found diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 0000000..d37fa63 --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,125 @@ +# Getting Started + +This guide will help you get started with the Poindexter library. + +## Installation + +To install Poindexter, use `go get`: + +```bash +go get github.com/Snider/Poindexter +``` + +## Basic Usage + +### Importing the Library + +```go +import "github.com/Snider/Poindexter" +``` + +### Using the Hello Function + +The `Hello` function returns a greeting message: + +```go +package main + +import ( + "fmt" + "github.com/Snider/Poindexter" +) + +func main() { + // Say hello to the world + fmt.Println(poindexter.Hello("")) + // Output: Hello, World! + + // Say hello to someone specific + fmt.Println(poindexter.Hello("Poindexter")) + // Output: Hello, Poindexter! +} +``` + +### Getting the Version + +You can check the library version: + +```go +package main + +import ( + "fmt" + "github.com/Snider/Poindexter" +) + +func main() { + version := poindexter.Version() + fmt.Println("Library version:", version) +} +``` + +## Sorting Data + +Poindexter includes comprehensive sorting utilities: + +### Basic Sorting + +```go +package main + +import ( + "fmt" + "github.com/Snider/Poindexter" +) + +func main() { + // Sort integers + numbers := []int{3, 1, 4, 1, 5, 9} + poindexter.SortInts(numbers) + fmt.Println(numbers) // [1 1 3 4 5 9] + + // Sort strings + words := []string{"banana", "apple", "cherry"} + poindexter.SortStrings(words) + fmt.Println(words) // [apple banana cherry] +} +``` + +### Advanced Sorting with Custom Keys + +```go +package main + +import ( + "fmt" + "github.com/Snider/Poindexter" +) + +type Product struct { + Name string + Price float64 +} + +func main() { + products := []Product{ + {"Apple", 1.50}, + {"Banana", 0.75}, + {"Cherry", 3.00}, + } + + // Sort by price using SortByKey + poindexter.SortByKey(products, func(p Product) float64 { + return p.Price + }) + + for _, p := range products { + fmt.Printf("%s: $%.2f\n", p.Name, p.Price) + } +} +``` + +## Next Steps + +- Check out the [API Reference](api.md) for detailed documentation +- Read about the [License](license.md) diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..9ce37bb --- /dev/null +++ b/docs/index.md @@ -0,0 +1,49 @@ +# Poindexter + +Welcome to the Poindexter Go library documentation! + +## Overview + +Poindexter is a Go library package licensed under EUPL-1.2. + +## Features + +- Simple and easy to use +- Comprehensive sorting utilities with custom comparators +- Generic sorting functions with type safety +- Binary search capabilities +- Well-documented API +- Comprehensive test coverage +- Cross-platform support + +## Quick Start + +Install the library: + +```bash +go get github.com/Snider/Poindexter +``` + +Use it in your code: + +```go +package main + +import ( + "fmt" + "github.com/Snider/Poindexter" +) + +func main() { + fmt.Println(poindexter.Hello("World")) + fmt.Println("Version:", poindexter.Version()) +} +``` + +## License + +This project is licensed under the European Union Public Licence v1.2 (EUPL-1.2). + +## Contributing + +Contributions are welcome! Please feel free to submit a Pull Request. diff --git a/docs/license.md b/docs/license.md new file mode 100644 index 0000000..baf413c --- /dev/null +++ b/docs/license.md @@ -0,0 +1,32 @@ +# License + +This project is licensed under the **European Union Public Licence v1.2 (EUPL-1.2)**. + +## About EUPL-1.2 + +The European Union Public Licence (EUPL) is a copyleft free/open source software license created on the initiative of and approved by the European Commission. + +## Key Points + +- **Open Source**: The EUPL is an OSI-approved open source license +- **Copyleft**: Derivative works must be distributed under the same or compatible license +- **Multilingual**: The license is available in all official EU languages +- **Compatible**: Compatible with other major open source licenses including GPL + +## Full License Text + +The complete license text can be found in the [LICENSE](https://github.com/Snider/Poindexter/blob/main/LICENSE) file in the repository. + +## Using EUPL-1.2 Licensed Code + +When using this library: + +1. You must retain copyright and license notices +2. You must state significant changes made to the code +3. Derivative works must be licensed under EUPL-1.2 or a compatible license + +## More Information + +For more information about the EUPL, visit: +- [Official EUPL Website](https://joinup.ec.europa.eu/collection/eupl) +- [EUPL on Wikipedia](https://en.wikipedia.org/wiki/European_Union_Public_Licence) diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..61dc59f --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/Snider/Poindexter + +go 1.24.9 diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..7d71e94 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,61 @@ +site_name: Poindexter +site_description: Poindexter Go Library Documentation +site_author: Snider +repo_url: https://github.com/Snider/Poindexter +repo_name: Snider/Poindexter +edit_uri: edit/main/docs/ + +theme: + name: material + palette: + # Palette toggle for light mode + - scheme: default + primary: indigo + accent: indigo + toggle: + icon: material/brightness-7 + name: Switch to dark mode + # Palette toggle for dark mode + - scheme: slate + primary: indigo + accent: indigo + toggle: + icon: material/brightness-4 + name: Switch to light mode + features: + - navigation.tabs + - navigation.sections + - navigation.top + - search.suggest + - search.highlight + - content.tabs.link + - content.code.annotation + - content.code.copy + +plugins: + - search + +markdown_extensions: + - pymdownx.highlight: + anchor_linenums: true + - pymdownx.inlinehilite + - pymdownx.snippets + - pymdownx.superfences + - pymdownx.tabbed: + alternate_style: true + - admonition + - pymdownx.details + - pymdownx.arithmatex: + generic: true + - footnotes + - pymdownx.mark + - attr_list + - md_in_html + +nav: + - Home: index.md + - Getting Started: getting-started.md + - API Reference: api.md + - License: license.md + +copyright: Copyright © 2025 Snider diff --git a/poindexter.go b/poindexter.go new file mode 100644 index 0000000..01e8aa8 --- /dev/null +++ b/poindexter.go @@ -0,0 +1,15 @@ +// Package poindexter provides functionality for the Poindexter library. +package poindexter + +// Version returns the current version of the library. +func Version() string { + return "0.1.0" +} + +// Hello returns a greeting message. +func Hello(name string) string { + if name == "" { + return "Hello, World!" + } + return "Hello, " + name + "!" +} diff --git a/poindexter_test.go b/poindexter_test.go new file mode 100644 index 0000000..92445cf --- /dev/null +++ b/poindexter_test.go @@ -0,0 +1,34 @@ +package poindexter + +import "testing" + +func TestVersion(t *testing.T) { + version := Version() + if version == "" { + t.Error("Version should not be empty") + } + if version != "0.1.0" { + t.Errorf("Expected version 0.1.0, got %s", version) + } +} + +func TestHello(t *testing.T) { + tests := []struct { + name string + input string + expected string + }{ + {"empty name", "", "Hello, World!"}, + {"with name", "Poindexter", "Hello, Poindexter!"}, + {"another name", "Go", "Hello, Go!"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := Hello(tt.input) + if result != tt.expected { + t.Errorf("Hello(%q) = %q, want %q", tt.input, result, tt.expected) + } + }) + } +} diff --git a/sort.go b/sort.go new file mode 100644 index 0000000..1ccfcff --- /dev/null +++ b/sort.go @@ -0,0 +1,89 @@ +package poindexter + +import "sort" + +// SortInts sorts a slice of integers in ascending order in place. +func SortInts(data []int) { + sort.Ints(data) +} + +// SortIntsDescending sorts a slice of integers in descending order in place. +func SortIntsDescending(data []int) { + sort.Sort(sort.Reverse(sort.IntSlice(data))) +} + +// SortStrings sorts a slice of strings in ascending order in place. +func SortStrings(data []string) { + sort.Strings(data) +} + +// SortStringsDescending sorts a slice of strings in descending order in place. +func SortStringsDescending(data []string) { + sort.Sort(sort.Reverse(sort.StringSlice(data))) +} + +// SortFloat64s sorts a slice of float64 values in ascending order in place. +func SortFloat64s(data []float64) { + sort.Float64s(data) +} + +// SortFloat64sDescending sorts a slice of float64 values in descending order in place. +func SortFloat64sDescending(data []float64) { + sort.Sort(sort.Reverse(sort.Float64Slice(data))) +} + +// SortBy sorts a slice using a custom less function. +// The less function should return true if data[i] should come before data[j]. +func SortBy[T any](data []T, less func(i, j int) bool) { + sort.Slice(data, less) +} + +// SortByKey sorts a slice by extracting a comparable key from each element. +// The key function should return a value that implements constraints.Ordered. +func SortByKey[T any, K int | float64 | string](data []T, key func(T) K) { + sort.Slice(data, func(i, j int) bool { + return key(data[i]) < key(data[j]) + }) +} + +// SortByKeyDescending sorts a slice by extracting a comparable key from each element in descending order. +func SortByKeyDescending[T any, K int | float64 | string](data []T, key func(T) K) { + sort.Slice(data, func(i, j int) bool { + return key(data[i]) > key(data[j]) + }) +} + +// IsSorted checks if a slice of integers is sorted in ascending order. +func IsSorted(data []int) bool { + return sort.IntsAreSorted(data) +} + +// IsSortedStrings checks if a slice of strings is sorted in ascending order. +func IsSortedStrings(data []string) bool { + return sort.StringsAreSorted(data) +} + +// IsSortedFloat64s checks if a slice of float64 values is sorted in ascending order. +func IsSortedFloat64s(data []float64) bool { + return sort.Float64sAreSorted(data) +} + +// BinarySearch performs a binary search on a sorted slice of integers. +// Returns the index where target is found, or -1 if not found. +func BinarySearch(data []int, target int) int { + idx := sort.SearchInts(data, target) + if idx < len(data) && data[idx] == target { + return idx + } + return -1 +} + +// BinarySearchStrings performs a binary search on a sorted slice of strings. +// Returns the index where target is found, or -1 if not found. +func BinarySearchStrings(data []string, target string) int { + idx := sort.SearchStrings(data, target) + if idx < len(data) && data[idx] == target { + return idx + } + return -1 +} diff --git a/sort_test.go b/sort_test.go new file mode 100644 index 0000000..7da83cb --- /dev/null +++ b/sort_test.go @@ -0,0 +1,347 @@ +package poindexter + +import ( + "reflect" + "testing" +) + +func TestSortInts(t *testing.T) { + tests := []struct { + name string + input []int + expected []int + }{ + {"empty slice", []int{}, []int{}}, + {"single element", []int{5}, []int{5}}, + {"already sorted", []int{1, 2, 3, 4, 5}, []int{1, 2, 3, 4, 5}}, + {"reverse sorted", []int{5, 4, 3, 2, 1}, []int{1, 2, 3, 4, 5}}, + {"unsorted", []int{3, 1, 4, 1, 5, 9, 2, 6}, []int{1, 1, 2, 3, 4, 5, 6, 9}}, + {"with negatives", []int{-3, 5, -1, 0, 2}, []int{-3, -1, 0, 2, 5}}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + data := make([]int, len(tt.input)) + copy(data, tt.input) + SortInts(data) + if !reflect.DeepEqual(data, tt.expected) { + t.Errorf("SortInts(%v) = %v, want %v", tt.input, data, tt.expected) + } + }) + } +} + +func TestSortIntsDescending(t *testing.T) { + tests := []struct { + name string + input []int + expected []int + }{ + {"empty slice", []int{}, []int{}}, + {"single element", []int{5}, []int{5}}, + {"ascending order", []int{1, 2, 3, 4, 5}, []int{5, 4, 3, 2, 1}}, + {"unsorted", []int{3, 1, 4, 1, 5, 9, 2, 6}, []int{9, 6, 5, 4, 3, 2, 1, 1}}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + data := make([]int, len(tt.input)) + copy(data, tt.input) + SortIntsDescending(data) + if !reflect.DeepEqual(data, tt.expected) { + t.Errorf("SortIntsDescending(%v) = %v, want %v", tt.input, data, tt.expected) + } + }) + } +} + +func TestSortStrings(t *testing.T) { + tests := []struct { + name string + input []string + expected []string + }{ + {"empty slice", []string{}, []string{}}, + {"single element", []string{"hello"}, []string{"hello"}}, + {"already sorted", []string{"a", "b", "c"}, []string{"a", "b", "c"}}, + {"reverse sorted", []string{"z", "y", "x"}, []string{"x", "y", "z"}}, + {"unsorted", []string{"banana", "apple", "cherry", "date"}, []string{"apple", "banana", "cherry", "date"}}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + data := make([]string, len(tt.input)) + copy(data, tt.input) + SortStrings(data) + if !reflect.DeepEqual(data, tt.expected) { + t.Errorf("SortStrings(%v) = %v, want %v", tt.input, data, tt.expected) + } + }) + } +} + +func TestSortStringsDescending(t *testing.T) { + tests := []struct { + name string + input []string + expected []string + }{ + {"empty slice", []string{}, []string{}}, + {"ascending order", []string{"a", "b", "c"}, []string{"c", "b", "a"}}, + {"unsorted", []string{"banana", "apple", "cherry"}, []string{"cherry", "banana", "apple"}}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + data := make([]string, len(tt.input)) + copy(data, tt.input) + SortStringsDescending(data) + if !reflect.DeepEqual(data, tt.expected) { + t.Errorf("SortStringsDescending(%v) = %v, want %v", tt.input, data, tt.expected) + } + }) + } +} + +func TestSortFloat64s(t *testing.T) { + tests := []struct { + name string + input []float64 + expected []float64 + }{ + {"empty slice", []float64{}, []float64{}}, + {"single element", []float64{3.14}, []float64{3.14}}, + {"unsorted", []float64{3.14, 1.41, 2.71, 1.73}, []float64{1.41, 1.73, 2.71, 3.14}}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + data := make([]float64, len(tt.input)) + copy(data, tt.input) + SortFloat64s(data) + if !reflect.DeepEqual(data, tt.expected) { + t.Errorf("SortFloat64s(%v) = %v, want %v", tt.input, data, tt.expected) + } + }) + } +} + +func TestSortFloat64sDescending(t *testing.T) { + tests := []struct { + name string + input []float64 + expected []float64 + }{ + {"empty slice", []float64{}, []float64{}}, + {"unsorted", []float64{3.14, 1.41, 2.71}, []float64{3.14, 2.71, 1.41}}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + data := make([]float64, len(tt.input)) + copy(data, tt.input) + SortFloat64sDescending(data) + if !reflect.DeepEqual(data, tt.expected) { + t.Errorf("SortFloat64sDescending(%v) = %v, want %v", tt.input, data, tt.expected) + } + }) + } +} + +func TestSortBy(t *testing.T) { + type Person struct { + Name string + Age int + } + + people := []Person{ + {"Alice", 30}, + {"Bob", 25}, + {"Charlie", 35}, + } + + // Sort by age + SortBy(people, func(i, j int) bool { + return people[i].Age < people[j].Age + }) + + expected := []Person{ + {"Bob", 25}, + {"Alice", 30}, + {"Charlie", 35}, + } + + if !reflect.DeepEqual(people, expected) { + t.Errorf("SortBy (by age) = %v, want %v", people, expected) + } +} + +func TestSortByKey(t *testing.T) { + type Product struct { + Name string + Price float64 + } + + products := []Product{ + {"Apple", 1.50}, + {"Banana", 0.75}, + {"Cherry", 3.00}, + } + + // Sort by price + SortByKey(products, func(p Product) float64 { + return p.Price + }) + + expected := []Product{ + {"Banana", 0.75}, + {"Apple", 1.50}, + {"Cherry", 3.00}, + } + + if !reflect.DeepEqual(products, expected) { + t.Errorf("SortByKey (by price) = %v, want %v", products, expected) + } +} + +func TestSortByKeyDescending(t *testing.T) { + type Student struct { + Name string + Score int + } + + students := []Student{ + {"Alice", 85}, + {"Bob", 92}, + {"Charlie", 78}, + } + + // Sort by score descending + SortByKeyDescending(students, func(s Student) int { + return s.Score + }) + + expected := []Student{ + {"Bob", 92}, + {"Alice", 85}, + {"Charlie", 78}, + } + + if !reflect.DeepEqual(students, expected) { + t.Errorf("SortByKeyDescending (by score) = %v, want %v", students, expected) + } +} + +func TestIsSorted(t *testing.T) { + tests := []struct { + name string + input []int + expected bool + }{ + {"empty slice", []int{}, true}, + {"single element", []int{5}, true}, + {"sorted ascending", []int{1, 2, 3, 4, 5}, true}, + {"not sorted", []int{1, 3, 2, 4}, false}, + {"sorted with duplicates", []int{1, 1, 2, 3}, true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := IsSorted(tt.input) + if result != tt.expected { + t.Errorf("IsSorted(%v) = %v, want %v", tt.input, result, tt.expected) + } + }) + } +} + +func TestIsSortedStrings(t *testing.T) { + tests := []struct { + name string + input []string + expected bool + }{ + {"sorted", []string{"a", "b", "c"}, true}, + {"not sorted", []string{"b", "a", "c"}, false}, + {"empty", []string{}, true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := IsSortedStrings(tt.input) + if result != tt.expected { + t.Errorf("IsSortedStrings(%v) = %v, want %v", tt.input, result, tt.expected) + } + }) + } +} + +func TestIsSortedFloat64s(t *testing.T) { + tests := []struct { + name string + input []float64 + expected bool + }{ + {"sorted", []float64{1.1, 2.2, 3.3}, true}, + {"not sorted", []float64{2.2, 1.1, 3.3}, false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := IsSortedFloat64s(tt.input) + if result != tt.expected { + t.Errorf("IsSortedFloat64s(%v) = %v, want %v", tt.input, result, tt.expected) + } + }) + } +} + +func TestBinarySearch(t *testing.T) { + data := []int{1, 3, 5, 7, 9, 11} + + tests := []struct { + name string + target int + expected int + }{ + {"found at start", 1, 0}, + {"found in middle", 5, 2}, + {"found at end", 11, 5}, + {"not found", 4, -1}, + {"not found - too small", 0, -1}, + {"not found - too large", 12, -1}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := BinarySearch(data, tt.target) + if result != tt.expected { + t.Errorf("BinarySearch(%v, %d) = %d, want %d", data, tt.target, result, tt.expected) + } + }) + } +} + +func TestBinarySearchStrings(t *testing.T) { + data := []string{"apple", "banana", "cherry", "date"} + + tests := []struct { + name string + target string + expected int + }{ + {"found at start", "apple", 0}, + {"found in middle", "banana", 1}, + {"found at end", "date", 3}, + {"not found", "grape", -1}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := BinarySearchStrings(data, tt.target) + if result != tt.expected { + t.Errorf("BinarySearchStrings(%v, %q) = %d, want %d", data, tt.target, result, tt.expected) + } + }) + } +}