Побудуйте пару шпигунів, які будуть кидати каміння в річку


20

Нещодавно на щойно випущеному Puzzling.SE виникла проблема з шпигунами, що кидали каміння в річку, що насправді було досить складним:

Два шпигуни повинні передавати один одному два секретні номери (по одному номеру за шпигуном), непоміченими їх ворогами. Вони домовились про спосіб цього зробити, використовуючи лише 26 нерозрізних каменів заздалегідь.

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

Кожен шпигун повинен кинути хоча б один камінь на свою чергу, поки всі камені не зникнуть.

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

Як вони можуть успішно обмінювати числа, якщо числа можуть бути від 1 до М?

Ваше завдання полягає в тому, щоб скласти пару програм spy1і spy2, які можуть вирішити цю проблему якнайвищо M.

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

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

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


Рішення означає алгоритм, або програма, яка генерує набір дисісій для кожного (i, j)?
klm123

Не одна програма, а дві. Вони повинні спілкуватися незалежно, як у вашій проблемі.
Джо Z.

3
Оскільки програмам потрібно буде поділитися своїм деревом рішень, чи можемо ми зробити це однією програмою, яка бере аргумент, щоб сказати, який це шпигун?
Пітер Тейлор

Поки ви можете гарантувати, що кожен шпигун спілкується незалежно і між ними немає обміну додатковою інформацією.
Джо Z.

Незалежно я перевірив, що 2535 є інформаційно-теоретичним максимумом для цієї проблеми. Зараз я досить твердо вірю, що жодна програма не може зробити кращого.
nneonneo

Відповіді:


8

C #, M = 2535

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

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

Якщо ви хочете , тест, який завершує менш ніж за хвилину, зміни Nв 16і Mдо 87.

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

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

            var root = BuildDecisionTree(N);

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

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

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

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

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

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

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

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

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

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

                trees = nextTrees;
            }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

                    myTurn = !myTurn;
                }

                _IO.Output(hisValue);
            }
        }
    }

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

Інструкції для користувачів Linux

Вам потрібно mono-cscбуде компілювати (в системах на базі Debian, це в mono-develпакеті) і monoзапустити ( mono-runtimeпакет). Тоді заклики є

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

тощо.


2
Це C #? Я не знаю, як це запустити в Linux.
Джо З.

Весь цей час я думав, що роблю щось не так. Як виявляється, побудова дерева рішень просто займає 30 хвилин ... Для запису це працює у Fedora 20: 1. yum install mono-core(як root). 2. dmcs Puzzle625.cs3.mono Puzzle625.exe --test
Денніс

@ Денніс, я думаю, що JIT Mono не так добре, як Microsoft. У мене є кілька ідей для оптимізації, але я не закінчив їх тестувати.
Пітер Тейлор

Репозиторії Fedora надають версію 2.10.8, яка старше двох років. Можливо, новіші версії швидші. Мені цікаво: скільки часу займає Microsoft?
Денніс

2
Від 30 хвилин до 39 мікросекунд. Ось що я називаю оптимізацією!
Денніс

1

Програма Python Tester

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

Тестова програма ( tester.py):

import sys
import shlex
from subprocess import Popen, PIPE

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

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

MAXSTONES = 26

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

    nstones = MAXSTONES

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

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

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

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

    p1.kill()
    p2.kill()

    return True

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

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

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

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

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

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

Приклад сеансу із сумісним шпигуном1 ( >позначає вхід до шпигуна)

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

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

Приклад програми ( spy.py), для 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.")

Приклад використання:

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

1

Ява, М = 2535

Гаразд, ось моя реалізація. На кожному кроці один шпигун робить рух. Кожен можливий хід представляє діапазон кодів. Шпигун вибирає хід, що відповідає його секретному коду. Оскільки вони кидають більше каменів, діапазон можливих кодів скорочується, поки, врешті-решт, для обох шпигунів залишається можливим лише один код відповідно до їх кроків.

Щоб відновити секретні коди, ви можете відтворити всі рухи та обчислити відповідні діапазони коду. Зрештою, для кожного шпигуна залишається лише один код, тобто секретний код, який він хотів передати.

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

Перший файл реалізує алгоритм Spy. Статична частина попередньо codeCountобчислює таблицю, яка згодом використовується для обчислення кожного ходу. Друга частина реалізує 2 процедури, одна - для вибору кількості каменів, яку потрібно кинути, інша для повторного руху ходу, щоб допомогти відновити секретні коди.

Другий файл широко перевіряє клас Spy. Метод simulateімітує процес. Він використовує клас Spy для генерації послідовності кидків із секретних кодів, а потім реконструює коди з послідовності.

Spy.java

package stackexchange;

import java.util.Arrays;

public class Spy
{
    // STATIC MEMBERS

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

    // STATIC METHODS

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

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

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

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

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

    // MEMBERS

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

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

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

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

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

ThrowingStones.java

package stackexchange;

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

        // ENCODING

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

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

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

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

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

        // DECODING

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

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

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

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

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

}

Для довідки, попередньо обчислений масив codeCount містить такі значення:

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

Це було пов'язано безпосередньо з наборами Tk Пітера Тейлора. Ми маємо:

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

Я не думаю, що це цілком відповідає специфікації без способу запустити двох шпигунів в окремі процеси та спілкуватися з кидками, не надаючи доступу до своїх rangeполів. Але мене дуже заінтригує ваш метод обчислення таблиці. Чи є у вас докази коректності? А вам цікаво співпрацювати над документом, який обговорює проблему та обчислює її рішення?
Пітер Тейлор

Діапазон інших шпигунів - це функція минулих ходів, оскільки він обчислюється методом "повтор". Я вважаю, що це правильно. Таблиця, яку я обчислюю, точно така ж, як ви встановлюєте Tk. Транспонуючи таблицю, обмінюється x і y, сума - це сума всіх можливих дітей з вузла. Я не довів це правильно, за винятком того, що перевірив це на 22 камені. Я спробував написати належну відповідь для puzzling.stackexchange, але мені не вдалося пояснити це чітко переконливо. І здебільшого, це те, що ви вже зробили.
Флоріан F

Добре. Я, мабуть, не маю часу на цьому тижні, але коли я менш зайнятий, я спробую знайти доказ того, що ваш метод покоління виробляє ту саму таблицю, що і моя, тому що я думаю, що це було б гарним доповненням до речей, які я ' вже написано.
Пітер Тейлор

Насправді це досить просто: її еквівалентність моєму методу обчислення зводиться до леми про те, що кон'югат мультисесійного об'єднання двох розділів дорівнює точкової сумі їх кон'югатів.
Пітер Тейлор

(плескаючи головою) Але звичайно! Чому я не думав про це раніше? :-)
Флоріан F

0

кш / зш, М = 126

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

Набір чисел не є суміжним. Від 0 до 126, але 127 немає. (Якщо обох шпигунів 127, їм потрібно 28 каменів, але у них 26 каменів. Тоді 128 - 158 є, 159 - поза, 160 - 174, - 175, 176 - 182, 183 - поза, 184 - 186 знаходиться в, 187 - поза, і так далі.

Запустіть автоматичну заміну на ksh spy.sh 125 126або запускайте окремих шпигунів із ksh spy.sh spy1 125та ksh spy.sh spy2 126. Тут kshможуть бути ksh93, pdksh або zsh.

EDIT 14 червня 2014: Виправлення проблеми з деякими спільними процесами в zsh. Вони будуть простоювати назавжди і не вийти, поки користувач не вбив їх.

(( stones = 26 ))

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    (( stones )) || break

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

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

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

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

if $pass
then
    print "SUCCESS: spy1 got $report1, spy2 got $report2."
    exit 0
else
    exit 1
fi
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.