Source code for abjad.enumerate

"""
Enumeration functions.
"""

import functools
import itertools
import typing

from . import math as _math
from . import sequence as _sequence


def _is_restricted_growth_function(sequence) -> bool:
    """
    Is true when ``sequence`` is a restricted growth function. A restricted
    growth function is a sequence ``l`` such that ``l[0] == 1`` and such that
    ``l[i] <= max(l[:i]) + 1`` for ``1 <= i <= len(l)``.

    ..  container:: example

        >>> abjad.enumerate._is_restricted_growth_function([1, 1, 1, 1])
        True

        >>> abjad.enumerate._is_restricted_growth_function([1, 1, 1, 2])
        True

        >>> abjad.enumerate._is_restricted_growth_function([1, 1, 2, 1])
        True

        >>> abjad.enumerate._is_restricted_growth_function([1, 1, 2, 2])
        True

    ..  container:: example

        >>> abjad.enumerate._is_restricted_growth_function([1, 1, 1, 3])
        False

        >>> abjad.enumerate._is_restricted_growth_function([17])
        False

    """
    try:
        for i, n in enumerate(sequence):
            if i == 0:
                if not n == 1:
                    return False
            else:
                if not n <= max(sequence[:i]) + 1:
                    return False
        return True
    except TypeError:
        return False


def _partition_by_rgf(sequence, rgf: list[int]) -> list[list]:
    """
    Partitions ``sequence`` by restricted growth function ``rgf``.

    ..  container:: example

        >>> sequence = list(range(10))
        >>> rgf = [1, 1, 2, 2, 1, 2, 3, 3, 2, 4]

        >>> abjad.enumerate._partition_by_rgf(sequence, rgf)
        [[0, 1, 4], [2, 3, 5, 8], [6, 7], [9]]

    """
    rgf = list(rgf)
    if not _is_restricted_growth_function(rgf):
        raise ValueError(f"must be restricted growth function: {rgf!r}.")
    if not len(sequence) == len(rgf):
        raise ValueError("lengths must be equal.")
    partition = []
    for part_index in range(max(rgf)):
        part: list = []
        partition.append(part)
    for n, part_number in zip(sequence, rgf):
        part_index = part_number - 1
        part = partition[part_index]
        part.append(n)
    partition = [type(sequence)(_) for _ in partition]
    return partition


def _yield_restricted_growth_functions(length) -> typing.Iterator[list[int]]:
    """
    Yields restricted growth functions of ``length`` in lex order.

    ..  container:: example

        >>> rgfs = abjad.enumerate._yield_restricted_growth_functions(4)
        >>> for rgf in rgfs:
        ...     rgf
        ...
        [1, 1, 1, 1]
        [1, 1, 1, 2]
        [1, 1, 2, 1]
        [1, 1, 2, 2]
        [1, 1, 2, 3]
        [1, 2, 1, 1]
        [1, 2, 1, 2]
        [1, 2, 1, 3]
        [1, 2, 2, 1]
        [1, 2, 2, 2]
        [1, 2, 2, 3]
        [1, 2, 3, 1]
        [1, 2, 3, 2]
        [1, 2, 3, 3]
        [1, 2, 3, 4]

    """
    assert _math.is_positive_integer(length), repr(length)
    last_rgf = list(range(1, length + 1))
    rgf = length * [1]
    yield list(rgf)
    while not rgf == last_rgf:
        for i, x in enumerate(reversed(rgf)):
            stop = -(i + 1)
            if x < max(rgf[:stop]) + 1:
                first_part = rgf[:stop]
                increased_part = [rgf[stop] + 1]
                trailing_ones = i * [1]
                rgf = first_part + increased_part + trailing_ones
                yield list(rgf)
                break


[docs] def outer_product(argument): """ Yields outer product of sequences in ``argument``. Returns list of lists. .. container:: example >>> sequences = [list([1, 2, 3]), list(["a", "b"])] >>> for sequence in abjad.enumerate.outer_product(sequences): ... sequence ... [1, 'a'] [1, 'b'] [2, 'a'] [2, 'b'] [3, 'a'] [3, 'b'] .. container:: example >>> sequences = [[1, 2, 3], ["a", "b"], ["X", "Y"]] >>> for sequence in abjad.enumerate.outer_product(sequences): ... sequence ... [1, 'a', 'X'] [1, 'a', 'Y'] [1, 'b', 'X'] [1, 'b', 'Y'] [2, 'a', 'X'] [2, 'a', 'Y'] [2, 'b', 'X'] [2, 'b', 'Y'] [3, 'a', 'X'] [3, 'a', 'Y'] [3, 'b', 'X'] [3, 'b', 'Y'] .. container:: example >>> sequences = [[1, 2, 3], [4, 5], [6, 7, 8]] >>> for sequence in abjad.enumerate.outer_product(sequences): ... sequence ... [1, 4, 6] [1, 4, 7] [1, 4, 8] [1, 5, 6] [1, 5, 7] [1, 5, 8] [2, 4, 6] [2, 4, 7] [2, 4, 8] [2, 5, 6] [2, 5, 7] [2, 5, 8] [3, 4, 6] [3, 4, 7] [3, 4, 8] [3, 5, 6] [3, 5, 7] [3, 5, 8] """ def _helper(sequence_1, sequence_2): result = [] for item_1 in sequence_1: for item_2 in sequence_2: result.extend([item_1 + [item_2]]) return result argument = list(argument) argument[0] = [[_] for _ in argument[0]] result = functools.reduce(_helper, argument) return result
[docs] def yield_combinations( argument, minimum_length=None, maximum_length=None ) -> typing.Iterator: """ Yields combinations of sequence items in binary string order. .. container:: example >>> sequence = list([1, 2, 3, 4]) >>> for combination in abjad.enumerate.yield_combinations(sequence): ... combination [] [1] [2] [1, 2] [3] [1, 3] [2, 3] [1, 2, 3] [4] [1, 4] [2, 4] [1, 2, 4] [3, 4] [1, 3, 4] [2, 3, 4] [1, 2, 3, 4] .. container:: example >>> sequence = list([1, 2, 3, 4]) >>> for combination in abjad.enumerate.yield_combinations( ... sequence, ... minimum_length=3, ... ): ... combination [1, 2, 3] [1, 2, 4] [1, 3, 4] [2, 3, 4] [1, 2, 3, 4] .. container:: example >>> sequence = list([1, 2, 3, 4]) >>> for combination in abjad.enumerate.yield_combinations( ... sequence, ... maximum_length=2, ... ): ... combination [] [1] [2] [1, 2] [3] [1, 3] [2, 3] [4] [1, 4] [2, 4] [3, 4] .. container:: example >>> sequence = list([1, 2, 3, 4]) >>> for combination in abjad.enumerate.yield_combinations( ... sequence, ... minimum_length=2, ... maximum_length=2, ... ): ... combination [1, 2] [1, 3] [2, 3] [1, 4] [2, 4] [3, 4] .. container:: example >>> sequence = list("text") >>> for combination in abjad.enumerate.yield_combinations(sequence): ... ''.join([str(_) for _ in combination]) '' 't' 'e' 'te' 'x' 'tx' 'ex' 'tex' 't' 'tt' 'et' 'tet' 'xt' 'txt' 'ext' 'text' """ length = len(argument) for i in range(2**length): binary_string = _math.integer_to_binary_string(i) binary_string = binary_string.zfill(length) sublist = [] for j, digit in enumerate(reversed(binary_string)): if digit == "1": sublist.append(argument[j]) yield_sublist = True if minimum_length is not None: if len(sublist) < minimum_length: yield_sublist = False if maximum_length is not None: if maximum_length < len(sublist): yield_sublist = False if yield_sublist: if isinstance(argument, str): yield "".join(sublist) else: yield type(argument)(sublist)
[docs] def yield_pairs(argument) -> typing.Iterator: """ Yields pairs sequence items. .. container:: example Without duplicate items: >>> for pair in abjad.enumerate.yield_pairs([1, 2, 3, 4]): ... pair ... [1, 2] [1, 3] [1, 4] [2, 3] [2, 4] [3, 4] .. container:: example With duplicate items: >>> for pair in abjad.enumerate.yield_pairs([1, 1, 1]): ... pair ... [1, 1] [1, 1] [1, 1] .. container:: example Length-1 sequence: >>> for pair in abjad.enumerate.yield_pairs([1]): ... pair ... .. container:: example Empty sequence: >>> for pair in abjad.enumerate.yield_pairs([]): ... pair ... """ for i, item in enumerate(argument): start = i + 1 for item_ in argument[start:]: pair = [item, item_] yield type(argument)(pair)
[docs] def yield_partitions(argument) -> typing.Iterator: """ Yields partitions of sequence. .. container:: example >>> for partition in abjad.enumerate.yield_partitions([0, 1, 2]): ... partition ... [[0, 1, 2]] [[0, 1], [2]] [[0], [1, 2]] [[0], [1], [2]] .. container:: example >>> for partition in abjad.enumerate.yield_partitions([0, 1, 2, 3]): ... partition ... [[0, 1, 2, 3]] [[0, 1, 2], [3]] [[0, 1], [2, 3]] [[0, 1], [2], [3]] [[0], [1, 2, 3]] [[0], [1, 2], [3]] [[0], [1], [2, 3]] [[0], [1], [2], [3]] """ length = len(argument) - 1 for i in range(2**length): binary_string = _math.integer_to_binary_string(i) binary_string = binary_string.zfill(length) part = list(argument[0:1]) partition = [part] for n, token in zip(argument[1:], binary_string): if int(token) == 0: part.append(n) else: part = [n] partition.append(part) parts = [type(argument)(_) for _ in partition] partition = type(argument)(parts) yield partition
[docs] def yield_permutations(argument) -> typing.Iterator: """ Yields permutations of sequence. .. container:: example >>> for permutation in abjad.enumerate.yield_permutations([1, 2, 3]): ... permutation ... [1, 2, 3] [1, 3, 2] [2, 1, 3] [2, 3, 1] [3, 1, 2] [3, 2, 1] """ _argument = list(argument) length = len(_argument) for permutation in itertools.permutations(tuple(range(length))): permutation = type(argument)(permutation) yield _sequence.permute(argument, permutation)
[docs] def yield_set_partitions(sequence) -> typing.Iterator[list[list]]: """ Yields set partitions of ``sequence`` in order of restricted growth function. .. container:: example >>> for set_partition in abjad.enumerate.yield_set_partitions([21, 22, 23, 24]): ... set_partition ... [[21, 22, 23, 24]] [[21, 22, 23], [24]] [[21, 22, 24], [23]] [[21, 22], [23, 24]] [[21, 22], [23], [24]] [[21, 23, 24], [22]] [[21, 23], [22, 24]] [[21, 23], [22], [24]] [[21, 24], [22, 23]] [[21], [22, 23, 24]] [[21], [22, 23], [24]] [[21, 24], [22], [23]] [[21], [22, 24], [23]] [[21], [22], [23, 24]] [[21], [22], [23], [24]] """ _sequence = list(sequence) length = len(_sequence) for rgf in _yield_restricted_growth_functions(length): partition = _partition_by_rgf(_sequence, rgf) yield partition
[docs] def yield_subsequences( sequence, minimum_length=0, maximum_length=None ) -> typing.Iterator: """ Yields subsequences of ``sequence``. .. container:: example >>> for subsequence in abjad.enumerate.yield_subsequences([0, 1, 2]): ... subsequence ... [] [0] [0, 1] [0, 1, 2] [1] [1, 2] [2] .. container:: example >>> for subsequence in abjad.enumerate.yield_subsequences( ... [0, 1, 2, 3, 4], ... minimum_length=3, ... ): ... subsequence ... [0, 1, 2] [0, 1, 2, 3] [0, 1, 2, 3, 4] [1, 2, 3] [1, 2, 3, 4] [2, 3, 4] .. container:: example >>> for subsequence in abjad.enumerate.yield_subsequences( ... [0, 1, 2, 3, 4], ... maximum_length=3, ... ): ... subsequence ... [] [0] [0, 1] [0, 1, 2] [1] [1, 2] [1, 2, 3] [2] [2, 3] [2, 3, 4] [3] [3, 4] [4] .. container:: example >>> for subsequence in abjad.enumerate.yield_subsequences( ... [0, 1, 2, 3, 4], ... minimum_length=3, ... maximum_length=3, ... ): ... subsequence ... [0, 1, 2] [1, 2, 3] [2, 3, 4] """ _sequence = sequence length = len(_sequence) if maximum_length is None: maximum_length = length for i in range(length): start_j = minimum_length + i stop_j = min(maximum_length + i, length) + 1 for j in range(start_j, stop_j): if i < j or i == 0: subsequence = _sequence[i:j] yield subsequence