Распечатать размеры интервалов внутри музыкального произведения

10

Фон

В западной музыке каждой музыкальной ноте присвоено имя. Внутри каждой октавы есть двенадцать уникальных нот в следующем порядке: «CC # / Db DD # / Eb EFF # / Gb GG # / Ab AA # / Bb B C», где конечное значение C на одну октаву выше первого.

Чтобы определить разницу между нотами разных октав, к концу имени ноты добавляется число (для этой задачи, ограниченное одной цифрой). Таким образом, C5 - это нота, которая на одну октаву выше C4. Bb6 выше B5.

Важным фактом является то, что B5 и C6 являются нотами, которые находятся рядом друг с другом, и что C0 и B9 являются самыми низкими и самыми высокими нотами.

Между любыми двумя нотами есть расстояние, которое является количеством полутонов между ними. Bb4 - это один полутон ниже B4, который сам на полутон ниже C5. В октаве двенадцать полутонов, поэтому Bb4 на расстоянии 12 от A # 3, так как он выше октавы (обратите внимание, что одна нота может иметь до двух имен).

Соревнование

Ваша задача - написать максимально короткую программу, которая может взять список музыкальных нот из STDIN и распечатать список интервальных изменений в STDOUT.

Ввод будет разделенный пробелами список музыкальных нот. Каждая заметка будет состоять из заглавной буквы AG, необязательного знака b или # и однозначного числа. Вам не придется иметь дело с E # / Fb или B # / Cb. Пример ввода:

C4 D4 E4 F4 G4 A4 B4 C5 C4

Выходными данными будет список целых чисел, разделенных пробелами, которые представляют расстояние между каждой последовательной заметкой, всегда с префиксом + или -, чтобы показать, была ли заметка восходящей или нисходящей относительно предыдущей. Всегда будет выводиться на один номер меньше, чем вводимые заметки. Пример вывода для вышеуказанного ввода:

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

Еще несколько примеров ввода:

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

И их соответствующие выводы:

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

Правила и ограничения

  1. Победитель определяется по количеству символов в исходном коде

  2. Ваша программа должна состоять только из печатных символов ASCII

  3. Вам не разрешается использовать какие-либо встроенные функции, связанные с музыкой или звуком.

  4. Кроме этого, применяются стандартные правила игры в гольф.

PhiNotPi
источник
Должен ли он печатать +0или -0или 0для двух одинаковых заметок?
Говард
@Howard Так как я не указал, любой из них приемлем.
PhiNotPi
1
«Bb4 - это один полутон ниже B4, который сам на полутон ниже C4». Вы имеете в виду C5 в конце этого, верно?
Кит Рэндалл
Вау, никогда не замечал этого. Спасибо за обнаружение ошибки. Это сейчас исправлено.
PhiNotPi

Ответы:

6

GolfScript, 61

" "/{)12*\{"bC#D EF G A B"?(+}/}%(\{.@-.`\0>{"+"\+}*\}/;]" "*
Говард
источник
4

Хаскель, 161 персонажа

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
Хаммар
источник
4

Perl, 103

#!/usr/bin/perl -an
/.((b)|(\D))?/,(($~,$,)=($,,12*$'+ord()%20%7*2+(ord()%7>3)-$-[2]+$-[3]))[0]&&printf'%+d ',$,-$~for@F
ephemient
источник
3

C, 123 символа

Основано на решении leftaroundabout, с некоторыми улучшениями.

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);
}

Некоторые трюки, о которых я думаю, заслуживают упоминания:
1. argv[0](здесь он называется b) - указатель на имя программы, но здесь он используется в качестве чистого буфера. Нам нужно всего 4 байта (например C#2\0), поэтому нам достаточно.
2. cэто число аргументов, поэтому оно начинается с 1 (при запуске без аргументов). Мы используем его для предотвращения печати в первом раунде.

Возможная проблема - c+=b[..c+=..]довольно странная. Я не думаю, что это неопределенное поведение, потому что ?:это точка последовательности, но, возможно, я ошибаюсь.

ugoren
источник
Если вы думаете об этом как о c = c + b[..c+=..], то это довольно явно неопределенное поведение. Независимо от последовательности внутри [..], вы не знаете, извлекается ли внешнее cдо, во время или после b[..].
Эфимент
@ephemient, я думаю, теоретически компилятор может это сделать REG=c;REG+=b[..c+=..];c=REG. Тем не менее, я буду удивлен, увидев что-то подобное на практике. Но это все еще UB.
Угорен
Это Code Golf - мы уже вызывали UB scanfбез прототипа, и это нормально. Просто полезно знать, что является и не является законным в реальной жизни :)
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;}}
перестал поворачиваться против часовой стрелки
источник
Вместо того, чтобы делать знак плюс самостоятельно, вы можете просто сделать printf("%+d ",c-d).
Хаммар
Вы можете пропустить включает ideone.com/G00fS
Hauleth
Очень хорошо. Некоторые предложения: F(*b-65)вместо c-=65;, b[1]<36&&++c||b[1]>97&&c--?2:1-> b[1]&16?1:(c+=b[1]%2*2-1,2)злоупотребьте argv: main(e,b,c,d)char*b{(используйте первый указатель аргумента в качестве рабочего буфера).
Угорен
Еще один - думаю, c=F(*b)%12можно заменить на c=-~*b*1.6;c%=12. Почему? sinв оригинале Fможно заменить на 9.6. c*1.6+9.6это (c+6)*1.6, c-=65и (c+6)стали c-59, а затем c+1(60 * 96% 12 == 0).
Угорен
Спасибо за все предложения! Они работают хорошо и делают его короче, но я думаю, что я оставлю это как сейчас; это не было бы моим решением больше без синуса.
перестал поворачиваться против часовой стрелки с
1

Фактор, 303 символа

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

С комментариями,

! 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.

Для этого сценария «разделенный пробелами список» может содержать 1 или более пробелов между элементами и 0 или более пробелов в начале или конце. Этот скрипт выводит дополнительный пробел в конце вывода, но он также принимает дополнительный пробел (или перевод строки) в конце ввода.

Если бы я принял более строгое определение, где «список, разделенный пробелами», имеет ровно 1 пробел между элементами и 0 пробелов в начале или конце, то я могу сократить contents R/ [#-b]+/ all-matching-slicesдо contents " " split(используя splitting, а не regexp). Тем не менее, мне нужно было бы добавить больше кода, чтобы предотвратить дополнительное пространство в конце вывода.

Если я использую устаревшее слово dupd, я могу сократить over [ - "%+d " printf ] dipдо dupd - "%+d " printf8 символов. Я не использую устаревшие слова, потому что они «должны быть удалены в ближайшее время».

kernigh
источник