Python 3, оценка = 4/3 = 1,33… (N = 4) оценка = 1,4 (N = 7)
Обновление: реализован поиск методом "грубой силы" в наборе "статических" решателей и получен новый результат
Я думаю, что это может быть улучшено путем поиска динамических решателей, которые могут использовать результаты взвешивания для дальнейших решений.
Вот код Python, который ищет во всех статических решателях небольшие n
значения (эти решатели всегда взвешивают одни и те же наборы монет, отсюда и «статическое» имя) и определяет количество шагов в наихудшем случае, просто проверяя, что результаты их измерений допускают только одну совпадающую монету. установить во всех случаях. Кроме того, он отслеживает лучшие результаты, найденные до сих пор, и ранние решатели чернослива, которые показали, что они определенно хуже, чем те, которые были найдены ранее. Это была важная оптимизация, иначе я не мог дождаться этого результата с n
= 7. (Но он все еще не очень хорошо оптимизирован)
Не стесняйтесь задавать вопросы, если не ясно, как это работает ...
#!/usr/bin/env python3
import itertools
from functools import partial
def get_all_possible_coinsets(n):
return tuple(itertools.product(*itertools.repeat((-1, 1), n)))
def weigh(coinset, indexes_to_weigh):
return sum(coinset[x] for x in indexes_to_weigh)
# made_measurements: [(indexes, weight)]
def filter_by_measurements(coinsets, made_measurements):
return filter(lambda cs: all(w == weigh(cs, indexes) for indexes, w in made_measurements), coinsets)
class Position(object):
def __init__(self, all_coinsets, coinset, made_measurements=()):
self.all_coinsets = all_coinsets
self.made_measurements = made_measurements
self.coins = coinset
def possible_coinsets(self):
return tuple(filter_by_measurements(self.all_coinsets, self.made_measurements))
def is_final(self):
possible_coinsets = self.possible_coinsets()
return (len(possible_coinsets) == 1) and possible_coinsets[0] == self.coins
def move(self, measurement_indexes):
measure_result = (measurement_indexes, weigh(self.coins, measurement_indexes))
return Position(self.all_coinsets, self.coins, self.made_measurements + (measure_result,))
def get_all_start_positions(coinsets):
for cs in coinsets:
yield Position(coinsets, cs)
def average(xs):
return sum(xs) / len(xs)
class StaticSolver(object):
def __init__(self, measurements):
self.measurements = measurements
def choose_move(self, position: Position):
index = len(position.made_measurements)
return self.measurements[index]
def __str__(self, *args, **kwargs):
return 'StaticSolver({})'.format(', '.join(map(lambda x: '{' + ','.join(map(str, x)) + '}', self.measurements)))
def __repr__(self):
return str(self)
class FailedSolver(Exception):
pass
def test_solvers(solvers, start_positions, max_steps):
for solver in solvers:
try:
test_results = tuple(map(partial(test_solver, solver=solver, max_steps=max_steps), start_positions))
yield (solver, max(test_results))
except FailedSolver:
continue
def all_measurement_starts(n):
for i in range(1, n + 1):
yield from itertools.combinations(range(n), i)
def next_measurement(n, measurement, include_zero):
shifted = filter(lambda x: x < n, map(lambda x: x + 1, measurement))
if include_zero:
return tuple(itertools.chain((0,), shifted))
else:
return tuple(shifted)
def make_measurement_sequence(n, start, zero_decisions):
yield start
m = start
for zero_decision in zero_decisions:
m = next_measurement(n, m, zero_decision)
yield m
def measurement_sequences_from_start(n, start, max_steps):
continuations = itertools.product(*itertools.repeat((True, False), max_steps - 1))
for c in continuations:
yield tuple(make_measurement_sequence(n, start, c))
def all_measurement_sequences(n, max_steps):
starts = all_measurement_starts(n)
for start in starts:
yield from measurement_sequences_from_start(n, start, max_steps)
def all_static_solvers(n, max_steps):
return map(StaticSolver, all_measurement_sequences(n, max_steps))
def main():
best_score = 1.0
for n in range(1, 11):
print('Searching with N = {}:'.format(n))
coinsets = get_all_possible_coinsets(n)
start_positions = tuple(get_all_start_positions(coinsets))
# we are not interested in solvers with worst case number of steps bigger than this
max_steps = int(n / best_score)
solvers = all_static_solvers(n, max_steps)
succeeded_solvers = test_solvers(solvers, start_positions, max_steps)
try:
best = min(succeeded_solvers, key=lambda x: x[1])
except ValueError: # no successful solvers
continue
score = n / best[1]
best_score = max(score, best_score)
print('{}, score = {}/{} = {}'.format(best, n, best[1], score))
print('That\'s all!')
def test_solver(start_position: Position, solver, max_steps):
p = start_position
steps = 0
try:
while not p.is_final():
steps += 1
if steps > max_steps:
raise FailedSolver
p = p.move(solver.choose_move(p))
return steps
except IndexError: # solution was not found after given steps — this solver failed to beat score 1
raise FailedSolver
if __name__ == '__main__':
main()
Выход:
Searching with N = 1:
(StaticSolver({0}), 1), score = 1/1 = 1.0
Searching with N = 2:
(StaticSolver({0}, {0,1}), 2), score = 2/2 = 1.0
Searching with N = 3:
(StaticSolver({0}, {0,1}, {0,1,2}), 3), score = 3/3 = 1.0
Searching with N = 4:
(StaticSolver({0,1}, {1,2}, {0,2,3}, {0,1,3}), 3), score = 4/3 = 1.3333333333333333
Searching with N = 5:
Searching with N = 6:
Searching with N = 7:
(StaticSolver({0,2}, {0,1,3}, {0,1,2,4}, {1,2,3,5}, {0,2,3,4,6}), 5), score = 7/5 = 1.4
Searching with N = 8:
Searching with N = 9:
(I gave up waiting at this moment)
Эта линия
(StaticSolver({0,2}, {0,1,3}, {0,1,2,4}, {1,2,3,5}, {0,2,3,4,6}), 5), score = 7/5 = 1.4
раскрывает лучший найденный решатель. Цифры в {}
скобках - это индексы монет, которые нужно надевать на весы на каждом шаге.