Computar a função binária mais eficiente

13

Hoje, calcularemos a função binária mais eficiente. Mais especificamente, calcularemos a função que, quando uma expressão é criada a partir da aplicação da função na entrada constante 0 ou em sua própria saída, pode representar todos os números inteiros positivos com as expressões mais curtas possíveis, colocando maior prioridade nos números inteiros menores.

Esta função é criada da seguinte maneira:

Para cada número inteiro, começando em 1 e subindo, escolha a expressão mais curta à qual ainda não atribuímos uma saída e torne esse número inteiro a saída dessa expressão. Os laços no comprimento da expressão serão quebrados pelo argumento menor à esquerda e depois pelo argumento menor à direita. Veja como funciona:

  • Inicialmente, 1 não está atribuído. A expressão mais curta não atribuída é f(0, 0), portanto, definiremos isso como 1.

  • Agora, 2 não está atribuído. As expressões mais curtas não atribuídas são f(f(0, 0), 0)= f(1, 0)e f(0, f(0, 0))= f(0, 1). Os laços são quebrados para um argumento menor à esquerda, então f(0, 1) = 2.

  • A expressão mais curta não atribuída restante é f(f(0, 0), 0)= f(1, 0), então f(1, 0) = 3.

  • Agora, estamos sem expressões com apenas 2 se f3 0s, então teremos que adicionar mais uma de cada. Rompendo laços com o argumento da esquerda, depois o argumento da direita, entendemos f(0, 2) = 4desde então f(0, f(0, f(0, 0))) = f(0, f(0, 1)) = f(0, 2).

  • Continuando, temos f(0, 3) = 5, f(1, 1) = 6, f(2, 0) = 7, f(3, 0) = 8, f(0, 4) = 9, ...

Aqui está uma tabela que preenchi para os primeiros valores:

    0  1  2  3  4  5  6  7  8
 /---------------------------
0|  1  2  4  5  9 10 11 12 13
1|  3  6 14 15 37 38 39 40 41
2|  7 16 42 43
3|  8 17 44 45
4| 18 46
5| 19 47
6| 20 48
7| 21 49
8| 22 50

Outra maneira de ver é que cada saída tem um tamanho igual à soma dos tamanhos de suas entradas mais um. A tabela é preenchida em ordem crescente de tamanho de saída, empates quebrados minimizando a entrada esquerda e a entrada direita.

Seu desafio é, considerando dois números inteiros não negativos como entrada, calcule e produza o valor dessa função. Isso é código de golfe. A solução mais curta, em bytes, vence. As brechas padrão são proibidas.

isaacg
fonte
Parece um A072766 , mas difere de f (3, 1).
kennytm
2
Este é o primeiro desafio em um tempo que me intriga a calcular de maneira eficiente. Acredito que algo é possível com os números catalães, mas não consigo pensar imediatamente em uma solução. Hmm ...
orlp 20/03/2019
2
Tudo bem, então eu não acho que seria uma boa resposta, mas o que você pode fazer para torná-la razoavelmente eficiente é subtrair repetidamente os números catalães dos argumentos da função até que eles sejam menores que o próximo número catalão. Então você encontrou o comprimento de suas expressões. Em seguida, você pode usar as funções de classificação / não classificação deste documento , com modificação, para calcular o resultado. Talvez depois de fazer tudo isso, seja possível 'cancelar' bits de código no meio e encontrar uma solução razoavelmente elegante.
orlp
Na verdade, a abordagem do meu comentário anterior não funciona. ((0, (0, (0, 0))), 0)é lexicograficamente menor que (((0, 0), 0), (0, 0)), no entanto, este último tem um lado esquerdo menor.
Ou orp

Respostas:

6

Haskell, 110 bytes

f q=head[i|let c=[(-1,0)]:[[(f a,f b)|n<-[0..k],a<-c!!n,b<-c!!(k-n)]|k<-[0..]],(p,i)<-zip(concat c)[0..],p==q]

O argumento aqui é considerado a tupla (x,y). Bem parecido com a resposta acima, mas a lista de pesquisa contém apenas os pares de índices esquerdo e direito em vez das árvores.

halfflat
fonte
1
Boa resposta! head[...]é [...]!!0e (p,i)<-zip(concat c)[0..]pode ser reduzido para (i,p)<-zip[0..]$id=<<c.
Laikoni 22/03/19
Obrigado pelas melhorias! Definitivamente adicionando id=<<ao repertório :)
halfflat
5

Python 3, 154 bytes

b=lambda n:[(l,r)for k in range(1,n)for l in b(k)for r in b(n-k)]+[0]*(n<2)
def f(x,y):r=sum((b(n)for n in range(1,x+y+3)),[]);return r.index((r[x],r[y]))

Não é muito rápido nem muito golfe, mas é um começo.

orlp
fonte
5

Uau! Na verdade, eu consegui criar um algoritmo de computação eficiente. Eu não esperava isso no começo. A solução é bastante elegante. Deduz repetidamente mais e mais, depois retorna até o caso base de 0. Nesta resposta, a função C (n) indica os números catalães .

O primeiro passo crucial é reconhecer que existem valores C (0) = 1 de comprimento zero (ou seja, o próprio 0), C (1) = 1 valores de comprimento um (ou seja, f (0, 0)), C (2) = 2 valores de comprimento dois (f (0, f (0, 0)) ef (f (0, 0), 0)).

Isso significa que, se estamos procurando a enésima expressão, e encontramos a maior k, de modo que C (0) + C (1) + ... + C (k) <= n, então sabemos que n tem comprimento k .

Mas agora podemos continuar! Porque a expressão que procuramos é a expressão n - C (0) - C (1) - ... - C (k) na sua classe de comprimento.

Agora podemos usar um truque semelhante para encontrar o comprimento do segmento esquerdo e a classificação nessa subseção. E depois recuar sobre as classificações que encontramos!

Ele descobriu que f (5030, 3749) = 1542317211 em um piscar de olhos.

Python, não-competitivo

def C(n):
    r = 1
    for i in range(n):
        r *= 2*n - i
        r //= i + 1
    return r//(n+1)

def unrank(n):
    if n == 0: return 0

    l = 0
    while C(l) <= n:
        n -= C(l)
        l += 1

    right_l = l - 1
    while right_l and n >= C(l - 1 - right_l) * C(right_l):
        n -= C(l - 1 - right_l) * C(right_l)
        right_l -= 1

    right_num = C(right_l)

    r_rank = n % right_num
    l_rank = n // right_num

    for sz in range(l - 1 - right_l): l_rank += C(sz)
    for sz in range(right_l): r_rank += C(sz)

    return (unrank(l_rank), unrank(r_rank))

def rank(e):
    if e == 0: return 0
    left, right = e

    l = str(e).count("(")
    left_l = str(left).count("(")
    right_l = str(right).count("(")
    right_num = C(right_l)

    n = sum(C(sz) for sz in range(l))
    n += sum(C(sz)*C(l - 1 - sz) for sz in range(left_l))

    n += (rank(left) - sum(C(sz) for sz in range(left_l))) * C(right_l)
    n += rank(right) - sum(C(sz) for sz in range(right_l))

    return n

def f(x, y):
    return rank((unrank(x), unrank(y)))

Estou bastante certo de que estou fazendo um monte de cálculos desnecessários e muitos passos intermediários podem ser removidos.

orlp
fonte