Zbuduj szpiegów, którzy wrzucą kamienie do rzeki

20

Niedawno na niedawno wydanym Puzzling.SE pojawił się problem z szpiegami rzucającymi kamieniami w rzekę, co w rzeczywistości było dość trudne:

Dwóch szpiegów musi przekazać sobie dwa tajne numery (jeden numer na szpiega), niezauważone przez swoich wrogów. Uzgodnili metodę zrobienia tego z wykorzystaniem tylko 26 nieodróżnialnych kamieni z góry.

Spotykają się nad rzeką, gdzie znajduje się stos 26 kamieni. Zaczynając od pierwszego szpiega, na zmianę wrzucają do rzeki grupę kamieni: pierwszy szpieg rzuca pewną liczbę kamieni, potem drugi, a potem znowu pierwszy ...

Każdy szpieg musi rzucić co najmniej jeden kamień w swojej turze, dopóki wszystkie kamienie nie znikną.

Obserwują wszystkie rzuty i rozchodzą się, gdy nie ma już kamieni. Przez cały czas milczą i żadne informacje nie są wymieniane, z wyjątkiem liczby kamieni rzucanych na każdym kroku.

Jak mogą z powodzeniem wymieniać liczby, jeśli liczby mogą wynosić od 1 do M?

Twoim zadaniem jest zbudować parę programów spy1i spy2, że można rozwiązać ten problem na najwyższy możliwy M.

Każdy z Twoich programów pobierze numer od 1wybranego Mjako dane wejściowe. Następnie spy1wyśle ​​liczbę reprezentującą liczbę kamieni, które rzuci do rzeki, do której zostanie wprowadzony, spy2a także wyśle ​​liczbę, którą należy wprowadzić spy1, i tak dalej, aż suma wyniesie liczb 26. Pod koniec rzucania, każdy program wyświetli liczbę, którą według niego miał inny program, która musi pasować do liczby, która faktycznie została wprowadzona do innego programu.

Twój program powinien działać na wszystkich możliwych zamówionych par liczb (i, j), gdzie zarówno ii jmogą się różnić od 1celu M.

Zwycięzcą będzie program, który działa dla największej M, a pierwsza odpowiedź zostanie rozstrzygnięta. Dodatkowo przyznam nagrodę za reputację +100 za pierwsze rozwiązanie, dla którego udowodniono działanie M >= 2286, oraz +300 za pierwsze rozwiązanie, dla którego sprawdzono działanie M >= 2535.

Joe Z.
źródło
Rozwiązanie oznacza algorytm lub program, który generuje zestaw błędów dla każdego (i, j)?
klm123
Nie jeden program, ale dwa. Muszą komunikować się niezależnie, jak w twoim problemie.
Joe Z.
3
Skoro programy będą musiały współdzielić swoje drzewo decyzyjne, czy możemy uczynić go jednym programem, który argumentuje, który to szpieg?
Peter Taylor
Tak długo, jak możesz zagwarantować, że każdy szpieg komunikuje się niezależnie i nie ma między nimi żadnych dodatkowych informacji.
Joe Z.
Niezależnie zweryfikowałem, że 2535 jest teoretycznym maksimum tego problemu. Teraz dość mocno wierzę, że żaden program nie może zrobić lepiej.
nneonneo

Odpowiedzi:

8

C #, M = 2535

To implementuje * system, który opisałem matematycznie w wątku, który wywołał ten konkurs. Odbieram premię za 300 powtórzeń. Program wykonuje automatyczne testy, jeśli uruchomisz go bez argumentów wiersza polecenia lub z --testargumentem wiersza polecenia; dla szpiega 1, uruchom z --spy1, i dla szpiega 2 z --spy2. W każdym przypadku bierze numer, który powinienem przekazać ze standardowego wejścia, a następnie wykonuje rzuty za pomocą standardowego wejścia i standardowego wejścia.

* Właściwie znalazłem optymalizację, która robi ogromną różnicę (od kilku minut do wygenerowania drzewa decyzyjnego, do mniej niż sekundy); drzewo, które generuje, jest zasadniczo takie samo, ale wciąż pracuję nad tego dowodem. Jeśli chcesz bezpośredniej implementacji systemu, który opisałem gdzie indziej, zobacz wersję 2 , chociaż możesz chcieć cofnąć dodatkowe rejestrowanie Maini lepsze komunikacje między wątkami TestSpyIO.

Jeśli chcesz, aby przypadek testowy został ukończony w mniej niż minutę, zmień N na 16i Mna 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;
        }
    }
}

Instrukcje dla użytkowników systemu Linux

Będziesz potrzebował mono-csc skompilować (w systemach opartych na Debianie, jest w mono-develpakiecie) i monouruchomić ( mono-runtimepakiet). Zatem zaklęcia są

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

itp.

Peter Taylor
źródło
2
Czy to C #? Nie wiem, jak to uruchomić w systemie Linux.
Joe Z.
Przez cały ten czas myślałem, że robię coś złego. Jak się okazuje, budowanie drzewa decyzyjnego zajmuje po prostu 30 minut ... Dla przypomnienia działa to w Fedorze 20: 1. yum install mono-core(jako root). 2. dmcs Puzzle625.cs3.mono Puzzle625.exe --test
Dennis
@Dennis, myślę, że JIT Mono nie jest tak dobry jak Microsoft. Mam kilka pomysłów na optymalizację, ale nie skończyłem ich testowania.
Peter Taylor
Repozytoria Fedory udostępniają wersję 2.10.8, która ma ponad dwa lata. Może nowsze wersje są szybsze. Jestem ciekawy: ile czasu zajmuje Microsoft?
Dennis
2
Od 30 minut do 39 mikrosekund. Tak nazywam optymalizację!
Dennis
1

Program testujący w języku Python

Myślę, że przydałby się program testowy, który może sprawdzić, czy twoja implementacja działa. Oba poniższe skrypty działają z Python 2 lub Python 3.

Program testujący ( 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)

Protokół: Zostaną uruchomione dwa programy szpiegowskie określone w wierszu polecenia. Oczekuje się, że będą oddziaływać wyłącznie poprzez stdin / stdout. Każdy program otrzyma przypisany numer jako pierwszy wiersz wprowadzania. W każdej turze szpieg 1 podaje liczbę kamieni do rzucenia, szpieg 2 odczytuje liczbę ze standardowego rzutu (reprezentującą rzut szpiega 1), a następnie powtarzają się (z odwróconymi pozycjami). Kiedy któryś szpieg ustali, że 26 kamieni zostało rzuconych, zatrzymuje się i wypuszcza przypuszczenie liczby drugiego szpiega.

Przykładowa sesja ze zgodnym szpiegiem 1 ( >oznacza dane wejściowe do szpiega)

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

Jeśli wybierzesz bardzo dużą literę M, a jej uruchomienie trwa zbyt długo, możesz przełączyć się test(na testrand(ostatnią linię, aby uruchomić losowe testy. W tym drugim przypadku pozostaw program uruchomiony na co najmniej kilka tysięcy prób, aby zwiększyć zaufanie.

Przykładowy program ( spy.py), dla M = 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.")

Przykładowe użycie:

python tester.py 42 'python spy.py spy1' 'python spy.py spy2'
nneonneo
źródło
1

Java, M = 2535

OK, oto moja implementacja. Na każdym kroku jeden szpieg wykonuje ruch. Każdy możliwy ruch reprezentuje zakres kodów. Szpieg wybiera ruch pasujący do jego tajnego kodu. Gdy rzucają więcej kamieni, zakres możliwych kodów zmniejsza się, aż w końcu dla obu szpiegów pozostaje tylko jeden kod, zgodnie z wykonanymi ruchami.

Aby odzyskać tajne kody, możesz odtworzyć wszystkie ruchy i obliczyć odpowiednie zakresy kodów. Na koniec dla każdego szpiega pozostaje tylko jeden kod, czyli tajny kod, który chciał przekazać.

Niestety algorytm opiera się na dużej, wstępnie obliczonej tabeli ze setkami tysięcy liczb całkowitych. Metodę tę nie można zastosować mentalnie w przypadku więcej niż 8-10 kamieni.

Pierwszy plik implementuje algorytm szpiega. Część statyczna codeCountoblicza wstępnie tabelę, która jest później używana do obliczania każdego ruchu. Druga część implementuje 2 procedury, jedną do wyboru, ile kamieni należy rzucić, drugą do odtworzenia ruchów, aby pomóc w rekonstrukcji tajnych kodów.

Drugi plik intensywnie testuje klasę Spy. Metoda simulatesymuluje proces. Wykorzystuje klasę Szpieg do generowania sekwencji rzutów z tajnych kodów, a następnie rekonstruuje kody z sekwencji.

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...
    }

}

W celach informacyjnych wstępnie obliczona tablica codeCount zawiera następujące wartości:

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]

Odnosi się to bezpośrednio do zestawów Tk Petera Taylora. Mamy:

(x,y) in Tk  <=>  y <= codeCount[x]
Florian F.
źródło
Nie wydaje mi się, żeby to całkiem spełniało specyfikę bez możliwości prowadzenia dwóch szpiegów w osobnych procesach i komunikowania rzutów bez dzielenia się dostępem do ich rangepól. Ale bardzo intryguje mnie twoja metoda obliczania tabeli. Czy masz dowód poprawności? Czy jesteś zainteresowany współpracą na papierze, który omawia problem i oblicza jego rozwiązanie?
Peter Taylor
Zasięg drugiego szpiega jest funkcją wcześniejszych ruchów, ponieważ jest obliczany metodą „powtórzenia”. Wierzę, że to prawda. Tabela, którą obliczam, jest dokładnie taka sama jak ustawiasz Tk. Po transponowaniu wymiany tablic xiy suma jest sumą wszystkich możliwych elementów potomnych z węzła. Nie udowodniłem, że jest poprawny, tyle że przetestowałem go do 22 kamieni. Próbowałem napisać prawidłową odpowiedź na puzzling.stackexchange, ale nie udało mi się wyjaśnić jej w jasny, przekonujący sposób. I głównie to już zrobiłeś.
Florian F
Dobrze. Prawdopodobnie nie mam czasu w tym tygodniu, ale kiedy będę mniej zajęty, spróbuję znaleźć dowód na to, że twoja metoda generowania produkuje taki sam stół jak mój, ponieważ myślę, że byłby to dobry dodatek do rzeczy, które ja ” już napisałem.
Peter Taylor
W rzeczywistości jest to dość proste: jego równoważność z moją metodą obliczeniową sprowadza się do lematu, że koniugat wielosetowej unii dwóch partycji jest równy punktowej sumie ich koniugatów.
Peter Taylor
(klepiąc mnie po głowie) Ale oczywiście! Dlaczego nie pomyślałem o tym wcześniej? :-)
Florian F
0

ksh / zsh, M = 126

W tym prostym systemie każdy szpieg rzuca cyframi binarnymi na drugiego szpiega. W każdym rzucie pierwszy kamień jest ignorowany, każdy kolejny kamień ma bit 0, a ostatni kamień to bit 1. Na przykład, aby rzucić 20, szpieg rzuci 4 kamienie (zignoruj, 0, 2, dodaj 4), następnie rzuć 3 kamieniami (zignoruj, 8, dodaj 16), ponieważ 4 + 16 = 20.

Zbiór liczb nie jest ciągły. Wejścia od 0 do 126, ale 127 nie ma. (Jeśli obaj szpiedzy mają 127, potrzebują 28 kamieni, ale mają 26 kamieni). Następnie 128 do 158 jest w środku, 159 jest w środku, 160 do 174 jest w środku, 175 jest w środku, 176 do 182 jest w środku, 183 jest w środku, 184 do 186 jest włączony, 187 jest wyłączony i tak dalej.

Uruchom automatyczną wymianę za pomocą ksh spy.sh 125 126lub uruchom pojedynczych szpiegów za pomocą ksh spy.sh spy1 125i ksh spy.sh spy2 126. Tutaj kshmoże być ksh93, pdksh lub zsh.

EDYCJA 14 czerwca 2014: Napraw problem z niektórymi koprocesami w zsh. Będą bezczynne na zawsze i nie wyjdą, dopóki użytkownik ich nie zabije.

(( 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
źródło