Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions lib/stream_data.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1142,6 +1142,44 @@ defmodule StreamData do
end
end

@doc """
Generates lists with the same elements as the provided `list` but in a random order.

## Examples

StreamData.shuffle([1, 2, 3, 4, 5])
|> Enum.take(3)
#=> [[4, 2, 5, 3, 1], [1, 3, 4, 5, 2], [3, 2, 5, 4, 1]]

## Shrinking

Shrinks towards not changed lists.
"""
def shuffle([]), do: constant([])

def shuffle(list) do
# convert to array for faster swapping
array = :array.from_list(list)
l = :array.size(array)

# Inspired by this clojure implementation:
# https://github.com/clojure/test.check/blob/0ee576eb73d4864c199305c4a0c1e8101d8d1b39/src/main/clojure/clojure/test/check/generators.cljc#L636
list_of({integer(0..(l - 1)), integer(0..(l - 1))}, length: 0..(2 * l))
|> map(fn swap_instructions ->
Enum.reduce(swap_instructions, array, fn {i, j}, array ->
array_swap(array, i, j)
end)
|> :array.to_list()
end)
end

defp array_swap(array, i, j) do
v_i = :array.get(i, array)
v_j = :array.get(j, array)
array = :array.set(i, v_j, array)
:array.set(j, v_i, array)
end

@doc ~S"""
Generates non-empty improper lists where elements of the list are generated
out of `first` and the improper ending out of `improper`.
Expand Down
24 changes: 24 additions & 0 deletions test/stream_data_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,22 @@ defmodule StreamDataTest do
end
end

describe "shuffle/1" do
property "shuffling retains same elements" do
input = [1, 2, 3, 4, 5]

check all list <- shuffle(input) do
assert Enum.sort(list) == input
end
end

property "shrinks towards not shuffled" do
check all input <- list_of(integer()) do
assert shrink(shuffle(input)) == input
end
end
end

property "nonempty_improper_list_of/2" do
check all list <- nonempty_improper_list_of(integer(), constant("")) do
assert list != []
Expand Down Expand Up @@ -764,6 +780,14 @@ defmodule StreamDataTest do
assert check_all(list_of(boolean()), options, property) == {:ok, %{}}
end

# Taken from: https://github.com/whatyouhide/stream_data/issues/160
defp shrink(generator) do
{:error, %{shrunk_failure: value}} =
check_all(generator, [initial_seed: :os.timestamp()], &{:error, &1})

value
end

defp each_improper_list([], _head_fun, _tail_fun) do
:ok
end
Expand Down
Loading