Énumérer les schémas de rimes

26

Un "schéma de rimes" est une chaîne de lettres aà z, de sorte que les premières occurrences des caractères soient dans l'ordre croissant (sans espaces), à partir de a. Par exemple (avec les premières occurrences marquées):

abccdbebdcfa
^^^ ^ ^   ^

Le nombre de schémas de rimes de longueur Nest donné par les nombres de Bell B(N) . ( OEIS A000110 )

Le défi

Votre tâche consiste à implémenter une énumération de ces schémas de rimes, c'est-à-dire un mappage bijectif d'entiers aux schémas de rimes. On vous donne un entier positif N <= 26, ainsi qu'un entier non négatif 0 <= i < B(N). Vous pouvez également utiliser la plage 1 <= i <= B(N). Vous devez sortir un schéma de rimes de longueur N, de sorte que chaque idonne une chaîne différente.

Vous pouvez écrire un programme ou une fonction, en prenant une entrée via STDIN (ou l'alternative la plus proche), un argument de ligne de commande ou un argument de fonction et en sortant le résultat via STDOUT (ou l'alternative la plus proche), une valeur de retour de fonction ou un paramètre de fonction (out).

Vous pouvez utiliser des minuscules ou des majuscules (systématiquement).

Votre code doit être capable de gérer toute entrée valide dans un délai raisonnable (par exemple pas plus de quelques heures pour N = 26, le pire des cas i). Cela devrait permettre des solutions qui évoluent de façon exponentielle avec N(pour les petites bases), même dans des langages lents, mais interdire les solutions qui évoluent de façon linéaire avec i(c'est-à-dire B(N)). En particulier, cela signifie que vous ne pouvez pas simplement parcourir tous les schémas de rimes valides Njusqu'à ce que vous ayez supprimé les ischémas.

Les règles de standard s'appliquent.

Exemples

L'affectation exacte des ischémas à (c'est-à-dire l'ordre des schémas pour une donnée N) dépend de vous. Mais supposons que vous ayez choisi l'ordre lexicographique, votre solution devrait correspondre au tableau suivant (avec -une entrée non valide):

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

Voici un court script CJam qui génère tous les schémas de rimes valides pour une longueur donnée (mais n'essayez pas plus de 10 ou vous attendrez un peu).

Défis liés

Martin Ender
la source
5
Je pourrais mettre une prime sur une solution de temps polynomial (bien gérée) (en N), à condition que cela ne se révèle pas assez trivial et que j'étais tout simplement trop stupide pour la trouver.
Martin Ender
Bien que la prime soit versée pour les solutions temporelles polynomiales, j'aimerais toujours voir des solutions temporelles exponentielles qui respectent la limite de temps. (Ma propre implémentation de référence Mathematica gagnerait toujours le défi.)
Martin Ender
B (26) est le plus petit nombre de Bell qui ne rentre pas dans un entier 64 bits. Meanie. :-(
Anders Kaseorg

Réponses:

3

CJam, 68 66 octets

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

Essayez-le en ligne.

Ceci est mon premier programme CJam. Il a commencé sa vie en tant que port de ma solution Perl et faisait initialement plus de 130 octets. D'autres suggestions de golf sont les bienvenues.

Comme avec mon programme Perl, il est en deux parties.

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)

Pour déboguer les tableaux créés par add Partie 1 ]_`o~entre les parties 1 et 2. Si n est 5, les tableaux ressemblera à ceci: [[1 1 1 1 1 1] [1 2 3 4 5] [2 5 10 17] [5 15 37] [15 52]]. Les 0 indices de chaque tableau ne sont pas utilisés, ils facilitent simplement le fait de ne pas avoir à calculer les décalages. Les tableaux sont calculés comme suit:

[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]

Il conserve une copie de l'ancien tableau lors du calcul du suivant. Les tableaux sont lus et jetés dans l'ordre inverse par la partie 2.

CJ Dennis
la source
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)

Il utilise l'ordre alphabétique et l'indexation basée sur 0.

Soit lla longueur d'un suffixe des lettres et ale nombre de lettres distinctes utilisées dans la partie précédente. Ensuite, une fonction p(l,a)qui calcule le nombre de façons de sélectionner les lettres restantes pourrait être de 40 octets:

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

Cependant, cela est trop lent pour le défi, donc à la place, les valeurs nécessaires sont précalculées et stockées dans le utableau. A chaque étape du calcul, si la lettre suivante est celle des adéjà utilisées, le n = k * p (l - 1, a) + n 'k est la lettre indexée 0 de l'alphabet et n' est la valeur de npour le prochain appel de fonction, qui contient les informations sur les lettres restantes. Si une nouvelle lettre est utilisée, alors n = a * p (l - 1, a) + n ' .

feersum
la source
1
Combien de temps faut-il pour une entrée dans le pire des cas?
Michael Klein
1
@MichaelKlein Un temps négligeable.
feersum
C'est exactement ce que j'avais l'intention de faire (sauf que je l'aurais fait avec JS). Bon travail! +1
ETHproductions
11

Haskell (GHC 7.10), 150 octets

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

L'opérateur n # icalcule le ischéma de rimes de longueur (indexé zéro) n. Il s'exécute en opérations O (n²) (grand entier), tirant parti des listes infinies paresseuses de Haskell pour la mémorisation automatique. Exemples de cycles:

*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"]

(Si le N maximum était de 25 au lieu de 26, le .fromEnumpourrait être supprimé, car B (25) tient dans un 64 bits Int.)

Anders Kaseorg
la source
1
Ça a l'air super. Pourriez-vous ajouter une version moins golfée pour un grokking plus facile?
Michael Klein
4

Perl 257 + 1 (indicateur -p) = 258

Perl 182 + 10 (drapeaux -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}

Merci à dev-nul d' avoir sauvé de nombreux octets! Je l'ai maintenant réécrit sur la base de ce que j'ai appris en faisant la version CJam.

Calcule la rime en ordre alphabétique croissant, 0 indexé.

Deux parties: la partie 1 fait 128 90 octets et calcule une matrice pour la partie 2. La partie 2 fait 129 92 octets et fait quelques calculs simples pour calculer chaque lettre. Si je pouvais me débarrasser de la matrice et la remplacer par deux nombres simples, je pourrais calculer un seul chemin à travers la matrice pour chaque numéro et économiser beaucoup d'octets! Apparemment, cette idée ne fonctionne pas!

Malheureusement, il ne produit pas les bonnes rimes pour les valeurs isupérieures à 9007199254740992, mais il fonctionne à merveille pour les valeurs faibles! J'ai ajouté la bibliothèque Bignum au coût de 11 octets. Il est exécuté à partir de la ligne de commande avec perl -pMbignum bell-rhyme.pl. -pMbignum = 10 octets. Il est également très rapide pour toute valeur d'entrée.

CJ Dennis
la source
2

Oracle SQL 11.2, 412 284 283 octets

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;

Malheureusement, il ne court que jusqu'à une longueur de 8. Toute valeur supérieure entraîne: ORA-01489: le résultat de la concaténation de chaînes est trop long

Non golfé

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;

La vue a génère les: i lettres de la colonne a et leur valeur en b.

La vue récursive v prend la chaîne en cours de construction comme paramètre v, la valeur de la dernière lettre utilisée en c et la valeur de la plus grande lettre utilisée en n. Le paramètre n est égal à la longueur de la chaîne sans lettre en double, c'est à cela que sert l'expression rationnelle.

Une lettre est valide si sa valeur est <= la valeur de la plus grande lettre déjà utilisée ou si c'est la prochaine lettre à utiliser.

D'une manière ou d'une autre, la requête a besoin de la partie LONGUEUR (s) <: n pour s'exécuter, il doit manquer quelque chose dans le fonctionnement de la requête.

Le SELECT principal se charge de filtrer les entrées invalides et les chaînes plus courtes construites avant que la longueur cible ne soit atteinte.

Version 412 octets

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'essayez pas la requête de 412 octets avec 26. Elle met la base de données en mode restreint, au moins sur ma version xe exécutée dans un conteneur docker sur un macbook. Je pourrais essayer les exadata au travail, mais malheureusement, je dois encore travailler pour gagner ma vie.

Jeto
la source
0

Mathematica, 136 octets

(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])&

Pour être complet, voici mon implémentation de référence golfée. Contrairement aux réponses existantes, cela ne s'exécute pas en temps polynomial (il est exponentiel Navec la base 2) mais répond à la contrainte de temps (le pire des cas s'exécuterait encore en moins d'une demi-heure).

L'idée est la suivante:

  • Pour chaque schéma de rimes, nous pouvons identifier les positions où le caractère maximal augmente jusqu'à présent:

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

    Nous pouvons traiter ces marquages ​​comme un nombre binaire qui facilite l'itération sur toutes ces structures. Nous devons itérer de 2 n-1 à 2 n (ou l'inverse), d'où la complexité temporelle exponentielle.

  • Pour chacune de ces structures, il est facile de déterminer le nombre de chaînes de ce type: seuls les espaces entre les marquages ​​peuvent être librement choisis, et le maximum devant l'espace nous indique combien de caractères différents sont valides dans chaque position. Ceci est un produit simple. Si ce nombre est inférieur à i, nous le soustrayons i. Sinon, nous avons trouvé la structure du schéma de rimes demandé.
  • Pour énumérer les schémas dans la structure donnée, nous représentons simplement i(ou ce qui en reste) comme un nombre à base mixte, où les poids des chiffres sont déterminés par le nombre de caractères autorisés dans les positions restantes.

Je me demande si cela permettrait une solution plus courte dans certaines des autres langues qui ont été soumises car cela ne nécessite aucune mémoisation ou précalcul.

Martin Ender
la source