Costruisci una coppia di spie che lanceranno pietre in un fiume


20

Di recente al recente rilascio di Puzzling.SE , si è verificato un problema con le spie che lanciavano pietre in un fiume che in realtà era piuttosto impegnativo:

Due spie devono scambiarsi due numeri segreti (un numero per spia), non notato dai loro nemici. Hanno concordato un metodo per farlo usando in anticipo solo 26 pietre indistinguibili.

Si incontrano in un fiume, dove c'è un mucchio di 26 pietre. A partire dalla prima spia, a turno lanciano un gruppo di pietre nel fiume: la prima spia lancia un certo numero di pietre, poi la seconda, poi la prima ancora ...

Ogni spia deve lanciare almeno una pietra nel suo turno, fino a quando tutte le pietre sono scomparse.

Osservano tutti i tiri e divergono quando non ci sono più pietre. Mantengono il silenzio tutto il tempo e non vengono scambiate informazioni tranne il numero di pietre lanciate ad ogni turno.

Come possono scambiare i numeri con successo se i numeri possono essere compresi tra 1 e M?

Il tuo compito è costruire una coppia di programmi spy1e spy2, in grado di risolvere questo problema nel modo più alto possibile M.

Ciascuno dei tuoi programmi prenderà un numero da 1quello che hai scelto Mcome input. Quindi, spy1produrrà un numero che rappresenta il numero di pietre che lancia nel fiume, a cui verrà immesso il spy2quale genererà anche un numero a cui immettere spy1, e così via fino a quando l'output dei numeri si sommerà 26. Alla fine del lancio, ciascun programma produrrà il numero che ritiene l'altro programma, che deve corrispondere al numero effettivamente inserito nell'altro programma.

Il programma deve funzionare per tutte le possibili coppie ordinate di numeri in (i, j)cui entrambi ie jpossono variare da 1a M.

Il programma che funziona per il più grande Msarà il vincitore, con la prima risposta che verrà pubblicata rompendo un pareggio. Inoltre, assegnerò un premio di reputazione +100 alla prima soluzione per cui è stato dimostrato di funzionare M >= 2286e +300 alla prima soluzione per cui è stato dimostrato di funzionare M >= 2535.


Soluzione significa algoritmo, o un programma, che genera una serie di dissezioni per ciascuno (i, j)?
klm123,

Non un programma, ma due. Devono comunicare in modo indipendente, come nel tuo problema.
Joe Z.

3
Dato che i programmi dovranno condividere il loro albero decisionale, possiamo trasformarlo in un programma che accetta un argomento per dire quale spia è?
Peter Taylor,

Finché puoi garantire che ogni spia comunichi in modo indipendente e non ci siano ulteriori informazioni scambiate tra di loro.
Joe Z.

Indipendentemente, ho verificato che 2535 è il massimo teorico delle informazioni per questo problema. Ora credo fermamente che nessun programma possa fare di meglio.
nneonneo,

Risposte:


8

C #, M = 2535

Questo implementa * il sistema che ho descritto matematicamente sul thread che ha provocato questo contest. Richiedo il bonus di 300 rep. Il programma esegue l'autotest se lo si esegue senza argomenti della riga di comando o con --testcome argomento della riga di comando; per spia 1, corri con --spy1, e per spia 2 con --spy2. In ogni caso, prende il numero che dovrei comunicare da stdin, quindi esegue i lanci tramite stdin e stdout.

* In realtà, ho trovato un'ottimizzazione che fa una differenza enorme (da diversi minuti per generare l'albero decisionale, a meno di un secondo); l'albero che genera è fondamentalmente lo stesso, ma sto ancora lavorando su una prova di ciò. Se si desidera un'implementazione diretta del sistema che ho descritto altrove, vedere la revisione 2 , anche se si potrebbe desiderare di eseguire il backport della registrazione extra da Maine le migliori comunicazioni tra thread TestSpyIO.

Se si desidera un caso di test che si completa in meno di un minuto, passare Na 16e Ma 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;
        }
    }
}

Istruzioni per utenti Linux

Dovrai mono-csccompilare (sui sistemi basati su Debian, è nel mono-develpacchetto) ed monoeseguire ( mono-runtimepacchetto). Quindi sono gli incantesimi

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

eccetera.


2
Quello è C #? Non so come eseguirlo su Linux.
Joe Z.

Per tutto questo tempo ho pensato di fare qualcosa di sbagliato. A quanto pare, la costruzione dell'albero decisionale richiede solo 30 minuti ... Per la cronaca, questo funziona su Fedora 20: 1. yum install mono-core(come root). 2. dmcs Puzzle625.cs3.mono Puzzle625.exe --test
Dennis

@Dennis, penso che il JIT di Mono non sia abbastanza buono come quello di Microsoft. Ho alcune idee per l'ottimizzazione, ma non ho ancora finito di testarle.
Peter Taylor,

I repository di Fedora forniscono la versione 2.10.8, che ha più di due anni. Forse le versioni più recenti sono più veloci. Sono curioso: quanto tempo ci vuole con Microsoft?
Dennis,

2
Da 30 minuti a 39 microsecondi. Questo è ciò che chiamo un'ottimizzazione!
Dennis,

1

Programma Python Tester

Immagino che sarebbe utile disporre di un programma di test in grado di verificare che l'implementazione funzioni. Entrambi gli script di seguito funzionano con Python 2 o Python 3.

Programma tester ( 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)

Protocollo: verranno eseguiti i due programmi spia specificati sulla riga di comando. Ci si aspetta che interagiscano solo attraverso stdin / stdout. A ciascun programma verrà assegnato il numero assegnato come prima riga di input. Ad ogni turno, la spia 1 emette il numero di pietre da lanciare, la spia 2 legge un numero dallo stdin (che rappresenta il tiro della spia 1), e poi si ripetono (con le posizioni invertite). Quando entrambe le spie determinano che sono state lanciate 26 pietre, si fermano ed emettono la loro ipotesi per il numero dell'altra spia.

Sessione di esempio con una spia compatibile 1 ( >indica l'input della spia)

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

Se si sceglie un grande M, e ci vuole troppo tempo per l'esecuzione, è possibile passare test(per testrand(nell'ultima riga per eseguire i test casuali. In quest'ultimo caso, lasciare il programma in esecuzione per almeno alcune migliaia di prove per aumentare la fiducia.

Esempio di programma ( spy.py), per 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.")

Esempio di utilizzo:

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

1

Java, M = 2535

OK, ecco la mia implementazione. Ad ogni passo una spia fa una mossa. Ogni possibile mossa rappresenta un intervallo di codici. La spia sceglie la mossa corrispondente al suo codice segreto. Man mano che lanciano più pietre, la gamma di possibili codici si riduce fino a quando, alla fine, per entrambe le spie, rimane un solo codice a seconda delle mosse che hanno fatto.

Per recuperare i codici segreti, è possibile riprodurre tutte le mosse e calcolare gli intervalli di codici corrispondenti. Alla fine, rimane un solo codice per ogni spia, che è il codice segreto che voleva trasmettere.

Sfortunatamente, l'algoritmo si basa su una grande tabella pre-calcolata con centinaia di migliaia di numeri interi. Il metodo non potrebbe essere applicato mentalmente con più di 8-10 pietre forse.

Il primo file implementa l'algoritmo di Spy. La parte statica precompone una codeCounttabella che verrà successivamente utilizzata per calcolare ogni spostamento. La seconda parte implementa 2 procedure, una per selezionare quante pietre lanciare, l'altra per ripetere una mossa per aiutare a ricostruire i codici segreti.

Il secondo file verifica ampiamente la classe Spy. Il metodo simulatesimula il processo. Utilizza la classe Spy per generare una sequenza di lanci dai codici segreti e quindi ricostruisce i codici dalla sequenza.

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

}

Per riferimento, l'array codeCount precompilato contiene i seguenti valori:

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]

Questo si riferiva direttamente ai set Tk di Peter Taylor. Abbiamo:

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

Non credo che questo soddisfi le specifiche senza un modo per eseguire le due spie in processi separati e comunicare i tiri senza condividere l'accesso ai loro rangecampi. Ma sono molto incuriosito dal tuo metodo di calcolo della tabella. Hai una prova di correttezza? E sei interessato a collaborare a un documento che discute il problema e calcola la sua soluzione?
Peter Taylor,

L'intervallo dell'altra spia è una funzione delle mosse passate, poiché è calcolato nel metodo "replay". Credo che sia corretto La tabella che computo è esattamente la stessa che imposta Tk. Trasponendo la tabella scambia xey, la somma è la somma di tutti i possibili figli da un nodo. Non l'ho dimostrato correttamente, tranne per il fatto che l'ho testato fino a 22 pietre. Ho provato a scrivere una risposta adeguata per puzzling.stackexchange, ma non sono riuscito a spiegarlo in modo chiaro e convincente. E soprattutto, è quello che hai già fatto.
Florian F,

Ok. Probabilmente non ho tempo questa settimana, ma quando sono meno impegnato cercherò di provare che il tuo metodo di generazione produce la stessa tabella della mia, perché penso che sarebbe una buona aggiunta alle cose che ho già scritto.
Peter Taylor,

In realtà è abbastanza semplice: la sua equivalenza al mio metodo di calcolo si riduce al lemma che il coniugato dell'unione multiset di due partizioni è uguale alla somma puntuale dei loro coniugati.
Peter Taylor,

(dandomi una pacca sulla testa) Ma certo! Perché non ci avevo pensato prima? :-)
Florian F

0

ksh / zsh, M = 126

In questo semplice sistema, ogni spia lancia cifre binarie sull'altra spia. In ogni lancio, la prima pietra viene ignorata, le pietre successive sono ciascuna bit 0 e l'ultima pietra è bit 1. Ad esempio, per lanciare 20, una spia lancia 4 pietre (ignora, 0, 2, aggiungi 4), quindi lancia 3 pietre (ignora, 8, aggiungi 16), perché 4 + 16 = 20.

L'insieme dei numeri non è contiguo. Sono presenti da 0 a 126, ma 127 è fuori. (Se entrambe le spie hanno 127, hanno bisogno di 28 pietre, ma hanno 26 pietre.) Quindi 128-158 sono dentro, 159 è fuori, 160-184 sono dentro, 175 è fuori, 176-182 sono dentro, 183 è fuori, 184 a 186 è dentro, 187 è fuori e così via.

Esegui uno scambio automatico con ksh spy.sh 125 126o esegui singole spie con ksh spy.sh spy1 125e ksh spy.sh spy2 126. Qui, kshpuò essere ksh93, pdksh o zsh.

EDIT 14 giu 2014: Risolvi un problema con alcuni coprocessi in zsh. Sarebbero rimasti inattivi per sempre e non sarebbero usciti, fino a quando l'utente non li avesse uccisi.

(( 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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.