diff --git a/.pylintrc b/.pylintrc index 84d749e..5a43040 100644 --- a/.pylintrc +++ b/.pylintrc @@ -192,6 +192,9 @@ min-public-methods=0 # Maximum number of public methods for a class (see R0904). max-public-methods=20 +# Minimum line length for functions/classes that require docstrings, shorter ones are exempt. +docstring-min-length=10 + # checks for # * external modules dependencies @@ -263,7 +266,7 @@ notes=FIXME,XXX,TODO [FORMAT] # Maximum number of characters on a single line. -max-line-length=90 +max-line-length=120 # Maximum number of lines in a module max-module-lines=1000 diff --git a/leetcode/math/__init__.py b/leetcode/math/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/math/lc263_ugly_number.py b/leetcode/math/lc263_ugly_number.py new file mode 100644 index 0000000..230f7e7 --- /dev/null +++ b/leetcode/math/lc263_ugly_number.py @@ -0,0 +1,36 @@ +""" +Write a program to check whether a given number is an ugly number. + +Ugly numbers are positive numbers whose prime factors only include 2, 3, 5. For example, 6, 8 are ugly while 14 is not +ugly since it includes another prime factor 7. + +Note that 1 is typically treated as an ugly number. +""" + + +def is_ugly_number(number): + """ + Returns True if the number is an ugly number. + :param number: the number + :return: True or False + """ + if number <= 0: + return False + if number == 1: + return True + + primes = {2, 3, 5} + # keep dividing until it reaches 1 or some other number + + while dividable_by_primes(number, primes): + for prime in primes: + if number % prime == 0: + number /= prime + return number == 1 + + +def dividable_by_primes(number, primes): + """ + Returns True if number is dividable by at least one of numbers in the list of `primes` + """ + return any(number % prime == 0 for prime in primes) diff --git a/leetcode/math/lc273_integer_to_english_words.py b/leetcode/math/lc273_integer_to_english_words.py new file mode 100644 index 0000000..d9c4bb1 --- /dev/null +++ b/leetcode/math/lc273_integer_to_english_words.py @@ -0,0 +1,117 @@ +""" +Convert a non-negative integer to its english words representation. +Given input is guaranteed to be less than 2^31 - 1. + +For example, +123 -> "One Hundred Twenty Three" +12345 -> "Twelve Thousand Three Hundred Forty Five" +1234567 -> "One Million Two Hundred Thirty Four Thousand Five Hundred Sixty Seven" + +""" + + +class Solution(object): + """ The mighty solution - silly pylint requires a docstring """ + + THOUSAND = 1000 + HUNDRED = 100 + + DIVISION_TO_SUFFIX = { + 0: '', + 1: 'Thousand', + 2: 'Million', + 3: 'Billion' + } + + INT_TO_ENGLISH = { + 1: 'One', + 2: 'Two', + 3: 'Three', + 4: 'Four', + 5: 'Five', + 6: 'Six', + 7: 'Seven', + 8: 'Eight', + 9: 'Nine', + 10: 'Ten', + 11: 'Eleven', + 12: 'Twelve', + 13: 'Thirteen', + 14: 'Fourteen', + 15: 'Fifteen', + 16: 'Sixteen', + 17: 'Seventeen', + 18: 'Eighteen', + 19: 'Nineteen', + 20: 'Twenty', + 30: 'Thirty', + 40: 'Forty', + 50: 'Fifty', + 60: 'Sixty', + 70: 'Seventy', + 80: 'Eighty', + 90: 'Ninety' + } + + def number_to_words(self, num): + """ + :type num: int + :rtype: str + """ + + if num < 0 or num > 2**31 - 1: + raise ValueError('Invalid input: num must be non-negative and less than 2^31 - 1') + + if num == 0: + return 'Zero' + + # fast track + try: + return self.INT_TO_ENGLISH[num] + except KeyError: + pass + + # keep dividing... + division_count = 0 + words = [] + + while num > 0: + # change / to // + num, residual = num // self.THOUSAND, num % self.THOUSAND + if residual: + words.insert(0, self._helper(residual, suffix=self.DIVISION_TO_SUFFIX[division_count])) + division_count += 1 + return ' '.join(words) + + def _helper(self, num, suffix=''): + """num is less than 1000""" + + if num == 0: + return '' + + words = [] + + # get hundreds + hundreds = num // self.HUNDRED + if hundreds: + words.append(self.INT_TO_ENGLISH[hundreds]) + words.append('Hundred') + + # get last two digits + last_two = num % self.HUNDRED + if last_two: + # fast track if possible + try: + words.append(self.INT_TO_ENGLISH[last_two]) + except KeyError: + ones = num % 10 + tens = (num % self.HUNDRED) - ones + if tens: + words.append(self.INT_TO_ENGLISH[tens]) + if ones: + words.append(self.INT_TO_ENGLISH[ones]) + + # apply suffix + if suffix: + words.append(suffix) + return ' '.join(words) diff --git a/leetcode/367.py b/leetcode/math/lc367_perfect_square.py similarity index 100% rename from leetcode/367.py rename to leetcode/math/lc367_perfect_square.py diff --git a/leetcode/math/test_lc263_ugly_number.py b/leetcode/math/test_lc263_ugly_number.py new file mode 100644 index 0000000..53bd290 --- /dev/null +++ b/leetcode/math/test_lc263_ugly_number.py @@ -0,0 +1,45 @@ +""" +Test file `lc263_ugly_number.py` +""" +# pylint: disable=no-self-use + +import random + +import pytest + +from leetcode.math.lc263_ugly_number import is_ugly_number + + +class TestIsUglyNumber(object): + """ The mighty test suite to the mighty solution. """ + + PRIMES = [2, 3, 5] + + @pytest.mark.parametrize('test_input', [ + 0, + -1234 + ]) + def test_non_positive(self, test_input): + assert not is_ugly_number(test_input) + + def test_one(self): + assert is_ugly_number(1) + + @pytest.mark.parametrize("test_input", PRIMES) + def test_primes(self, test_input): + assert is_ugly_number(test_input) + + def test_multiple_of_primes(self): + turns = random.randint(0, 20) + number = 1 + for _ in range(turns): + number *= random.choice(self.PRIMES) + assert is_ugly_number(number) + + def test_ugly_factor(self): + the_uglies = [7, 11, 13, 17, 29] + turns = random.randint(0, 20) + number = random.choice(the_uglies) + for _ in range(turns): + number *= random.choice(self.PRIMES) + assert not is_ugly_number(number) diff --git a/leetcode/math/test_lc273_integer_to_english_words.py b/leetcode/math/test_lc273_integer_to_english_words.py new file mode 100644 index 0000000..0cb6ca0 --- /dev/null +++ b/leetcode/math/test_lc273_integer_to_english_words.py @@ -0,0 +1,44 @@ +""" +Test file `lc273_integer_to_english_words.py` +""" + +import pytest + +from leetcode.math import lc273_integer_to_english_words + + +class TestNumberToWords(object): + """ Test the number to words function. """ + + SOLUTION = lc273_integer_to_english_words.Solution() + + def test_zero(self): + number = 0 + assert self.SOLUTION.number_to_words(number) == 'Zero' + + def test_negative(self): + number = -1 + with pytest.raises(ValueError): + self.SOLUTION.number_to_words(number) + + def test_large_number(self): + number = 2**32 + with pytest.raises(ValueError): + self.SOLUTION.number_to_words(number) + + def test_trailing_zero(self): + number = 10000000 + assert self.SOLUTION.number_to_words(number) == 'Ten Million' + + def test_full_of_numbers(self): + number = 2134435666 + assert self.SOLUTION.number_to_words(number) == 'Two Billion One Hundred Thirty Four Million Four Hundred ' \ + 'Thirty Five Thousand Six Hundred Sixty Six' + + def test_below_one_thousand(self): + number = 434 + assert self.SOLUTION.number_to_words(number) == 'Four Hundred Thirty Four' + + def test_zeros(self): + number = 101010101 + assert self.SOLUTION.number_to_words(number) == 'One Hundred One Million Ten Thousand One Hundred One'