Construisez une paire d'espions qui jettera des pierres dans une rivière


20

Récemment, lors du nouveau Puzzling.SE , il y avait un problème avec les espions jetant des pierres dans une rivière qui était en fait assez difficile:

Deux espions doivent se passer deux numéros secrets (un numéro par espion), inaperçus par leurs ennemis. Ils se sont mis d'accord sur une méthode pour ce faire en utilisant seulement 26 pierres indiscernables à l'avance.

Ils se rencontrent à une rivière, où il y a un tas de 26 pierres. En commençant par le premier espion, ils jettent à tour de rôle un groupe de pierres dans la rivière: le premier espion lance un certain nombre de pierres, puis la seconde, puis la première encore ...

Chaque espion doit lancer au moins une pierre à son tour, jusqu'à ce que toutes les pierres soient parties.

Ils observent tous les lancers et divergent lorsqu'il n'y a plus de pierres. Ils gardent le silence tout le temps et aucune information n'est échangée sauf le nombre de pierres lancées à chaque tour.

Comment peuvent-ils échanger les numéros avec succès si les numéros peuvent aller de 1 à M?

Votre tâche consiste à créer une paire de programmes, spy1et spy2cela peut résoudre ce problème pour le plus haut possible M.

Vos programmes prendront chacun un numéro de 1votre choix Men entrée. Ensuite, spy1sortira un nombre représentant le nombre de pierres qu'il jette dans la rivière, qui sera entré dans spy2lequel sortira également un nombre à entrer spy1, et ainsi de suite jusqu'à ce que les nombres sortants s'additionnent 26. À la fin du lancement, chaque programme affichera le nombre qu'il pense que l'autre programme avait, qui doit correspondre au nombre qui a été réellement entré dans l'autre programme.

Votre programme doit fonctionner pour toutes les paires de numéros ordonnées possibles (i, j)où les deux iet jpeuvent varier de 1à M.

Le programme qui fonctionne pour le plus grand Msera le gagnant, avec la première réponse à être publiée en cas d'égalité. De plus, j'accorderai une prime de réputation de +100 à la première solution qui a fait ses preuves M >= 2286et +300 à la première solution qui a fait ses preuves M >= 2535.


La solution signifie un algorithme, ou un programme, qui génère un ensemble de dissisions pour chacun (i, j)?
klm123

Pas un programme, mais deux. Ils doivent communiquer de manière indépendante, comme dans votre problème.
Joe Z.

3
Étant donné que les programmes vont devoir partager leur arbre de décision, pouvons-nous en faire un programme qui prend un argument pour dire de quel espion il s'agit?
Peter Taylor

Tant que vous pouvez garantir que chaque espion communique indépendamment et qu'il n'y a pas d'informations supplémentaires échangées entre eux.
Joe Z.

Indépendamment, j'ai vérifié que 2535 est le maximum théorique de l'information pour ce problème. Je crois fermement maintenant qu'aucun programme ne peut faire mieux.
nneonneo

Réponses:


8

C #, M = 2535

Cela implémente * le système que j'ai décrit mathématiquement sur le fil qui a provoqué ce concours. Je réclame le bonus de 300 rep. Le programme s'auto-teste si vous l'exécutez sans arguments de ligne de commande ou avec --testcomme argument de ligne de commande; pour spy 1, run with --spy1et pour spy 2 with --spy2. Dans chaque cas, il prend le numéro que je dois communiquer de stdin, puis effectue les lancers via stdin et stdout.

* En fait, j'ai trouvé une optimisation qui fait une énorme différence (de plusieurs minutes pour générer l'arbre de décision à moins d'une seconde); l'arbre qu'il génère est fondamentalement le même, mais je travaille toujours sur une preuve de cela. Si vous voulez une implémentation directe du système que j'ai décrit ailleurs, voir la révision 2 , bien que vous souhaitiez peut-être rétroporter la journalisation supplémentaire Mainet les meilleures communications inter-thread TestSpyIO.

Si vous souhaitez un scénario de test qui se termine en moins d'une minute, passez Nà 16et 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;
        }
    }
}

Instructions pour les utilisateurs Linux

Vous devrez mono-csccompiler (sur les systèmes basés sur Debian, c'est dans le mono-develpaquet) et monoexécuter ( mono-runtimepaquet). Ensuite, les incantations sont

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

etc.


2
Est-ce C #? Je ne sais pas comment faire ça sous Linux.
Joe Z.

Pendant tout ce temps, j'ai pensé que je faisais quelque chose de mal. Il s'avère que la construction de l'arbre de décision prend simplement 30 minutes ... Pour mémoire, cela fonctionne sur Fedora 20: 1. yum install mono-core(en tant que root). 2. dmcs Puzzle625.cs3.mono Puzzle625.exe --test
Dennis

@ Dennis, je pense que le JIT de Mono n'est pas aussi bon que celui de Microsoft. J'ai quelques idées d'optimisation, mais je n'ai pas fini de les tester.
Peter Taylor

Les référentiels de Fedora fournissent la version 2.10.8, qui a plus de deux ans. Peut-être que les nouvelles versions sont plus rapides. Je suis curieux: combien de temps cela prend-il avec Microsoft?
Dennis

2
De 30 minutes à 39 microsecondes. C'est ce que j'appelle une optimisation!
Dennis

1

Programme de testeur Python

Je pense qu'il serait utile d'avoir un programme de test qui puisse vérifier que votre implémentation fonctionne. Les deux scripts ci-dessous fonctionnent avec Python 2 ou Python 3.

Programme de testeur ( 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)

Protocole: les deux programmes espions spécifiés sur la ligne de commande seront exécutés. On s'attend à ce qu'ils interagissent uniquement via stdin / stdout. Chaque programme recevra son numéro attribué comme première ligne d'entrée. À chaque tour, l'espion 1 sort le nombre de pierres à lancer, l'espion 2 lit un nombre à partir de stdin (représentant le jet de l'espion 1), puis ils répètent (avec les positions inversées). Lorsque l'un ou l'autre espion détermine que 26 pierres ont été lancées, il s'arrête et émet sa supposition pour le numéro de l'autre espion.

Exemple de session avec un spy1 compatible ( >indique l'entrée à l'espion)

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

Si vous choisissez un M très grand, et il prend trop de temps à courir, vous pouvez passer test(à testrand(la dernière ligne pour effectuer des tests aléatoires. Dans ce dernier cas, laissez le programme en cours d'exécution pendant au moins quelques milliers d'essais pour renforcer la confiance.

Exemple de programme ( spy.py), pour 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.")

Exemple d'utilisation:

python tester.py 42 'python spy.py spy1' 'python spy.py spy2'

1

Java, M = 2535

OK, voici mon implémentation. À chaque étape, un espion fait un pas. Chaque mouvement possible représente une gamme de codes. L'espion choisit le coup correspondant à son code secret. Comme ils jettent plus de pierres, la gamme de codes possibles se réduit jusqu'à ce que, pour les deux espions, un seul code reste possible en fonction des mouvements qu'ils ont effectués.

Pour récupérer les codes secrets, vous pouvez rejouer tous les mouvements et calculer les plages de codes correspondantes. À la fin, il ne reste qu'un code pour chaque espion, c'est le code secret qu'il voulait transmettre.

Malheureusement, l'algorithme repose sur une grande table précalculée avec des centaines de milliers d'entiers. La méthode ne pouvait pas être appliquée mentalement avec plus de 8 à 10 pierres peut-être.

Le premier fichier implémente l'algorithme de Spy. La partie statique précalcule une codeCounttable qui est ensuite utilisée pour calculer chaque déplacement. La deuxième partie met en œuvre 2 procédures, l'une pour sélectionner le nombre de pierres à lancer, l'autre pour rejouer un coup pour aider à reconstruire les codes secrets.

Le deuxième fichier teste la classe Spy de manière approfondie. La méthode simulatesimule le processus. Il utilise la classe Spy pour générer une séquence de lancers à partir des codes secrets, puis reconstruit les codes à partir de la séquence.

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

}

Pour référence, le tableau codeCount précalculé contient les valeurs suivantes:

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]

Cela était directement lié aux ensembles Tk de Peter Taylor. Nous avons:

(x,y) in Tk  <=>  y <= codeCount[x]

Je ne pense pas que cela réponde parfaitement aux spécifications sans un moyen d'exécuter les deux espions dans des processus séparés et de communiquer les lancers sans partager l'accès à leurs rangechamps. Mais je suis très intrigué par votre méthode de calcul du tableau. Avez-vous une preuve d'exactitude? Et êtes-vous intéressé à collaborer sur un article qui traite du problème et à calculer sa solution?
Peter Taylor

La portée de l'autre espion est fonction des mouvements passés, car elle est calculée dans la méthode de "rejeu". Je pense que c'est correct. Le tableau que je calcule est exactement le même que celui que vous définissez Tk. En transposant les échanges de table x et y, la somme est la somme de tous les enfants possibles d'un nœud. Je ne l'ai pas prouvé correct, sauf que je l'ai testé jusqu'à 22 pierres. J'ai essayé d'écrire une bonne réponse pour puzzling.stackexchange, mais je n'ai pas réussi à l'expliquer de manière claire et convaincante. Et c'est surtout ce que vous avez déjà fait.
Florian F

D'accord. Je n'ai probablement pas le temps cette semaine, mais quand je serai moins occupé, j'essaierai de trouver une preuve que votre méthode de génération produit la même table que la mienne, car je pense que ce serait un bon ajout à ce que je fais. ai déjà écrit.
Peter Taylor

En fait, c'est assez simple: son équivalence avec ma méthode de calcul se résume au lemme selon lequel le conjugué de l'union multi-ensemble de deux partitions est égal à la somme ponctuelle de leurs conjugués.
Peter Taylor

(me claquant la tête) Mais bien sûr! Pourquoi n'y ai-je pas pensé plus tôt? :-)
Florian F

0

ksh / zsh, M = 126

Dans ce système simple, chaque espion envoie des chiffres binaires à l'autre espion. Dans chaque lancer, la première pierre est ignorée, les pierres suivantes sont chaque bit 0, et la dernière pierre est le bit 1. Par exemple, pour lancer 20, un espion lancerait 4 pierres (ignorer, 0, 2, ajouter 4), puis lancez 3 pierres (ignorer, 8, ajouter 16), car 4 + 16 = 20.

L'ensemble des nombres n'est pas contigu. 0 à 126 sont entrés, mais 127 sont sortis. (Si les deux espions en ont 127, ils ont besoin de 28 pierres, mais ils ont 26 pierres.) Ensuite, 128 à 158 sont entrés, 159 sont sortis, 160 à 174 sont entrés, 175 sont sortis, 176 à 182 sont entrés, 183 est sorti, 184 à 186 est entré, 187 est sorti, etc.

Exécutez un échange automatique avec ksh spy.sh 125 126ou exécutez des espions individuels avec ksh spy.sh spy1 125et ksh spy.sh spy2 126. Ici, kshpeut être ksh93, pdksh ou zsh.

EDIT 14 juin 2014: correction d'un problème avec certains co-processus dans zsh. Ils resteraient inactifs pour toujours et ne sortiraient pas, jusqu'à ce que l'utilisateur les tue.

(( 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
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.