首页 > 解决方案 > How to build a strategy to create array of tuples with pairs of identical values?

问题描述

I'd like to generate a strategy for NumPy testing with an output like:

array([[-2, -2],
       [-3, -3],
       [5,  5],
       [-1, -1]], dtype=int16)

What I tried was:

import numpy as np
from hypothesis.strategies import integers
from hypothesis.extra.numpy import arrays
arrays(np.int16, (4,2), elements=integers(-10, 10)).example()

Unfortunately, I can't make values inside tuples identical so the query above returns:

array([[ 5,  5],
       [-7,  5],
       [ 5,  5],
       [ 5,  5]], dtype=int16)

标签: pythonpython-hypothesis

解决方案


I have found that if I need to control content within an existing strategy's structure (e.g. pairs of identical values within an array) I need to skip that strategy for lower level ones with which I can build a "ready-made" value that can seed the type I care to generate.

Let's leverage that numpy.array accepts a list of lists to create an array. Let's also assume you want each row to be unique, as your example does not show duplicate rows. If that is not desired, remove the unique_by=str from the depth_strategy definition

  1. Generate an integer and create a list of that value repeated a number of times to meet the WIDTH.
  2. Generate a list of a DEPTH length of the kind of lists we created in the first step.
  3. Combine the two strategies by nesting them.
  4. Feed the result of the third step into numpy.array, making sure the dtype matches the strategy used to generate values in the first step.
# %%
"""Hypothesis strategy for array of tuples with pairs of identical values."""
from hypothesis import given, settings, strategies as st

import numpy as np

WIDTH = 2
DEPTH = 4
MIN_VALUE = -10
MAX_VALUE = 10

# Build the row - Here for clarification only
width_strategy = st.integers(MIN_VALUE, MAX_VALUE).map(
    lambda i: tuple(i for _ in range(WIDTH))
)

# Build the array of rows - Here for clarification only
depth_strategy = st.lists(
    width_strategy, min_size=DEPTH, max_size=DEPTH, unique_by=str
).map(lambda lot: np.array(lot, dtype=np.int64))

# All-in-One
complete_strategy = st.lists(
    st.integers(MIN_VALUE, MAX_VALUE).map(
        lambda i: tuple(i for _ in range(WIDTH))
    ),
    min_size=DEPTH,
    max_size=DEPTH,
    unique_by=str,
).map(lambda lot: np.array(lot, dtype=np.int64))


@settings(max_examples=10)
@given(an_array=complete_strategy)
def create_numpy_array(an_array):
    """Turn list of lists into numpy array."""
    print(f"A numpy array could be:\n{an_array}")


create_numpy_array()

This generates something like:

A numpy array could be:
[[ 3  3]
 [ 9  9]
 [-5 -5]
 [ 0  0]]
A numpy array could be:
[[ 3  3]
 [-2 -2]
 [ 4  4]
 [-5 -5]]
A numpy array could be:
[[ 7  7]
 [ 0  0]
 [-2 -2]
 [-1 -1]]

Note that I set the max_examples to 10 as Hypothesis gives a higher occurrences ratio to values it deems "troublesome", such as zero, NaN, Infinity and such. So example() or a lower number of examples would probably generate a lot of 2x4 arrays of all zeroes. Fortunately the unique_by constraint helps us here.


推荐阅读