Домашни > Pitches love the D > Решения > Решението на Нели Илиева

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

11 точки общо

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

  1MAX_COUNT_TONES = 12
  2
  3
  4class Tone:
  5    """Represent a musical tone."""
  6    TONE_NAMES = ("C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B")
  7
  8    def __init__(self, name):
  9        """Initialize a Tone instance with name."""
 10        self.name = name
 11
 12    @property
 13    def name(self):
 14        """Return the name of the tone."""
 15        return self.__name
 16
 17    @name.setter
 18    def name(self, value):
 19        """Validate tone and set the tone name."""
 20        if value not in self.TONE_NAMES:
 21            raise ValueError("Invalid tone name")
 22        self.__name = value
 23
 24    def __str__(self):
 25        """Return string representation of the tone."""
 26        return self.name
 27
 28    def __add__(self, other):
 29        """Add an interval or a tone to the current tone."""
 30        if isinstance(other, Tone):
 31            return Chord(self, other)
 32        if isinstance(other, Interval):
 33            idx = (self.TONE_NAMES.index(self.name) + other.steps) % MAX_COUNT_TONES
 34            return Tone(self.TONE_NAMES[idx])
 35        raise TypeError("Invalid operation")
 36
 37    def __sub__(self, other):
 38        """Subtract an interval or a tone from the current tone."""
 39        if isinstance(other, Tone):
 40            steps = (Tone.TONE_NAMES.index(self.name) - Tone.TONE_NAMES.index(other.name)) % MAX_COUNT_TONES
 41            return Interval(steps)
 42        if isinstance(other, Interval):
 43            idx = (self.TONE_NAMES.index(self.name) - other.steps) % MAX_COUNT_TONES
 44            return Tone(self.TONE_NAMES[idx])
 45        raise TypeError("Invalid operation")
 46
 47    def __eq__(self, other):
 48        """Check if two tones are equal."""
 49        return isinstance(other, Tone) and self.name == other.name
 50
 51
 52class Interval:
 53    """Represent a musical interval between two tones."""
 54    _INTERVAL_NAMES = ("unison", "minor 2nd", "major 2nd", "minor 3rd", "major 3rd",
 55                       "perfect 4th", "diminished 5th", "perfect 5th", "minor 6th",
 56                       "major 6th", "minor 7th", "major 7th")
 57
 58    def __init__(self, steps):
 59        """Initialize a Interval instance with steps."""
 60        self.steps = steps
 61
 62    @property
 63    def steps(self):
 64        """Return the number of the steps for the interval."""
 65        return self.__steps
 66
 67    @steps.setter
 68    def steps(self, value):
 69        """Validate steps and set the number of steps for the interval."""
 70        if not isinstance(value, int):
 71            raise ValueError("Invalid steps")
 72        self.__steps = value % MAX_COUNT_TONES
 73
 74    def __str__(self):
 75        """Return string representation of the interval."""
 76        return self._INTERVAL_NAMES[self.steps]
 77
 78    def __add__(self, other):
 79        """Add an interval to the current tone."""
 80        if isinstance(other, Interval):
 81            return Interval(self.steps + other.steps)
 82        raise TypeError("Invalid operation")
 83
 84    def __neg__(self):
 85        """Negates the interval"""
 86        return Interval(-self.steps)
 87
 88
 89class Chord:
 90    """Represent a musical chord consisting of a root tone and additional tones."""
 91    __MIN_POSSIBLE_TONES = 2
 92
 93    def __init__(self, root, *tones):
 94        """Initialize a Chord instance with a root tone and other tones."""
 95        self.root = root
 96        self.tones = tones
 97
 98    @property
 99    def root(self):
100        """Return the root tone."""
101        return self.__root
102
103    @root.setter
104    def root(self, value):
105        """Validate root and set the root tone of the chord."""
106        if not isinstance(value, Tone):
107            raise ValueError("Invalid root")
108        self.__root = value
109
110    @property
111    def tones(self):
112        """Return the list of tones in the chord."""
113        return self.__tones
114
115    @tones.setter
116    def tones(self, value):
117        """Validate list of tones and set the list of tones of the chord."""
118        unique_tones = [self.root]
119
120        for tone in value:
121            if not isinstance(tone, Tone):
122                raise ValueError("Invalid tone")
123            if tone not in unique_tones:
124                unique_tones.append(tone)
125
126        if len(unique_tones) < self.__MIN_POSSIBLE_TONES:
127            raise TypeError("Cannot have a chord made of only 1 unique tone")
128
129        root_idx = Tone.TONE_NAMES.index(self.root.name)
130        self.__tones = sorted(unique_tones, key=lambda x: (Tone.TONE_NAMES.index(x.name) - root_idx) % MAX_COUNT_TONES)
131
132    def __str__(self):
133        """Return string representation of the chord."""
134        return "-".join(str(tone) for tone in self.__tones)
135
136    def _check_interval(self, steps):
137        """Helper method to check if the chord contains a tone with a specific interval from the root."""
138        root_idx = Tone.TONE_NAMES.index(self.root.name)
139
140        for t in self.__tones:
141            if t != self.root:
142                tone_index = Tone.TONE_NAMES.index(t.name)
143                interval = (tone_index - root_idx) % MAX_COUNT_TONES
144                if interval == steps:
145                    return True
146        return False
147
148    def is_minor(self):
149        """Check if the chord is a minor chord."""
150        return self._check_interval(3)
151
152    def is_major(self):
153        """Check if the chord is a major chord."""
154        return self._check_interval(4)
155
156    def is_power_chord(self):
157        """Check if the chord is a power chord."""
158        return not self.is_minor() and not self.is_major()
159
160    def __add__(self, other):
161        """Add a tone or a chord to the current chord."""
162        if isinstance(other, Tone):
163            if other in self.tones:
164                return self
165            return Chord(self.root, *self.__tones, other)
166        if isinstance(other, Chord):
167            combined_tones = list(self.__tones) + list(other.tones)
168            return Chord(self.root, *combined_tones)
169        raise TypeError("Invalid operation")
170
171    def __sub__(self, other):
172        """Remove a tone from the current chord."""
173        if isinstance(other, Tone):
174            if other not in self.tones:
175                raise TypeError(f"Cannot remove tone {other} from chord {self}")
176            new_tones = [t for t in self.tones if t != other]
177            if len(new_tones) < self.__MIN_POSSIBLE_TONES:
178                raise TypeError("Cannot have a chord made of only 1 unique tone")
179            new_root = new_tones[0] if other == self.root else self.root
180            return Chord(new_root, *new_tones)
181        raise TypeError("Invalid operation")
182
183    def transposed(self, interval):
184        """Transpose the chord by given interval."""
185        if not isinstance(interval, Interval):
186            raise TypeError("Invalid interval")
187
188        transposed_tones = [t + interval for t in self.tones]
189        return Chord(*transposed_tones)

...........................F.........
======================================================================
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.002s

FAILED (failures=1)

Дискусия
История
Това решение има само една версия.