randmap

package module
v0.0.0-...-9e3c222 Latest Latest
Warning

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

Go to latest
Published: Nov 25, 2016 License: MIT Imports: 5 Imported by: 1

README

randmap

GoDoc Go Report Card

go get github.com/lukechampine/randmap

randmap provides methods for (efficiently) accessing random elements of maps, and iterating through maps in random order. Here is a blog post that dives deeper into how I accomplished this, if you're interested in that sort of thing.

WARNING: randmap uses the unsafe package to access the internal Go map type. In general, you should think twice before running any code that imports unsafe. Please read the full README (and the code!) if you are considering using randmap in any serious capacity. If you want the randmap functionality without the risks, you can import the randmap/safe package instead, which is far less efficient but does not use unsafe.

First, it is important to clear up a misconception about Go's map type: that range iterates through maps in random order. Well, what does the Language Specification say?

The iteration order over maps is not specified and is not guaranteed to be the same from one iteration to the next. [1]

This says nothing at all about whether the iteration is random! In fact, you can confirm for yourself that iteration order is not very random at all. The (current) map iterator implementation simply chooses a random index in the map data and iterates forward from there, skipping empty "buckets" and wrapping around at the end. This can lead to surprising behavior:

m := map[int]int{
	0: 0,
	1: 1,
}
for i := range m {
	fmt.Printf("selected %v!\n", i)
	break
}

How frequently do you think 0 will be selected, and how frequently will 1 be selected? The answer is that 0 will be selected 7x more frequently than 1! So we can conclude that map iteration does not produce uniformly random elements. (Actually, it's even worse than that; for certain map states, there are elements that range will never start on!)

This leaves us with only two options: we can flatten the map and select a random index, or we can iterate a random number of times. But these incur costs of O(n) memory and O(n) time, respectively. That might be ok for small maps, but maps aren't always small.

Neither of these options were acceptable to me, so I now present a third option: random map access in constant space and time. Specifically, this approach uses O(1) space and O(L * (1 + k/n)) time, where n is the number of elements in the map, k is the "capacity" of the map (how many elements it can hold before being resized), and L is the length of the longest "bucket chain." Since Go maps double in size when they grow, the total time will generally not exceed 2x the normal map lookup time.

The algorithm is as follows: we begin the same way as the builtin algorithm, by selecting a random index. If the index contains an element, we return it. If it is empty, we try another random index. (The builtin algorithm seeks forward until it hits a non-empty index.) That's it! In theory, this approach actually has an unbounded run time (because you may select the same index twice), but in practice this is rare. I may improve the implementation later to iterate through a random permutation of indices, which would guarantee eventual termination.

Random Iteration

randmap provides Iter and FastIter functions for iterating through maps in random or pseudeorandom order. The standard way to do this is to flatten the map and use math/rand.Perm to iterate through the slice. Although this is sufficiently random, it requires O(n) space and time, which is infeasible for large maps. randmap instead uses a Feistel network to simultaneously generate and iterate through permutations in constant space. This approach is further detailed in the docstring of the perm subpackage. The main tradeoff is that the generator approach will be much slower when n is small.

Examples

m := map[int]int{
	0: 0,
	1: 1,
}

// select a random key
k := randmap.Key(m).(int)

// select a random value
v := randmap.Val(m).(int)

// select a pseudorandom key
k := randmap.FastKey(m).(int)

// select a pseudorandom value
v := randmap.FastVal(m).(int)

// iterate in random order
var k, v int
i := randmap.Iter(m, &k, &v)
for i.Next() {
	// use k and v
}

// iterate in pseudorandom order
i := randmap.FastIter(m, &k, &v)
for i.Next() {
	// use k and v
}

In case it wasn't obvious, Key/Val/Iter use crypto/rand, while their Fast equivalents use math/rand. You should use the former in any code that requires cryptographically strong randomness.

Caveats

This package obviously depends heavily on the internal representation of the map type. If it changes, this package may break. By the way, the map type is changing in Go 1.8. As stated above, use the randmap/safe package if you want the functionality of randmap without the risks.

The runtime code governing maps is a bit esoteric, and uses constructs that aren't available outside of the runtime. Concurrent map operations are especially tricky. For now, no guarantees are made about concurrent use of the functions in this package. Guarding map accesses with a mutex should be sufficient to prevent any problems.

The provided Iterators are not guaranteed to uniformly cover the full permutation space of a given map. This is because the number of permutations may be much larger than the entropy of the iterator's seed. Nevertheless, the seed is sufficient to prevent an attacker from guessing which permutation was selected.

Documentation

Overview

Package randmap provides methods for accessing random elements of maps, and iterating through maps in random order.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func FastKey

func FastKey(m interface{}) interface{}

FastKey returns a pseudorandom key of m, which must be a non-empty map.

func FastVal

func FastVal(m interface{}) interface{}

FastVal returns a pseudorandom value of m, which must be a non-empty map.

func Key

func Key(m interface{}) interface{}

Key returns a uniform random key of m, which must be a non-empty map.

func Val

func Val(m interface{}) interface{}

Val returns a uniform random value of m, which must be a non-empty map.

Types

type Iterator

type Iterator struct {
	// contains filtered or unexported fields
}

An Iterator iterates over a map in random or pseudorandom order. It is intended to be used in a for loop like so:

m := make(map[int]int)
var k, v int
i := Iterator(m, &k, &v)
for i.Next() {
    // use k and v
}

func FastIter

func FastIter(m, k, v interface{}) *Iterator

FastIter returns a pseudorandom iterator for m. Each call to Next will store the next key/value pair in k and v, which must be pointers. Modifying the map during iteration will result in undefined behavior.

func Iter

func Iter(m, k, v interface{}) *Iterator

Iter returns a random iterator for m. Each call to Next will store the next key/value pair in k and v, which must be pointers. Modifying the map during iteration will result in undefined behavior.

func (*Iterator) Next

func (i *Iterator) Next() bool

Next advances the Iterator to the next element in the map, storing its key and value in the pointers passed during initialization. It returns false when all of the elements have been enumerated.

Directories

Path Synopsis
Package perm provides a generator object which can be used to iterate through a permutation of a given range in constant space.
Package perm provides a generator object which can be used to iterate through a permutation of a given range in constant space.

Jump to

Keyboard shortcuts

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