10
\$\begingroup\$

Given an unordered list of musical pitches, write the shortest program/function (scored in bytes) to sort the list from lowest pitch to highest.

Pitches will be given in scientific pitch notation, consisting of a tone name followed by an octave number. Here, the tone name will be a single letter A–G, possibly followed by a single # or b character (representing sharp and flat, respectively). The octave number will be a single digit 0–9.

From lowest to highest, the order of pitches in this notation may be represented as follows:

<--- lower pitch                              higher pitch --->
_______________________________________________________________
Cb0 C0 C#0
       Db0 D0 D#0
              Eb0 E0  E#0
                  Fb0 F0  F#0
                          Gb0 G0 G#0
                                 Ab0 A0 A#0
                                        Bb0 B0  B#0
                                            Cb1 C1  C#1
                                                    Db1 D1 D#1
                                                           etc.

In this representation, pitch rises from left to right, so vertically aligned notes (enharmonic pairs) have the same pitch. Each row contains three notes in the order flat, natural, sharp (e.g. Cb0, C0, C#0) and the order of rows within an octave is C, D, E, F, G, A, B. Notice that by a quirk of the notation, the indentation pattern differs for the C and F rows.

Your code may sort enharmonic pitches in any order. For example, given [D#0, Eb0, D#0] as input, any of [D#0, D#0, Eb0], [D#0, Eb0, D#0], and [Eb0, D#0, D#0] is a valid output.

Test cases

[A2, B2, C2, D2, E2, F2, G2, A3] -> [C2, D2, E2, F2, G2, A2, B2, A3]
[E5, D#5, E5, D#5, E5, B4, D5, C5, A4] -> [A4, B4, C5, D5, D#5, D#5, E5, E5, E5]
[E#6, Fb6, B#6, Cb7, C7] -> [Fb6, E#6, Cb7, B#6, C7] or [Fb6, E#6, Cb7, C7, B#6]
\$\endgroup\$

9 Answers 9

4
\$\begingroup\$

Jelly,  31 30  29 bytes

FµḢ“¤ı¶“Œ’ḥ;ṪV×12Ʋ;=”#Ḥ’ƊSµ$Þ

A monadic Link that accepts a list of lists of characters and yields a sorted version.

Try it online!

How?

FµḢ“¤ı¶“Œ’ḥ;ṪV×12Ʋ;=”#Ḥ’ƊSµ$Þ - Link: notes
                           $Þ - sort by: last two links as a monad:
 µ                            -   monadic chain:
F                             -     flatten (this is to get a copy so we don't
                                             alter what we are sorting in place)
                          µ   -   monadic chain:
  Ḣ                           -     head of note (removes it too)
          ḥ                   -     hash with salt & domain:
   “¤ı¶“Œ’                    -       base 250 numbers = [256628,20]
                                        X = CDEFGAB -> 4,6,8,9,11,13,15
                 Ʋ            -     last four links as a monad:
            Ṫ                 -       tail of note (removes it too)
             V                -       evaluate as Jelly code -> integer
               12             -       twelve
              ×               -       multiply
                                        Y = 12 × octave
           ;                  -     concatenate -> [X,Y]
                        Ɗ     -     last three links as a monad - f(v):
                                        (v being one of [], ['#'], or ['b'])
                   =”#        -       equals '#'? (vectorises)
                      Ḥ       -       double
                       ’      -       decrement
                                        O = accidental offsets [], [1], or [-1]
                  ;           -     concatenate -> [X,Y]+O
                         S    -     sum -> note number
\$\endgroup\$
3
\$\begingroup\$

Python 3, 92 83 bytes

-9 bytes thanks to G B

Golfed code

lambda l:sorted(l,key=lambda i:12*int(i[-1])+" D EF G A B".find(i[0])-ord(i[1])/48)

Try it online!

Commented code

lambda l: sorted(l,                 # Sort the list 
    key=lambda i:                   # Key used to sort the list, takes a single string as input
                                    # Outputs an integer; the lower the integer, the lower the note
        12 * int(i[-1])             # Multiply the octave number by 12
        + " D EF G A B".find(i[0])  # Add the number of the note within that octave;
                                    # C = -1 up to B = 10
        -ord(i[1])/48               # Subtract the ASCII code of the second character; 
                                    # Ends up with sharpened notes having higher values than
                                    # no-accidental ones, which have a higher value than flattened ones
)
\$\endgroup\$
1
  • 1
    \$\begingroup\$ +abs("b #".find(i[1])) can be replaced by -ord(i[1])/48 \$\endgroup\$
    – G B
    Mar 14, 2022 at 10:56
2
\$\begingroup\$

JavaScript (Node.js), 99 bytes

n=>n.sort((a,b,l=e=>`b${D=e.slice(-1)}#`.search(e[1])+12*D+'C D EF G A B'.search(e[0]))=>l(a)-l(b))

Try it online!

\$\endgroup\$
2
\$\begingroup\$

APL+WIN, 83 bytes

Prompts for notes as nested vector

m←⊃3↑¨¯1⌽¨s←⎕⋄s[⍋((¯1×m[;3]='b')+m[;3]='#')+(12×(⍎¨m[;1]))+¯1+'C D EF G A B'⍳m[;2]]

Try it online! Thanks to Dyalog APL Classic

Here is an improved 68 byte version but it does not work on Dyalog because of ⎕fi

s[⍋∊(¯1+'C D EF G A B'⍳↑¨s)+(+/¨((⊂'b#')∊¨s)ר⊂¯1 1)+12×+/¨⎕fi¨¨s←⎕]
\$\endgroup\$
2
\$\begingroup\$

Ruby, 70 bytes

->l{l.sort_by{|x|x[-1].ord*12+"C D EF G A B".index(x[0])-x[1].ord/48}}

Try it online!

\$\endgroup\$
2
\$\begingroup\$

JavaScript (ES6),  87 86  85 bytes

-1 thanks to @l4m2

a=>a.sort((a,b)=>(g=([a,b,c])=>(b|c)*12+' D EF G A B'.search(a)-!!c+2*(b<g))(a)-g(b))

Try it online!

Commented

a =>                  // a[] = input array
a.sort((a, b) =>      // for each pair of notes (a, b) to be sorted:
  ( g =               //   g is a helper function taking a note
    ([a, b, c]) =>    //   split into [a, b, c] (c may be undefined)
      (b | c)         //     use whichever of b or c is a digit ...
      * 12            //     ... as the octave
      + ' D EF G A B' //     add the index of the leading letter:
        .search(a)    //       "CDEFGAB" -> [-1,1,3,4,6,8,10]
      - !!c           //     subtract 1 if there's an accidental
      + 2 * (b < g)   //     add 2 if the accidental is '#'
  )(a)                //   invoke g with the 1st note
  - g(b)              //   invoke g with the 2nd note and subtract
)                     // end of sort()
\$\endgroup\$
1
  • 1
    \$\begingroup\$ c||b => c|b \$\endgroup\$
    – l4m2
    Dec 5, 2023 at 0:25
1
\$\begingroup\$

Retina 0.8.2, 95 bytes

\d
$*-===$&
-
12$*=
(.+?)=+
$1$&
#=
==
b=

[CDE]=
$&=
T`L`56d`.=
\d=
$*=$&$*=
O$`.+?(=+).
$1
=

Try it online! Takes newline-separated input but link is to test suite that converts from and to comma-separated input. Explanation:

\d
$*-===$&
-
12$*=

Representing the pitch in unary using = signs, multiply the octave by 12 and add 3 (so that I can subtract 2 from it later without it falling to zero).

(.+?)=+
$1$&

Duplicate the note name.

#=
==
b=

Adjust the pitch for a sharp or a flat.

[CDE]=
$&=
T`L`56d`.=
\d=
$*=$&$*=

Adjust the pitch according to the note name.

O$`.+?(=+).
$1

Sort by the pitch.

=

Delete all of the = signs.

\$\endgroup\$
1
\$\begingroup\$

JavaScript (Node.js), 81 bytes

a=>a.sort((a,b)=>(g=([a,b,c])=>(c|b)*7+('0x'+a-5||4)%7-(c?b>g?.6:-.6:0))(a)-g(b))

Try it online!

Notice that we needn't care whether difference between each pair of letter is 1/2 or 1, just give them 3/4 works

    #C    #D    #E    #F    #G    #A    #B
 C     D     E     F     G     A     B     C
  bD    bE    bF    bG    bA    bB    bC
[][  ][][  ][  ][  ][  ][][  ][][  ][  ][  ]
\$\endgroup\$
1
\$\begingroup\$

Scala, 59 bytes

A port of @G B' Ruby answer in Scala.


Golfed version. Try it online!

_.sortBy(x=>x.last*12+"C D EF G A B".indexOf(x(0))-x(1)/48)

Ungolfed version. Try it online!

object Main extends App {
  def sortNotes(notes: List[String]): List[String] = {
    notes.sortBy(note => {
      val noteChars = note.toCharArray
      noteChars.last.toInt * 12 + "C D EF G A B".indexOf(noteChars.head) - noteChars(1).toInt / 48
    })
  }

  println(sortNotes(List("Ab1", "B#2", "Cb3", "D#0", "Ab0", "G0")))
  println(sortNotes(List("A2", "B2", "C2", "D2", "E2", "F2", "G2", "A3")))
  println(sortNotes(List("E5", "D#5", "E5", "D#5", "E5", "B4", "D5", "C5", "A4")))
  println(sortNotes(List("E#6", "Fb6", "B#6", "Cb7", "C7")))
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.