Skip to content

Created harshad_numbers.py #9023

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Sep 6, 2023
Merged
Changes from 11 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
109 changes: 55 additions & 54 deletions maths/harshad_numbers.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
"""
A Harshad number is divisible by the sum of its digits in any base n.
A harshad number (or more specifically an n-harshad number) is a number that's
divisible by the sum of its digits in some given base n.
Reference: https://en.wikipedia.org/wiki/Harshad_number
"""


def int_to_base(number: int, base_of_interest: int) -> str:
def int_to_base(number: int, base: int) -> str:
digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
result = ""

if number < 0:
raise ValueError("number must be a positive integer")

while number > 0:
number, remainder = divmod(number, base_of_interest)
number, remainder = divmod(number, base)
result = digits[remainder] + result

if result == "":
Expand All @@ -18,11 +22,11 @@ def int_to_base(number: int, base_of_interest: int) -> str:
return result


def sum_of_digits(num: int, base_of_interest: int) -> str:
def sum_of_digits(num: int, base: int) -> str:
"""
Calculate the sum of digit values in a positive integer
converted to the given 'base_of_interest'.
Where 'base_of_interest' ranges from 2 to 36.
converted to the given 'base'.
Where 'base' ranges from 2 to 36.

Examples:
>>> sum_of_digits(103, 12)
Expand All @@ -35,95 +39,92 @@ def sum_of_digits(num: int, base_of_interest: int) -> str:
>>> sum_of_digits(543, 1)
Traceback (most recent call last):
...
ValueError: 'base_of_interest' must be between 36 and 2 inclusive
ValueError: 'base' must be between 36 and 2 inclusive
>>> sum_of_digits(543, 37)
Traceback (most recent call last):
...
ValueError: 'base_of_interest' must be between 36 and 2 inclusive
ValueError: 'base' must be between 36 and 2 inclusive
"""

if (base_of_interest > 36) or (base_of_interest < 2):
raise ValueError("'base_of_interest' must be between 36 and 2 inclusive")
if (base > 36) or (base < 2):
raise ValueError("'base' must be between 36 and 2 inclusive")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (base > 36) or (base < 2):
raise ValueError("'base' must be between 36 and 2 inclusive")
if base < 2 or base > 36:
raise ValueError("'base' must be between 2 and 36 inclusive")

Parentheses are unnecessary, and it's kinda strange to read the range backward


num_str = int_to_base(num, base_of_interest)
num_str = int_to_base(num, base)
res = 0
for char in num_str:
res += int(char, base_of_interest)
res_str = int_to_base(res, base_of_interest)
res += int(char, base)
res_str = int_to_base(res, base)
return res_str


def all_harshad_numbers(num: int, base_of_interest: int) -> tuple[int, list[str]]:
def harshad_numbers_in_base(limit: int, base: int) -> list[str]:
"""
Finds all Harshad numbers smaller than num in base 'base_of_interest'.
Where 'base_of_interest' ranges from 2 to 36.
Finds all Harshad numbers smaller than num in base 'base'.
Where 'base' ranges from 2 to 36.

Examples:
>>> all_harshad_numbers(15, 2)
(7, ['1', '10', '100', '110', '1000', '1010', '1100'])
>>> all_harshad_numbers(12, 34)
(11, ['1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B'])
>>> all_harshad_numbers(12, 4)
(7, ['1', '2', '3', '10', '12', '20', '21'])
>>> harshad_numbers_in_base(15, 2)
['1', '10', '100', '110', '1000', '1010', '1100']
>>> harshad_numbers_in_base(12, 34)
['1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B']
>>> harshad_numbers_in_base(12, 4)
['1', '2', '3', '10', '12', '20', '21']
>>> # bases beyond 36 and below 2 will error
>>> all_harshad_numbers(234, 37)
>>> harshad_numbers_in_base(234, 37)
Traceback (most recent call last):
...
ValueError: 'base_of_interest' must be between 36 and 2 inclusive
>>> all_harshad_numbers(234, 1)
ValueError: 'base' must be between 36 and 2 inclusive
>>> harshad_numbers_in_base(234, 1)
Traceback (most recent call last):
...
ValueError: 'base_of_interest' must be between 36 and 2 inclusive
ValueError: 'base' must be between 36 and 2 inclusive
"""

if (base_of_interest > 36) or (base_of_interest < 2):
raise ValueError("'base_of_interest' must be between 36 and 2 inclusive")
if (base > 36) or (base < 2):
raise ValueError("'base' must be between 36 and 2 inclusive")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (base > 36) or (base < 2):
raise ValueError("'base' must be between 36 and 2 inclusive")
if base < 2 or base > 36:
raise ValueError("'base' must be between 2 and 36 inclusive")

See previous comment


result = 0
numbers = []
if num >= 0:
for i in range(1, num):
y = sum_of_digits(i, base_of_interest)
if i % int(y, base_of_interest) == 0:
result += 1
numbers.append(int_to_base(i, base_of_interest))
if limit >= 0:
for i in range(1, limit):
y = sum_of_digits(i, base)
if i % int(y, base) == 0:
numbers.append(int_to_base(i, base))

return result, numbers
return numbers


def is_harshad_number(num: int, base_of_interest: int) -> bool:
def is_harshad_number_in_base(num: int, base: int) -> bool:
"""
Determines whether n in base 'base_of_interest' is a harshad number.
Where 'base_of_interest' ranges from 2 to 36.
Determines whether n in base 'base' is a harshad number.
Where 'base' ranges from 2 to 36.

Examples:
>>> is_harshad_number(18, 10)
>>> is_harshad_number_in_base(18, 10)
True
>>> is_harshad_number(21, 10)
>>> is_harshad_number_in_base(21, 10)
True
>>> is_harshad_number(-21, 5)
>>> is_harshad_number_in_base(-21, 5)
False
>>> # bases beyond 36 and below 2 will error
>>> is_harshad_number(45, 37)
>>> is_harshad_number_in_base(45, 37)
Traceback (most recent call last):
...
ValueError: 'base_of_interest' must be between 36 and 2 inclusive
>>> is_harshad_number(45, 1)
ValueError: 'base' must be between 36 and 2 inclusive
>>> is_harshad_number_in_base(45, 1)
Traceback (most recent call last):
...
ValueError: 'base_of_interest' must be between 36 and 2 inclusive
ValueError: 'base' must be between 36 and 2 inclusive
"""

if (base_of_interest > 36) or (base_of_interest < 2):
raise ValueError("'base_of_interest' must be between 36 and 2 inclusive")
if (base > 36) or (base < 2):
raise ValueError("'base' must be between 36 and 2 inclusive")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (base > 36) or (base < 2):
raise ValueError("'base' must be between 36 and 2 inclusive")
if base < 2 or base > 36:
raise ValueError("'base' must be between 2 and 36 inclusive")


if num >= 0:
n = int_to_base(num, base_of_interest)
d = sum_of_digits(num, base_of_interest)
if int(n, base_of_interest) % int(d, base_of_interest) == 0:
return True
if num < 0:
return False

return False
n = int_to_base(num, base)
d = sum_of_digits(num, base)
return int(n, base) % int(d, base) == 0


if __name__ == "__main__":
Expand Down