1

I want to generate a list of lists of integers of size 2 with the following conditions.

  • the first element should be smaller than the second and
  • all the data should be unique.

I could generate each tuple with a custom function but don't know how to use that to satisfy the second condition.

from hypothesis import strategies as st

@st.composite
def generate_data(draw):
    min_val, max_val = draw(st.lists(st.integers(1, 1e2), min_size=2, max_size=2))
    st.assume(min_val < max_val)
    return [min_val, max_val]

I could generate the data by iterating over generate_date a few times in this (inefficient ?) way:

>>> [generate_data().example() for _ in range(3)]
    [[5, 31], [1, 12], [33, 87]]

But how can I check that the data is unique?

E.g, the following values are invalid:

[[1, 2], [1, 5], ...]  # (1 is repeated)
[[1, 2], [1, 2], ...]  # (repeated data)

but the following is valid:

[[1, 2], [3, 4], ...]
Kevad
  • 2,781
  • 2
  • 18
  • 28

2 Answers2

3

I think the following strategy satisfies your requirements:

import hypothesis.strategies as st

@st.composite
def unique_pair_lists(draw):
    data = draw(st.lists(st.integers(), unique=True)
    if len(data) % 2 != 0:
        data.pop()
    result = [data[i:i+2] for i in range(0, len(data), 2)]
    for pair in result:
        pair.sort()
    return result

The idea here is that we generate something that gives the right elements, and then we transform it into something of the right shape. Rather than trying to generate pairs of lists of integers, we just generate a list of unique integers and then group them into pairs (we drop the last element if there's an odd number of integers). We then sort each pair to ensure it's in the right order.

DRMacIver
  • 2,259
  • 1
  • 17
  • 17
2

David's solution permits an integer to appear in two sub-lists - for totally unique integers I'd use the following:

@st.composite
def list_of_pairs_of_unique_elements(draw):
    seen = set()
    new_int = st.integers(1, 1e2)\
        .filter(lambda n: n not in seen)\  # Check that it's unique
        .map(lambda n: seen.add(n) or n)   # Add to filter before next draw
    return draw(st.lists(st.tuples(new_int, new_int).map(sorted))
  • The .filter(...) method is probably what you're looking for.
  • .example() is only for interactive use - you'll get a warning (or error) if you use it in @given().
  • If you might end up filtering out most elements in the range (eg outer list of length > 30, meaning 60/100 possible unique elements), you might get better performance by creating a list of possible elements and popping out of it rather than rejecting seen elements.
Zac Hatfield-Dodds
  • 2,455
  • 6
  • 19