Постройте пару шпионов, которые будут бросать камни в реку

20

Недавно на недавно выпущенном Puzzling.SE была проблема с шпионами, бросающими камни в реку, что было довольно сложно:

Два шпиона должны передать друг другу два секретных номера (по одному на шпиона), незаметно для их врагов. Они заранее договорились о методе для этого, используя только 26 неразличимых камней.

Они встречаются у реки, где есть груда из 26 камней. Начиная с первого шпиона, они по очереди бросают в реку группу камней: первый шпион бросает некоторое количество камней, затем второй, затем снова первый ...

Каждый шпион должен бросить хотя бы один камень на своем ходу, пока все камни не исчезнут.

Они наблюдают все броски и расходятся, когда камней больше нет. Они все время молчат, и никакой информации не обменивается, кроме количества камней, брошенных за каждый ход.

Как они могут успешно обменять числа, если числа могут быть от 1 до M?

Ваша задача состоит в том, чтобы построить пару программ, spy1и spy2, которые могут решить эту проблему максимально возможным M.

Каждая из ваших программ будет принимать число от 1выбранного вами в Mкачестве входных данных. Затем spy1будет выведено число, представляющее количество камней, которые он выбрасывает в реку, которое будет входить в spy2которое также будет выводить число для ввода spy1, и так далее, пока количество выводимых сумм не сложится 26. В конце броска каждая программа выведет число, которое, как она считает, имела другая программа, которое должно совпадать с числом, которое фактически было введено другой программе.

Ваша программа должна работать для всех возможных упорядоченных пар чисел, (i, j)где оба iи jмогут варьироваться от 1до M.

Программа, которая работает для крупнейшего, Mстанет победителем, а первый ответ будет опубликован с разрывом связи. Кроме того, я присуждаю +100 репутации за первое решение, на которое доказано, что оно работает M >= 2286, и +300 за первое решение, на которое доказано, что оно работает M >= 2535.

Джо З.
источник
Решение означает алгоритм или программу, которая генерирует множество решений для каждого (i, j)?
klm123
Не одна программа, а две. Они должны общаться самостоятельно, как в вашей проблеме.
Джо З.
3
Поскольку программы должны будут делиться своим деревом решений, можем ли мы сделать из него одну программу, которая принимает аргумент, чтобы сказать, какой это шпион?
Питер Тейлор
Пока вы можете гарантировать, что каждый шпион общается независимо и между ними нет никакой дополнительной информации.
Джо З.
Я независимо проверил, что 2535 является информационно-теоретическим максимумом для этой проблемы. Сейчас я твердо верю, что ни одна программа не может быть лучше.
nneonneo

Ответы:

8

C #, M = 2535

Это реализует * систему, которую я математически описал в теме, которая спровоцировала этот конкурс. Я требую 300 повторений бонуса. Программа самопроверяется, если вы запускаете ее без аргументов командной строки или с аргументом --testкомандной строки; для шпиона 1 - с --spy1, а для шпиона 2 - с --spy2. В каждом случае он принимает число, которое я должен сообщить от stdin, а затем выполняет броски через stdin и stdout.

* На самом деле, я нашел оптимизацию, которая имеет огромное значение (от нескольких минут до генерации дерева решений до менее секунды); дерево, которое оно генерирует, в основном то же самое, но я все еще работаю над доказательством этого. Если вам нужна прямая реализация системы, которую я описал в другом месте, см. Редакцию 2 , хотя вы можете сделать бэкпорт для дополнительной регистрации Mainи лучшего обмена между потоками TestSpyIO.

Если вам нужен тестовый пример, который завершается менее чем за минуту, измените Nна 16и Mна 87.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

namespace CodeGolf
{
    internal class Puzzle625
    {
        public static void Main(string[] args)
        {
            const int N = 26;
            const int M = 2535;

            var root = BuildDecisionTree(N);

            if (args.Length == 0 || args[0] == "--test")
            {
                DateTime startUtc = DateTime.UtcNow;
                Console.WriteLine("Built decision tree in {0}", DateTime.UtcNow - startUtc);
                startUtc = DateTime.UtcNow;

                int ok = 0;
                int fail = 0;
                for (int i = 1; i <= M; i++)
                {
                    for (int j = 1; j <= M; j++)
                    {
                        if (Test(i, j, root)) ok++;
                        else fail++;
                    }
                    double projectedTimeMillis = (DateTime.UtcNow - startUtc).TotalMilliseconds * M / i;
                    Console.WriteLine("Interim result: ok = {0}, fail = {1}, projected test time {2}", ok, fail, TimeSpan.FromMilliseconds(projectedTimeMillis));
                }
                Console.WriteLine("All tested: ok = {0}, fail = {1}, in {2}", ok, fail, DateTime.UtcNow - startUtc);
                Console.ReadKey();
            }
            else if (args[0] == "--spy1")
            {
                new Spy(new ConsoleIO(), root, true).Run();
            }
            else if (args[0] == "--spy2")
            {
                new Spy(new ConsoleIO(), root, false).Run();
            }
            else
            {
                Console.WriteLine("Usage: Puzzle625.exe [--test|--spy1|--spy2]");
            }
        }

        private static bool Test(int i, int j, Node root)
        {
            TestSpyIO io1 = new TestSpyIO("Spy 1");
            TestSpyIO io2 = new TestSpyIO("Spy 2");
            io1.Partner = io2;
            io2.Partner = io1;

            // HACK! Prime the input
            io2.Output(i);
            io1.Output(j);

            Spy spy1 = new Spy(io1, root, true);
            Spy spy2 = new Spy(io2, root, false);

            Thread th1 = new Thread(spy1.Run);
            Thread th2 = new Thread(spy2.Run);
            th1.Start();
            th2.Start();

            th1.Join();
            th2.Join();

            // Check buffer contents. Spy 2 should output spy 1's value, so it's lurking in io1, and vice versa.
            return io1.Input() == i && io2.Input() == j;
        }

        private static Node BuildDecisionTree(int numStones)
        {
            NodeValue[] trees = new NodeValue[] { NodeValue.Trivial };
            for (int k = 2; k <= numStones; k++)
            {
                int[] prev = trees.Select(nv => nv.Y).ToArray();
                List<int> row = new List<int>(prev);
                int cap = prev.Length;
                for (int i = 1; i <= prev[0]; i++)
                {
                    while (prev[cap - 1] < i) cap--;
                    row.Add(cap);
                }

                int[] next = row.OrderByDescending(x => x).ToArray();
                NodeValue[] nextTrees = new NodeValue[next.Length];
                nextTrees[0] = trees.Last().Reverse();
                for (int i = 1; i < next.Length; i++)
                {
                    int cp = next[i] - 1;
                    nextTrees[i] = trees[cp].Combine(trees[i - prev[cp]]);
                }

                trees = nextTrees;
            }

            NodeValue best = trees.MaxElement(v => Math.Min(v.X, v.Y));
            return BuildDecisionTree(numStones, best, new Dictionary<Pair<int, NodeValue>, Node>());
        }

        private static Node BuildDecisionTree(int numStones, NodeValue val, IDictionary<Pair<int, NodeValue>, Node> cache)
        {
            // Base cases
            // NB We might get passed val null with 0 stones, so we hack around that
            if (numStones == 0) return new Node(NodeValue.Trivial, new Node[0]);

            // Cache
            Pair<int, NodeValue> key = new Pair<int, NodeValue>(numStones, val);
            Node node;
            if (cache.TryGetValue(key, out node)) return node;

            // The pair-of-nodes construction is based on a bijection between
            //     $\prod_{i<k} T_i \cup \{(\infty, 0)\}$
            // and
            //     $(T_{k-1} \cup \{(\infty, 0)\}) \times \prod_{i<k-1} T_i \cup \{(\infty, 0)\}$

            // val.Left represents the element of $T_{k-1} \cup \{(\infty, 0)\}$ (using null for the $(\infty, 0)$)
            // and val.Right represents $\prod_{i<k-1} T_i \cup \{(\infty, 0)\}$ by bijection with $T_{k-1} \cup \{(\infty, 0)\}$.
            // so val.Right.Left represents the element of $T_{k-2}$ and so on.
            // The element of $T_{k-i}$ corresponds to throwing $i$ stones.
            Node[] children = new Node[numStones];
            NodeValue current = val;
            for (int i = 0; i < numStones && current != null; i++)
            {
                children[i] = BuildDecisionTree(numStones - (i + 1), current.Left, cache);
                current = current.Right;
            }
            node = new Node(val, children);

            // Cache
            cache[key] = node;
            return node;
        }

        class Pair<TFirst, TSecond>
        {
            public readonly TFirst X;
            public readonly TSecond Y;

            public Pair(TFirst x, TSecond y)
            {
                this.X = x;
                this.Y = y;
            }

            public override string ToString()
            {
                return string.Format("({0}, {1})", X, Y);
            }

            public override bool Equals(object obj)
            {
                Pair<TFirst, TSecond> other = obj as Pair<TFirst, TSecond>;
                return other != null && object.Equals(other.X, this.X) && object.Equals(other.Y, this.Y);
            }

            public override int GetHashCode()
            {
                return X.GetHashCode() + 37 * Y.GetHashCode();
            }
        }

        class NodeValue : Pair<int, int>
        {
            public readonly NodeValue Left;
            public readonly NodeValue Right;

            public static NodeValue Trivial = new NodeValue(1, 1, null, null);

            private NodeValue(int x, int y, NodeValue left, NodeValue right) : base(x, y)
            {
                this.Left = left;
                this.Right = right;
            }

            public NodeValue Reverse()
            {
                return new NodeValue(Y, X, this, null);
            }

            public NodeValue Combine(NodeValue other)
            {
                return new NodeValue(other.X + Y, Math.Min(other.Y, X), this, other);
            }
        }

        class Node
        {
            public readonly NodeValue Value;
            private readonly Node[] _Children;

            public Node this[int n]
            {
                get { return _Children[n]; }
            }

            public int RemainingStones
            {
                get { return _Children.Length; }
            }

            public Node(NodeValue value, IEnumerable<Node> children)
            {
                this.Value = value;
                this._Children = children.ToArray();
            }
        }

        interface SpyIO
        {
            int Input();
            void Output(int i);
        }

        // TODO The inter-thread communication here can almost certainly be much better
        class TestSpyIO : SpyIO
        {
            private object _Lock = new object();
            private int? _Buffer;
            public TestSpyIO Partner;
            public readonly string Name;

            internal TestSpyIO(string name)
            {
                this.Name = name;
            }

            public int Input()
            {
                lock (_Lock)
                {
                    while (!_Buffer.HasValue) Monitor.Wait(_Lock);

                    int rv = _Buffer.Value;
                    _Buffer = null;
                    Monitor.PulseAll(_Lock);
                    return rv;
                }
            }

            public void Output(int i)
            {
                lock (Partner._Lock)
                {
                    while (Partner._Buffer.HasValue) Monitor.Wait(Partner._Lock);
                    Partner._Buffer = i;
                    Monitor.PulseAll(Partner._Lock);
                }
            }
        }

        class ConsoleIO : SpyIO
        {
            public int Input()
            {
                return Convert.ToInt32(Console.ReadLine());
            }

            public void Output(int i)
            {
                Console.WriteLine("{0}", i);
            }
        }

        class Spy
        {
            private readonly SpyIO _IO;
            private Node _Node;
            private bool _MyTurn;

            internal Spy(SpyIO io, Node root, bool isSpy1)
            {
                this._IO = io;
                this._Node = root;
                this._MyTurn = isSpy1;
            }

            internal void Run()
            {
                int myValue = _IO.Input() - 1;
                int hisValue = 1;

                bool myTurn = _MyTurn;
                Node n = _Node;
                while (n.RemainingStones > 0)
                {
                    if (myTurn)
                    {
                        if (myValue >= n.Value.X) throw new Exception("Internal error");
                        for (int i = 0; i < n.RemainingStones; i++)
                        {
                            // n[i] allows me to represent n[i].Y values: 0 to n[i].Y - 1
                            if (myValue < n[i].Value.Y)
                            {
                                _IO.Output(i + 1);
                                n = n[i];
                                break;
                            }
                            else myValue -= n[i].Value.Y;
                        }
                    }
                    else
                    {
                        int thrown = _IO.Input();
                        for (int i = 0; i < thrown - 1; i++)
                        {
                            hisValue += n[i].Value.Y;
                        }
                        n = n[thrown - 1];
                    }

                    myTurn = !myTurn;
                }

                _IO.Output(hisValue);
            }
        }
    }

    static class LinqExt
    {
        // I'm not sure why this isn't built into Linq.
        public static TElement MaxElement<TElement>(this IEnumerable<TElement> e, Func<TElement, int> f)
        {
            int bestValue = int.MinValue;
            TElement best = default(TElement);
            foreach (var elt in e)
            {
                int value = f(elt);
                if (value > bestValue)
                {
                    bestValue = value;
                    best = elt;
                }
            }
            return best;
        }
    }
}

Инструкции для пользователей Linux

Вам нужно mono-cscбудет скомпилировать (в системах на основе Debian он находится в mono-develпакете) и monoзапустить ( mono-runtimeпакет). Тогда заклинания

mono-csc -out:codegolf31673.exe codegolf31673.cs
mono codegolf31673.exe --test

и т.п.

Питер Тейлор
источник
2
Это C #? Я не знаю, как запустить это в Linux.
Джо З.
Все это время я думал, что делаю что-то не так. Как выясняется, построение дерева решений просто занимает 30 минут ... Для справки, это работает на Fedora 20: 1. yum install mono-core(с правами root). 2. dmcs Puzzle625.cs3.mono Puzzle625.exe --test
Деннис
@ Денис, я думаю, что JIT Моно не так хорош, как Microsoft. У меня есть некоторые идеи по оптимизации, но я еще не закончил их тестирование.
Питер Тейлор
Репозитории Fedora предоставляют версию 2.10.8, которой более двух лет. Возможно, новые версии быстрее. Мне любопытно: сколько времени это займет с Microsoft?
Деннис
2
От 30 минут до 39 микросекунд. Это то, что я называю оптимизацией!
Деннис
1

Программа Python Tester

Я полагаю, было бы полезно иметь тестовую программу, которая может проверить, работает ли ваша реализация. Оба скрипта ниже работают с Python 2 или Python 3.

Программа тестирования ( tester.py):

import sys
import shlex
from subprocess import Popen, PIPE

def writen(p, n):
    p.stdin.write(str(n)+'\n')
    p.stdin.flush()

def readn(p):
    return int(p.stdout.readline().strip())

MAXSTONES = 26

def test_one(spy1cmd, spy2cmd, n1, n2):
    p1 = Popen(spy1cmd, stdout=PIPE, stdin=PIPE, universal_newlines=True)
    p2 = Popen(spy2cmd, stdout=PIPE, stdin=PIPE, universal_newlines=True)

    nstones = MAXSTONES

    writen(p1, n1)
    writen(p2, n2)

    p1turn = True
    while nstones > 0:
        if p1turn:
            s = readn(p1)
            writen(p2, s)
        else:
            s = readn(p2)
            writen(p1, s)
        if s <= 0 or s > nstones:
            print("Spy %d output an illegal number of stones: %d" % ([2,1][p1turn], s))
            return False
        p1turn = not p1turn
        nstones -= s

    n1guess = readn(p2)
    n2guess = readn(p1)

    if n1guess != n1:
        print("Spy 2 output wrong answer: expected %d, got %d" % (n1, n1guess))
        return False
    elif n2guess != n2:
        print("Spy 1 output wrong answer: expected %d, got %d" % (n2, n2guess))
        return False

    p1.kill()
    p2.kill()

    return True

def testrand(spy1, spy2, M):
    import random
    spy1cmd = shlex.split(spy1)
    spy2cmd = shlex.split(spy2)

    n = 0
    while 1:
        i = random.randrange(1, M+1)
        j = random.randrange(1, M+1)
        test_one(spy1cmd, spy2cmd, i, j)
        n += 1
        if n % 100 == 0:
            print("Ran %d tests" % n)

def test(spy1, spy2, M):
    spy1cmd = shlex.split(spy1)
    spy2cmd = shlex.split(spy2)
    for i in range(1, M+1):
        print("Testing %d..." % i)
        for j in range(1, M+1):
            if not test_one(spy1cmd, spy2cmd, i, j):
                print("Spies failed the test.")
                return
    print("Spies passed the test.")

if __name__ == '__main__':
    if len(sys.argv) != 4:
        print("Usage: %s <M> <spy1> <spy2>: test programs <spy1> and <spy2> with limit M" % sys.argv[0])
        exit()

    M = int(sys.argv[1])
    test(sys.argv[2], sys.argv[3], M)

Протокол: две шпионские программы, указанные в командной строке, будут выполнены. Ожидается, что они будут взаимодействовать исключительно через stdin / stdout. Каждая программа получит свой присвоенный номер в качестве первой строки ввода. В каждом ходу шпион 1 выводит количество брошенных камней, шпион 2 читает число из стандартного ввода (представляющее бросок шпиона 1), а затем они повторяются (с перевернутыми позициями). Когда один из шпионов определяет, что было брошено 26 камней, он останавливается и выдает свои предположения относительно номера другого шпиона.

Пример сеанса с совместимым шпионом 1 ( >обозначает вход для шпиона)

> 42
7
> 5
6
> 3
5
27
<program quits>

Если вы выберете очень большое значение M, и его запуск займет слишком много времени, вы можете переключиться test(на testrand(последнюю строку для запуска случайных тестов. В последнем случае оставьте программу работающей как минимум на несколько тысяч попыток, чтобы укрепить доверие.

Пример программы ( spy.py), для М = 42:

import sys

# Carry out the simple strategy for M=42

def writen(n):
    sys.stdout.write(str(n)+"\n")
    sys.stdout.flush()

def readn():
    return int(sys.stdin.readline().strip())

def spy1(n):
    m1,m2 = divmod(n-1, 6)
    writen(m1+1)
    o1 = readn() # read spy2's number

    writen(m2+1)
    o2 = readn()

    rest = 26 - (m1+m2+o1+o2+2)
    if rest > 0:
        writen(rest)
    writen((o1-1)*6 + (o2-1) + 1)

def spy2(n):
    m1,m2 = divmod(n-1, 6)
    o1 = readn() # read spy1's number
    writen(m1+1)

    o2 = readn()
    writen(m2+1)

    rest = 26 - (m1+m2+o1+o2+2)
    if rest > 0:
        readn()

    writen((o1-1)*6 + (o2-1) + 1)

if __name__ == '__main__':
    if len(sys.argv) != 2:
        print("Usage: %s [spy1|spy2]" % (sys.argv[0]))
        exit()

    n = int(input())
    if sys.argv[1] == 'spy1':
        spy1(n)
    elif sys.argv[1] == 'spy2':
        spy2(n)
    else:
        raise Exception("Must give spy1 or spy2 as an argument.")

Пример использования:

python tester.py 42 'python spy.py spy1' 'python spy.py spy2'
nneonneo
источник
1

Ява, М = 2535

Хорошо, вот моя реализация. На каждом шаге один шпион делает ход. Каждый возможный ход представляет диапазон кодов. Шпион выбирает ход, соответствующий его секретному коду. По мере того как они бросают больше камней, диапазон возможных кодов уменьшается до тех пор, пока в конце для обоих шпионов не останется возможным только один код в соответствии с ходами, которые они сделали.

Чтобы восстановить секретные коды, вы можете воспроизвести все ходы и вычислить соответствующие диапазоны кодов. В конце для каждого шпиона остается только один код, то есть секретный код, который он хотел передать.

К сожалению, алгоритм основан на большой предварительно вычисленной таблице с сотнями тысяч целых чисел. Метод не может быть применен мысленно с более чем 8-10 камнями возможно.

Первый файл реализует алгоритм шпиона. Статическая часть предварительно вычисляет codeCountтаблицу, которая позже используется для вычисления каждого движения. Во второй части реализованы 2 процедуры: одна для выбора количества брошенных камней, другая для воспроизведения ходов, чтобы помочь восстановить секретные коды.

Второй файл тщательно тестирует класс Spy. Метод simulateимитирует процесс. Он использует класс Spy для генерации последовательности бросков из секретных кодов, а затем восстанавливает коды из последовательности.

Spy.java

package stackexchange;

import java.util.Arrays;

public class Spy
{
    // STATIC MEMBERS

    /** Size of code range for a number of stones left to the other and the other spy's range */
    static int[][] codeCount;

    // STATIC METHODS

    /** Transpose an array of code counts */
    public static int[] transpose(int[] counts){
        int[] transposed = new int[counts[1]+1];
        int s = 0;
        for( int i=counts.length ; i-->0 ; ){
            while( s<counts[i] ){
                transposed[++s] = i;
            }
        }
        return transposed;
    }

    /** Add two integer arrays by element.  Assume the first is longer. */
    public static int[] add(int[] a, int[] b){
        int[] sum = a.clone();
        for( int i=0 ; i<b.length ; i++ ){
            sum[i] += b[i];
        }
        return sum;
    }

    /** Compute the code range for every response */
    public static void initCodeCounts(int maxStones){
        codeCount = new int[maxStones+1][];
        codeCount[0] = new int[] {0,1};
        int[] sum = codeCount[0];
        for( int stones=1 ; stones<=maxStones ; stones++ ){
            codeCount[stones] = transpose(sum);
            sum = add(codeCount[stones], sum);
        }
    }

    /** display the code counts */
    public static void dispCodeCounts(int maxStones){
        for( int stones=1 ; stones<=maxStones ; stones++ ){
            if( stones<=8 ){
                System.out.println(stones + ": " + Arrays.toString(codeCount[stones]));
            }
        }
        for( int s=1 ; s<=maxStones ; s++ ){
            int[] row = codeCount[s];
            int best = 0;
            for( int r=1 ; r<row.length ; r++ ){
                int min = r<row[r] ? r : row[r];
                if( min>=best ){
                    best = min;
                }
            }
            System.out.println(s + ": " + row.length + " " + best);
        }
    }

    /** Find the maximum symmetrical code count M for a number of stones */
    public static int getMaxValue(int stones){
        int[] row = codeCount[stones];
        int maxValue = 0;
        for( int r=1 ; r<row.length ; r++ ){
            int min = r<row[r] ? r : row[r];
            if( min>=maxValue ){
                maxValue = min;
            }
        }
        return maxValue;
    }

    // MEMBERS

    /** low end of range, smallest code still possible */
    int min;

    /** range size, number of codes still possible */
    int range;

    /** Create a spy for a certain number of stones */
    Spy(int stones){
        min = 1;
        range = getMaxValue(stones);
    }

    /** Choose how many stones to throw */
    public int throwStones(int stonesLeft, int otherRange, int secret){
        for( int move=1 ; ; move++ ){
            // see how many codes this move covers
            int moveRange = codeCount[stonesLeft-move][otherRange];
            if( secret < this.min+moveRange ){
                // secret code is in move range
                this.range = moveRange;
                return move;
            }
            // skip to next move
            this.min += moveRange;
            this.range -= moveRange;
        }
    }

    /* Replay the state changes for a given move */
    public void replayThrow(int stonesLeft, int otherRange, int stonesThrown){
        for( int move=1 ; move<stonesThrown ; move++ ){
            int moveRange = codeCount[stonesLeft-move][otherRange];
            this.min += moveRange;
            this.range -= moveRange;
        }
        this.range = codeCount[stonesLeft-stonesThrown][otherRange];
    }
}

ThrowingStones.java

package stackexchange;

public class ThrowingStones
{
    public boolean simulation(int stones, int secret0, int secret1){

        // ENCODING

        Spy spy0 = new Spy(stones);
        Spy spy1 = new Spy(stones);

        int[] throwSequence = new int[stones+1];
        int turn = 0;
        int stonesLeft = stones;

        while( true ){
            // spy 0 throws
            if( stonesLeft==0 ) break;
            throwSequence[turn] = spy0.throwStones(stonesLeft, spy1.range, secret0);
            stonesLeft -= throwSequence[turn++];
            // spy 1 throws
            if( stonesLeft==0 ) break;
            throwSequence[turn] = spy1.throwStones(stonesLeft, spy0.range, secret1);
            stonesLeft -= throwSequence[turn++];
        }

        assert (spy0.min==secret0 && spy0.range==1 );
        assert (spy1.min==secret1 && spy1.range==1 );

//      System.out.println(Arrays.toString(throwSequence));

        // DECODING

        spy0 = new Spy(stones);
        spy1 = new Spy(stones);

        stonesLeft = stones;
        turn = 0;
        while( true ){
            // spy 0 throws
            if( throwSequence[turn]==0 ) break;
            spy0.replayThrow(stonesLeft, spy1.range, throwSequence[turn]);
            stonesLeft -= throwSequence[turn++];
            // spy 1 throws
            if( throwSequence[turn]==0 ) break;
            spy1.replayThrow(stonesLeft, spy0.range, throwSequence[turn]);
            stonesLeft -= throwSequence[turn++];
        }
        int recovered0 = spy0.min;
        int recovered1 = spy1.min;

        // check the result
        if( recovered0 != secret0 || recovered1 != secret1 ){
            System.out.println("error recovering (" + secret0 + "," + secret1 + ")"
                    + ", returns (" + recovered0 + "," + recovered1 + ")");
            return false;
        }
        return true;
    }

    /** verify all possible values */
    public void verifyAll(int stones){
        int count = 0;
        int countOK = 0;
        int maxValue = Spy.getMaxValue(stones);
        for( int a=1 ; a<=maxValue ; a++ ){
            for( int b=1 ; b<=maxValue ; b++ ){
                count++;
                if( simulation(stones, a, b) ) countOK++;
            }
        }
        System.out.println("verified: " + countOK + "/" + count);
    }

    public static void main(String[] args) {
        ThrowingStones app = new ThrowingStones();
        Spy.initCodeCounts(26);
        Spy.dispCodeCounts(26);
        app.verifyAll(20);
//      app.verifyAll(26); // never managed to complete this one...
    }

}

Для справки, предварительно вычисленный массив codeCount содержит следующие значения:

1: [0, 1]
2: [0, 1, 1]
3: [0, 2, 1, 1]
4: [0, 3, 2, 1, 1, 1]
5: [0, 5, 3, 2, 2, 1, 1, 1, 1]
6: [0, 8, 5, 4, 3, 2, 2, 2, 1, 1, 1, 1, 1, 1]

Это напрямую связано с наборами Питера Тейлора. У нас есть:

(x,y) in Tk  <=>  y <= codeCount[x]
Флориан Ф
источник
Я не думаю, что это вполне соответствует спецификации без способа запуска двух шпионов в отдельных процессах и передачи бросков без разделения доступа к их rangeполям. Но я очень заинтригован вашим методом расчета таблицы. У вас есть доказательства правильности? И вы заинтересованы в совместной работе над документом, в котором обсуждается проблема и рассчитывается ее решение?
Питер Тейлор
Диапазон другого шпиона является функцией прошлых ходов, поскольку он вычисляется в методе «воспроизведения». Я считаю, что это правильно. Таблица, которую я вычисляю, точно такая же, как вы устанавливаете Tk. Транспонируя таблицу, происходит обмен x и y, сумма является суммой всех возможных дочерних элементов узла. Я не доказал это правильно, за исключением того, что я проверил это до 22 камней. Я попытался написать правильный ответ для puzzling.stackexchange, но мне не удалось объяснить это убедительным образом. И в основном это то, что вы уже сделали.
Florian F
Ok. Возможно, у меня нет времени на этой неделе, но когда я буду менее занят, я попытаюсь найти доказательство того, что ваш метод генерации создает ту же таблицу, что и моя, потому что я думаю, что это было бы хорошим дополнением к тому, что я делаю ». мы уже написали.
Питер Тейлор
На самом деле все довольно просто: его эквивалентность моему методу вычислений сводится к лемме о том, что сопряжение мультимножества объединения двух разбиений равно поточечной сумме их сопряженных.
Питер Тейлор
(шлепая меня по голове) Но конечно! Почему я не подумал об этом раньше? :-)
Florian F
0

кш / зш, М = 126

В этой простой системе каждый шпион бросает двоичные цифры другому шпиону. В каждом броске первый камень игнорируется, следующие камни - каждый бит 0, а последний камень - бит 1. Например, чтобы бросить 20, шпион бросил бы 4 камня (игнорировать 0, 2, добавить 4), затем бросьте 3 камня (игнорируйте, 8, добавьте 16), потому что 4 + 16 = 20.

Набор чисел не является смежным. 0 до 126, но 127 нет. (Если у обоих шпионов 127, им нужно 28 камней, но у них есть 26 камней.) Затем 128–158 входят, 159 нет, 160–174 находятся, 175 нет, 176–182 находятся, 183 нет, 184 - 186, 187 - и так далее.

Запустите автоматический обмен ksh spy.sh 125 126или запустите отдельных шпионов с помощью ksh spy.sh spy1 125и ksh spy.sh spy2 126. Здесь kshможет быть ksh93, pdksh или zsh.

РЕДАКТИРОВАТЬ 14 июня 2014: исправить проблему с некоторыми процессами в Zsh. Они будут бездействовать вечно и не смогут выйти, пока пользователь их не убьет.

(( stones = 26 ))

# Initialize each spy.
spy_init() {
    (( wnum = $1 ))  # my number
    (( rnum = 0 ))   # number from other spy
    (( rlog = -1 ))  # exponent from other spy
}

# Read stone count from other spy.
spy_read() {
    read count || exit
    (( stones -= count ))

    # Ignore 1 stone.
    (( count > 1 )) && {
        # Increment exponent.  Add bit to number.
        (( rlog += count - 1 ))
        (( rnum += 1 << rlog ))
    }
}

# Write stone count to other spy.
spy_write() {
    if (( wnum ))
    then
        # Find next set bit.  Prepare at least 2 stones.
        (( count = 2 ))
        until (( wnum & 1 ))
        do
            (( wnum >>= 1 ))
            (( count += 1 ))
        done

        (( wnum >>= 1 ))  # Remove this bit.
        (( stones -= count ))
        print $count      # Throw stones.
    else
        # Throw 1 stone for other spy to ignore.
        (( stones -= 1 ))
        print 1
    fi
}

# spy1 writes first.
spy1() {
    spy_init "$1"
    while (( stones ))
    do
        spy_write
        (( stones )) || break
        spy_read
    done
    print $rnum
}

# spy2 reads first.
spy2() {
    spy_init "$1"
    while (( stones ))
    do
        spy_read
        (( stones )) || break
        spy_write
    done
    print $rnum
}

(( $# == 2 )) || {
    name=${0##*/}
    print -u2 "usage: $name number1 number2"
    print -u2 "   or: $name spy[12] number"
    exit 1
}

case "$1" in
    spy1)
        spy1 "$2"
        exit;;
    spy2)
        spy2 "$2"
        exit;;
esac

(( number1 = $1 ))
(( number2 = $2 ))

if [[ -n $KSH_VERSION ]]
then
    eval 'cofork() { "$@" |& }'
elif [[ -n $ZSH_VERSION ]]
then
    # In zsh, a co-process stupidly inherits its own >&p, so it never
    # reads end of file.  Use 'coproc :' to close <&p and >&p.
    eval 'cofork() {
        coproc {
            coproc :
            "$@"
        }
    }'
fi

# Fork spies in co-processes.
[[ -n $KSH_VERSION ]] && eval 'coproc() { "$@" |& }'
cofork spy1 number1
exec 3<&p 4>&p
cofork spy2 number2
exec 5<&p 6>&p

check_stones() {
    (( stones -= count ))
    if (( stones < 0 ))
    then
        print -u2 "$1 is in trouble! " \
            "Needs $count stones, only had $((stones + count))."
        exit 1
    else
        print "$1 threw $count stones.  Pile has $stones stones."
    fi
}

# Relay stone counts while spies throw stones.
while (( stones ))
do
    # First, spy1 writes to spy2.
    read -u3 count report1 || mia spy1
    check_stones spy1
    print -u6 $count

    (( stones )) || break

    # Next, spy2 writes to spy1.
    read -u5 count report2 || mia spy2
    check_stones spy2
    print -u4 $count
done

mia() {
    print -u2 "$1 is missing in action!"
    exit 1
}

# Read numbers from spies.
read -u3 report1 || mia spy1
read -u5 report2 || mia spy2

pass=true
(( number1 != report2 )) && {
    print -u2 "FAILURE: spy1 put $number1, but spy2 got $report2."
    pass=false
}
(( number2 != report1 )) && {
    print -u2 "FAILURE: spy2 put $number2, but spy1 got $report1."
    pass=false
}

if $pass
then
    print "SUCCESS: spy1 got $report1, spy2 got $report2."
    exit 0
else
    exit 1
fi
kernigh
источник