Enumerar esquemas de rima

26

Um "esquema de rima" é uma sequência de letras apara z, de modo que as primeiras ocorrências dos caracteres estejam em ordem crescente (sem lacunas), a partir de a. Por exemplo (com as primeiras ocorrências marcadas):

abccdbebdcfa
^^^ ^ ^   ^

O número de esquemas de rima de comprimento Né dado pelos números de Bell B(N) . ( OEIS A000110 )

O desafio

Sua tarefa é implementar uma enumeração desses esquemas de rima, ou seja, um mapeamento bijetivo de números inteiros para esquemas de rima. Você recebe um número inteiro positivo e um número inteiro N <= 26não negativo 0 <= i < B(N). Como alternativa, você pode usar o intervalo 1 <= i <= B(N). Você deve produzir um esquema de rima de comprimento N, de modo que cada ium produza uma sequência diferente.

Você pode escrever um programa ou função, recebendo entrada via STDIN (ou alternativa mais próxima), argumento da linha de comando ou argumento da função e emitindo o resultado via STDOUT (ou alternativa mais próxima), valor de retorno da função ou parâmetro da função (saída).

Você pode usar letras maiúsculas ou minúsculas (de forma consistente).

Seu código deve ser capaz de lidar com qualquer entrada válida em quantidade razoável de tempo (por exemplo, não mais do que algumas horas para N = 26, pior caso i). Isso deve permitir soluções que escalam exponencialmente com N(para pequenas bases), mesmo em linguagens lentas, mas proíbem soluções que escalam linearmente com i(ie B(N)). Em particular, isso significa que você não pode simplesmente repetir todos os esquemas de rima válidos Naté ter descartado os iesquemas.

Aplicam-se as regras de padrão .

Exemplos

A atribuição exata dos iesquemas a (isto é, a ordem dos esquemas para um determinado N) depende de você. Mas digamos que você escolheu a ordem lexicográfica, sua solução deve corresponder à seguinte tabela (com a -indicação de entrada inválida):

N\i 1    2    3    4    5    6    7    8    9    10   11   12   13   14   15
1   a    -    -    -    -    -    -    -    -    -    -    -    -    -    -
2   aa   ab   -    -    -    -    -    -    -    -    -    -    -    -    -
3   aaa  aab  aba  abb  abc  -    -    -    -    -    -    -    -    -    -
4   aaaa aaab aaba aabb aabc abaa abab abac abba abbb abbc abca abcb abcc abcd

Aqui está um pequeno script CJam que gera todos os esquemas de rima válidos para um determinado comprimento (mas não tente mais de 10 ou você esperará um pouco).

Desafios relacionados

Martin Ender
fonte
5
Eu poderia colocar uma recompensa em uma solução de tempo polinomial (bem-golfe) (in N), desde que isso não seja razoavelmente trivial e eu seja estúpido demais para encontrá-la.
Martin Ender
Embora a recompensa seja por soluções de tempo polinomial, eu ainda gostaria de ver soluções de tempo exponencial que atendem ao limite de tempo. (Atualmente, minha própria implementação de referência do Mathematica ainda venceria o desafio).
Martin Ender
B (26) é o menor número de Bell que não cabe em um número inteiro de 64 bits. Meanie. :-(
Anders Kaseorg

Respostas:

3

CJam, 68 66 bytes

r~:W)1a*{__(;\);_,,.*.+}W(*r~{X@\=_2$\/:CX<!{X:C):X;}&C*-C'a+o}W*;

Experimente online.

Este é o meu primeiro programa CJam. Começou a vida como uma porta da minha solução Perl e tinha inicialmente mais de 130 bytes de comprimento. Mais sugestões de golfe são bem-vindas.

Como no meu programa Perl, ele é dividido em duas partes.

Part 1:
r~:W                                         | Read the first input (n) and store it in W
    )1a*                                     | Create an array of n+1 1s
        {              }W(*                  | Repeat n-1 times:
         __                                  | Duplicate array twice
           (;\);                             | Remove first element of 1st array. Swap
                                             | arrays. Remove last element of 2nd array
                _,,                          | Duplicate array. Count items. Create range
                   .*.+                      | Multiply arrays. Add 1st array to result

Part 2:
r~                                           | Read the second input (i)
   {                                  }W*    | Repeat n times:
    X@                                       | Push y (initially 1). Bring item 2 (last array) to top
     \=                                      | Swap top two items. Pop array[y] (v)
       _2$                                   | Duplicate v. Copy item 2 (i) to top
          \/:CX                              | Swap i & v. i/v. Store in C (c). Push y
               <!{       }&                  | If !(i/v < c):
                  X:C):X;                    | c = y. ++y (store in X)
                           C*-C'a+o          | i -= c * v. Push y. Push "a". Add c. Print
                                         ;   | Discard top item (integer 0)

Para depurar as matrizes criadas pela Parte 1 add ]_`o~entre Parts 1 & 2. Se n é 5, as matrizes será parecido com este: [[1 1 1 1 1 1] [1 2 3 4 5] [2 5 10 17] [5 15 37] [15 52]]. Os índices 0 de cada matriz não são usados, apenas facilitam o cálculo de compensações. As matrizes são calculadas assim:

[2 5 10 17] [2 5 10 17] [2 5 10 17]        | Duplicate twice
[2 5 10 17] [2 5 10 17] [5 10 17]          | Discard first item of array
[2 5 10 17] [5 10 17] [2 5 10 17]          | Swap last two arrays
[2 5 10 17] [5 10 17] [2 5 10]             | Discard last item of array
[2 5 10 17] [5 10 17] [2 5 10] [2 5 10]    | Duplicate array
[2 5 10 17] [5 10 17] [2 5 10] 3           | Count items in array
[2 5 10 17] [5 10 17] [2 5 10] [0 1 2]     | Integer to range 0 - n-1
[2 5 10 17] [5 10 17] [0 5 20]             | Multiply arrays [2*0 5*1 10*2]
[2 5 10 17] [5 15 37]                      | Add arrays [5+0 10+5 17+20]

Ele mantém uma cópia da matriz antiga enquanto calcula a próxima. As matrizes são lidas e descartadas na ordem inversa pela Parte 2.

CJ Dennis
fonte
13

Python 2, 153

u=[1]*999;i=60;exec"u[i]=i%30*u[i-30]+u[i-29];i+=1;"*900
def x(l,n,a=0):m=u[30*l+a];c=n>=a*m;return'.'*l and chr(65+min(n/m,a))+x(l-1,[n%m,n-m*a][c],a+c)

Ele usa ordem alfabética e indexação baseada em 0.

Vamos ldenotar o comprimento de um sufixo das letras e ao número de letras distintas que foram usadas na parte anterior. Em seguida, uma função p(l,a)que calcula o número de maneiras de selecionar as letras restantes pode ter 40 bytes:

p=lambda l,a:l<1or a*p(l-1,a)+p(l-1,a+1)

No entanto, isso é muito lento para o desafio; portanto, os valores necessários são pré-calculados e armazenados na umatriz. Em cada estágio do cálculo, se a próxima letra for a ajá usada, n = k * p (l - 1, a) + n ' onde k é a letra do alfabeto indexada em 0 e n' é o valor de npara a próxima chamada de função, que contém as informações sobre as letras restantes. Se uma nova letra for usada, então n = a * p (l - 1, a) + n ' .

feersum
fonte
1
Quanto tempo leva para a entrada do pior caso?
Michael Klein
1
@MichaelKlein Uma quantidade insignificante de tempo.
feersum
Isso é exatamente o que eu estava planejando fazer (exceto que eu teria feito isso com JS). Bom trabalho! +1
ETHproductions 06/02
11

Haskell (GHC 7.10), 150 bytes

s=(1,\_->[]):s
k!((y,b):l@((x,a):_))|let h i|i<x=k:a i|(p,q)<-divMod(i-x)y=p:b q=(x+k*y,h):(k+1)!l
n#i=(['a'..]!!).fromEnum<$>snd(iterate(0!)s!!n!!0)i

O operador n # icalcula o ith esquema de rima (indexado a zero) de comprimento n. É executado em operações O (n²) (número inteiro grande), aproveitando as listas infinitas e preguiçosas de Haskell para memorização automática. Amostras de execuções:

*Main> 26 # 0
"abcdefghijklmnopqrstuvwxyz"
*Main> 26 # 1
"abcdefghijklmnopqrstuvwxya"
*Main> 26 # 2
"abcdefghijklmnopqrstuvwxyb"
*Main> 26 # 49631246523618756271
"aaaaaaaaaaaaaaaaaaaaaaaabb"
*Main> 26 # 49631246523618756272
"aaaaaaaaaaaaaaaaaaaaaaaaab"
*Main> 26 # 49631246523618756273
"aaaaaaaaaaaaaaaaaaaaaaaaaa"
*Main> [1 # i | i <- [0..0]]
["a"]
*Main> [2 # i | i <- [0..1]]
["ab","aa"]
*Main> [3 # i | i <- [0..4]]
["abc","aba","abb","aab","aaa"]
*Main> [4 # i | i <- [0..14]]
["abcd","abca","abcb","abcc","abac","abaa","abab","abbc","abba","abbb","aabc","aaba","aabb","aaab","aaaa"]

(Se o N máximo fosse 25 em vez de 26, .fromEnumpoderia ser removido, porque B (25) se encaixa em um de 64 bits Int.)

Anders Kaseorg
fonte
1
Parece ótimo. Você se importaria de adicionar uma versão menos golfe para grokking mais fácil?
Michael Klein
4

Perl 257 + 1 (-p sinalizador) = 258

Perl 182 + 10 (sinalizadores -pMbignum) = 192

($n,$i)=split;@m=[@a=(1)x($n+1)];while($a[2]){push@m,[@a=map{$a[$_]*$_+$a[$_+1]}0..$#a-1]}$_='';$y=1;while($w=pop@m){$c=int($i/($v=$$w[$y]));$c=$y++if($c>=$y);$i-=$c*$v;$_.=chr$c+65}

Obrigado ao dev-nul por salvar muitos bytes! Eu o reescrevi com base no que aprendi com a versão do CJam.

Calcula a rima em ordem alfabética crescente, 0 indexada.

Duas partes: a Parte 1 tem 128 90 bytes e calcula uma matriz para a Parte 2. A Parte 2 tem 129 92 bytes e faz algumas contas simples para calcular cada letra. Se eu conseguisse me livrar da matriz e substituí-la por dois números simples, poderia calcular um único caminho através da matriz para cada número e economizar muitos bytes! Aparentemente, essa ideia não funciona!

Infelizmente, não produz as rimas certas para valores isuperiores a 9007199254740992, mas funciona lindamente para valores baixos! Eu adicionei a biblioteca Bignum a um custo de 11 bytes. É executado a partir da linha de comando com perl -pMbignum bell-rhyme.pl. -pMbignum = 10 bytes. Também é muito rápido para qualquer valor de entrada.

CJ Dennis
fonte
2

Oracle SQL 11.2, 412 284 283 bytes

WITH a AS(SELECT CHR(96+LEVEL)d,LEVEL b FROM DUAL CONNECT BY LEVEL<=:i),v(s,c,n)AS(SELECT d,1,1 FROM a WHERE b=1 UNION ALL SELECT s||d,b,LENGTH(REGEXP_REPLACE(s||d,'([a-z])\1+','\1'))FROM v,a WHERE(b<=n OR b=c+1)AND LENGTH(s)<:n)SELECT s FROM v WHERE:n=LENGTH(s)AND:i<=:n ORDER BY 1;

Infelizmente, ele só executa até um comprimento de 8. Qualquer valor maior resulta em: ORA-01489: o resultado da concatenação de strings é muito longo

Sem golfe

WITH a AS(SELECT CHR(96+LEVEL)d,LEVEL b FROM DUAL CONNECT BY LEVEL<=:i),
v(s,c,n) AS
(
  SELECT d,1,1 FROM a WHERE b=1
  UNION ALL
  SELECT s||d,b,LENGTH(REGEXP_REPLACE(s||d,'([a-z])\1+','\1')) 
  FROM v,a 
  WHERE (b<=n OR b=c+1) AND LENGTH(s)<:n
)
SELECT s FROM v WHERE LENGTH(s)=:n AND :i<=:n ORDER BY 1;

A visualização a gera as letras: i na coluna a e seu valor em b.

A visualização recursiva v assume a cadeia que está sendo construída como parâmetro v, o valor da última letra usada em ce o valor da maior letra usada em n. O parâmetro n é igual ao comprimento da string sem nenhuma letra duplicada, é para isso que serve a regex.

Uma letra é válida se seu valor for <= o valor da maior letra já usada ou se for a próxima letra a ser usada.

De alguma forma, a consulta precisa da parte LENGTH (s) <: n para ser executada. Devo estar faltando alguma coisa em como a consulta funciona.

O SELECT principal cuida da filtragem das entradas inválidas e das seqüências mais curtas construídas antes que o comprimento desejado seja atingido.

Versão de 412 bytes

WITH a AS(SELECT * FROM(SELECT d,b,ROW_NUMBER()OVER(PARTITION BY b ORDER BY d)l FROM(SELECT CHR(64+DECODE(MOD(LEVEL,:i),0,:i,MOD(LEVEL,:i)))d,CEIL(LEVEL/:i)b FROM DUAL CONNECT BY LEVEL<=:i*:n))WHERE l<=b),v(s,c,p)AS(SELECT d,1,l FROM a WHERE b=1 UNION ALL SELECT s||d,c+1,l FROM v,a WHERE c+1=b AND(l<=LENGTH(REGEXP_REPLACE(s,'([A-Z])\1+','\1'))OR l=p+1))SELECT s FROM v WHERE LENGTH(s)=:n AND :i<=:n ORDER BY 1;

Não tente a consulta de 412 bytes com 26. Ele coloca o banco de dados no modo restrito, pelo menos na minha versão xe em execução em um contêiner de docker em um macbook. Eu poderia experimentar os exadados no trabalho, mas, infelizmente, ainda preciso trabalhar para viver.

Jeto
fonte
0

Mathematica, 136 bytes

(For[j=2^#-1;t=#2,c=1;m=0;x=t;r=If[#>0,++m,c*=m;d=x~Mod~m+1;x=⌊x/m⌋;d]&/@j~IntegerDigits~2;;c<=t,t-=c;--j];FromCharacterCode[r+64])&

Para completar, aqui está a minha implementação de referência de golfe. Ao contrário das respostas existentes, isso não é executado no tempo polinomial (é exponencial na Nbase 2), mas atende à restrição de tempo (o pior caso ainda seria executado em menos de meia hora).

A ideia é esta:

  • Para cada esquema de rima, podemos identificar as posições em que o caractere máximo até agora aumenta:

    ABCDEFGHDIJDEKBBIJEIKHDFII
    ^^^^^^^^ ^^  ^
    

    Podemos tratar essas marcações como um número binário, o que facilita a iteração em todas essas estruturas. Precisamos iterar de 2 n-1 a 2 n (ou vice-versa), que é a origem da complexidade exponencial do tempo.

  • Para cada uma dessas estruturas, é fácil determinar quantas seqüências de caracteres existem: somente as lacunas entre as marcações podem ser livremente escolhidas, e o máximo na frente da lacuna nos diz quantos caracteres diferentes são válidos em cada posição. Este é um produto simples. Se esse número for menor que i, subtraímos de i. Caso contrário, encontramos a estrutura do esquema de rima solicitado.
  • Para enumerar os esquemas na estrutura fornecida, simplesmente representamos i(ou o que resta dela) como um número de base mista, onde os pesos dos dígitos são determinados pelo número de caracteres permitidos nas posições restantes.

Eu me pergunto se isso permitiria uma solução mais curta em alguns dos outros idiomas que foram submetidos, uma vez que não requer nenhuma memória ou pré-computação.

Martin Ender
fonte