Imprime los tamaños de intervalos dentro de una pieza musical

10

Antecedentes

En la música occidental, cada nota musical tiene un nombre asignado. Dentro de cada octava, hay doce notas únicas en el siguiente orden: "CC # / Db DD # / Eb EFF # / Gb GG # / Ab AA # / Bb B C", donde la C final está una octava por encima de la primera.

Para diferenciar entre notas de diferentes octavas, se agrega un número (para este desafío restringido a un solo dígito) al final del nombre de la nota. Por lo tanto, C5 es la nota que está una octava por encima de C4. Bb6 está por encima de B5.

Un hecho importante es que B5 y C6 son notas que están una al lado de la otra, y que C0 y B9 son las notas más bajas y más altas.

Entre dos notas, hay una distancia que es el número de semitonos entre ellas. Bb4 es un semitono debajo de B4, que es en sí mismo un semitono debajo de C5. Hay doce semitonos en una octava, por lo que Bb4 está a una distancia de 12 de A # 3, ya que está una octava por encima (observe cómo una sola nota puede tener hasta dos nombres).

El reto

Su desafío es escribir el programa más corto posible que pueda tomar una lista de notas musicales de STDIN e imprimir la lista de cambios de intervalo en STDOUT.

La entrada será una lista de notas musicales separadas por espacios. Cada nota constará de una letra mayúscula AG, un signo opcional b o # y un número de un solo dígito. No tendrá que lidiar con E # / Fb o B # / Cb. Entrada de ejemplo:

C4 D4 E4 F4 G4 A4 B4 C5 C4

La salida será una lista de enteros separados por espacios que representan la distancia entre cada nota sucesiva, siempre con un prefijo + o - para mostrar si la nota fue ascendente o descendente en relación con la anterior. Siempre habrá un número menos generado que las notas ingresadas. Ejemplo de salida para la entrada anterior:

+2 +2 +1 +2 +2 +2 +1 -12

Algunas entradas de ejemplo más:

E5 D#5 E5 B4 E5 F#5 E5 B4
C0 B0 Bb1 A2 G#3 G4 F#5 F6
G4 Ab4 Gb4 A4 F4 A#4

Y sus salidas correspondientes:

-1 +1 -5 +5 +2 -2 -5
+11 +11 +11 +11 +11 +11 +11
+1 -2 +3 -4 +5

Reglas y restricciones

  1. El ganador está determinado por la cantidad de caracteres en el código fuente

  2. Su programa debe constar solo de caracteres ASCII imprimibles

  3. No está autorizado a utilizar ningún tipo de función integrada relacionada con la música o el sonido.

  4. Aparte de eso, se aplican reglas de golf de código estándar

PhiNotPi
fuente
En caso de que imprimir +0o -0o 0por dos notas idénticas?
Howard
@Howard Como no especifiqué, cualquiera de los dos es aceptable.
PhiNotPi
1
"Bb4 es un semitono debajo de B4, que es en sí mismo un semitono debajo de C4". Te refieres a C5 al final de eso, ¿verdad?
Keith Randall
Wow, nunca me di cuenta de eso. Gracias por detectar el error. Ya está arreglado.
PhiNotPi

Respuestas:

6

GolfScript, 61

" "/{)12*\{"bC#D EF G A B"?(+}/}%(\{.@-.`\0>{"+"\+}*\}/;]" "*
Howard
fuente
4

Haskell, 161 caracteres

f(c:r)=maybe(12*read[c])(+f r).lookup c$zip"bC#D.EF.G.A.B"[-1..]
g n|n<0=show n|1<3='+':show n
h x=zipWith(-)(tail x)x
main=interact$unwords.map g.h.map f.words
hammar
fuente
4

Perl, 103

#!/usr/bin/perl -an
/.((b)|(\D))?/,(($~,$,)=($,,12*$'+ord()%20%7*2+(ord()%7>3)-$-[2]+$-[3]))[0]&&printf'%+d ',$,-$~for@F
efímero
fuente
3

C, 123 caracteres

Basado en la solución de leftaroundabout, con algunas mejoras.

main(c,b,d)
    char*b;
{
    while(d=c,~scanf("%s",b)?c=-~*b*1.6,c%=12,c+=b[~b[1]&16?c+=1-b[1]/40,2:1]*12:0)
        d>1&&printf("%+d ",c-d);
}

Algunos trucos que creo que vale la pena mencionar:
1. argv[0](aquí llamado b) es un puntero al nombre del programa, pero se usa aquí como un búfer de memoria virtual. Solo necesitamos 4 bytes (por ejemplo C#2\0), por lo que tenemos suficiente.
2)c es el número de argumentos, por lo que comienza como 1 (cuando se ejecuta sin argumentos). Lo usamos para evitar la impresión en la primera ronda.

Posible problema: c+=b[..c+=..]es un poco extraño. No creo que sea un comportamiento indefinido, porque ?:es un punto de secuencia, pero tal vez me equivoque.

Ugoren
fuente
Si lo piensas así c = c + b[..c+=..], entonces es un comportamiento claramente indefinido. Independientemente de la secuencia interna [..], no sabes si lo externo cse obtiene antes, durante o después b[..].
Ephemient
@ephemient, supongo que en teoría un compilador podría hacer REG=c;REG+=b[..c+=..];c=REG. Sin embargo, me sorprendería ver algo así en la práctica. Pero sigue siendo UB.
ugoren
Es Code Golf: ya hemos invocado a UB usando scanfun prototipo, y eso está bien. Es bueno saber qué es y qué no es legal en la vida real :)
ephemient
2

C, 241 229 183

F(c){c-=65;return c*1.6+sin(c/5.+.3)+9;}
#define r if(scanf("%s",b)>0){c=F(*b)%12;c+=b[b[1]<36&&++c||b[1]>97&&c--?2:1]*12
main(e,c,d){char b[4];r;}s:d=c;r;printf("%+d ",c-d);goto s;}}
dejó de girar en sentido antihorario
fuente
En lugar de hacer el signo más usted mismo, simplemente puede hacerlo printf("%+d ",c-d).
hammar
Puede omitir incluye ideone.com/G00fS
Hauleth
Muy agradable. Algunas sugerencias: en F(*b-65)lugar de c-=65;, b[1]<36&&++c||b[1]>97&&c--?2:1-> b[1]&16?1:(c+=b[1]%2*2-1,2), abusar de argv por: main(e,b,c,d)char*b{(Use el primer puntero de argumento como búfer de trabajo).
ugoren
Otro, creo que c=F(*b)%12puede ser reemplazado por c=-~*b*1.6;c%=12. ¿Por qué? sinen el original Fse puede reemplazar con 9.6. c*1.6+9.6es (c+6)*1.6, c-=65y se (c+6)convierte c-59, y luego c+1(60 * 96% 12 == 0).
ugoren
¡Gracias por todas las sugerencias! Funcionan bien y lo hacen más corto, pero creo que lo dejaré como está ahora; ya no sería mi solución sin el seno.
dejó de girar en contra del reloj el
1

Factor, 303 caracteres

USING: combinators formatting io kernel math regexp sequences ;
f contents R/ [#-b]+/ all-matching-slices
[ 0 swap [ {
{ [ dup 48 < ] [ drop 1 ] }
{ [ dup 65 < ] [ 48 - 12 * ] }
{ [ dup 98 < ] [ "C-D-EF-G-A-B" index ] }
[ drop -1 ]
} cond + ] each
swap [ over [ - "%+d " printf ] dip ] when* ] each drop

Con comentarios,

! combinators => cond
! formatting => printf
! io => contents
! kernel => swap dup drop over dip when*
! math => < - * +
! regexp => R/ all-matching-slices
! sequences => each
USING: combinators formatting io kernel math regexp sequences ;

f       ! Push false, no previous note value.

! Find notes (as string slices) in standard input. The golfed regexp
! R/ [#-b]+/ matches all notes and no whitespace.
contents R/ [#-b]+/ all-matching-slices

! For each string slice:
[
    0       ! Push 0, initial note value.
    swap    ! Move note slice to top of stack, above note value.

    ! For each Unicode codepoint in note:
    [
        ! Convert the Unicode codepoint to its value in semitones.
        ! For golf, [ 48 ] is shorter than [ CHAR: A ].
        {
            ! Sharp # {35} has 1 semitone.
            { [ dup 48 < ] [ drop 1 ] }
            ! 0-9 {48-57} has 0 to 9 octaves (1 octave = 12 semitones).
            { [ dup 65 < ] [ 48 - 12 * ] }
            ! A-G {65-71} has 0 to 11 semitones.
            { [ dup 98 < ] [ "C-D-EF-G-A-B" index ] }
            ! Flat b {98} has -1 semitone.
            [ drop -1 ]
        } cond

        +       ! Add semitones to cumulative note value.
    ] each

    swap    ! Move previous note value to top of stack.
    ! When there is a previous note value:
    [
        ! Keep current note on stack.
        over [
            ! Compute and print interval.
            - "%+d " printf
        ] dip
    ] when*
    ! Current note replaces previous note at top of stack.
] each

drop    ! Drop previous note, so stack is empty.

Para este script, una "lista separada por espacios" puede tener 1 o más espacios entre elementos, y 0 o más espacios al principio o al final. Este script imprime un espacio adicional al final de la salida, pero también acepta un espacio adicional (o nueva línea) al final de la entrada.

Si adoptara una definición más estricta, donde una "lista separada por espacios" tiene exactamente 1 espacio entre elementos y 0 espacios al principio o al final, entonces puedo acortar contents R/ [#-b]+/ all-matching-slicesa contents " " split(usando splitting, no regexp). Sin embargo, necesitaría agregar más código para evitar el espacio adicional al final de la salida.

Si uso la palabra en desuso dupd, puedo acortar over [ - "%+d " printf ] dipy dupd - "%+d " printfguardar 8 caracteres. No estoy usando palabras obsoletas porque "están destinadas a eliminarse pronto".

kernigh
fuente