https://github.com/zaataylor/cartesian
A Golang package for computing Cartesian products
https://github.com/zaataylor/cartesian
combinatorics go golang math
Last synced: 10 months ago
JSON representation
A Golang package for computing Cartesian products
- Host: GitHub
- URL: https://github.com/zaataylor/cartesian
- Owner: zaataylor
- License: mit
- Created: 2022-10-23T06:29:04.000Z (over 3 years ago)
- Default Branch: main
- Last Pushed: 2022-10-28T05:32:57.000Z (over 3 years ago)
- Last Synced: 2025-02-25T04:43:52.894Z (over 1 year ago)
- Topics: combinatorics, go, golang, math
- Language: Go
- Homepage:
- Size: 25.4 KB
- Stars: 2
- Watchers: 2
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Table of Contents
- [Description](#description)
- [What is the Cartesian Product?](#what-is-the-cartesian-product)
- [What is `cartesian`?](#what-is-cartesian)
- [Requires](#requires)
- [Example](#example)
- [Using `cartesian`](#using-cartesian)
- [Creating the `CartesianProduct`](#creating-the-cartesianproduct)
- [Iterating over `CartesianProduct` Elements](#iterating-over-cartesianproduct-elements)
- [Computing the full Cartesian product and using `Values()`](#computing-the-full-cartesian-product-and-using-values)
- [When to use `Indices()` instead of `Values()`](#when-to-use-indices-instead-of-values)
# Description
## What is the Cartesian Product?
Given two sets of "things", `A` and `B`, the [Cartesian product](https://en.wikipedia.org/wiki/Cartesian_product) is the set of all possible pairs `(a, b)` where `a` is a thing from set `A` and `b` is a thing from set `B`. A real life example: let's say you have 3 shirts and 2 pairs of pants. What possible outfits can you create?
Call the set of shirts `S`. Then, `S = {shirt-1, shirt-2, shirt-3}`.
Call the set of pants `P`. Then `P = {pants-1, pants-2}`.
How many ways can we combine these items into an outfit (i.e. a shirt and pants)? Let's represent the possibilities as pairs of `(s, p)`, where `s` is in `S` and `p` is in `P`. Then we have:
```
(shirt-1, pants-1)
(shirt-2, pants-1)
(shirt-3, pants-1)
(shirt-1, pants-2)
(shirt-2, pants-2)
(shirt-3, pants-2)
```
This collection of ordered pairs is the Cartesian product of `S` and `P`.
## What is `cartesian`?
`cartesian` is a Go package that makes it easy to compute and return the Cartesian product of an arbitrary number of slices of varying types.
# Requires
- Go 1.18 or higher
# Example
Consider the following code:
```golang
package main
import (
"fmt"
"github.com/zaataylor/cartesian/cartesian"
)
type Job struct {
name string
id int
}
func main() {
sliceA := []int{1, 8}
sliceB := []bool{true, false}
sliceJ := []Job{
{
name: "test job",
id: 1,
},
{
name: "another test job",
id: 2,
},
}
slices := []any{sliceA, sliceB, sliceJ}
// Construct Cartesian product of these slices
cp := cartesian.NewCartesianProduct(slices)
fmt.Printf("Cartesian product of slices:\n%s", cp)
}
```
Running this code returns:
```
Cartesian product of slices:
[
[1, true, {name:test job id:1}],
[1, true, {name:another test job id:2}],
[1, false, {name:test job id:1}],
[1, false, {name:another test job id:2}],
[8, true, {name:test job id:1}],
[8, true, {name:another test job id:2}],
[8, false, {name:test job id:1}],
[8, false, {name:another test job id:2}],
]
```
# Using `cartesian`
The sections below illustrate how to use `cartesian` effectively.
## Creating the `CartesianProduct`
Put the slices you want to compute the cartesian of inside of an `[]any`-type slice. Then, provide this slice as input to `NewCartesianProduct()`. For example:
```golang
sliceA := []int{4, 5, 8}
sliceB := []bool{true, false}
input := []any{sliceA, sliceB}
cp := cartesian.NewCartesianProduct(input)
```
## Iterating over `CartesianProduct` Elements
First, use `Iterator()` to create a `CartesianProductIterator`. Then, use the iterator's `HasNext()` method as an indicator of when to continue iterating, and `Next()` to return the iterands themselves. If you want to iterate over indices instead, use `NextIndices()`. For example:
```golang
// Construct the Cartesian product
sliceA := []int{4, 5, 8}
sliceB := []bool{true, false}
input := []any{sliceA, sliceB}
cp := cartesian.NewCartesianProduct(input)
// Construct the Cartesian Product iterator
cpi := cp.Iterator()
// Iterate over its values and print them out
for cpi.HasNext() {
value := cpi.Next()
fmt.Printf("Value is: %v\n", element)
}
// Reset the iterator, if you'd like...
cpi.ResetIterator()
// Iterate over its indices and print them out
for cpi.HasNext() {
indices := cpi.NextIndices()
fmt.Printf("Indices are: %v\n", indices)
}
// ...or, create a new iterator
newCpi := cp.Iterator()
```
## Computing the full Cartesian product and using `Values()`
When you first call `NewCartesianProduct()`, it computes the full Cartesian Product as part of its initialization process. You can then use `Values()` to print or iterate over the values of the product. Example:
```golang
// Construct the Cartesian product
sliceA := []int{4, 5, 8}
sliceB := []bool{true, false}
input := []any{sliceA, sliceB}
cp := cartesian.NewCartesianProduct(input)
// Iterate over its values and print them out
for _, v := range cp.Values() {
fmt.Printf("Value is: %#v\n", v)
// Assign values to individual variables
firstValue, secondValue := v[0], v[1]
fmt.Printf("First item: %v; Second item: %v", firstValue, secondValue)
}
```
## When to use `Indices()` instead of `Values()`
There are times when you might want/need to obtain indices into each of the passed-in slices, then directly index into each slice yourself to obtain the values corresponding to an element of the Cartesian product. In this case, it's best to use `Indices()`, as it will return a slice of `int`s where each element is an index into a specific slice. One use case for this is computing Cartesian products with a slice of functions and slices of args, then applying the args from the product to the functions:
```golang
// Is it stonks???
func isStonksFunc(isStonks bool, company string) string {
if isStonks {
return fmt.Sprintf("%s is stonks! 👍", company)
}
return fmt.Sprintf("%s is NOT stonks! 👎", company)
}
func isOneFunc(isOne bool, _ string) string {
if isOne {
return "isOne"
}
return "isZero"
}
func main() {
sliceB := []bool{true, false}
sliceS := []string{"MSFT", "GOOG", "META"}
// Function slice
sliceF := make([]func(bool, string) string, 0)
sliceF = append(sliceF, isStonksFunc)
sliceF = append(sliceF, isOneFunc)
// Construct Cartesian product
anotherInput := []any{sliceF, sliceB, sliceS}
cp := cartesian.NewCartesianProduct(anotherInput)
// Explanation: Iterate over indices, and use them to obtain a specific
// Cartesian product by indexing into each slice one by one.
// Then, split the product into a function and two args, and apply those
// args to the function, printing out the result.
for _, indexes := range cp.Indices() {
fn, boolArg, stringArg := sliceF[indexes[0]], sliceB[indexes[1]], sliceS[indexes[2]]
fmt.Printf("Function Name: %v, boolArg: %v, stringArg: %v --> Result: %v\n", cartesian.GetFunctionName(fn), boolArg, stringArg, fn(boolArg, stringArg))
}
}
```