Skip to content

zaataylor/cartesian

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

21 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Club Badge

Table of Contents

Description

What is the Cartesian Product?

Given two sets of "things", A and B, the 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:

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:

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:

// 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:

// 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 ints 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:

// 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))
	}
}

About

A Golang package for computing Cartesian products

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages