Skip to content

Commit

Permalink
python/luhn: 1st iteration
Browse files Browse the repository at this point in the history
  • Loading branch information
vpayno committed Apr 18, 2024
1 parent f79a455 commit 5cb69c1
Show file tree
Hide file tree
Showing 15 changed files with 1,242 additions and 6 deletions.
1 change: 1 addition & 0 deletions python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,4 @@
- [roman-numerals](./roman-numerals/README.md)
- [scrabble-score](./scrabble-score/README.md)
- [difference-of-squares](./difference-of-squares/README.md)
- [luhn](./luhn/README.md)
1 change: 1 addition & 0 deletions python/luhn/.coverage
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
!coverage.py: This is a private format, don't read it directly!{"arcs":{"/home/vpayno/git_vpayno/exercism-workspace/python/luhn/test/__init__.py":[[0,0],[0,-1]],"/home/vpayno/git_vpayno/exercism-workspace/python/luhn/luhn.py":[[0,1],[1,3],[3,4],[4,7],[7,7],[7,8],[8,10],[10,27],[27,-7],[7,55],[55,55],[55,56],[56,58],[58,61],[61,95],[95,137],[137,-55],[55,-1],[58,59],[59,-58],[61,87],[87,88],[88,89],[10,25],[25,-10],[89,93],[95,129],[27,37],[37,50],[50,52],[37,48],[48,-37],[52,52],[52,-27],[129,131],[137,175],[175,177],[177,178],[178,180],[180,182],[182,183],[183,185],[185,186],[186,188],[188,182],[182,191],[191,193],[193,195],[195,198],[198,-137],[131,133],[133,135],[135,-95],[93,-61],[195,196],[196,198],[87,91],[91,-61],[185,188],[89,91]]}}
65 changes: 65 additions & 0 deletions python/luhn/.coverage.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?xml version="1.0" ?>
<coverage branch-rate="0.9" branches-covered="9" branches-valid="10" complexity="0" line-rate="0.9737" lines-covered="37" lines-valid="38" timestamp="1713414237576" version="4.5.4">
<!-- Generated by coverage.py: https://coverage.readthedocs.io -->
<!-- Based on https://raw.githubusercontent.com/cobertura/web/master/htdocs/xml/coverage-04.dtd -->
<sources>
<source>/home/vpayno/git_vpayno/exercism-workspace/python/luhn</source>
</sources>
<packages>
<package branch-rate="0.9" complexity="0" line-rate="0.9737" name=".">
<classes>
<class branch-rate="0.9" complexity="0" filename="luhn.py" line-rate="0.9737" name="luhn.py">
<methods/>
<lines>
<line hits="0" number="0"/>
<line hits="1" number="3"/>
<line hits="1" number="4"/>
<line hits="1" number="7"/>
<line hits="1" number="10"/>
<line hits="1" number="25"/>
<line hits="1" number="27"/>
<line hits="1" number="37"/>
<line hits="1" number="48"/>
<line hits="1" number="50"/>
<line branch="true" condition-coverage="50% (1/2)" hits="1" missing-branches="exit" number="52"/>
<line hits="1" number="55"/>
<line hits="1" number="58"/>
<line hits="1" number="59"/>
<line hits="1" number="61"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="86"/>
<line hits="1" number="91"/>
<line hits="1" number="93"/>
<line hits="1" number="95"/>
<line hits="1" number="129"/>
<line hits="1" number="131"/>
<line hits="1" number="133"/>
<line hits="1" number="135"/>
<line hits="1" number="137"/>
<line hits="1" number="175"/>
<line hits="1" number="177"/>
<line hits="1" number="178"/>
<line hits="1" number="180"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="182"/>
<line hits="1" number="183"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="185"/>
<line hits="1" number="186"/>
<line hits="1" number="188"/>
<line hits="1" number="191"/>
<line hits="1" number="193"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="195"/>
<line hits="1" number="196"/>
<line hits="1" number="198"/>
</lines>
</class>
</classes>
</package>
<package branch-rate="1" complexity="0" line-rate="1" name="test">
<classes>
<class branch-rate="1" complexity="0" filename="test/__init__.py" line-rate="1" name="__init__.py">
<methods/>
<lines/>
</class>
</classes>
</package>
</packages>
</coverage>
2 changes: 2 additions & 0 deletions python/luhn/.coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[run]
omit = __init__.py, *_test.py
1 change: 1 addition & 0 deletions python/luhn/.pylintrc
7 changes: 6 additions & 1 deletion python/luhn/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,9 @@ Sum the digits

### Based on

The Luhn Algorithm on Wikipedia - https://en.wikipedia.org/wiki/Luhn_algorithm
The Luhn Algorithm on Wikipedia - https://en.wikipedia.org/wiki/Luhn_algorithm

### My Solution

- [my solution](./luhn.py)
- [run-tests](./run-tests-python.txt)
202 changes: 197 additions & 5 deletions python/luhn/luhn.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,198 @@
class Luhn:
def __init__(self, card_num):
pass
"""Python Luhn Exercism"""

def valid(self):
pass
import re
from itertools import chain


class String(str):
"""Extending String class/type"""

def match(self, regex: str) -> bool:
"""Does the String match a regex?
>>> s: String = String("1234 5678")
>>> s
'1234 5678'
>>> s.match(r"^[0-9 ]+$")
True
>>> s.match(r"^[a-z ]+$")
False
:param regex: rstr
:return: bool
"""

return bool(re.match(regex, self))

def extract_digits(self) -> list[int]:
"""Extracts digits from a code.
>>> s: String = String("1234 5678")
>>> s.extract_digits()
[1, 2, 3, 4, 5, 6, 7, 8]
:return: list[int]
"""

def is_digit(rune: str) -> bool:
"""Test function for filter.
>>> is_digit("a")
False
>>> is_digit("7")
True
:return: True if rune is a digit
"""

return rune.isdigit()

filtered = filter(is_digit, list(self))

return [int(r) for r in filtered]


class Luhn: # pylint: disable=too-few-public-methods
"""Determine if a number is a valid Luhn number."""

def __init__(self, card_num: str) -> None:
self.code: String = String(card_num.strip())

def valid(self) -> bool:
"""Is this a valid Luhn number?
>>> l: Luhn = Luhn("055 444 285")
>>> l.valid()
True
>>> l: Luhn = Luhn("055 444 286")
>>> l.valid()
False
>>> l: Luhn = Luhn("234 567 891 234")
>>> l.valid()
True
>>> n: String = String("234 567 891 234")
>>> n == "0"
False
>>> len(n) == 0
False
>>> n == "0"
False
>>> n.match(r"^([0-9 ])+$")
True
:return: bool
"""

if (
self.code == "0"
or len(self.code) == 0
or not self.code.match(r"^([0-9 ])+$")
):
return False

return self.__is_luhn_number()

def __is_luhn_number(self) -> bool:
"""Is the code a valid luhn number?
>>> l: Luhn = Luhn("055 444 285")
>>> l._Luhn__is_luhn_number()
True
>>> l: Luhn = Luhn("055 444 286")
>>> l._Luhn__is_luhn_number()
False
>>> digits: list[int] = l.code.extract_digits()
>>> digits
[0, 5, 5, 4, 4, 4, 2, 8, 6]
>>> numbers: list[int] = l._Luhn__step_one_and_two(digits)
>>> numbers
[1, 5, 8, 4, 8, 2, 7, 6, 0]
>>> digit_sum: int = sum(numbers)
>>> digit_sum
41
>>> l: Luhn = Luhn("234 567 891 234")
>>> l._Luhn__is_luhn_number()
True
>>> digits: list[int] = l.code.extract_digits()
>>> digits
[2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4]
>>> numbers: list[int] = l._Luhn__step_one_and_two(digits)
>>> numbers
[4, 3, 8, 5, 3, 7, 7, 9, 2, 2, 6, 4]
>>> digit_sum: int = sum(numbers)
>>> digit_sum
60
:return: bool
"""

digits: list[int] = self.code.extract_digits()

numbers: list[int] = self.__step_one_and_two(digits)

digit_sum: int = sum(numbers)

return (digit_sum % 10) == 0

def __step_one_and_two(self, digits: list[int]) -> list[int]:
"""Performs steps one and two of the luhn number verification algorithm.
>>> l: Luhn = Luhn("0123456789")
>>> d: list[int] = l.code.extract_digits()
>>> d
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> l._Luhn__step_one_and_two(d)
[0, 1, 4, 3, 8, 5, 3, 7, 7, 9]
>>> l: Luhn = Luhn("59")
>>> d: list[int] = l.code.extract_digits()
>>> d
[5, 9]
>>> s = l._Luhn__step_one_and_two(d)
>>> s
[1, 9]
>>> sum(s)
10
>>> l: Luhn = Luhn(" 0")
>>> d: list[int] = l.code.extract_digits()
>>> d
[0]
>>> l._Luhn__step_one_and_two(d)
[0]
>>> sum(l._Luhn__step_one_and_two(d))
0
>>> l: Luhn = Luhn("234 567 891 234")
>>> d: list[int] = l.code.extract_digits()
>>> d
[2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4]
>>> l._Luhn__step_one_and_two(d)
[4, 3, 8, 5, 3, 7, 7, 9, 2, 2, 6, 4]
>>> sum(l._Luhn__step_one_and_two(d))
57
:return: list[int]
"""

digits.reverse()

even: list[int] = digits[0::2]
odd: list[int] = digits[1::2]

new_odd: list[int] = []

for n in odd:
n *= 2

if n > 9:
n -= 9

new_odd.append(n)

# flatten the list of tuples
new_list: list[int] = list(chain.from_iterable(zip(even, new_odd)))

new_list.reverse()

if len(even) != len(odd):
new_list.append(even[-1])

return new_list
Loading

0 comments on commit 5cb69c1

Please sign in to comment.