Checking for Musical Palindromes using Queues

Originally published on Learning DSA with Music on Substack.

Continuing with the discussion on queues, one easy type of question is Valid Palindrome. We can check if a string is a palindrome using a queue. The palindrome is defined as a string that, after converting all uppercase letters into lowercase letters and removing all non-alphanumeric characters, reads the same forward and backward. We can check if a string has alphanumeric characters using isalnum() and we can convert a string to lowercase letters using lower(). Additionally, we can use the deque class of queues (double-ended queues) to literally check both ends of the string.

from collections import deque

def is_palindrome(s):

    s_alnum_deque = deque()

    for i in range(len(s)):
        if s[i].isalnum():
            s_alnum_deque.append(s[i].lower())  # lowercase and add to deque if alphanumeric

    while len(s_alnum_deque) > 1:
        if s_alnum_deque.popleft() == s_alnum_deque.pop():
            pass
        else:
            return False

    return True

Let’s hear a palindrome! This is just a regular C major that goes up and down the scale: [60, 62, 64, 65, 67, 65, 64, 62, 60]

What I was curious to explore are the common scales in general, to understand if there are palindromic elements to them. In particular, I looked at the pattern of the whole steps and half steps between each of the notes (i.e. intervals) to see if these intervals are palindromes. These are the scales that I explored in the key of C:

7-Note Heptatonic Scales

Major (Ionian): [60, 62, 64, 65, 67, 69, 71, 72]

Dorian: [60, 62, 63, 65, 67, 69, 70, 72]

Phrygian: [60, 61, 63, 65, 67, 68, 70, 72]

Lydian: [60, 62, 64, 66, 67, 69, 71, 72]

Mixolydian: [60, 62, 64, 65, 67, 69, 70, 72]

Natural Minor (Aeolian): [60, 62, 63, 65, 67, 68, 70, 72]

Locrian: [60, 61, 63, 65, 66, 68, 70, 72]

Harmonic Minor: [60, 62, 63, 65, 67, 68, 71, 72]

Melodic Minor: [60, 62, 63, 65, 67, 69, 71, 72]

Pentatonic Scales

Major Pentatonic: [60, 62, 64, 67, 69, 72]

Minor Pentatonic: [60, 63, 65, 67, 70, 72]

Blues: [60, 63, 65, 66, 67, 70, 72]

Symmetric Scales

Whole Tone: [60, 62, 64, 66, 68, 70, 72]

Diminished (Whole-half): [60, 62, 63, 65, 66, 68, 69, 71, 72]

Diminished (Half-whole): [60, 61, 63, 64, 66, 67, 69, 70, 72]

Chromatic: [60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72]

def get_intervals(midi_notes):
    """Compute the intervals (semitone differences) between consecutive notes."""
    return [midi_notes[i+1] - midi_notes[i] for i in range(len(midi_notes) - 1)]

def is_interval_palindrome(intervals):
    """Check if an interval list is a palindrome using a deque."""
    dq = deque(intervals)
    while len(dq) > 1:
        if dq.popleft() != dq.pop():
            return False
    return True

Using these functions, we learn the following intervals are palindromes. Note that 2 is a whole step and 1 is a half step:

Palindromic scale intervals

Finally, after following up on recursions, I wanted to know if there were Fibonacci sequences that were palindromic that I could play. Turns out there are! There is something called a Fibonacci word, which is a specific sequence of binary digits formed by repeated concatenation, in the same way that the Fibonacci numbers are formed by repeated addition. A cool fact is that suppressing the last two letters of a Fibonacci word creates a palindrome.

def fibonacci_word(n):
    if n == 1:
        return "0"
    if n == 2:
        return "01"
    return fibonacci_word(n - 1) + fibonacci_word(n - 2)  # concatenate, not add!

We can use the same is_palindrome() function to check if the Fibonacci word is a palindrome. If we go up to 11 digits:

And finally, we can play some of these (shortened) Fibonacci words!

Word: 0100101001001010010