Baue ein Paar Spione, die Steine ​​in einen Fluss werfen

20

Kürzlich an der neu veröffentlichten Puzzling.SE , gab es ein Problem , über Spione Steine in einen Fluss werfen , die eigentlich recht war eine Herausforderung:

Zwei Spione müssen sich zwei geheime Nummern geben (eine Nummer pro Spion), die ihre Feinde nicht bemerken. Sie haben sich auf eine Methode geeinigt, bei der nur 26 nicht unterscheidbare Steine ​​im Voraus verwendet werden.

Sie treffen sich an einem Fluss, wo sich ein Stapel von 26 Steinen befindet. Beginnend mit dem ersten Spion werfen sie abwechselnd eine Gruppe Steine ​​in den Fluss: Der erste Spion wirft einige Steine, dann den zweiten, dann den ersten wieder ...

Jeder Spion muss mindestens einen Stein werfen, bis alle Steine ​​verschwunden sind.

Sie beobachten alle Würfe und gehen auseinander, wenn es keine Steine ​​mehr gibt. Sie schweigen die ganze Zeit und es werden keine Informationen ausgetauscht, außer die Anzahl der Steine, die in jeder Runde geworfen werden.

Wie können sie die Nummern erfolgreich austauschen, wenn die Nummern von 1 bis M sein können?

Ihre Aufgabe ist es, ein Paar von Programmen zu bauen, spy1und spy2, dass dieses Problem für die höchstmögliche lösen kann M.

Ihre Programme nehmen jeweils eine Nummer von 1zu Ihrer Wahl Mals Eingabe. Anschließend spy1wird eine Zahl ausgegeben, die die Anzahl der Steine ​​darstellt, die in den Fluss geworfen werden. Diese spy2Zahl wird eingegeben spy1, und es wird auch eine Zahl ausgegeben, in die eingegeben werden soll , usw., bis sich die ausgegebenen Zahlen summieren 26. Am Ende des Wurfs gibt jedes Programm die Nummer aus, von der es glaubt, dass sie das andere Programm hatte, die mit der Nummer übereinstimmen muss, die tatsächlich in das andere Programm eingegeben wurde.

Ihr Programm muss für alle möglichen geordneten Zahlenpaare funktionieren, (i, j)wobei beide iund jvon 1bis variieren können M.

Das Programm, das für das größte Programm funktioniert M, ist der Gewinner, und die erste Antwort wird als Unentschieden gemeldet. Zusätzlich werde ich eine Reputationsprämie von +100 für die erste Lösung vergeben, die nachweislich funktioniert M >= 2286, und +300 für die erste Lösung, die nachweislich funktioniert M >= 2535.

Joe Z.
quelle
Lösung bedeutet Algorithmus oder ein Programm, das für jedes (i, j) eine Menge von Dissektionen erzeugt?
klm123
Nicht ein Programm, sondern zwei. Sie müssen unabhängig kommunizieren, wie in Ihrem Problem.
Joe Z.
3
Da die Programme ihren Entscheidungsbaum teilen müssen, können wir es zu einem Programm machen, das ein Argument benötigt, um zu sagen, um welchen Spion es sich handelt?
Peter Taylor
Solange Sie garantieren können, dass jeder Spion unabhängig kommuniziert und keine zusätzlichen Informationen zwischen ihnen ausgetauscht werden.
Joe Z.
Unabhängig davon habe ich überprüft, dass 2535 das informationstheoretische Maximum für dieses Problem ist. Ich bin der festen Überzeugung, dass es kein Programm besser machen kann.
Nneonneo

Antworten:

8

C #, M = 2535

Dies implementiert das System, das ich mathematisch in dem Thread beschrieben habe, der diesen Wettbewerb provoziert hat. Ich beanspruche den 300 Wiederholungsbonus. Das Programm führt einen Selbsttest durch, wenn Sie es entweder ohne Befehlszeilenargumente oder --testals Befehlszeilenargument ausführen . für Spion 1 laufen mit --spy1und für Spion 2 mit --spy2. In jedem Fall nimmt es die Nummer, die ich von stdin mitteilen soll, und führt dann die Würfe über stdin und stdout aus.

* Tatsächlich habe ich eine Optimierung gefunden, die einen großen Unterschied macht (von einigen Minuten zum Generieren des Entscheidungsbaums bis zu weniger als einer Sekunde). Der Baum, den es erzeugt, ist im Grunde derselbe, aber ich arbeite immer noch an einem Beweis dafür. Wenn Sie eine direkte Implementierung des Systems wünschen, das ich an anderer Stelle beschrieben habe, lesen Sie Revision 2 , obwohl Sie möglicherweise die zusätzliche Protokollierung von Mainund die bessere Kommunikation zwischen Threads von zurückportieren möchten TestSpyIO.

Wenn Sie einen Testfall wünschen, der in weniger als einer Minute abgeschlossen ist, wechseln Sie Nzu 16und Mzu 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;
        }
    }
}

Anweisungen für Linux-Benutzer

Sie müssen mono-csckompilieren (auf Debian-basierten Systemen ist es im mono-develPaket enthalten) und monoausführen ( mono-runtimePaket). Dann sind die Beschwörungsformeln

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

etc.

Peter Taylor
quelle
2
Ist das C #? Ich kann das nicht unter Linux ausführen.
Joe Z.
Die ganze Zeit dachte ich, ich mache etwas falsch. Wie sich herausstellt, dauert das Erstellen des Entscheidungsbaums nur 30 Minuten ... Im Grunde funktioniert dies auf Fedora 20: 1. yum install mono-core(als Root). 2. dmcs Puzzle625.cs3.mono Puzzle625.exe --test
Dennis
@ Tennis, ich denke, dass Monos JIT nicht ganz so gut ist wie das von Microsoft. Ich habe einige Optimierungsideen, aber ich habe sie noch nicht fertig getestet.
Peter Taylor
Fedoras Repositorys enthalten Version 2.10.8, die älter als zwei Jahre ist. Möglicherweise sind neuere Versionen schneller. Ich bin gespannt: Wie lange dauert es bei Microsoft?
Dennis,
2
30 Minuten bis 39 Mikrosekunden. Das nenne ich eine Optimierung!
Dennis
1

Python-Tester-Programm

Ich denke, es wäre nützlich, ein Testprogramm zu haben, das überprüfen kann, ob Ihre Implementierung funktioniert. Die beiden folgenden Skripte funktionieren entweder mit Python 2 oder Python 3.

Testerprogramm ( 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)

Protokoll: Die beiden in der Befehlszeile angegebenen Spionageprogramme werden ausgeführt. Es wird erwartet, dass sie nur über stdin / stdout interagieren. Jedes Programm erhält seine zugewiesene Nummer als erste Eingabezeile. In jedem Zug gibt Spion 1 die Anzahl der Steine ​​aus, die geworfen werden sollen, Spion 2 liest eine Zahl aus dem Standardwert (der den Wurf von Spion 1 darstellt) und wiederholt sie dann (mit umgekehrten Positionen). Wenn einer der beiden Spione feststellt, dass 26 Steine ​​geworfen wurden, halten sie an und geben ihre Schätzung für die Nummer des anderen Spions ab.

Beispielsitzung mit einem kompatiblen Spion1 ( >kennzeichnet die Eingabe für den Spion)

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

Wenn Sie einen sehr großen M wählen, und es dauert zu lange laufen, können Sie schalen test(für testrand(in der letzten Zeile zufällige Tests auszuführen. Im letzteren Fall lassen Sie das Programm mindestens einige tausend Versuche laufen, um Vertrauen aufzubauen.

Beispielprogramm ( spy.py) für 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.")

Anwendungsbeispiel:

python tester.py 42 'python spy.py spy1' 'python spy.py spy2'
nneonneo
quelle
1

Java, M = 2535

OK, hier ist meine Implementierung. Bei jedem Schritt macht ein Spion eine Bewegung. Jede mögliche Bewegung repräsentiert eine Reihe von Codes. Der Spion wählt den Zug, der seinem Geheimcode entspricht. Wenn sie mehr Steine ​​werfen, verringert sich die Reichweite der möglichen Codes, bis am Ende für beide Spione nur noch ein Code möglich ist, je nach den von ihnen ausgeführten Zügen.

Um die Geheimcodes wiederherzustellen, können Sie alle Züge wiederholen und die entsprechenden Codebereiche berechnen. Am Ende bleibt nur ein Code für jeden Spion, das ist der Geheimcode, den er senden wollte.

Leider basiert der Algorithmus auf einer großen vorberechneten Tabelle mit hunderttausenden ganzen Zahlen. Die Methode konnte vielleicht nicht mit mehr als 8-10 Steinen mental angewendet werden.

Die erste Datei implementiert den Algorithmus von Spy. Der statische Teil berechnet vorab eine codeCountTabelle, die später zur Berechnung jeder Bewegung verwendet wird. Im zweiten Teil werden zwei Verfahren implementiert, eines zum Auswählen der Anzahl der zu werfenden Steine ​​und das andere zum Wiederholen von Zügen, um die Wiederherstellung der Geheimcodes zu unterstützen.

Die zweite Datei testet die Spy-Klasse ausgiebig. Die Methode simulatesimuliert den Prozess. Es verwendet die Spy-Klasse, um eine Folge von Würfen aus den Geheimcodes zu generieren, und rekonstruiert dann die Codes aus der Folge.

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

}

Als Referenz enthält das vorberechnete codeCount-Array die folgenden Werte:

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]

Dies bezog sich direkt auf Peter Taylors Tk-Sets. Wir haben:

(x,y) in Tk  <=>  y <= codeCount[x]
Florian F
quelle
Ich glaube nicht, dass dies der Spezifikation entspricht, ohne die beiden Spione in getrennten Prozessen ausführen und die Würfe kommunizieren zu können, ohne den Zugriff auf ihre rangeFelder zu teilen . Aber ich bin sehr fasziniert von Ihrer Methode zur Berechnung der Tabelle. Haben Sie einen Richtigkeitsnachweis? Und sind Sie daran interessiert, an einem Papier mitzuarbeiten, in dem das Problem erörtert und dessen Lösung berechnet wird?
Peter Taylor
Die Reichweite des anderen Spions ist eine Funktion der vergangenen Züge, wie sie in der "Wiederholungsmethode" berechnet wird. Ich glaube es ist richtig. Die Tabelle, die ich berechne, ist genau so, wie Sie Tk setzen. Beim Vertauschen der Tabellen x und y ist die Summe die Summe aller möglichen untergeordneten Elemente eines Knotens. Ich habe es nicht richtig bewiesen, außer dass ich es bis zu 22 Steine ​​getestet habe. Ich habe versucht, eine richtige Antwort für puzzling.stackexchange zu schreiben, aber ich habe es nicht geschafft, es klar und überzeugend zu erklären. Und meistens ist es das, was du bereits getan hast.
Florian F
Okay. Ich habe diese Woche wahrscheinlich keine Zeit, aber wenn ich weniger beschäftigt bin, werde ich versuchen, einen Beweis dafür zu finden, dass Ihre Erzeugungsmethode dieselbe Tabelle wie meine erzeugt, weil ich denke, dass dies eine gute Ergänzung zu den Sachen ist, die ich geschrieben habe. habe schon geschrieben.
Peter Taylor
Eigentlich ist es ganz einfach: Die Entsprechung zu meiner Berechnungsmethode ergibt sich aus dem Lemma, dass das Konjugat der Multiset-Vereinigung zweier Partitionen gleich der punktweisen Summe ihrer Konjugate ist.
Peter Taylor
(schlägt mir auf den Kopf) Aber natürlich! Warum habe ich früher nicht daran gedacht? :-)
Florian F
0

ksh / zsh, M = 126

In diesem einfachen System wirft jeder Spion Binärziffern an den anderen Spion. Bei jedem Wurf wird der erste Stein ignoriert, die nächsten Steine ​​sind jeweils Bit 0 und der letzte Stein ist Bit 1. Wenn ein Spion beispielsweise 20 wirft, wirft er 4 Steine ​​(ignoriere, 0, 2, addiere 4). wirf dann 3 Steine ​​(ignoriere, 8, addiere 16), denn 4 + 16 = 20.

Die Zahlenreihe ist nicht zusammenhängend. 0 bis 126 sind in, aber 127 ist out. (Wenn beide Spione 127 haben, brauchen sie 28 Steine, aber sie haben 26 Steine.) Dann sind 128 bis 158 im Spiel, 159 sind im Spiel, 160 bis 174 sind im Spiel, 175 sind im Spiel, 176 bis 182 sind im Spiel, 183 sind im Spiel. 184 bis 186 sind drin, 187 sind draußen und so weiter.

Führen Sie einen automatischen Tausch mit ksh spy.sh 125 126oder einzelne Spione mit ksh spy.sh spy1 125und aus ksh spy.sh spy2 126. Hier kshkann ksh93, pdksh oder zsh sein.

BEARBEITEN 14. Juni 2014: Behebung eines Problems mit einigen Co-Prozessen in zsh. Sie würden für immer untätig bleiben und nicht verlassen, bis der Benutzer sie tötete.

(( 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
quelle