powerset

package module
v0.0.0-...-31faa4c Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 10, 2018 License: MIT Imports: 4 Imported by: 0

README

Powerset.go

Build Status Go Report Card Coverage Status

A flexible library for generating powersets incrementally to solve problems that require intelligently considering the set of all possible subsets, often used with backtracking. See it in action: N-Queens Problem

Usage

There are two simple functions, FixedSize, and VariableSize to generate a powerset and return the results over a channel. There is also a very advanced function Callback to also generate a powerset over a channel, but utilizes a callback as it traverses the nodes of the powerset tree. All 3 functions support early termination.

Fixed-size method

out, stop := powerset.FixedSize(3)
for indices := range out {
    fmt.Println(indices)
}

Output:

[false false false]
[false false true]
[false true false]
[false true true]
[true false false]
[true false true]
[true true false]
[true true true]

out is the output channel that will yield indices of type [size]bool, where size is the integer passed into FixedSize. Each array of indices contains a true if that index is included, or a false if it isn't. For example, [false, false, false] corresponds to the null set, while [true, false, true] is the set {0,2}.

stop is a function with signature func(). When called, it terminates the goroutine generating the powerset. An example of stopping:

out, stop := powerset.FixedSize(3)
for indices := range out {
    if indices[1] {
        stop()
        break
    }
    fmt.Println(indices)
}

Variable-size method

out, stop := powerset.VariableSize(3)
for indices := range out {
    fmt.Println(indices)
}

Output:

[]
[2]
[1]
[2 1]
[0]
[2 0]
[1 0]
[2 1 0]

out is the output channel that will yield indices of type []int. Each indices element contains only the indices included. For example, [] is the null set, while [0,2] is the set {0,2}.

Callback method

The callback version is the most advanced version of powerset generation. This version allows you to provide a callback that is called at each intermediary node of the powerset tree, and gives you the option to terminate generation up to an arbitrary parent if you want to discontinue a subtree. This allows you to choose to not evaluate specific branches of the powerset based on user logic in the callback.

cb := func(path powerset.Path, isLeaf bool, state interface{}, out chan<- interface{}) (bool, int, interface{}) {
    out <- path
    return false, 0, nil
}

out := powerset.Callback(3, cb, nil)
for path := range out {
    fmt.Println(path)
}

Output:

{}
{0 false}
{1 false} {0 false}
{2 false} {1 false} {0 false}
{2 true} {1 false} {0 false}
{1 true} {0 false}
{2 false} {1 true} {0 false}
{2 true} {1 true} {0 false}
{0 true}
{1 false} {0 true}
{2 false} {1 false} {0 true}
{2 true} {1 false} {0 true}
{1 true} {0 true}
{2 false} {1 true} {0 true}
{2 true} {1 true} {0 true}

The callback cb is evaluated at each node of the powerset tree. The path to the current node is represented with the first argument to the callback, of value powerset.Path. Here, we're writing path to our output channel, to visualize it. You can see the each path corresponds to each node of the powerset tree:

Each leaf in the tree is a specific set in the powerset, starting from the null set (far left), to the set {0,1,2} (far right). The intermediary nodes are paths through the tree specified by whether or not indices were included or excluded.

The callback's first argument is receiving a Path, which is a type alias for []*PathNode. Each PathNode struct contains an index and whether or not the index is explicitly included at the current powerset node. Below is an example pathway through the tree. The green dotted line indicates the pathway {+1,-0}. So the Path that the callback would receive at this node would be []*PathNodes{{1,true}, {0,false}}:

Arguments

The first argument is our powerset.Path which we covered above.

The second argument, isLeaf bool is a simple flag to let your callback know if we're on a leaf or intermediary node.

The third argument, state interface{} gives us the bulk of the power. It represents some arbitrary state that we can mutate, whose value will propagate only to child nodes. For example, in the n-queens problem at the end of this README, the state represents the board with the queens positions. When new branches of the powerset tree are explored, this state is updated to add new queens, and when we backtrack to previous nodes, we automatically re-use old board states. State can be anything you want, hence interface{}, but it should be noted, deep copies must be made manually at each node. If your state is a struct containing a map, for example, although the struct itself is passed by value to the callback, the underlying mapping will share its heap-allocated data with the previous state, causing problems in your algorithm. If you experience weird behavior, it's likely that you didn't fully copy your state between nodes.

The last argument is self explanatory and is a channel for communicating your solutions to the caller. In the n-queens example, we use this to write out our valid board positions.

Return value

The return value of the callback is a tuple bool, int, interface{}.

The first and second argument go together. The bool is whether or not we should terminate generation of child nodes, and the int is which parent we should continue the algorithm. In most cases, when you terminate, you will usually want to continue at the parent, which would corrspond to len(path) - 1. You can see in this image, from the blue lines to the right, that the root node is zero-indexed, which would make our current node len(path), because a node's height index will receive that many path segments:

If we terminate to the parent from a left branch, processing will continue at the right sibling. If we terminate to the parent from the right branch, processing will continue with the grandparent (since the parent has no more children to explore). Using true, 0 will terminate to the root, while true, -1 will terminate to before the root, meaning the algorithm terminates completely.

This termination logic is critical in exploring large state space trees for solutions, since we can backtrack early and skip potentially quintillions (not a typo, see the n-queens output!) of nodes.

Example: N-Queens

The n-queens problem is about finding all possible arrangements of n queens on an n-by-n sized chess board, such that no queens can attack eachother. We can solve this using backtracking, which fits in nicely with powerset.Callback.

The basic idea is that we'll represent our n-by-n board grid cells as integers and examine the powerset of the integers. If a cell is included in a set of the powerset, it has a queen on it, otherwise it doesn't. A brute force search would have to visit (2^(n*n-1))-1 nodes just to search the solution space for all possible arrangements. Fortunately, by backtracking when we immediately find an invalid solution, we can skip out on the vast majority of nodes. On an 8x8 sized board, the number of nodes we actually examine is only 1,849,097, while the number of nodes we skip is 18,446,744,073,707,702,518.

We'll choose to backtrack up to the parent node whenever valid() is false. We'll also yield board results on the output channel when the number of queens on the board matches n:

examples/nqueens.go

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Callback

func Callback(lenItems int, cb NodeCallback, state interface{}) <-chan interface{}

Callback generates the powerset but at each leaf node call the callback

func FixedSize

func FixedSize(lenItems int) (<-chan []bool, func())

FixedSize generates a powerset of fixed size items. each item returned on the output channel has a length of lenItems and each element is either true or false, indicating that the index is included in the combination

func ValidatePath

func ValidatePath(path Path, check Path) bool

ValidatePath is a helper for validating that two Paths match. useful in a callback

func VariableSize

func VariableSize(lenItems int) (<-chan []int, func())

VariableSize generates a variable size powerset. each slice returned on the output channel is a variable size slice containing the index numbers othemselves of the items included in each combination

Types

type NodeCallback

type NodeCallback func(Path, bool, interface{}, chan<- interface{}) (bool, int, interface{})

NodeCallback represents a callback to Callback

type Path

type Path []*PathNode

Path is an alias for a slice of PathNodes

func (Path) String

func (path Path) String() string

type PathNode

type PathNode struct {
	Index    int
	Included bool
}

PathNode represents a node in a pathway through the powerset tree. going left through the tree means the index at which we've decided to go left will not be included, going right means it will be included

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL