Gerando ritmos euclidianos

8

Você sabia que o algoritmo euclidiano é capaz de gerar ritmos musicais tradicionais ? Vamos ver como isso funciona, descrevendo um algoritmo semelhante, mas um pouco diferente do algoritmo no artigo.

Escolha um número inteiro positivo n, o número total de batidas e um número inteiro positivo k, o número de batidas que são tocadas (notas). Podemos pensar no ritmo como uma sequência de nbits, kdos quais são 1s. A idéia do algoritmo é distribuir os 1s da maneira mais uniforme possível entre os 0s.

Por exemplo, com n = 8e k = 5, começamos com 5 uns e 3 zeros, cada um em sua própria sequência:

[1] [1] [1] [1] [1] [0] [0] [0]

A qualquer momento, teremos dois tipos de sequências - as que estão no início são as "principais" e as que estão no final são as "restantes". Aqui os núcleos são se [1]os restantes são [0]s. Contanto que tenhamos mais de uma sequência restante, as distribuímos entre os núcleos:

[1 0] [1 0] [1 0] [1] [1]

Agora, o núcleo é [1 0]e o restante é [1], e distribuímos novamente:

[1 0 1] [1 0 1] [1 0]

Agora o núcleo é [1 0 1]e o restante é [1 0]. Como só temos uma sequência restante, paramos e obtemos a string

10110110

Isso é o que parece, repetidas 4 vezes:

Aqui está outro exemplo, com n = 16e k = 6:

[1] [1] [1] [1] [1] [1] [0] [0] [0] [0] [0] [0] [0] [0] [0] [0]
[1 0] [1 0] [1 0] [1 0] [1 0] [1 0] [0] [0] [0] [0]
[1 0 0] [1 0 0] [1 0 0] [1 0 0] [1 0] [1 0]
[1 0 0 1 0] [1 0 0 1 0] [1 0 0] [1 0 0]
[1 0 0 1 0 1 0 0] [1 0 0 1 0 1 0 0]
1001010010010100

Observe como terminamos na última etapa com duas seqüências principais e nenhuma sequência restante. Aqui está, repetido duas vezes:

Entrada

Você pode escrever uma função ou um programa completo lendo STDIN, linha de comando ou similar. A entrada terá dois números inteiros positivos ne k <= n, o que você pode assumir em qualquer ordem.

Resultado

Sua tarefa é produzir o ritmo gerado como uma nsequência longa de caracteres. Você pode escolher dois caracteres ASCII imprimíveis distintos (0x20 a 0x7E) para representar notas e pausas.

Casos de teste

Os seguintes casos de teste são usados xpara representar notas .es para representar restos. A entrada é dada na ordem n, então k.

1 1    ->  x
3 2    ->  xx.
5 5    ->  xxxxx
8 2    ->  x...x...
8 5    ->  x.xx.xx.
8 7    ->  xxxxxxx.
10 1   ->  x.........
11 5   ->  x.x.x.x.x..
12 7   ->  x.xx.x.xx.x.
16 6   ->  x..x.x..x..x.x..
34 21  ->  x.xx.x.xx.xx.x.xx.x.xx.xx.x.xx.x.x
42 13  ->  x...x..x..x..x...x..x..x..x...x..x..x..x..
42 18  ->  x..x.x.x..x.x.x..x.x.x..x.x.x..x.x.x..x.x.

Pontuação

Isso é código-golfe, então o código com o menor número de bytes vence.

Sp3000
fonte

Respostas:

5

CJam, 37 34 30 27 24 bytes

3 bytes salvos pelo user23013.

q~,f>{_(a-W<}{e`:a:e~z}w

Isso leva a entrada como kprimeiro, nsegundo. Ele usa 1e 0para indicar notas e pausas, respectivamente.

Experimente aqui. Como alternativa, aqui está um equipamento de teste que executa todos os casos de teste fornecidos (trocando a ordem de entrada) e também reformata a saída a ser usada xe .para uma comparação mais fácil com as soluções de referência. (Os resultados que aparecem na entrada do equipamento de teste são descartados antes da execução do código. Eles podem ser removidos com segurança sem afetar o programa.)

Explicação

q~                       e# Read and eval the input, dumping k and n on the stack.
  ,                      e# Create a range [0 .. n-1]
   f>                    e# For each of them, check if it's less than k.
                         e# This gives k 1s and n-k 0s.
     {      }{        }w e# While the first block yields a truthy result, execute
                         e# the second block.
      _                  e# Duplicate the array.
       (a-               e# Remove all instances of core sequences.
          W<             e# Remove one remainder sequence if there is one. This
                         e# gives an empty array (falsy) whenever there are less
                         e# than two remainder sequences.
              e`         e# Run-length encode. I.e. this turns an array into a list
                         e# of pairs of run-length and element.
                :a:e~    e# Wrap each RLE pair in an array and RLD it. This
                         e# partitions the array into cores and remainders.
                     z   e# Zip the two parts, interleaving cores and remainders.

Esse processo aninha matrizes muito profundas, mas a parte importante é que todos os núcleos (e todos os demais) sempre têm a mesma estrutura aninhada, portanto, ainda são iguais um ao outro. O CJam imprime automaticamente o conteúdo da pilha no final do programa e omite os delimitadores da matriz inteiramente, portanto, essa estrutura também não afeta a saída.

Martin Ender
fonte
2 ou 3 bytes mais curto: q~\,f>.
Jimmy23013
@ user23013 Oh, isso é legal! Obrigado. :)
Martin Ender
Por 3 bytes, quis dizer que a entrada pode estar em qualquer ordem.
Jimmy23013
@ user23013 Ah, claro, eu esqueci disso.
Martin Ender
1

Haskell, 125 bytes

r=replicate
g z(a:b)(c:d)=g((a++c):z)b d
g z x y=(z,x++y)
f(x,y)|length y>1=f$g[]x y|1<2=concat$x++y
n#k=f(r k "x",r(n-k)".")

Exemplo de uso: 42 # 13-> x...x..x..x..x...x..x..x..x...x..x..x..x...

fusa dois parâmetros: lista principal e restante. Ele fecha as duas listas (via g) e chama a si próprio novamente ou para se o restante for muito curto. A função principal #cria as listas e chamadas iniciais f.

nimi
fonte
1

Python, 85

f=lambda n,k,a='1',b='0':n-k<2and a*k+b*(n-k)or[f(n-k,k,a+b,b),f(k,n-k,a+b,a)][2*k>n]

A solução recursiva natural. Os parâmetros (n,k,a,b)representam um núcleo de k a's seguido por um restante de n-k b's.

Essa solução é ineficiente porque as duas chamadas recursivas fsão avaliadas, causando um número exponencial de chamadas.

Eu tentei comprimir a invocação condicional de flike

f(max(n-k,k),min(n-k,k),a+b,[a,b][2*k<n])
f(*[n-k,k,k,n-k,b,a][2*k>n::2]+[a+b])

mas nenhum saiu mais curto.

xnor
fonte