Домашни > Pitches love the D > Решения > Решението на Ева Ганчева

Резултати
9 точки от тестове
1 точки от учител

10 точки общо

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

  1INTERVAL_NAMES = {
  2    0: "unison", 1: "minor 2nd", 2: "major 2nd", 3: "minor 3rd",
  3    4: "major 3rd", 5: "perfect 4th", 6: "diminished 5th",
  4    7: "perfect 5th", 8: "minor 6th", 9: "major 6th",
  5    10: "minor 7th", 11: "major 7th"
  6}
  7NOTES = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]
  8NOTES_COUNT = 12
  9
 10
 11class Tone:
 12    """Represents a musical tone."""
 13
 14    def __init__(self, tone):
 15        """ Initializes Tone."""
 16        self._tone = tone
 17
 18    def __str__(self):
 19        """ Returns the tone as a string."""
 20        return self._tone
 21
 22    @property
 23    def tone(self):
 24        """ Returns tone name."""
 25        return self._tone
 26
 27    def __sub__(self, other):
 28        """Calculates the interval between this tone and another tone or interval."""
 29        if isinstance(other, Tone):
 30            distance = (NOTES.index(self._tone) - NOTES.index(other.tone)) % NOTES_COUNT
 31            return Interval(distance)
 32        elif isinstance(other, Interval):
 33            new_index = (NOTES.index(self._tone) - other.semitones_count) % NOTES_COUNT
 34            return Tone(NOTES[new_index])
 35        raise TypeError("Invalid operation")
 36
 37    def __add__(self, other):
 38        """Adds an interval to the tone or combines it with another tone to form a chord."""
 39        if isinstance(other, Tone):
 40            return Chord(self, other)
 41        elif isinstance(other, Interval):
 42            new_index = (NOTES.index(self._tone) + other.semitones_count) % NOTES_COUNT
 43            return Tone(NOTES[new_index])
 44        raise TypeError("Invalid operation")
 45
 46    def __eq__(self, other):
 47        """Compares this Tone with other"""
 48        if isinstance(other, Tone):
 49            return self._tone == other._tone
 50        return False
 51
 52
 53class Interval:
 54    """Represents a musical interval."""
 55
 56    def __init__(self, semitones_count):
 57        """ Initializes Interval"""
 58        self._semitones_count = semitones_count % NOTES_COUNT
 59
 60    def __str__(self):
 61        """ Returns Interval as a string."""
 62        return INTERVAL_NAMES[self._semitones_count]
 63
 64    @property
 65    def semitones_count(self):
 66        """Returns number of semitones in the interval"""
 67        return self._semitones_count
 68
 69    def __add__(self, other):
 70        """Adds two intervals together"""
 71        if isinstance(other, Interval):
 72            return Interval((self.semitones_count + other.semitones_count) % NOTES_COUNT)
 73        raise TypeError("Invalid operation")
 74
 75    def __neg__(self):
 76        """Returns the negation of the interval"""
 77        return Interval(-self._semitones_count)
 78
 79
 80class Chord:
 81    """ Represents a musical chord"""
 82
 83    def _sort_tones(self):
 84        """Sorts the tones in the chord based on their distance from the root tone"""
 85        root_index = NOTES.index(str(self._root))
 86        return sorted(self._tones, key=lambda tone: (NOTES.index(str(tone)) - root_index) % NOTES_COUNT)
 87
 88    @staticmethod
 89    def _init_tones(root, tones):
 90        """Initializes the list of tones for a chord, ensuring uniqueness"""
 91        memory = {str(root)}
 92        result = [root]
 93        for tone in tones:
 94            if not str(tone) in memory:
 95                memory.add(str(tone))
 96                result.append(tone)
 97        return result
 98
 99    def __init__(self, root, *tones):
100        """ Initializes a Chord"""
101        if not tones or (len(tones) == 1 and str(root) == str(tones[0])):
102            raise TypeError("Cannot have a chord made of only 1 unique tone")
103
104        self._root = root
105        self._tones = self._init_tones(root, tones)
106        self._tones = self._sort_tones()
107
108    def __str__(self):
109        """Returns the chord as a string"""
110        return "-".join(str(tone) for tone in self._tones)
111
112    def _is_interval(self, target_interval):
113        """Checks if the chord contains a specific interval"""
114        for tone in self._tones:
115            current_interval = tone - self._root
116            if current_interval.semitones_count == target_interval:
117                return True
118        return False
119
120    def is_major(self):
121        """Checks if the chord is major"""
122        return self._is_interval(4)
123
124    def is_minor(self):
125        """ Checks if the chord is minor"""
126        return self._is_interval(3)
127
128    def is_power_chord(self):
129        """Checks if the chord is a power chord"""
130        return not self.is_minor() and not self.is_major()
131
132    def __add__(self, other):
133        """ Adds a tone or chord to the current chord"""
134        if isinstance(other, Tone):
135            return Chord(self._root, *self._tones, other)
136        if isinstance(other, Chord):
137            return Chord(self._root, *self._tones, *other._tones)
138        raise TypeError("Invalid operation")
139
140    def _remove_element(self, other):
141        """Removes a specific tone from the chord."""
142        result = []
143        for tone in self._tones:
144            if str(tone) != str(other):
145                result.append(tone)
146        return result
147
148    def __sub__(self, other):
149        """Removes a tone from the chord"""
150        if not isinstance(other, Tone):
151            raise TypeError("Invalid operation")
152        if other not in self._tones:
153            raise TypeError(f"Cannot remove tone {other} from chord {self}")
154        remaining_tones = self._remove_element(other)
155        if len(remaining_tones) < 2:
156            raise TypeError("Cannot have a chord made of only 1 unique tone")
157        new_root = remaining_tones[0] if self._root == other else self._root
158        return Chord(new_root, *remaining_tones)
159
160    def transposed(self, interval):
161        """Transposes all tones in the chord by a given interval"""
162        transposed_tones = []
163        for tone in self._tones:
164            transposed_tones.append(tone + interval)
165        return Chord(transposed_tones[0], *transposed_tones[1:])

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_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

----------------------------------------------------------------------
Ran 37 tests in 0.001s

FAILED (failures=2)

Дискусия
Виктор Бечев
05.11.2024 15:36

Доста чисто решение, браво! Единственото нещо, което мога да добавя, отвъд дребните коментари, е още по-дребно - по конвенция използваме пасивната форма на глаголите в докстринговете, ето ти пример от стандартната библиотека: ``` help(map) Help on class map in module builtins: class map(object) | map(func, *iterables) --> map object | | Make an iterator that computes the function using arguments from | each of the iterables. Stops when the shortest iterable is exhausted. ``` С други думи при теб следва да бъде `"""Remove a tone from the chord"""`, а не "Remove**s**".
История
Това решение има само една версия.