Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 67 additions & 65 deletions electronics/resistor_color_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,27 @@
}


def get_color_value[T](color: str, color_values: dict[str, T], band_name: str) -> T:
"""
Function returns the value stored for the color in color_values. It raises a
ValueError naming the band if the color is not allowed for that band. The
get_* functions below share this lookup and validation.

>>> get_color_value('Red', {'Red': 2}, 'example band')
2

>>> get_color_value('Aqua', {'Red': 2}, 'example band')
Traceback (most recent call last):
...
ValueError: Aqua is not a valid color for example band

"""
if color not in color_values:
msg = f"{color} is not a valid color for {band_name}"
raise ValueError(msg)
return color_values[color]


def get_significant_digits(colors: list) -> str:
"""
Function returns the digit associated with the color. Function takes a
Expand All @@ -149,13 +170,14 @@ def get_significant_digits(colors: list) -> str:
ValueError: Aqua is not a valid color for significant figure bands

"""
digit = ""
for color in colors:
if color not in significant_figures_color_values:
msg = f"{color} is not a valid color for significant figure bands"
raise ValueError(msg)
digit = digit + str(significant_figures_color_values[color])
return str(digit)
return "".join(
str(
get_color_value(
color, significant_figures_color_values, "significant figure bands"
)
)
for color in colors
)


def get_multiplier(color: str) -> float:
Expand All @@ -172,10 +194,7 @@ def get_multiplier(color: str) -> float:
ValueError: Ivory is not a valid color for multiplier band

"""
if color not in multiplier_color_values:
msg = f"{color} is not a valid color for multiplier band"
raise ValueError(msg)
return multiplier_color_values[color]
return get_color_value(color, multiplier_color_values, "multiplier band")


def get_tolerance(color: str) -> float:
Expand All @@ -192,10 +211,7 @@ def get_tolerance(color: str) -> float:
ValueError: Indigo is not a valid color for tolerance band

"""
if color not in tolerance_color_values:
msg = f"{color} is not a valid color for tolerance band"
raise ValueError(msg)
return tolerance_color_values[color]
return get_color_value(color, tolerance_color_values, "tolerance band")


def get_temperature_coeffecient(color: str) -> int:
Expand All @@ -212,10 +228,9 @@ def get_temperature_coeffecient(color: str) -> int:
ValueError: Cyan is not a valid color for temperature coeffecient band

"""
if color not in temperature_coeffecient_color_values:
msg = f"{color} is not a valid color for temperature coeffecient band"
raise ValueError(msg)
return temperature_coeffecient_color_values[color]
return get_color_value(
color, temperature_coeffecient_color_values, "temperature coeffecient band"
)


def get_band_type_count(total_number_of_bands: int, type_of_band: str) -> int:
Expand Down Expand Up @@ -277,19 +292,16 @@ def check_validity(number_of_bands: int, colors: list) -> bool:
ValueError: Cyan is not a valid color

"""
if number_of_bands >= 3 and number_of_bands <= 6:
if number_of_bands == len(colors):
for color in colors:
if color not in valid_colors:
msg = f"{color} is not a valid color"
raise ValueError(msg)
return True
else:
msg = f"Expecting {number_of_bands} colors, provided {len(colors)} colors"
raise ValueError(msg)
else:
msg = "Invalid number of bands. Resistor bands must be 3 to 6"
if not 3 <= number_of_bands <= 6:
raise ValueError("Invalid number of bands. Resistor bands must be 3 to 6")
if number_of_bands != len(colors):
msg = f"Expecting {number_of_bands} colors, provided {len(colors)} colors"
raise ValueError(msg)
for color in colors:
if color not in valid_colors:
msg = f"{color} is not a valid color"
raise ValueError(msg)
return True


def calculate_resistance(number_of_bands: int, color_code_list: list) -> dict:
Expand Down Expand Up @@ -331,41 +343,31 @@ def calculate_resistance(number_of_bands: int, color_code_list: list) -> dict:
ValueError: Lime is not a valid color

"""
is_valid = check_validity(number_of_bands, color_code_list)
if is_valid:
number_of_significant_bands = get_band_type_count(
number_of_bands, "significant"
)
significant_colors = color_code_list[:number_of_significant_bands]
significant_digits = int(get_significant_digits(significant_colors))
multiplier_color = color_code_list[number_of_significant_bands]
multiplier = get_multiplier(multiplier_color)
if number_of_bands == 3:
tolerance_color = None
else:
tolerance_color = color_code_list[number_of_significant_bands + 1]
tolerance = (
20 if tolerance_color is None else get_tolerance(str(tolerance_color))
)
if number_of_bands != 6:
temperature_coeffecient_color = None
else:
temperature_coeffecient_color = color_code_list[
number_of_significant_bands + 2
]
temperature_coeffecient = (
0
if temperature_coeffecient_color is None
else get_temperature_coeffecient(str(temperature_coeffecient_color))
)
resisitance = significant_digits * multiplier
if temperature_coeffecient == 0:
answer = f"{resisitance}Ω ±{tolerance}% "
else:
answer = f"{resisitance}Ω ±{tolerance}% {temperature_coeffecient} ppm/K"
return {"resistance": answer}
else:
if not check_validity(number_of_bands, color_code_list):
raise ValueError("Input is invalid")
number_of_significant_bands = get_band_type_count(number_of_bands, "significant")
significant_digits = int(
get_significant_digits(color_code_list[:number_of_significant_bands])
)
multiplier = get_multiplier(color_code_list[number_of_significant_bands])
# 3 band resistors have no tolerance band (default 20%); only 6 band resistors
# have a temperature coeffecient band (0 means "not present")
tolerance = (
get_tolerance(color_code_list[number_of_significant_bands + 1])
if number_of_bands >= 4
else 20
)
temperature_coeffecient = (
get_temperature_coeffecient(color_code_list[number_of_significant_bands + 2])
if number_of_bands == 6
else 0
)
resistance = significant_digits * multiplier
if temperature_coeffecient == 0:
answer = f"{resistance}Ω ±{tolerance}% "
else:
answer = f"{resistance}Ω ±{tolerance}% {temperature_coeffecient} ppm/K"
return {"resistance": answer}


if __name__ == "__main__":
Expand Down
42 changes: 42 additions & 0 deletions work/R23-T058/REFACTOR.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# R23-T058: refactor `electronics/resistor_color_code.py` (no behaviour change)

## What was duplicated
`get_multiplier`, `get_tolerance`, `get_temperature_coeffecient` and `get_significant_digits`
each repeated the same pattern: "if the colour is not in this band's table, build an
error message and raise ValueError, otherwise return the table value". `check_validity` and
`calculate_resistance` were deeply nested with `None` placeholders and an if/else that could never reach its `else`.

## What changed
1. **New shared helper** `get_color_value(color, color_values, band_name)`: the one place that does the lookup and raises `ValueError(f"{color} is not a valid color for {band_name}")`. The four `get_*` functions now call it with their own band name, so every error message is character-for-character the same as before (including the original "coeffecient" spelling).
2. **`check_validity`**: nested `if/if/for/else/else` replaced by guard clauses (same three errors, same order, same messages). Nesting depth 4 -> 2.
3. **`calculate_resistance`**: guard clause instead of `if is_valid: ... else:`; removed the `tolerance_color`/`temperature_coeffecient_color` `None` placeholders and the redundant `str(...)` wrappers; read the optional bands directly (`if number_of_bands >= 4` / `== 6`); fixed the local variable typo `resisitance` -> `resistance`.
4. Public names, signatures, tables and all 24 original doctests are untouched. Two doctests were added for the new helper (26 total).

## Complexity (McCabe, `ruff check --select C901 --config 'lint.mccabe.max-complexity = 0'`)
| function | before | after |
|---|---|---|
| get_color_value (new) | - | 2 |
| get_significant_digits | 3 | 1 |
| get_multiplier | 2 | 1 |
| get_tolerance | 2 | 1 |
| get_temperature_coeffecient | 2 | 1 |
| get_band_type_count | 3 | 3 |
| check_validity | **5** | **5** |
| calculate_resistance | **5** | **3** |
| total | 22 | 17 |

The worst functions before were `check_validity` and `calculate_resistance` (both 5).
`calculate_resistance` (the longest function: 35 -> 25 lines of code) is now 3.
`check_validity` stays at 5 because its three error conditions and the colour loop are inherent;
it is flatter and easier to read, but the score does not drop. (Repo config allows up to 17, so
`ruff check --select C90` passed before and after.)

## Behaviour proof
- `python -m doctest -v electronics/resistor_color_code.py`: 24 passed before; 26 passed after (24 original + 2 new).
- `work/R23-T058/equivalence_test.py` (60 tests) loads the **original** file and the **refactored** one and compares results and exception type + message on: every colour in every getter (valid, invalid, empty, wrong case, `None`, `int`), all band counts, 5,000 random `check_validity` calls, 20,000 random `calculate_resistance` calls, all 12^3 three-band and 12^4 four-band colour combinations, and one input per `raise` statement. All pass. A deliberate one-word change to an error message makes 7 of them fail, so the test does detect differences.
- Raise paths: all 10 `raise` statements remain. The last one (`raise ValueError("Input is invalid")` in `calculate_resistance`) can never run, because `check_validity` returns True or raises; it was kept so behaviour and the raise count are unchanged.

## Coverage
Baseline and after output are in `coverage_before.txt` / `coverage_after.txt`. The same three statements are
uncovered before and after: the unreachable `raise ValueError("Input is invalid")` and the two lines of the
`if __name__ == "__main__":` block. The refactor reduced the file's statement count (about 74 -> 54 by a line tracer) without losing coverage.
9 changes: 9 additions & 0 deletions work/R23-T058/complexity_after.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
electronics/resistor_color_code.py:138:5: C901 `get_color_value` is too complex (2 > 0)
electronics/resistor_color_code.py:159:5: C901 `get_significant_digits` is too complex (1 > 0)
electronics/resistor_color_code.py:183:5: C901 `get_multiplier` is too complex (1 > 0)
electronics/resistor_color_code.py:200:5: C901 `get_tolerance` is too complex (1 > 0)
electronics/resistor_color_code.py:217:5: C901 `get_temperature_coeffecient` is too complex (1 > 0)
electronics/resistor_color_code.py:236:5: C901 `get_band_type_count` is too complex (3 > 0)
electronics/resistor_color_code.py:275:5: C901 `check_validity` is too complex (5 > 0)
electronics/resistor_color_code.py:307:5: C901 `calculate_resistance` is too complex (3 > 0)
Found 8 errors.
8 changes: 8 additions & 0 deletions work/R23-T058/complexity_before.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
electronics/resistor_color_code.py:138:5: C901 `get_significant_digits` is too complex (3 > 0)
electronics/resistor_color_code.py:161:5: C901 `get_multiplier` is too complex (2 > 0)
electronics/resistor_color_code.py:181:5: C901 `get_tolerance` is too complex (2 > 0)
electronics/resistor_color_code.py:201:5: C901 `get_temperature_coeffecient` is too complex (2 > 0)
electronics/resistor_color_code.py:221:5: C901 `get_band_type_count` is too complex (3 > 0)
electronics/resistor_color_code.py:260:5: C901 `check_validity` is too complex (5 > 0)
electronics/resistor_color_code.py:295:5: C901 `calculate_resistance` is too complex (5 > 0)
Found 7 errors.
53 changes: 53 additions & 0 deletions work/R23-T058/coverage_after.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
============================= test session starts ==============================
platform darwin -- Python 3.13.18, pytest-9.2.0, pluggy-1.7.0 -- /Users/paulamibhosle/TheAlgorithms-Python/.venv/bin/python
cachedir: .pytest_cache
hypothesis profile 'default'
rootdir: /Users/paulamibhosle/TheAlgorithms-Python
configfile: pyproject.toml
plugins: hypothesis-6.170.15, cov-7.1.0
collecting ... collected 8 items

electronics/resistor_color_code.py::electronics.resistor_color_code.calculate_resistance PASSED [ 12%]
electronics/resistor_color_code.py::electronics.resistor_color_code.check_validity PASSED [ 25%]
electronics/resistor_color_code.py::electronics.resistor_color_code.get_band_type_count PASSED [ 37%]
electronics/resistor_color_code.py::electronics.resistor_color_code.get_color_value PASSED [ 50%]
electronics/resistor_color_code.py::electronics.resistor_color_code.get_multiplier PASSED [ 62%]
electronics/resistor_color_code.py::electronics.resistor_color_code.get_significant_digits PASSED [ 75%]
electronics/resistor_color_code.py::electronics.resistor_color_code.get_temperature_coeffecient PASSED [ 87%]
electronics/resistor_color_code.py::electronics.resistor_color_code.get_tolerance PASSED [100%]

================================ tests coverage ================================
______________ coverage: platform darwin, python 3.13.18-final-0 _______________

Name Stmts Miss Cover Missing
----------------------------------------------------------------------
electronics/apparent_power.py 11 11 0% 1-37
electronics/builtin_voltage.py 18 18 0% 1-66
electronics/capacitor_equivalence.py 20 20 0% 3-53
electronics/carrier_concentration.py 20 20 0% 5-75
electronics/charging_capacitor.py 12 12 0% 18-72
electronics/charging_inductor.py 12 12 0% 29-97
electronics/circular_convolution.py 26 26 0% 15-98
electronics/coulombs_law.py 24 24 0% 3-85
electronics/electric_conductivity.py 19 19 0% 1-73
electronics/electric_power.py 20 20 0% 2-59
electronics/electrical_impedance.py 15 15 0% 7-46
electronics/full_wave_rectifier.py 27 27 0% 1-130
electronics/half_wave_rectifier.py 27 27 0% 1-130
electronics/ic_555_timer.py 13 13 0% 1-75
electronics/ind_reactance.py 21 21 0% 2-69
electronics/ohms_law.py 16 16 0% 2-42
electronics/power_factor_correction.py 23 23 0% 4-125
electronics/real_and_reactive_power.py 12 12 0% 1-49
electronics/resistor_equivalence.py 20 20 0% 3-56
electronics/resonant_frequency.py 11 11 0% 11-49
electronics/star_delta_conversions.py 25 25 0% 5-103
electronics/wheatstone_bridge.py 8 8 0% 2-72
electronics/resistor_color_code.py 54 3 94% 347, 374-376
electronics/__init__.py 0 0 100%
----------------------------------------------------------------------
TOTAL 454 403 11%
============================= slowest 10 durations =============================

(10 durations < 0.005s hidden. Use -vv to show these durations.)
============================== 8 passed in 0.14s ===============================
52 changes: 52 additions & 0 deletions work/R23-T058/coverage_before.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
============================= test session starts ==============================
platform darwin -- Python 3.13.18, pytest-9.2.0, pluggy-1.7.0 -- /Users/paulamibhosle/TheAlgorithms-Python/.venv/bin/python
cachedir: .pytest_cache
hypothesis profile 'default'
rootdir: /Users/paulamibhosle/TheAlgorithms-Python
configfile: pyproject.toml
plugins: hypothesis-6.170.15, cov-7.1.0
collecting ... collected 7 items

electronics/resistor_color_code.py::electronics.resistor_color_code.calculate_resistance PASSED [ 14%]
electronics/resistor_color_code.py::electronics.resistor_color_code.check_validity PASSED [ 28%]
electronics/resistor_color_code.py::electronics.resistor_color_code.get_band_type_count PASSED [ 42%]
electronics/resistor_color_code.py::electronics.resistor_color_code.get_multiplier PASSED [ 57%]
electronics/resistor_color_code.py::electronics.resistor_color_code.get_significant_digits PASSED [ 71%]
electronics/resistor_color_code.py::electronics.resistor_color_code.get_temperature_coeffecient PASSED [ 85%]
electronics/resistor_color_code.py::electronics.resistor_color_code.get_tolerance PASSED [100%]

================================ tests coverage ================================
______________ coverage: platform darwin, python 3.13.18-final-0 _______________

Name Stmts Miss Cover Missing
----------------------------------------------------------------------
electronics/apparent_power.py 11 11 0% 1-37
electronics/builtin_voltage.py 18 18 0% 1-66
electronics/capacitor_equivalence.py 20 20 0% 3-53
electronics/carrier_concentration.py 20 20 0% 5-75
electronics/charging_capacitor.py 12 12 0% 18-72
electronics/charging_inductor.py 12 12 0% 29-97
electronics/circular_convolution.py 26 26 0% 15-98
electronics/coulombs_law.py 24 24 0% 3-85
electronics/electric_conductivity.py 19 19 0% 1-73
electronics/electric_power.py 20 20 0% 2-59
electronics/electrical_impedance.py 15 15 0% 7-46
electronics/full_wave_rectifier.py 27 27 0% 1-130
electronics/half_wave_rectifier.py 27 27 0% 1-130
electronics/ic_555_timer.py 13 13 0% 1-75
electronics/ind_reactance.py 21 21 0% 2-69
electronics/ohms_law.py 16 16 0% 2-42
electronics/power_factor_correction.py 23 23 0% 4-125
electronics/real_and_reactive_power.py 12 12 0% 1-49
electronics/resistor_equivalence.py 20 20 0% 3-56
electronics/resonant_frequency.py 11 11 0% 11-49
electronics/star_delta_conversions.py 25 25 0% 5-103
electronics/wheatstone_bridge.py 8 8 0% 2-72
electronics/resistor_color_code.py 74 3 96% 368, 372-374
electronics/__init__.py 0 0 100%
----------------------------------------------------------------------
TOTAL 474 403 15%
============================= slowest 10 durations =============================

(10 durations < 0.005s hidden. Use -vv to show these durations.)
============================== 7 passed in 0.14s ===============================
Loading