Домашни > Pitches love the D > Решения > Решението на Камелия Михайлова

Резултати
8 точки от тестове
0 точки от учител

8 точки общо

31 успешни теста
6 неуспешни теста
Код
Скрий всички коментари

  1_TONES = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]
  2_INTERVALS = {
  3        0: "unison", 1: "minor 2nd", 2: "major 2nd", 3: "minor 3rd", 4: "major 3rd", 
  4        5: "perfect 4th", 6: "tritone", 7: "perfect 5th", 8: "minor 6th", 
  5        9: "major 6th", 10: "minor 7th", 11: "major 7th"
  6    }
  7    
  8
  9class Tone:
 10
 11    def __init__(self, name):
 12        if name not in _TONES:
 13            raise ValueError("Invalid tone name")
 14        self.name = name
 15        self.index = _TONES.index(name)
 16
 17    def __str__(self):
 18        return self.name
 19
 20    def __add__(self, other):
 21        if isinstance(other, Tone):
 22            return Chord(self, other)
 23        elif isinstance(other, Interval):
 24            new_index = (self.index + other.semitones) % 12
 25            return Tone(_TONES[new_index])
 26        raise TypeError("Invalid operation")
 27
 28    def __sub__(self, other):
 29        if isinstance(other, Tone):
 30            semitone_difference = (self.index - other.index) % 12
 31            return Interval(semitone_difference)
 32        elif isinstance(other, Interval):
 33            new_index = (self.index - other.semitones) % 12
 34            return Tone(_TONES[new_index])
 35        raise TypeError("Invalid operation")
 36
 37
 38class Interval:
 39   
 40    def __init__(self, semitones):
 41        self.semitones = semitones % 12
 42        self.name = _INTERVALS.get(self.semitones)
 43
 44    def __str__(self):
 45        return self.name
 46
 47    def __add__(self, other):
 48        if isinstance(other, Interval):
 49            return Interval(self.semitones + other.semitones)
 50        raise TypeError("Invalid operation")
 51    def __neg__(self):
 52        return Interval(-self.semitones % 12)
 53
 54
 55class Chord:
 56
 57    def __init__(self, root, *other_tones):
 58        self.root = root
 59        unique_tones = {root} | {tone for tone in other_tones}
 60
 61        if len(unique_tones) == 1:
 62            raise TypeError("Cannot have a chord made of only 1 unique tone")
 63        root_index = _TONES.index(root.name)
 64        self.tones = sorted(unique_tones, key=lambda tone: (_TONES.index(tone.name) - root_index) % len(_TONES))
 65
 66    def __str__(self):
 67        return "-".join(str(tone) for tone in self.tones)
 68
 69    def is_minor(self):
 70        minor_third = Interval(3)
 71        return any((tone - self.root).semitones == minor_third.semitones for tone in self.tones[1:])
 72
 73    def is_major(self):
 74        major_third = Interval(4)
 75        return any((tone - self.root).semitones == major_third.semitones for tone in self.tones[1:])
 76
 77    def is_power_chord(self):
 78        return not self.is_minor() and not self.is_major()
 79
 80    def __add__(self, other):
 81        if isinstance(other, Tone):
 82            if other.name in [tone.name for tone in self.tones]:
 83                return self
 84            return Chord(self.root, *(self.tones + [other]))
 85        elif isinstance(other, Chord):
 86            new_tones = {tone.name: tone for tone in self.tones + other.tones}
 87            return Chord(self.root, *new_tones.values())
 88        raise TypeError("Invalid operation")
 89
 90    def __sub__(self, other):
 91        if isinstance(other, Tone):
 92            if other.name not in [tone.name for tone in self.tones]:
 93                raise TypeError(f"Cannot remove tone {other} from chord {self}")
 94            new_tones = [tone for tone in self.tones if tone.name != other.name]
 95            if len(new_tones) < 2:
 96                raise TypeError("Cannot have a chord made of only 1 unique tone")
 97            return Chord(self.root, *new_tones)
 98        raise TypeError("Invalid operation")
 99
100    def transposed(self, interval):
101        if not isinstance(interval, Interval):
102            raise TypeError("Invalid transposition interval")
103        transposed_tones = [tone + interval for tone in self.tones]
104        return Chord(transposed_tones[0], *transposed_tones[1:])

F..F.........F..F..........F.....F...
======================================================================
FAIL: test_chord_not_enough_tones (test.TestBasicChordFunctionality.test_chord_not_enough_tones)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 83, in test_chord_not_enough_tones
with self.assertRaises(TypeError) as err:
AssertionError: TypeError not raised

======================================================================
FAIL: test_chord_tone_repetition (test.TestBasicChordFunctionality.test_chord_tone_repetition)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 74, in test_chord_tone_repetition
self.assertEqual(str(a_minor_chord), "A-C-E")
AssertionError: 'A-A-A-A-A-A-A-C-C-C-C-C-C-C-C-C-C-C-C-C-C-E-E-E-E-E-E-E-E-E-E-E-E-E-E-E-E-E-E' != 'A-C-E'
- A-A-A-A-A-A-A-C-C-C-C-C-C-C-C-C-C-C-C-C-C-E-E-E-E-E-E-E-E-E-E-E-E-E-E-E-E-E-E
+ A-C-E

======================================================================
FAIL: test_interval_str (test.TestBasicIntervalFunctionality.test_interval_str)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 42, in test_interval_str
self.assertEqual(str(Interval(index)), interval)
AssertionError: 'tritone' != 'diminished 5th'
- tritone
+ diminished 5th

======================================================================
FAIL: test_add_chords_repeating_notes (test.TestOperations.test_add_chords_repeating_notes)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 305, in test_add_chords_repeating_notes
self.assertEqual(str(result_chord), "C-G")
AssertionError: 'C-C-G' != 'C-G'
- C-C-G
? --
+ C-G

======================================================================
FAIL: test_subtract_interval_from_tone_left_side_error (test.TestOperations.test_subtract_interval_from_tone_left_side_error)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 235, in test_subtract_interval_from_tone_left_side_error
self.assertEqual(str(err.exception), INVALID_OPERATION)
AssertionError: "unsupported operand type(s) for -: 'Interval' and 'Tone'" != 'Invalid operation'
- unsupported operand type(s) for -: 'Interval' and 'Tone'
+ Invalid operation

======================================================================
FAIL: test_tone_addition_same_tone (test.TestOperations.test_tone_addition_same_tone)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 166, in test_tone_addition_same_tone
with self.assertRaises(TypeError) as err:
AssertionError: TypeError not raised

----------------------------------------------------------------------
Ran 37 tests in 0.003s

FAILED (failures=6)

Дискусия
Виктор Бечев
06.11.2024 16:45

Коментарите по решението са предимно по отношение на дизайна и някои питонски идиоми, но всъщност решението е чисто, с добър стил и се чете лесно.
История
Това решение има само една версия.