#!/usr/bin/env python
"""
whisker/random.py
===============================================================================
Copyright © 2011-2020 Rudolf Cardinal (rudolf@pobox.com).
This file is part of the Whisker Python client library.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
===============================================================================
**Randomization functions that may be used by Whisker tasks.**
"""
from itertools import islice
import logging
import operator
import random
from typing import Any, Callable, Generator, Iterable, List, Sequence
from cardinal_pythonlib.lists import flatten_list, sort_list_by_index_list
from cardinal_pythonlib.reprfunc import auto_repr
log = logging.getLogger(__name__)
SHUFFLE_FUNC_TYPE = Callable[[Sequence[Any]], List[int]]
# =============================================================================
# Basic list functions
# =============================================================================
[docs]def last_index_of(x: List[Any], value: Any) -> int:
"""
Gets the index of the last occurrence of ``value`` in the list ``x``.
"""
return len(x) - 1 - x[::-1].index(value)
[docs]def get_unique_values(iterable: Iterable[Any]) -> List[Any]:
"""
Gets the unique values of its input.
See
https://stackoverflow.com/questions/12897374/get-unique-values-from-a-list-in-python.
(We don't use ``list(set(x))``, because if the elements of ``x``
are themselves lists (perfectly common!), that gives ``TypeError:
unhashable type: 'list'``.)
""" # noqa
seen = set()
result = []
for element in iterable:
hashed = element
if isinstance(element, dict):
hashed = tuple(sorted(element.items()))
elif isinstance(element, list):
hashed = tuple(element)
if hashed not in seen:
result.append(element)
seen.add(hashed)
return result
[docs]def get_indexes_for_value(x: List[Any], value: Any) -> List[int]:
"""
Returns a list of indexes of ``x`` where its value is ``value``.
"""
return [i for i, item in enumerate(x) if item == value]
# =============================================================================
# Draw-without-replacement
# =============================================================================
[docs]def make_dwor_hat(values: Iterable[Any], multiplier: int = 1) -> List[Any]:
"""
Makes a "hat" to draw values from. See :func:`gen_dwor`. Does not modify
the starting list; returns a copy.
"""
assert multiplier >= 1, "Bad DWOR multiplier"
hat = []
for v in values:
for _ in range(multiplier):
hat.append(v)
random.shuffle(hat) # shuffle in place
return hat
[docs]def gen_dwor(values: Iterable[Any],
multiplier: int = 1) -> Generator[Any, None, None]:
"""
Generates values using a draw-without-replacement (DWOR) system.
Args:
values: values to generate
multiplier: DWOR multiplier; see below.
Yields:
successive values
Here's how it works.
- Suppose ``values == [A, B, C]``.
- We'll call :math:`n` the number of values (here, 3), and :math:`k` the
"multiplier" parameter.
- If you iterate through ``gen_dwor(values, multiplier=1)``, you will
get a sequence that might look like this (with spaces added for
clarity):
.. code-block:: none
CAB ABC BCA BAC BAC ACB CBA ...
That is, individual are drawn randomly from a "hat" of size :math:`n =
3`, containing one of each thing from ``values``. When the hat is empty,
it is refilled with :math:`n` more.
- If you iterate through ``gen_dwor(values, multiplier=2)``, however, you
might get this:
.. code-block:: none
AACBBC CABBAC BAACCB ...
The computer has put :math:`k` copies of each value in the hat, and then
draws one each time at random (so the hat starts with :math:`nk` values
in it). When the hat is exhausted, it re-populates.
The general idea is to provide randomness, but randomness that is
constrained to prevent unlikely but awkward sequences like
.. code-block:: none
AAAAAAAAAAAAAAAA ... unlikely but possible with full randomness!
yet also have the option to avoid predictability. With :math:`k = 1`, then
a clever subject could infer exactly what's coming up on every
:math:`n`\ th trial. So a low value of :math:`k` brings very few "runs" but
some predictability; as :math:`k` approaches infinity, it's equivalent to
full randomness; some reasonably low value of :math:`k` in between may be a
useful experimental sweet spot.
See also, for example:
- http://www.whiskercontrol.com/help/FiveChoice/index.html?fivechoice_dwor.htm
""" # noqa
hat = []
while True:
if not hat:
hat = make_dwor_hat(values, multiplier)
value = hat.pop()
yield value
[docs]def get_dwor_list(values: Iterable[Any],
length: int,
multiplier: int = 1) -> List[Any]:
"""
Makes a fixed-length list via :func:`gen_dwor`.
Args:
values: values to pick from
length: list length
multiplier: DWOR multiplier
Returns:
list of length ``length``
Example:
.. code-block:: python
from whisker.random import get_dwor_list
values = ["a", "b", "c"]
print(get_dwor_list(values, length=24, multiplier=1))
print(get_dwor_list(values, length=24, multiplier=2))
print(get_dwor_list(values, length=24, multiplier=3))
"""
return list(islice(gen_dwor(values, multiplier), length))
# =============================================================================
# Simple randomness
# =============================================================================
[docs]def shuffle_list_slice(x: List[Any],
start: int = None, end: int = None) -> None:
"""
Shuffles a segment of a list, ``x[start:end]``, in place.
Note that ``start=None`` means "from the beginning" and ``end=None`` means
"to the end".
"""
# log.debug("x={}, start={}, end={}".format(x, start, end))
copy = x[start:end]
random.shuffle(copy)
x[start:end] = copy
[docs]def shuffle_list_subset(x: List[Any], indexes: List[int]) -> None:
"""
Shuffles some elements of a list (in place). The elements to interchange
(shuffle) as specified by ``indexes``.
"""
elements = [x[i] for i in indexes]
random.shuffle(elements)
for element_idx, x_idx in enumerate(indexes):
x[x_idx] = elements[element_idx]
# =============================================================================
# Randomness within/across chunks
# =============================================================================
[docs]def shuffle_list_within_chunks(x: List[Any], chunksize: int) -> None:
"""
Divides a list into chunks and shuffles WITHIN each chunk (in place).
For example:
.. code-block:: python
x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
shuffle_list_within_chunks(x, 4)
``x`` might now be:
.. code-block:: none
[4, 1, 3, 2, 7, 5, 6, 8, 9, 12, 11, 10]
^^^^^^^^^^ ^^^^^^^^^^ ^^^^^^^^^^^^^
"""
starts = list(range(0, len(x), chunksize))
# noinspection PyTypeChecker
ends = starts[1:] + [None]
for start, end in zip(starts, ends):
shuffle_list_slice(x, start, end)
[docs]def shuffle_list_chunks(x: List[Any], chunksize: int) -> None:
"""
Divides a list into chunks and shuffles the chunks themselves (in place).
For example:
.. code-block:: python
x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
shuffle_list_chunks(x, 4)
``x`` might now be
.. code-block:: none
[5, 6, 7, 8, 1, 2, 3, 4, 9, 10, 11, 12]
^^^^^^^^^^ ^^^^^^^^^^ ^^^^^^^^^^^^^
Uses :func:`cardinal_pythonlib.lists.flatten_list` and
:func:`cardinal_pythonlib.lists.sort_list_by_index_list`. (I say that
mainly to test Intersphinx, when it is enabled.)
"""
starts = list(range(0, len(x), chunksize))
ends = starts[1:] + [len(x)]
# Shuffle the indexes rather than the array, then we can write back
# in place.
chunks = []
for start, end in zip(starts, ends):
chunks.append(list(range(start, end)))
random.shuffle(chunks)
indexes = flatten_list(chunks)
sort_list_by_index_list(x, indexes)
# =============================================================================
# Hierarchical randomness: early methods
# =============================================================================
[docs]def block_shuffle_by_item(x: List[Any],
indexorder: List[int],
start: int = None,
end: int = None) -> None:
"""
**DEPRECATED:** :func:`layered_shuffle` is more powerful.
Shuffles the list ``x[start:end]`` hierarchically, in place.
Args:
x: list to shuffle
indexorder: a list of indexes of each item of ``x``
The first index varies slowest; the last varies fastest.
start: start index of ``x``
end: end index of ``x``
For example:
.. code-block:: python
p = list(itertools.product("ABC", "xyz", "123"))
``x`` is now a list of tuples looking like ``('A', 'x', '1')``.
.. code-block:: python
block_shuffle_by_item(p, [0, 1, 2])
``p`` might now look like:
.. code-block:: none
C z 1 } all values of "123" appear } first "xyz" block
C z 3 } once, but randomized }
C z 2 } }
}
C y 2 } next "123" block }
C y 1 } }
C y 3 } }
}
C x 3 }
C x 2 }
C x 1 }
A y 3 } second "xyz" block
... } ...
A clearer explanation is in :func:`block_shuffle_by_attr`.
"""
item_idx_order = indexorder.copy()
item_idx = item_idx_order.pop(0)
# 1. Take copy
sublist = x[start:end]
# 2. Sort then shuffle in chunks (e.g. at the "ABC" level)
sublist.sort(key=operator.itemgetter(item_idx))
# Note below that we must convert things to tuples to put them into
# sets; if you take a set() of lists, you get
# TypeError: unhashable type: 'list'
unique_values = set(tuple(x[item_idx]) for x in sublist)
chunks = [
[i for i, v in enumerate(sublist) if tuple(v[item_idx]) == value]
for value in unique_values
]
random.shuffle(chunks)
indexes = flatten_list(chunks)
sort_list_by_index_list(sublist, indexes)
# 3. Call recursively (e.g. at the "xyz" level next)
if item_idx_order: # more to do?
starts_ends = [(min(chunk), max(chunk) + 1) for chunk in chunks]
for s, e in starts_ends:
block_shuffle_by_item(sublist, item_idx_order, s, e)
# 4. Write back
x[start:end] = sublist
[docs]def block_shuffle_by_attr(x: List[Any],
attrorder: List[str],
start: int = None,
end: int = None) -> None:
"""
**DEPRECATED:** :func:`layered_shuffle` is more powerful.
Exactly as for :func:`block_shuffle_by_item`, but by item attribute
rather than item index number.
For example:
.. code-block:: python
from collections import namedtuple
import itertools
from whisker.random import block_shuffle_by_attr
p = list(itertools.product("ABC", "xyz", "123"))
Trio = namedtuple("Trio", ["upper", "lower", "digit"])
q = [Trio(*x) for x in p]
block_shuffle_by_attr(q, ['upper', 'lower', 'digit'])
``q`` started off as:
.. code-block:: none
[
Trio(upper='A', lower='x', digit='1'),
Trio(upper='A', lower='x', digit='2'),
Trio(upper='A', lower='x', digit='3'),
Trio(upper='A', lower='y', digit='1'),
Trio(upper='A', lower='y', digit='2'),
Trio(upper='A', lower='y', digit='3'),
Trio(upper='A', lower='z', digit='1'),
Trio(upper='A', lower='z', digit='2'),
Trio(upper='A', lower='z', digit='3'),
Trio(upper='B', lower='x', digit='1'),
Trio(upper='B', lower='x', digit='2'),
Trio(upper='B', lower='x', digit='3'),
Trio(upper='B', lower='y', digit='1'),
Trio(upper='B', lower='y', digit='2'),
Trio(upper='B', lower='y', digit='3'),
Trio(upper='B', lower='z', digit='1'),
Trio(upper='B', lower='z', digit='2'),
Trio(upper='B', lower='z', digit='3'),
Trio(upper='C', lower='x', digit='1'),
Trio(upper='C', lower='x', digit='2'),
Trio(upper='C', lower='x', digit='3'),
Trio(upper='C', lower='y', digit='1'),
Trio(upper='C', lower='y', digit='2'),
Trio(upper='C', lower='y', digit='3'),
Trio(upper='C', lower='z', digit='1'),
Trio(upper='C', lower='z', digit='2'),
Trio(upper='C', lower='z', digit='3')
]
but after the shuffle ``q`` might now be:
.. code-block:: none
[
Trio(upper='B', lower='z', digit='1'),
Trio(upper='B', lower='z', digit='3'),
Trio(upper='B', lower='z', digit='2'),
Trio(upper='B', lower='x', digit='1'),
Trio(upper='B', lower='x', digit='3'),
Trio(upper='B', lower='x', digit='2'),
Trio(upper='B', lower='y', digit='3'),
Trio(upper='B', lower='y', digit='2'),
Trio(upper='B', lower='y', digit='1'),
Trio(upper='A', lower='z', digit='2'),
Trio(upper='A', lower='z', digit='1'),
Trio(upper='A', lower='z', digit='3'),
Trio(upper='A', lower='x', digit='1'),
Trio(upper='A', lower='x', digit='2'),
Trio(upper='A', lower='x', digit='3'),
Trio(upper='A', lower='y', digit='3'),
Trio(upper='A', lower='y', digit='1'),
Trio(upper='A', lower='y', digit='2'),
Trio(upper='C', lower='x', digit='2'),
Trio(upper='C', lower='x', digit='3'),
Trio(upper='C', lower='x', digit='1'),
Trio(upper='C', lower='y', digit='2'),
Trio(upper='C', lower='y', digit='1'),
Trio(upper='C', lower='y', digit='3'),
Trio(upper='C', lower='z', digit='1'),
Trio(upper='C', lower='z', digit='2'),
Trio(upper='C', lower='z', digit='3')
]
You can see that the ``A``/``B``/``C`` group has been shuffled as blocks.
Then, within ``B``, the ``x``/``y``/``z`` groups have been shuffled (and so
on for ``A`` and ``C``). Then, within ``B.z``, the ``1``/``2``/``3`` values
have been shuffled (and so on).
"""
item_attr_order = attrorder.copy()
item_attr = item_attr_order.pop(0)
# 1. Take copy
sublist = x[start:end]
# 2. Sort then shuffle in chunks
sublist.sort(key=operator.attrgetter(item_attr))
unique_values = set(tuple(getattr(x, item_attr)) for x in sublist)
chunks = [
[
i for i, v in enumerate(sublist)
if tuple(getattr(v, item_attr)) == value
]
for value in unique_values
]
random.shuffle(chunks)
indexes = flatten_list(chunks)
sort_list_by_index_list(sublist, indexes)
# 3. Call recursively (e.g. at the "xyz" level next)
if item_attr_order: # more to do?
starts_ends = [(min(chunk), max(chunk) + 1) for chunk in chunks]
for s, e in starts_ends:
block_shuffle_by_attr(sublist, item_attr_order, s, e)
# 4. Write back
x[start:end] = sublist
[docs]def shuffle_where_equal_by_attr(x: List[Any], attrname: str) -> None:
"""
**DEPRECATED:** :func:`layered_shuffle` is more powerful.
Shuffles a list ``x``, in place, where list members are equal as judged by
the attribute ``attrname``.
This is easiest to show by example:
.. code-block:: python
from collections import namedtuple
import itertools
from whisker.random import shuffle_where_equal_by_attr
p = list(itertools.product("ABC", "xyz", "123"))
Trio = namedtuple("Trio", ["upper", "lower", "digit"])
q = [Trio(*x) for x in p]
shuffle_where_equal_by_attr(q, 'digit')
``q`` started off as:
.. code-block:: none
[
Trio(upper='A', lower='x', digit='1'),
Trio(upper='A', lower='x', digit='2'),
Trio(upper='A', lower='x', digit='3'),
Trio(upper='A', lower='y', digit='1'),
Trio(upper='A', lower='y', digit='2'),
Trio(upper='A', lower='y', digit='3'),
Trio(upper='A', lower='z', digit='1'),
Trio(upper='A', lower='z', digit='2'),
Trio(upper='A', lower='z', digit='3'),
Trio(upper='B', lower='x', digit='1'),
Trio(upper='B', lower='x', digit='2'),
Trio(upper='B', lower='x', digit='3'),
Trio(upper='B', lower='y', digit='1'),
Trio(upper='B', lower='y', digit='2'),
Trio(upper='B', lower='y', digit='3'),
Trio(upper='B', lower='z', digit='1'),
Trio(upper='B', lower='z', digit='2'),
Trio(upper='B', lower='z', digit='3'),
Trio(upper='C', lower='x', digit='1'),
Trio(upper='C', lower='x', digit='2'),
Trio(upper='C', lower='x', digit='3'),
Trio(upper='C', lower='y', digit='1'),
Trio(upper='C', lower='y', digit='2'),
Trio(upper='C', lower='y', digit='3'),
Trio(upper='C', lower='z', digit='1'),
Trio(upper='C', lower='z', digit='2'),
Trio(upper='C', lower='z', digit='3')
]
but after the shuffle ``q`` might now be:
.. code-block:: none
[
Trio(upper='A', lower='x', digit='1'),
Trio(upper='A', lower='y', digit='2'),
Trio(upper='A', lower='z', digit='3'),
Trio(upper='B', lower='z', digit='1'),
Trio(upper='A', lower='z', digit='2'),
Trio(upper='C', lower='x', digit='3'),
Trio(upper='B', lower='y', digit='1'),
Trio(upper='A', lower='x', digit='2'),
Trio(upper='C', lower='y', digit='3'),
Trio(upper='A', lower='y', digit='1'),
Trio(upper='C', lower='y', digit='2'),
Trio(upper='C', lower='z', digit='3'),
Trio(upper='C', lower='y', digit='1'),
Trio(upper='C', lower='z', digit='2'),
Trio(upper='A', lower='y', digit='3'),
Trio(upper='B', lower='x', digit='1'),
Trio(upper='B', lower='z', digit='2'),
Trio(upper='B', lower='y', digit='3'),
Trio(upper='C', lower='z', digit='1'),
Trio(upper='C', lower='x', digit='2'),
Trio(upper='B', lower='z', digit='3'),
Trio(upper='C', lower='x', digit='1'),
Trio(upper='B', lower='x', digit='2'),
Trio(upper='A', lower='x', digit='3'),
Trio(upper='A', lower='z', digit='1'),
Trio(upper='B', lower='y', digit='2'),
Trio(upper='B', lower='x', digit='3')
]
As you can see, the ``digit`` attribute seems to have stayed frozen and
everything else has jumbled. What has actually happened is that everything
with ``digit == 1`` has been shuffled among themselves, and similarly for
``digit == 2`` and ``digit == 3``.
"""
unique_values = set(tuple(getattr(item, attrname)) for item in x)
for value in unique_values:
indexes = [
i for i, item in enumerate(x)
if tuple(getattr(item, attrname)) == value
]
shuffle_list_subset(x, indexes)
# =============================================================================
# Hierarchical randomness: full power
# =============================================================================
[docs]def random_shuffle_indexes(x: List[Any]) -> List[int]:
"""
Returns a list of indexes of ``x``, randomly shuffled.
"""
indexes = list(range(len(x)))
random.shuffle(indexes) # in place
return indexes
[docs]def block_shuffle_indexes_by_value(x: List[Any]) -> List[int]:
"""
Returns a list of indexes of ``x``, block-shuffled by value.
That is: we aggregate items into blocks, defined by value, and shuffle
those blocks, returning the corresponding indexes of the original list.
"""
unique_values = get_unique_values(x)
list_of_chunks = [
get_indexes_for_value(x, value)
for value in unique_values
]
random.shuffle(list_of_chunks)
indexes = flatten_list(list_of_chunks)
return indexes
[docs]def dwor_shuffle_indexes(x: List[Any], multiplier: int = 1) -> List[int]:
"""
Returns a list of indexes of ``x``, DWOR-shuffled by value.
This is a bit tricky as we don't have a guarantee of equal numbers. It
does sensible things in those circumstances.
"""
assert multiplier >= 1, "Bad DWOR multiplier"
unique_values = get_unique_values(x)
list_of_chunks = [
get_indexes_for_value(x, value)
for value in unique_values
]
def unusual_make_dwor_hat() -> List[int]:
hat_ = []
for _ in range(multiplier):
for chunk in list_of_chunks:
if chunk:
hat_.append(chunk.pop(0))
random.shuffle(hat_) # in place
return hat_
hat = []
indexes = [] # type: List[int]
finished = False
while not finished:
if not hat:
hat = unusual_make_dwor_hat()
if hat:
indexes.append(hat.pop())
else:
finished = True
return indexes
[docs]def sort_indexes(x: List[Any]) -> List[int]:
"""
Returns the indexes of ``x`` in an order that would sort ``x`` by value.
See https://stackoverflow.com/questions/7851077/how-to-return-index-of-a-sorted-list
""" # noqa
return sorted(range(len(x)), key=lambda index: x[index])
[docs]def reverse_sort_indexes(x: List[Any]) -> List[int]:
"""
Returns the indexes of ``x`` in an order that would reverse-sort ``x`` by
value.
"""
return sorted(range(len(x)), key=lambda index: x[index], reverse=True)
[docs]class ShuffleLayerMethod(object):
"""
Class to representing instructions to :func:`layered_shuffle` (q.v.).
"""
def __init__(self,
flat: bool = False,
layer_key: int = None,
layer_attr: str = None,
layer_func: Callable[[Any], Any] = None,
shuffle_func: SHUFFLE_FUNC_TYPE = None) -> None:
"""
Args:
flat: take data as ``x[index]``?
layer_key: take data as ``x[index][layer_key]``?
layer_attr: take data as ``getattr(x[index], layer_attr)``?
layer_func: take data as ``layer_func(x[index])``?
shuffle_func: function (N.B. may be a lambda function with
parameters attached) that takes a list of objects and returns
a list of INDEXES, suitably shuffled.
Typical values of ``shuffle_func``:
- ``None``: no shuffle
- ``random_shuffle_indexes``: plain shuffle; see
:func:`random_shuffle_indexes`
- ``dwor_shuffle_indexes``: DWOR-style shuffle (will need a ``lambda``
for its ``multiplier parameter``; see :func:`dwor_shuffle_indexes`
- ``block_shuffle_indexes_by_value``: aggregate items into blocks,
defined by value, and shuffle those blocks; see
:func:`block_shuffle_indexes_by_value`
- ``sort_indexes``: not exactly shuffling! See :func:`sort_indexes`.
- ``reverse_sort_indexes``: not exactly shuffling! See
:func:`reverse_sort_indexes`.
"""
assert (
flat +
(layer_key is not None) +
bool(layer_attr)
) == 1, "Specify exactly one way of accessing data from the layer!"
if layer_key is not None:
assert isinstance(layer_key, int)
assert layer_key > 0
elif layer_attr:
assert isinstance(layer_attr, str)
elif layer_func:
assert callable(layer_func)
self.flat = flat
self.layer_key = layer_key
self.layer_attr = layer_attr
self.layer_func = layer_func
self.shuffle_func = shuffle_func
def __repr__(self) -> str:
return auto_repr(self)
[docs] def get_values(self, x: List[Any]) -> List[Any]:
"""
Returns all the values of interest from ``x`` for this layer.
"""
if self.flat:
return x[:] # a copy, just in case someone wants to modify it
elif self.layer_attr:
attr = self.layer_attr
return [getattr(item, attr) for item in x]
elif self.layer_func:
func = self.layer_func
return [func(item) for item in x]
else:
# key, via item[key], meaning item.__getitem__(key)
key = self.layer_key
return [item[key] for item in x]
[docs] def get_unique_values(self, x: List[Any]) -> List[Any]:
"""
Returns all the unique values of ``x`` for this layer.
"""
return get_unique_values(self.get_values(x))
[docs] def get_indexes_for_value(self, x: List[Any], value: Any) -> List[int]:
"""
Returns a list of indexes of ``x`` where its value (as defined by this
layer) is ``value``.
"""
return get_indexes_for_value(self.get_values(x), value)
[docs]def layered_shuffle(x: List[Any],
layers: List[ShuffleLayerMethod]) -> None:
r"""
Most powerful hierarchical shuffle command here.
Shuffles ``x`` in place in a layered way as specified by the sequence of
methods.
In more detail:
- for each layer, it shuffles values of ``x`` as defined by the
:class:`ShuffleLayerMethod` (for example: "shuffle ``x`` in blocks based
on the value of ``x.someattr``", or "shuffle ``x`` randomly")
- it then proceeds to deeper layers *within* sub-lists defined by each
unique value from the previous layer.
Args:
x: sequence (e.g. list) to shuffle
layers: list of :class:`ShuffleLayerMethod` instructions
Examples:
.. code-block:: python
from collections import namedtuple
import itertools
import logging
import random
from whisker.random import *
logging.basicConfig(level=logging.DEBUG)
startlist = ["a", "b", "c", "d", "a", "b", "c", "d", "a", "b", "c", "d"]
x1 = startlist[:]
x2 = startlist[:]
x3 = startlist[:]
x4 = startlist[:]
do_nothing_method = ShuffleLayerMethod(flat=True, shuffle_func=None)
do_nothing_method.get_unique_values(x1)
do_nothing_method.get_indexes_for_value(x1, "b")
layered_shuffle(x1, [do_nothing_method])
print(x1)
flat_randomshuffle_method = ShuffleLayerMethod(
flat=True, shuffle_func=random_shuffle_indexes)
flat_randomshuffle_method.get_unique_values(x1)
flat_randomshuffle_method.get_indexes_for_value(x1, "b")
layered_shuffle(x1, [flat_randomshuffle_method])
print(x1)
flat_blockshuffle_method = ShuffleLayerMethod(
flat=True, shuffle_func=block_shuffle_indexes_by_value)
layered_shuffle(x2, [flat_blockshuffle_method])
print(x2)
flat_dworshuffle_method = ShuffleLayerMethod(
flat=True, shuffle_func=dwor_shuffle_indexes)
layered_shuffle(x3, [flat_dworshuffle_method])
print(x3)
flat_dworshuffle2_method = ShuffleLayerMethod(
flat=True, shuffle_func=lambda x: dwor_shuffle_indexes(x, multiplier=2))
layered_shuffle(x4, [flat_dworshuffle2_method])
print(x4)
p = list(itertools.product("ABC", "xyz", "123"))
Trio = namedtuple("Trio", ["upper", "lower", "digit"])
q = [Trio(*x) for x in p]
print("\n".join(str(x) for x in q))
upper_method = ShuffleLayerMethod(
layer_attr="upper", shuffle_func=block_shuffle_indexes_by_value)
lower_method = ShuffleLayerMethod(
layer_attr="lower", shuffle_func=reverse_sort_indexes)
digit_method = ShuffleLayerMethod(
layer_attr="digit", shuffle_func=random_shuffle_indexes)
layered_shuffle(q, [upper_method, lower_method, digit_method])
print("\n".join(str(x) for x in q))
""" # noqa
if not layers:
return
layer = layers[0]
subsequent_layers = layers[1:]
if layer.shuffle_func:
values = layer.get_values(x)
shuffled_indexes = layer.shuffle_func(values)
log.debug("Applying function {!r} to values {!r} gives indexes "
"{!r}".format(layer.shuffle_func, values, shuffled_indexes))
sort_list_by_index_list(x, shuffled_indexes)
if subsequent_layers:
unique_values = layer.get_unique_values(x)
for value in unique_values:
indexes_for_value = layer.get_indexes_for_value(x, value)
subelements = [x[i] for i in indexes_for_value]
# Recursion:
layered_shuffle(subelements, subsequent_layers)
# Put the (shuffled( elements back:
for element_idx, x_idx in enumerate(indexes_for_value):
x[x_idx] = subelements[element_idx]