Перечислите схемы рифмы

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
Я мог бы назначить награду за (хорошо сыгранное) решение за полиномиальное время (in 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.

CJ Деннис
источник
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)

Он использует алфавитный порядок и индексацию на основе 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 - это индексированная буквой алфавита, а n' - значение nдля следующего вызова функции, которое содержит информацию об оставшихся буквах. Если используется новая буква, то n = a * p (l - 1, a) + n ' .

feersum
источник
1
Сколько времени потребуется для ввода в худшем случае?
Майкл Кляйн
1
@MichaelKlein Незначительное количество времени.
feersum
Это именно то, что я планировал сделать (за исключением того, что я сделал бы это с JS). Хорошая работа! +1
ETHпродукция
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вычисляет iрифмовую схему th (с нулевым индексом) длины 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 flags) = 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 байт. Это также очень быстро для любого входного значения.

CJ Деннис
источник
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: результат объединения строк слишком длинный

Un-golfed

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 генерирует буквы: 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, работающей в док-контейнере на MacBook. Я мог бы примерить exadata на работе, но, к сожалению, мне все равно нужно зарабатывать на жизнь.

школа для водителей
источник
0

Mathematica, 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(или то, что от него осталось) как смешанное базовое число, где веса цифр определяются количеством разрешенных символов в оставшихся позициях.

Интересно, позволит ли это более короткое решение на некоторых других представленных языках, поскольку оно не требует запоминания или предварительного вычисления.

Мартин Эндер
источник