Одна из причин, по которой двоичные структуры так полезны, заключается в том, что их очень легко хранить в памяти и индексировать с помощью всего лишь нескольких математических приемов.
Если все ваши условия известны заранее, очень легко рассмотреть ваш список ['pass', 'fail', 'pass', 'fail' etc..]
как двоичное число ([1, 0, 1, 0]: 10
), которое можно использовать в качестве индекса в списке, предварительно выделенном для длины от всех возможных результатов (2 N , где N - количество условий).
Если вам нужно хранить значения на каждом этапе принятия решения (т.е. сохранять значение для ['pass','fail']
, а также ['pass', 'fail', 'fail']
), это немного сложнее, но все же не так сложно. Мы знаем, что любое количество условий дает 2 N возможных результатов, поэтому мы также можем знать, сколько результатов дано при N-1 условиях, N-2 и т. Д. Всего имеется 2 N -1 условий всех более коротких списков условий перед условиями 2 N , которые относятся к N условиям. Добавляя наше двоичное число, как мы его нашли ранее, к 2 N -1, мы можем получить уникальный индекс для каждого возможного списка условий.
Если у вас длинные списки условий, довольно легко увидеть, что список возможных результатов растет в геометрической прогрессии. Если вы никогда не планируете посещать все возможные результаты, может быть полезно использовать словарь с числовыми ключами, а не список для хранения всех возможных результатов.
с учетом вашего примера:
<root> (0)
/ \
<one_pass> (1) <one_fail> (2)
/ \ / \
<two_pass>(3) <two_fail>(4) <two_pass>(5) <two_fail>(6)
/ \ / \ / \ / \
(7) (8) (9) (10) (11) (12) (13) (14)
/ \ / \ / \ / \ / \ / \ / \ / \
15, 16, 17, 18| 19, 20, 21, 22| 23, 24, 25, 26| 27, 28, 29, 30
отсюда легко придумать некоторые функции для преобразования списка ['pass', 'fail', 'pass', 'fail']
в индекс:
def list_to_number(L):
acc = 0
for item in L:
acc *= 2 #shift number left as we shift in values from the right
if item == 'fail': #assumes pass == 0 fail == 1
acc += 1
return acc
def idx(L):
return 2**len(L) - 1 + list_to_number(L)
Полный пример:
# Assume we will never have more than 4 events:
# With 4 events we have 2**4 outcomes.
# In total there are 2**5-1 possible outcomes including intermediate steps.
# We will preallocate a list filled with `None` to length 2**5-1 so we have
# enough space for all possible outcomes.
tree_data = [None]*(2**5-1)
#insert value 42 given [event_1_pass, event_2_pass, event_3_fail]
tree_data[idx(['pass', 'pass', 'fail'])] = 42
#insert value 56 given [event_1_pass, event_2_pass, event_3_pass, event_4_pass]
tree_data[idx(['pass', 'pass', 'pass', 'pass'])] = 56
#retrieve value given [event_1_pass, event_2_pass, event_3_fail]
print(tree_data[idx(['pass', 'pass', 'fail'])])
# prints: 42
#retrieve value given [event_1_fail, event_2_pass, event_3_fail]
print(tree_data[idx(['fail', 'pass', 'fail'])])
# prints: None because we never stored anything there.