11

物語:

現在、次のルールを持つ整数のリストのリストを期待するテスト対象の関数があります。

  1. サブリストの数 ( と呼びましょうN) は 1 から 50 までです
  2. サブリスト内の値の数は、すべてのサブリストで同じ (長方形の形式) であり、>= 0 および <= 5 である必要があります
  3. サブリスト内の値は、サブリストの総数以上にすることはできません。つまり、サブリスト内の各値は整数 >= 0 および <N

有効な入力の例:

[[0]]
[[2, 1], [2, 0], [3, 1], [1, 0]]
[[1], [0]]

無効な入力の例:

[[2]]  # 2 is more than N=1 (total number of sublists)
[[0, 1], [2, 0]]  # 2 is equal to N=2 (total number of sublists)

私はプロパティベースのテストでそれにアプローチし、hypothesisライブラリでさまざまな有効な入力を生成しようとしていて、頭をラップしようとしてlists()integers()ますが、機能させることはできません:

  • 条件 #1 はlists()andmin_sizeおよびmax_size引数を使用して簡単にアプローチできます
  • 条件 #2 は以下でカバーされていますChaining strategies together
  • 条件#3は私が苦労しているものです-rectangle_lists上記の例を使用すると、内部の「親」リストの長さへの参照がありませんintegers()

質問:

サブリスト内の整数値をサブリストの総数よりも少なくするにはどうすればよいですか?


私の試みのいくつか:

from hypothesis import given
from hypothesis.strategies import lists, integers

@given(lists(lists(integers(min_value=0, max_value=5), min_size=1, max_size=5), min_size=1, max_size=50))
def test(l):
    # ...

これは要件を満たすにはほど遠いものでした。リストは厳密には長方形ではなく、生成された整数値は生成されたリストのサイズを超える可能性があります。

from hypothesis import given
from hypothesis.strategies import lists, integers

@given(integers(min_value=0, max_value=5).flatmap(lambda n: lists(lists(integers(min_value=1, max_value=5), min_size=n, max_size=n), min_size=1, max_size=50)))
def test(l):
    # ...

ここで、要件 #1 と #2 は満たされていましたが、整数値はリストのサイズよりも大きくなる可能性があります。要件 #3 は満たされていません。

4

3 に答える 3

11

このようなトリッキーな制約を解決しようとするときに役立つことが多い、優れた一般的な手法があります。必要なものに少し似ているが、すべての制約を満たさないものを作成してから、それを変更する関数で構成してみてください (例:不良ビットを破棄するか、完全に機能しないビットにパッチを当てることによって) 制約を満たすようにします。

あなたの場合、次のようなことができます:

from hypothesis.strategies import builds, lists, integers

def prune_list(ls):
    n = len(ls)
    return [
       [i for i in sublist if i < n][:5]
       for sublist in ls
    ]

limited_list_strategy = builds(
   prune_list,
   lists(lists(integers(0, 49), average_size=5), max_size=50, min_size=1)
)

この中で私たちは:

  1. おおよそ正しいように見えるリストを生成します (これは整数のリストのリストであり、整数は有効な可能性のあるすべてのインデックスと同じ範囲にあります)。
  2. サブリストから無効なインデックスを取り除く
  3. まだ 5 つ以上の要素が含まれているサブリストを切り捨てます

結果は、必要な 3 つの条件をすべて満たす必要があります。

average_size パラメーターは厳密には必要ではありませんが、これを試してみると、空のサブリストが生成される傾向が少し強すぎることがわかりました。

ETA: 申し訳ありません。私はあなたの条件の 1 つを読み違えていたことに気づきました。これは、各リストが同じ長さであることを保証しないため、実際にはあなたが望んでいることにはなりません。これを修正するためにこれを変更する方法を次に示します (少し複雑になるため、ビルドの代わりにコンポジットを使用するように切り替えました)。

from hypothesis.strategies import composite, lists, integers, permutations


@composite
def limisted_lists(draw):
    ls = draw(
        lists(lists(integers(0, 49), average_size=5), max_size=50, min_size=1)
    )
    filler = draw(permutations(range(50)))
    sublist_length = draw(integers(0, 5))

    n = len(ls)
    pruned = [
       [i for i in sublist if i < n][:sublist_length]
       for sublist in ls
    ]

    for sublist in pruned:
        for i in filler:
            if len(sublist) == sublist_length:
               break
            elif i < n:
               sublist.append(i)
    return pruned

アイデアは、サブリストがどのように見えるかのデフォルトを提供する「フィラー」リストを生成し(したがって、互いに類似する方向に縮小する傾向があります)、プルーニングするサブリストの長さを描画します。その一貫性を取得します。

これはかなり複雑になりました。RecursivelyIronic の flatmap ベースのバージョンを使用することをお勧めします。私がこれよりもこれを好む主な理由は、よりよく縮小する傾向があるため、より良い例が得られるからです.

于 2016-09-20T12:16:15.390 に答える
7

flatmap少しゆがみますが、これを で行うこともできます。

from hypothesis import strategies as st
from hypothesis import given, settings

number_of_lists = st.integers(min_value=1, max_value=50)
list_lengths = st.integers(min_value=0, max_value=5)

def build_strategy(number_and_length):
    number, length = number_and_length
    list_elements = st.integers(min_value=0, max_value=number - 1)
    return st.lists(
        st.lists(list_elements, min_size=length, max_size=length),
        min_size=number, max_size=number)

mystrategy = st.tuples(number_of_lists, list_lengths).flatmap(build_strategy)

@settings(max_examples=5000)
@given(mystrategy)
def test_constraints(list_of_lists):
    N = len(list_of_lists)

    # condition 1
    assert 1 <= N <= 50

    # Condition 2
    [length] = set(map(len, list_of_lists))
    assert 0 <= length <= 5

    # Condition 3
    assert all((0 <= element < N) for lst in list_of_lists for element in lst)

David が述べたように、これは多くの空のリストを生成する傾向があるため、平均サイズの調整が必要になります。

>>> mystrategy.example()
[[24, 6, 4, 19], [26, 9, 15, 15], [1, 2, 25, 4], [12, 8, 18, 19], [12, 15, 2, 31], [3, 8, 17, 2], [5, 1, 1, 5], [7, 1, 16, 8], [9, 9, 6, 4], [22, 24, 28, 16], [18, 11, 20, 21], [16, 23, 30, 5], [13, 1, 16, 16], [24, 23, 16, 32], [13, 30, 10, 1], [7, 5, 14, 31], [31, 15, 23, 18], [3, 0, 13, 9], [32, 26, 22, 23], [4, 11, 20, 10], [6, 15, 32, 22], [32, 19, 1, 31], [20, 28, 4, 21], [18, 29, 0, 8], [6, 9, 24, 3], [20, 17, 31, 8], [6, 12, 8, 22], [32, 22, 9, 4], [16, 27, 29, 9], [21, 15, 30, 5], [19, 10, 20, 21], [31, 13, 0, 21], [16, 9, 8, 29]]
>>> mystrategy.example()
[[28, 18], [17, 25], [26, 27], [20, 6], [15, 10], [1, 21], [23, 15], [7, 5], [9, 3], [8, 3], [3, 4], [19, 29], [18, 11], [6, 6], [8, 19], [14, 7], [25, 3], [26, 11], [24, 20], [22, 2], [19, 12], [19, 27], [13, 20], [16, 5], [6, 2], [4, 18], [10, 2], [26, 16], [24, 24], [11, 26]]
>>> mystrategy.example()
[[], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], []]
>>> mystrategy.example()
[[], [], [], [], [], [], [], [], [], [], [], [], [], [], []]
>>> mystrategy.example()
[[6, 8, 22, 21, 22], [3, 0, 24, 5, 18], [16, 17, 25, 16, 11], [2, 12, 0, 3, 15], [0, 12, 12, 12, 14], [11, 20, 6, 6, 23], [5, 19, 2, 0, 12], [16, 0, 1, 24, 10], [2, 13, 21, 19, 15], [2, 14, 27, 6, 7], [22, 25, 18, 24, 9], [26, 21, 15, 18, 17], [7, 11, 22, 17, 21], [3, 11, 3, 20, 16], [22, 13, 18, 21, 11], [4, 27, 21, 20, 25], [4, 1, 13, 5, 13], [16, 19, 6, 6, 25], [19, 10, 14, 12, 14], [18, 13, 13, 16, 3], [12, 7, 26, 26, 12], [25, 21, 12, 23, 22], [11, 4, 24, 5, 27], [25, 10, 10, 26, 27], [8, 25, 20, 6, 23], [8, 0, 12, 26, 14], [7, 11, 6, 27, 26], [6, 24, 22, 23, 19]]
于 2016-09-21T01:19:48.143 に答える