Skip to content

Commit fe67bd3

Browse files
spicychickensaucespicychickensauce
andauthored
Add shuffle/1 (whatyouhide#211)
Co-authored-by: spicychickensauce <[email protected]>
1 parent 1c291fe commit fe67bd3

File tree

2 files changed

+62
-0
lines changed

2 files changed

+62
-0
lines changed

lib/stream_data.ex

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1142,6 +1142,44 @@ defmodule StreamData do
11421142
end
11431143
end
11441144

1145+
@doc """
1146+
Generates lists with the same elements as the provided `list` but in a random order.
1147+
1148+
## Examples
1149+
1150+
StreamData.shuffle([1, 2, 3, 4, 5])
1151+
|> Enum.take(3)
1152+
#=> [[4, 2, 5, 3, 1], [1, 3, 4, 5, 2], [3, 2, 5, 4, 1]]
1153+
1154+
## Shrinking
1155+
1156+
Shrinks towards not changed lists.
1157+
"""
1158+
def shuffle([]), do: constant([])
1159+
1160+
def shuffle(list) do
1161+
# convert to array for faster swapping
1162+
array = :array.from_list(list)
1163+
l = :array.size(array)
1164+
1165+
# Inspired by this clojure implementation:
1166+
# https://github.com/clojure/test.check/blob/0ee576eb73d4864c199305c4a0c1e8101d8d1b39/src/main/clojure/clojure/test/check/generators.cljc#L636
1167+
list_of({integer(0..(l - 1)), integer(0..(l - 1))}, length: 0..(2 * l))
1168+
|> map(fn swap_instructions ->
1169+
Enum.reduce(swap_instructions, array, fn {i, j}, array ->
1170+
array_swap(array, i, j)
1171+
end)
1172+
|> :array.to_list()
1173+
end)
1174+
end
1175+
1176+
defp array_swap(array, i, j) do
1177+
v_i = :array.get(i, array)
1178+
v_j = :array.get(j, array)
1179+
array = :array.set(i, v_j, array)
1180+
:array.set(j, v_i, array)
1181+
end
1182+
11451183
@doc ~S"""
11461184
Generates non-empty improper lists where elements of the list are generated
11471185
out of `first` and the improper ending out of `improper`.

test/stream_data_test.exs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,22 @@ defmodule StreamDataTest do
452452
end
453453
end
454454

455+
describe "shuffle/1" do
456+
property "shuffling retains same elements" do
457+
input = [1, 2, 3, 4, 5]
458+
459+
check all list <- shuffle(input) do
460+
assert Enum.sort(list) == input
461+
end
462+
end
463+
464+
property "shrinks towards not shuffled" do
465+
check all input <- list_of(integer()) do
466+
assert shrink(shuffle(input)) == input
467+
end
468+
end
469+
end
470+
455471
property "nonempty_improper_list_of/2" do
456472
check all list <- nonempty_improper_list_of(integer(), constant("")) do
457473
assert list != []
@@ -764,6 +780,14 @@ defmodule StreamDataTest do
764780
assert check_all(list_of(boolean()), options, property) == {:ok, %{}}
765781
end
766782

783+
# Taken from: https://github.com/whatyouhide/stream_data/issues/160
784+
defp shrink(generator) do
785+
{:error, %{shrunk_failure: value}} =
786+
check_all(generator, [initial_seed: :os.timestamp()], &{:error, &1})
787+
788+
value
789+
end
790+
767791
defp each_improper_list([], _head_fun, _tail_fun) do
768792
:ok
769793
end

0 commit comments

Comments
 (0)