Skip to content

Commit

Permalink
Merge pull request #3716 from Feoramund/slice-permute
Browse files Browse the repository at this point in the history
Add in-place permutation iterator to `core:slice`
  • Loading branch information
Kelimion committed Jun 9, 2024
2 parents 7c529e9 + 047b505 commit 4ea593b
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 1 deletion.
105 changes: 105 additions & 0 deletions core/slice/permute.odin
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package slice

import "base:runtime"

// An in-place permutation iterator.
Permutation_Iterator :: struct($T: typeid) {
index: int,
slice: []T,
counters: []int,
}

/*
Make an iterator to permute a slice in-place.
*Allocates Using Provided Allocator*
This procedure allocates some state to assist in permutation and does not make
a copy of the underlying slice. If you want to permute a slice without altering
the underlying data, use `clone` to create a copy, then permute that instead.
Inputs:
- slice: The slice to permute.
- allocator: (default is context.allocator)
Returns:
- iter: The iterator, to be passed to `permute`.
- error: An `Allocator_Error`, if allocation failed.
*/
make_permutation_iterator :: proc(
slice: []$T,
allocator := context.allocator,
) -> (
iter: Permutation_Iterator(T),
error: runtime.Allocator_Error,
) #optional_allocator_error {
iter.slice = slice
iter.counters = make([]int, len(iter.slice), allocator) or_return

return
}
/*
Free the state allocated by `make_permutation_iterator`.
Inputs:
- iter: The iterator created by `make_permutation_iterator`.
- allocator: The allocator used to create the iterator. (default is context.allocator)
*/
destroy_permutation_iterator :: proc(
iter: Permutation_Iterator($T),
allocator := context.allocator,
) {
delete(iter.counters, allocator = allocator)
}
/*
Permute a slice in-place.
Note that the first iteration will always be the original, unpermuted slice.
Inputs:
- iter: The iterator created by `make_permutation_iterator`.
Returns:
- ok: True if the permutation succeeded, false if the iteration is complete.
*/
permute :: proc(iter: ^Permutation_Iterator($T)) -> (ok: bool) {
// This is an iterative, resumable implementation of Heap's algorithm.
//
// The original algorithm was described by B. R. Heap as "Permutations by
// interchanges" in The Computer Journal, 1963.
//
// This implementation is based on the nonrecursive version described by
// Robert Sedgewick in "Permutation Generation Methods" which was published
// in ACM Computing Surveys in 1977.

i := iter.index

if i == 0 {
iter.index = 1
return true
}

n := len(iter.counters)
#no_bounds_check for i < n {
if iter.counters[i] < i {
if i & 1 == 0 {
iter.slice[0], iter.slice[i] = iter.slice[i], iter.slice[0]
} else {
iter.slice[iter.counters[i]], iter.slice[i] = iter.slice[i], iter.slice[iter.counters[i]]
}

iter.counters[i] += 1
i = 1

break
} else {
iter.counters[i] = 0
i += 1
}
}
if i == n {
return false
}
iter.index = i
return true
}
32 changes: 31 additions & 1 deletion tests/core/slice/test_core_slice.odin
Original file line number Diff line number Diff line change
Expand Up @@ -184,4 +184,34 @@ test_binary_search :: proc(t: ^testing.T) {
index, found = slice.binary_search(b, 0)
testing.expect(t, index == 0, "Expected index to be 0.")
testing.expect(t, found == false, "Expected found to be false.")
}
}

@test
test_permutation_iterator :: proc(t: ^testing.T) {
// Big enough to do some sanity checking but not overly large.
FAC_5 :: 120
s := []int{1, 2, 3, 4, 5}
seen: map[int]bool
defer delete(seen)

iter := slice.make_permutation_iterator(s)
defer slice.destroy_permutation_iterator(iter)

permutations_counted: int
for slice.permute(&iter) {
n := 0
for item, index in s {
n *= 10
n += item
}
if n in seen {
testing.fail_now(t, "Permutation iterator made a duplicate permutation.")
return
}
seen[n] = true
permutations_counted += 1
}

testing.expect_value(t, len(seen), FAC_5)
testing.expect_value(t, permutations_counted, FAC_5)
}

0 comments on commit 4ea593b

Please sign in to comment.