Wydrukuj rozmiary interwałów w utworze muzycznym

10

tło

W muzyce zachodniej każda nuta ma przypisaną nazwę. W każdej oktawie jest dwanaście unikalnych nut w następującej kolejności: „CC # / Db DD # / Eb EFF # / Gb GG # / Ab AA # / Bb B C”, gdzie końcowe C jest o jedną oktawę powyżej pierwszej.

Aby odróżnić nuty różnych oktaw, na końcu nazwy nuty dołączana jest liczba (dla tego wyzwania ograniczona do jednej cyfry). Zatem C5 jest nutą o jedną oktawę powyżej C4. Bb6 jest powyżej B5.

Ważnym faktem jest to, że B5 i C6 to nuty, które są tuż obok siebie, a C0 i B9 to najniższe i najwyższe nuty.

Między dowolnymi dwiema nutami znajduje się odległość, która jest liczbą półtonów między nimi. Bb4 to jeden półton poniżej B4, który sam jest jednym półtonem poniżej C5. W oktawie jest dwanaście półtonów, więc Bb4 to odległość 12 od A # 3, ponieważ jest to oktawa nad nim (zauważ, jak jedna nuta może mieć do dwóch nazw).

Wyzwanie

Twoim wyzwaniem jest napisanie możliwie najkrótszego programu, który może pobrać listę nut z STDIN i wydrukować listę zmian interwałów w STDOUT.

Dane wejściowe będą oddzieloną spacjami listą nut. Każda nuta będzie składać się z dużej litery AG, opcjonalnego znaku b lub # oraz cyfry jednocyfrowej. Nie będziesz musiał radzić sobie z E # / Fb lub B # / Cb. Przykładowe dane wejściowe:

C4 D4 E4 F4 G4 A4 B4 C5 C4

Wyjściem będzie oddzielona spacjami lista liczb całkowitych, które reprezentują odległość między kolejnymi nutami, zawsze poprzedzona znakiem + lub -, aby pokazać, czy notka rosła, czy opadała w stosunku do poprzedniej. Zawsze będzie wypisywana jedna liczba mniejsza niż wprowadzane nuty. Przykładowe dane wyjściowe dla powyższego wejścia:

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

Kilka innych przykładowych danych wejściowych:

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

I odpowiadające im wyniki:

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

Zasady i ograniczenia

  1. Zwycięzca zależy od liczby znaków w kodzie źródłowym

  2. Twój program powinien składać się wyłącznie z drukowalnych znaków ASCII

  3. Nie wolno używać żadnych wbudowanych funkcji związanych z muzyką lub dźwiękiem

  4. Poza tym obowiązują standardowe zasady gry w golfa

PhiNotPi
źródło
Czy powinien drukować, +0czy -0może 0dla dwóch identycznych notatek?
Howard,
@Howard Ponieważ nie sprecyzowałem, jedno z nich jest dopuszczalne.
PhiNotPi
1
„Bb4 to jeden półton poniżej B4, który sam jest jednym półtonem poniżej C4”. Masz na myśli C5 na końcu, prawda?
Keith Randall,
Wow, nigdy tego nie zauważyłem. Dziękujemy za wykrycie błędu. Teraz jest naprawione.
PhiNotPi

Odpowiedzi:

6

GolfScript, 61

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

Haskell, 161 znaków

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
źródło
4

Perl, 103

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

C, 123 znaki

Oparty na rozwiązaniu po lewej stronie, z pewnymi ulepszeniami.

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

Niektóre sztuczki, które moim zdaniem są warte wspomnienia:
1. argv[0](tutaj nazywane b) to wskaźnik do nazwy programu, ale używany tutaj jako bufor scratch. Potrzebujemy tylko 4 bajtów (np. C#2\0), Więc mamy dość.
2. cjest liczbą argumentów, więc zaczyna się od 1 (gdy działa bez argumentów). Używamy go, aby zapobiec drukowaniu w pierwszej rundzie.

Możliwy problem - c+=b[..c+=..]jest dość dziwny. Nie sądzę, że jest to niezdefiniowane zachowanie, ponieważ ?:jest to punkt sekwencyjny, ale może się mylę.

ugoren
źródło
Jeśli myślisz o tym jako o c = c + b[..c+=..], to jest to dość wyraźnie niezdefiniowane zachowanie. Bez względu na sekwencjonowanie wewnątrz [..], nie wiesz, czy zewnętrzna cjest pobierana przed, w trakcie czy po b[..].
ephemient
@ephemient, chyba teoretycznie kompilator mógłby to zrobić REG=c;REG+=b[..c+=..];c=REG. Będę jednak zaskoczony, widząc coś takiego w praktyce. Ale to wciąż UB.
ugoren
To Code Golf - już wywołaliśmy UB, używając scanfbez prototypu, i to jest w porządku. Po prostu dobrze wiedzieć, co jest, a co nie jest legalne w prawdziwym życiu :)
ephemient
2

DO, 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;}}
przestał się obracać w lewo
źródło
Zamiast samodzielnie wykonać znak plus, możesz to zrobić printf("%+d ",c-d).
hammar
Możesz pominąć zawiera ideone.com/G00fS
Hauleth
Bardzo dobrze. Kilka sugestii: F(*b-65)zamiast c-=65;, b[1]<36&&++c||b[1]>97&&c--?2:1-> b[1]&16?1:(c+=b[1]%2*2-1,2), nadużywaj argumentu przez: main(e,b,c,d)char*b{(Użyj pierwszego wskaźnika argumentu jako buforu roboczego).
ugoren
Kolejny - myślę, że c=F(*b)%12można go zastąpić c=-~*b*1.6;c%=12. Dlaczego? sinw oryginale Fmożna zastąpić 9.6. c*1.6+9.6jest (c+6)*1.6, c-=65i (c+6)staje się c-59, a następnie c+1(60 * 96% 12 == 0).
ugoren
Dzięki za wszystkie sugestie! Działają dobrze i sprawiają, że jest krótszy, ale myślę, że zostawię go takim, jaki jest teraz; tak naprawdę nie byłoby to moim rozwiązaniem bez sinusa.
przestał obracać przeciwnie do zegara
1

Współczynnik, 303 znaków

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

Z komentarzami

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

W tym skrypcie „lista rozdzielona spacjami” może zawierać 1 lub więcej spacji między elementami oraz 0 lub więcej spacji na początku lub na końcu. Ten skrypt drukuje dodatkową spację na końcu danych wyjściowych, ale akceptuje również dodatkową spację (lub znak nowej linii) na końcu danych wejściowych.

Jeśli przyjąłbym bardziej rygorystyczną definicję, w której „lista rozdzielona spacjami” ma dokładnie 1 spację między elementami i 0 spacji na początku lub na końcu, to mogę skrócić contents R/ [#-b]+/ all-matching-slicesdo contents " " split(używając splitting, nie regexp). Musiałbym jednak dodać więcej kodu, aby zapobiec dodatkowej przestrzeni na końcu danych wyjściowych.

Jeśli używam przestarzałej słowo dupd, mogę skrócić over [ - "%+d " printf ] dipdo dupd - "%+d " printf, oszczędzając 8 znaków. Nie używam przestarzałych słów, ponieważ „mają zostać wkrótce usunięte”.

kernigh
źródło