Problema de doze moedas

14

fundo

O problema das doze moedas é um quebra-cabeça clássico da balança, comumente usado em entrevistas de emprego. O quebra-cabeça apareceu pela primeira vez em 1945 e foi colocado no meu pai por meu avô quando ele pediu para casar com minha mãe! No quebra-cabeça, existem doze moedas, uma das quais é mais pesada ou mais leve que as outras (você não sabe qual). O problema é usar uma balança escalada três vezes para determinar a moeda única. Em algumas variações, também é necessário identificar se a moeda é mais pesada ou mais leve.

A tarefa aqui envolve resolver o problema geral que envolve n moedas, usando o menor número possível de pesagens no pior dos casos. Não é necessário identificar se a moeda é mais pesada ou mais leve, apenas qual é. Além disso, você não tem acesso a moedas adicionais fora do conjunto especificado (o que, curiosamente, faz a diferença).

Acontece que k pesagens são suficientes para até (3 ^ k-1) / 2 moedas (portanto, 4 pesagens nessa variação podem realmente lidar com 13 moedas). Além disso (e surpreendentemente), é possível (mas não exigido aqui) selecionar antecipadamente o conjunto completo de pesagens, em vez de as pesagens futuras dependerem dos resultados anteriores. Para descrições de duas soluções possíveis, consulte este documento e esta resposta do Quora .

Tarefa

Escreva uma função ou programa, usando um número inteiro n como entrada via STDIN, argumento de linha de comando ou argumento de função, que resolve o problema de n moedas usando o menor número possível de pesagens no pior dos casos. O programa deve:

  • Imprima as pesagens para STDOUT no formato 1,2,3-4,5,6para indicar as listas de moedas em cada lado da balança. Quaisquer moedas que não estejam sendo pesadas não devem ser mencionadas. As moedas são numeradas implicitamente de 1 a n e não precisam ser impressas em ordem numérica ( 2,1-3,4o mesmo ocorre com 1,2-3,4).
  • Após cada pesagem, o programa deve aguardar uma entrada via STDIN, que deve ser <, =ou >, indicando se o lado esquerdo da balança é mais claro, igual ou mais pesado que o lado direito.
  • Após o último resultado da pesagem, o programa deve imprimir ou retornar o número da moeda única.
  • O programa não precisa manipular entradas de resultados inconsistentes do usuário.
  • O programa não precisa manipular n menos de 3.

Saídas de exemplo

>> 3
1-2
>> =
1-3
>> <
3

# using Quora algorithm
>> 13
1,2,3,4-5,6,7,8
>> <
1,2,5-3,4,6
>> >
3-4
>> <
3

# using paper algorithm
>> 13
1,2,3,4-5,6,7,8
>> <
2,6,7,9-3,8,10,11
>> >
6,8,10,12-4,5,7,11
>> =
3

Pontuação

O menor código vence. Aplicam-se regras padrão.

Uri Granta
fonte

Respostas:

2

Python 3: 497 bytes

I=lambda a,b:input(",".join(a)+"-"+",".join(b)+"\n>> ")
def A(a,b):
 l,L=len(a),len(b)
 if l+L==1:return(a or b)[0]
 m=(2*l+1-L)//3;M=m+L-l;x,y,X,Y=a[:m],a[m:2*m],b[:M],b[M:2*M];r=I(x+X,y+Y);return A(a[2*m:],b[2*M:])if r=="="else A(x,Y)if r=="<"else A(y,X)
def B(a,n=[]):
 if len(a)==1:return a[0]
 m=len(n);l=(len(a)+1+m)//3;x,y,z=a[:l],a[l:2*l-m],a[2*l-m:];r=I(x,y+n);return B(z,a[:1])if r=="="else A(x+z[:1-m],y)if r=="<"else A(y+z[:1-m],x)
print(B(list(map(str,range(1,int(input("N= "))+1)))))

Suspeito que isso possa ter diminuído um pouco mais, mas não vejo mais lugares óbvios (após cerca de 5 versões diferentes de cada função).

O código implementa uma versão ligeiramente modificada do algoritmo desta página usando três funções. A Ifunção executa o IO (imprimindo as opções e retornando a resposta do usuário). As funções Ae Bimplementam o principal do algoritmo. Ausa duas listas que diferem em tamanho por exatamente um elemento (embora uma das listas possa ser a maior): uma moeda apode ser mais leve que o normal ou uma moeda bpode ser mais pesada. Bfaz duplo dever. É necessária uma lista de moedas ae, opcionalmente, uma segunda lista com uma única moeda conhecida como o peso correto. O comportamento de arredondamento do comprimento precisa ser diferente entre os dois casos, o que não causou um fim às dores de cabeça.

As duas funções do algoritmo podem encontrar a moeda com kpeso incomum em pesagens, considerando entradas de até os seguintes tamanhos:

  • A: 3^ktotal de moedas, divididas em duas listas de (3^k-1)/2e (3^k+1)/2.
  • B: (3^k + 1)/2moedas se uma moeda em bom estado for fornecida, (3^k - 1)/2 caso contrário.

A questão colocada aqui especifica que não tem nenhum conhecido de boa moedas no início, para que possamos resolver encontrar a má moeda em um conjunto de (3^k - 1)/2em kpesagens.

Aqui está uma função de teste que escrevi para garantir que meu código não solicitasse pesagens falsas ou usasse mais do que o número de pesagens que deveria:

def test(n):
    global I
    orig_I = I
    try:
        for x in range(3,n+1):
            max_count = 0
            for y in range(x*2):
                count = 0
                def I(a, b):
                    assert len(a) == len(b), "{} not the same length as {}".format(a,b)
                    nonlocal count
                    count += 1
                    if y//2 in a: return "<"if y%2 else ">"
                    if y//2 in b: return ">"if y%2 else "<"
                    return "="
                assert B(list(range(x)))==y//2, "{} {} not found in size {}".format(['heavy','light'][y%2], y//2+1, x)
                if count > max_count:
                    max_count = count
            print(x, max_count)
    finally:
        I = orig_I

Isso imprime o pior número de pesagens para um determinado conjunto após o teste com cada combinação de moeda e peso ruim (pesado ou leve).

Aqui está a saída de teste para conjuntos de até 125:

>>> test(150)
3 2
4 2
5 3
6 3
7 3
8 3
9 3
10 3
11 3
12 3
13 3
14 4
15 4
16 4
17 4
18 4
19 4
20 4
21 4
22 4
23 4
24 4
25 4
26 4
27 4
28 4
29 4
30 4
31 4
32 4
33 4
34 4
35 4
36 4
37 4
38 4
39 4
40 4
41 5
42 5
43 5
44 5
45 5
46 5
47 5
48 5
49 5
50 5
51 5
52 5
53 5
54 5
55 5
56 5
57 5
58 5
59 5
60 5
61 5
62 5
63 5
64 5
65 5
66 5
67 5
68 5
69 5
70 5
71 5
72 5
73 5
74 5
75 5
76 5
77 5
78 5
79 5
80 5
81 5
82 5
83 5
84 5
85 5
86 5
87 5
88 5
89 5
90 5
91 5
92 5
93 5
94 5
95 5
96 5
97 5
98 5
99 5
100 5
101 5
102 5
103 5
104 5
105 5
106 5
107 5
108 5
109 5
110 5
111 5
112 5
113 5
114 5
115 5
116 5
117 5
118 5
119 5
120 5
121 5
122 6
123 6
124 6
125 6

Os pontos de interrupção são exatamente onde você esperaria, entre (3^k - 1)/2e (3^k + 1)/2.

Blckknght
fonte