Перерахуйте схеми римування


26

"Схема рими" - це рядок літер aдо z, таким чином, що перші зустрічі символів відбуваються у порядку зростання (без прогалин), починаючи з a. Наприклад (із позначеними першими появами):

abccdbebdcfa
^^^ ^ ^   ^

Кількість рифмових схем довжини Nзадається цифрами Белла B(N) . ( OEIS A000110 )

Змагання

Ваше завдання - здійснити перерахування цих схем римування, тобто бієктивне відображення від цілих чисел до римових схем. Вам присвоєно додатне ціле число N <= 26, а також невід'ємне ціле число 0 <= i < B(N). Як варіант, ви можете використовувати діапазон 1 <= i <= B(N). Ви повинні вивести схему рифми по довжині N, щоб кожен iдавав різний рядок.

Ви можете написати програму або функцію, взявши введення через STDIN (або найближчу альтернативу), аргумент командного рядка або аргумент функції та вивівши результат через STDOUT (або найближчу альтернативу), значення повернення функції або параметр функції (out).

Ви можете використовувати або малі, або великі літери (послідовно).

Ваш код повинен мати можливість обробляти будь-який дійсний вклад в розумну кількість часу (наприклад, не більше кількох годин, за N = 26найгіршого випадку i). Це повинно дозволяти рішенням, які масштабуються в експоненціальному масштабі N(навіть для невеликих основ), навіть у повільних мовах, але забороняють рішення, які масштабуються лінійно з i(тобто B(N)). Зокрема, це означає, що ви не можете просто повторити всі дійсні рифмові схеми довжини, Nпоки ви не відкинете iсхеми.

Діють стандартні правила .

Приклади

Точне призначення iсхем (тобто порядок схем для певної N) залежить від вас. Але скажіть, що ви вибрали лексикографічне впорядкування, ваше рішення повинно відповідати наступній таблиці (із -позначенням недійсного введення):

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

Ось короткий скрипт CJam, який генерує всі дійсні схеми римування для будь-якої заданої довжини (але не намагайтеся більше 10, або ви почекаєте деякий час).

Супутні виклики


5
Я міг би поставити щедрість на (добре гольф) поліноміально-часове рішення (в N), за умови, що це не виявиться досить тривіальним, і я був занадто дурним, щоб його знайти.
Мартін Ендер

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

B (26) - це найменше число Белла, яке не входить у 64-бітове ціле число. Меані. :-(
Андерс Касеорг

Відповіді:


3

CJam, 68 66 байт

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

Спробуйте в Інтернеті.

Це моя перша програма CJam. Він почав життя як порт мого рішення Perl і спочатку був довше 130 байт. Подальші пропозиції щодо гольфу вітаються.

Як і в моїй програмі Perl, вона складається з двох частин.

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)

Для налагодження масивів , створених Частина 1 оних ]_`o~між Частиною 1 і 2. Якщо п 5, масиви будуть виглядати наступним чином : [[1 1 1 1 1 1] [1 2 3 4 5] [2 5 10 17] [5 15 37] [15 52]]. 0 індексів кожного масиву не використовуються, вони просто полегшують обчислення компенсацій. Масиви обчислюються так:

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

Він зберігає копію старого масиву під час обчислення наступного. Масиви зчитуються та відкидаються у зворотному порядку частиною 2.


13

Пітон 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)

Він використовує алфавітний порядок та індексацію на основі 0.

Нехай lпозначають довжину суфікса букв і aпозначають кількість відокремлених літер, які були використані в попередній частині. Тоді функція, p(l,a)яка обчислює кількість способів вибору решти літер, може становити 40 байт:

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

Однак це занадто повільно для виклику, тому натомість необхідні значення попередньо розраховуються та зберігаються в uмасиві. На кожному етапі обчислення, якщо наступна літера є однією з aуже використаних, n = k * p (l - 1, a) + n ', де k - 0-індексована літера алфавіту, а n' - значення nдля наступного виклику функції, що містить інформацію про літери, що залишилися. Якщо використовується нова літера, то n = a * p (l - 1, a) + n ' .


1
Скільки часу потрібно для найгіршого випадку?
Майкл Кляйн

1
@MichaelKlein Незначна кількість часу.
feersum

Це саме те, що я планував зробити (за винятком того, що я зробив би це з JS). Хороша робота! +1
ETHproductions

11

Haskell (GHC 7.10), 150 байт

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

Оператор n # iобчислює ith (нульову індексовану) схему рифми довжини n. Він працює в операціях O (n²) (з великим цілим числом), скориставшись ледачим нескінченним списками Haskell для автоматичного запам'ятовування. Проби:

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

(Якщо максимальне число N було 25 замість 26, то його .fromEnumможна було б видалити, оскільки B (25) вписується у 64-біт Int.)


1
Виглядає здорово. Ви б не хотіли додати менш гольф-версію для легшого походу?
Майкл Клейн

4

Perl 257 + 1 (-p прапор) = 258

Perl 182 + 10 (прапорці -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}

Завдяки dev-nul за збереження багатьох байтів! Зараз я її переписав на основі того, що я навчився робити версію CJam.

Обчислює риму у висхідному алфавітному порядку, індексується 0.

Дві частини: Частина 1 - 128 90 байт і обчислює матрицю для Частини 2. Частина 2 - 129 92 байт і робить кілька простих математик для обчислення кожної літери. Якби я міг позбутися матриці і замінити її двома простими числами, я міг би обчислити один шлях через матрицю для кожного числа і зберегти багато байтів! Мабуть, ця ідея не працює!

На жаль, він не виводить правильні рими для значень, що iперевищують 9007199254740992, але прекрасно працює для низьких значень! Я додав бібліотеку Bignum вартістю 11 байт. Запускається з командного рядка з perl -pMbignum bell-rhyme.pl. -pMbignum = 10 байт. Це також дуже швидко для будь-якого вхідного значення.


2

Oracle SQL 11.2, 412 284 283 байт

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;

На жаль, він працює лише в довжину 8. Будь-яке велике значення призводить до: ORA-01489: результат конкатенації рядків занадто довгий

Без гольфу

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;

Вигляд генерує: i букви у стовпці a та їх значення в b.

Рекурсивний вигляд v приймає рядок, що будується як параметр v, значення останньої літери, що використовується в c, і значення найбільшої літери, що використовується в n. Параметр n дорівнює довжині рядка без жодної копії літери, для чого призначений регулярний вираз.

Лист є дійсним, якщо його значення <= значення найбільшої вже використаної літери або це наступна літера, яка буде використана.

Якось запиту потрібна частина LENGTH (s) <: n для запуску, я повинен щось бракувати в тому, як працює запит.

Основний SELECT піклується про фільтрацію недійсних входів та коротших рядків, побудованих до досягнення потрібної довжини.

Версія 412 байт

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;

Не пробуйте запит на 412 байт з 26. Він переводить базу даних в обмежений режим, принаймні, на мою версію xe, що працює в контейнері docker на macbook. Я міг би спробувати на екзадаті на роботі, але, на жаль, мені все одно потрібно заробляти на життя.


0

Математика, 136 байт

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

Для повноти, ось моя довідкова реалізація. На відміну від існуючих відповідей це не працює в поліноміальний час (це експоненціально в Nоснові 2), але відповідає часовим обмеженням (найгірший випадок все-таки триватиме за півгодини).

Ідея така:

  • Для кожної схеми римування ми можемо визначити позиції, де наразі збільшується максимальний символ:

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

    Ми можемо розглядати ці позначення як двійкове число, що дозволяє легко перебирати всі такі структури. Нам потрібно перебрати від 2 n-1 до 2 n (або навпаки), звідки походить експоненціальна часова складність.

  • Для кожної такої структури легко визначити, скільки таких рядків є: лише проміжки між маркуванням можна вільно вибирати, а максимум перед пробілом повідомляє нам, скільки різних символів дійсні в кожній позиції. Це простий продукт. Якщо це число менше i, ми віднімаємо його i. В іншому випадку ми знайшли структуру запитуваної схеми римування.
  • Для перерахування схем у даній структурі ми просто представляємо i(або те, що залишилося від нього) як змішане базове число, де ваги цифр визначаються кількістю дозволених символів на інших позиціях.

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.