115 lines
3.7 KiB
Python
115 lines
3.7 KiB
Python
################ solver
|
|
from itertools import chain, combinations
|
|
from functools import reduce
|
|
|
|
|
|
def powerset(iterable):
|
|
"powerset([1,2,3]) --> () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)"
|
|
s = list(iterable)
|
|
return chain.from_iterable(combinations(s, r) for r in range(len(s) + 1))
|
|
|
|
|
|
def check_solution(selection, operator):
|
|
return reduce(
|
|
lambda a, b: tuple(operator(x, y) for x, y in zip(a, b)), selection
|
|
)
|
|
|
|
|
|
def tuple_selector(tuples, operator):
|
|
solution_count = 0
|
|
for selection in powerset(tuples):
|
|
if not selection:
|
|
continue
|
|
if all(check_solution(selection, operator)):
|
|
print(f"found a solution! {selection}")
|
|
solution_count += 1
|
|
print(f"{solution_count=}")
|
|
|
|
|
|
################# problem statement
|
|
|
|
|
|
def operator_with_secret(a: int, b: int) -> int:
|
|
either = {a, b}
|
|
if 1 in either:
|
|
return a + b % 2
|
|
# if 0 in either:
|
|
# return 0
|
|
|
|
# final element that must be included
|
|
if 2 in either:
|
|
# The x's represent free choices.
|
|
# 0bxxxxxxx00
|
|
secret = 0b010110100
|
|
return 3 if secret in either else 0
|
|
|
|
# build your own number kit
|
|
return a | b
|
|
|
|
|
|
tuple_selector(
|
|
[
|
|
# make a choice to set a bit in the first number
|
|
(0b000000100, 0, 1, 0, 0, 0, 0, 0, 0),
|
|
(0b000001000, 0, 0, 1, 0, 0, 0, 0, 0),
|
|
(0b000010000, 0, 0, 0, 1, 0, 0, 0, 0),
|
|
(0b000100000, 0, 0, 0, 0, 1, 0, 0, 0),
|
|
(0b001000000, 0, 0, 0, 0, 0, 1, 0, 0),
|
|
(0b010000000, 0, 0, 0, 0, 0, 0, 1, 0),
|
|
(0b100000000, 0, 0, 0, 0, 0, 0, 0, 1),
|
|
# .. or not
|
|
(0b000000000, 0, 1, 0, 0, 0, 0, 0, 0),
|
|
(0b000000000, 0, 0, 1, 0, 0, 0, 0, 0),
|
|
(0b000000000, 0, 0, 0, 1, 0, 0, 0, 0),
|
|
(0b000000000, 0, 0, 0, 0, 1, 0, 0, 0),
|
|
(0b000000000, 0, 0, 0, 0, 0, 1, 0, 0),
|
|
(0b000000000, 0, 0, 0, 0, 0, 0, 1, 0),
|
|
(0b000000000, 0, 0, 0, 0, 0, 0, 0, 1),
|
|
# this final element that must be included
|
|
(0b000000010, 1, 0, 0, 0, 0, 0, 0, 0),
|
|
],
|
|
operator_with_secret,
|
|
)
|
|
|
|
|
|
# Commutative operator variant
|
|
def operator_with_secret(a: int, b: int) -> int:
|
|
|
|
# We're going to create a bitpacked representation of the state of the
|
|
# computation. The state is the number of records included, and the value
|
|
# is a bitwise OR of the records included. The state is stored in the
|
|
# first slot of each tuple, but this operator doesn't know which slot is
|
|
# first, so we just check to see if the value is greater than 1.
|
|
value_bits = 9
|
|
total_records_to_select = 8
|
|
|
|
def extract(bitpacked: int) -> Tuple[int, int]:
|
|
count = bitpacked >> value_bits
|
|
value = bitpacked & ((1 << value_bits) - 1)
|
|
return (count if count else 1), value
|
|
|
|
def pack(count: int, value: int) -> int:
|
|
return (count << value_bits) | value
|
|
|
|
either = {a, b}
|
|
# If we're building our state in the first slot, handle it
|
|
if any(x > 1 for x in either):
|
|
# Unpack the state from each input
|
|
# State is just how many records have been included so far, and the
|
|
# value which has been built
|
|
ac, av = extract(a)
|
|
bc, bv = extract(b)
|
|
count, value = ac + bc, av | bv
|
|
# If we haven't inluded eight records yet, just build up the number
|
|
if count < total_records_to_select:
|
|
return pack(count, value)
|
|
# If we've included eight records, we're done and need to check the
|
|
# value is what we wanted
|
|
# The x's represent free choices.
|
|
# 0bxxxxxxx00
|
|
secret = 0b010110100
|
|
return 3 if value == secret else 0
|
|
|
|
# Otherwise, we're just checking that we select at least one truthy value
|
|
return a | b
|