5
\$\begingroup\$

I've been working on a simple program that matches a scale with its key signature. It's fairly simple, but I feel there is redundancy in the dict I created since I don't know how I could assign two keys for a value. Furthermore, I tried setting the dict in classes, but I didn't know how to format it correctly.

The strings are written in Spanish since this was a program requested by my friend.

major_and_sharp_notes = {
    'do mayor':'ninguna',
    'sol mayor':'fa#',
    're mayor':'fa#, do#',
    'la mayor':'fa#, do#, sol#',
    'mi mayor':'fa♯, do♯, sol♯, re♯',
    'si mayor':'fa♯, do♯, sol♯, re♯, la♯', 
    'fa sostenido mayor':'fa♯, do♯, sol♯, re♯, la♯, mi♯',
    'do sostenido mayor':'fa♯, do♯, sol♯, re♯, la♯, mi♯, si♯',
}

minor_and_sharp_notes = {
    'la menor':'ninguna',
    'mi menor':'fa♯',
    'si menor':'fa♯, do♯',
    'fa sostenido menor':'fa♯, do♯, sol♯',
    'do sostenido menor':'fa♯, do♯, sol♯, re♯',
    'sol sostenido menor':'fa♯, do♯, sol♯, re♯, la♯',
    're sostenido menor':'fa♯, do♯, sol♯, re♯, la♯, mi♯',
    'la sostenido menor':'fa♯, do♯, sol♯, re♯, la♯, mi♯, si♯',
}

major_and_flat_notes = {
    'fa mayor':'si♭',
    'si bemol mayor':'si♭, mi♭',
    'mi bemol mayor':'si♭, mi♭, la♭',
    'la bemol mayor':'si♭, mi♭, la♭, re♭',
    're bemol mayor':'si♭, mi♭, la♭, re♭, sol♭',
    'sol bemol mayor':'si♭, mi♭, la♭, re♭, sol♭, do♭',
    'do bemol mayor':'si♭, mi♭, la♭, re♭, sol♭, do♭, fa♭',
}



minor_and_flat_notes = {
    're menor':'si♭',
    'sol menor':'si♭, mi♭',
    'do menor':'si♭, mi♭, la♭',
    'fa menor':'si♭, mi♭, la♭, re♭',
    'si bemol menor':'si♭, mi♭, la♭, re♭, sol♭',
    'mi bemol menor':'si♭, mi♭, la♭, re♭, sol♭, do♭',
    'la bemol menor':'si♭, mi♭, la♭, re♭, sol♭, do♭, fa♭',
}

def search():   
    while True: 
        user_search = input('Escriba la tonalidad para encontrar su armadura:').lower()
        response = "La armadura de %s es: " % user_search

        if user_search in major_and_sharp_notes:
            print(response + major_and_sharp_notes[user_search])

        elif user_search in minor_and_sharp_notes:
            print(response + minor_and_sharp_notes[user_search])

        elif user_search in major_and_flat_notes:
            print(response + major_and_flat_notes[user_search])

        elif user_search in minor_and_flat_notes:
            print(response + minor_and_flat_notes[user_search])

        elif user_search == 'quit':
            return False

        else:
            print("Estas seguro que esa es una tonalidad?") 

def main():
    print ('Bienvenido al buscador de armaduras por Jfelix\n'
           'Escriba \'quit\' cuando termine de buscar.\n'       
        )
    search()

if __name__ == '__main__':
    main() 
\$\endgroup\$
4
  • 1
    \$\begingroup\$ You can not assign two keys for a value. That is not one of the properties of dictionary. \$\endgroup\$
    – arsho
    May 20 '17 at 5:17
  • 1
    \$\begingroup\$ I am not that fluent in Spanish. Care to translate armadura and nunguna? That said, a single dictionary seem to suffice, \$\endgroup\$
    – vnp
    May 20 '17 at 5:22
  • \$\begingroup\$ uhh, I thought it was possible to have two keys. Armadura is the same as note and ninguna is the same as nothing. Btw, does anyone know about the classes? \$\endgroup\$
    – Jfelix
    May 20 '17 at 15:57
  • \$\begingroup\$ "I don't know how I could assign two keys for a value" is ambiguous, given the context: by key do you mean index into the dictionary or musical key? \$\endgroup\$ May 24 '17 at 8:18
1
\$\begingroup\$

As with many programming problems, this one can be solved with an extra layer of indirection. As you know, the sharps and flats are added in a predictable order (the circle of fifths). A highly compressed way to describe a key signature is to state how many sharps or flats there are. Here, I'll choose to encode sharps as positive numbers, and flats as negative numbers. I'll also write it using a two-column layout.

SCALES = {
    'do bemol mayor': -7,           'la bemol menor': -7,
    'sol bemol mayor': -6,          'mi bemol menor': -6,
    're bemol mayor': -5,           'si bemol menor': -5,
    'la bemol mayor': -4,           'fa menor': -4,
    'mi bemol mayor': -3,           'do menor': -3,
    'si bemol mayor': -2,           'so menor': -2,
    'fa mayor': -1,                 're menor': -1,
    'do mayor': 0,                  'la menor': 0,
    'sol mayor': +1,                'mi menor': +1,
    're mayor': +2,                 'si menor': +2,
    'la mayor': +3,                 'fa sostenido menor': +3,
    'mi mayor': +4,                 'do sostenido menor': +4,
    'si mayor': +5,                 'sol sostenido menor': +5,
    'fa sostenido mayor': +6,       're sostenido menor': +6,
    'do sostenido mayor': +7,       'la sostenido menor': +7,
}

The trick, then, is to write some code to perform a lookup using that data structure. Here's how I would do it:

KEY_SIGNATURE_DECODERS = (
    lambda n: ['ninguna'],
    lambda n: ['fa♯', 'do♯', 'sol♯', 're♯', 'la♯', 'mi♯', 'si♯'][0:+n],
    lambda n: ['si♭', 'mi♭', 'la♭', 're♭', 'sol♭', 'do♭', 'fa♭'][0:-n],
)

def key_signature(scale):
    """
    Given the name of a major or minor scale in Spanish (e.g. "re mayor"),
    return a list of the sharps or flats in its key signature.
    For C Major and A Minor, return a one-element list containing
    the string "ninguna".
    """
    def signum(n): return -1 if n < 0 else +1 if n > 0 else 0
    count = SCALES[scale]
    return KEY_SIGNATURE_DECODERS[signum(count)](count)

Note that I would divide the work between the search() and main() functions differently, such that one function is purely responsible for the lookup, and the other serves as the user interface (prompting, formatting, and looping).

def main():
    print("Bienvenido al buscador de armaduras por Jfelix\n"
          "Escriba 'quit' cuando termine de buscar.")
    while True:
        scale = input('Escriba la tonalidad para encontrar su armadura: ')
        scale = scale.lower()
        if scale == 'quit':
            break
        try:
            print('La armadura de {0} es: {1}'.format(
                scale,
                ', '.join(key_signature(scale))
            ))
        except KeyError:    # Ha ha, KeyError -- get it?
            print('¿Estas seguro que esa es una tonalidad?')

if __name__ == '__main__':
    main()
\$\endgroup\$
1
  • \$\begingroup\$ KeyError, nice :) \$\endgroup\$
    – mkrieger1
    May 31 '17 at 11:30
0
\$\begingroup\$

@arsho is correct, you can't assign two keys to the same value.

Since you only have to worry about 14 notes (A-F sharps and flats) you could pair each key signature to a list of numbers, where each number corresponds to a different scale degree. Then map from the integers to the notes themselves.

Here's my take:

keys = {'do mayor':[0], 'sol mayor':[1], ..., 'fa menor',[8, 9, 10, 11], ...}
degree_to_note = {0:"ninguna", 1:"fa#", ...}

def search():
    while True:
        user_search = input('Escriba la tonalidad para encontrar su armadura:').lower()
        response = "La armadura de %s es: " % user_search

        if user_search in keys:
            degrees = list(x for x in keys[user_search])
            notes =  list(degree_to_note[n] for n in y)
            x = "".join(str(x) + ", "  for x in notes).rstrip(", ")
            print(response + x)
        else:
            print("Estas seguro que esa es una tonalidad?") 

EDIT:

My mapping is redundant. Also, this dictionary isn't that large so why not keep all the key signatures and their keys in one

keys = {'do mayor':['ninguna'], 'sol mayor':['fa#'], ...}

def search():
    user_search = input('Escriba la tonalidad para encontrar su armadura:').lower()
    input('Escriba la tonalidad para encontrar su armadura:').lower()

    if user_search in keys:
        x = "".join(x + ", " for x in keys[user_search]).rstrip(", ")
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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