Skip to content
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

📈 Increase Code Coverage to 100% #110

Merged
merged 18 commits into from
Aug 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
b56dbb5
:wrench: Exclude private files not in the VCS from coverage reports
TeoZosa Aug 18, 2021
c675d86
:recycle: For closed set membership testing, make final case an `else`
TeoZosa Aug 18, 2021
d948e23
:boom: Throw exception on pre-condition violation
TeoZosa Aug 18, 2021
f64ef63
:coffin: Remove deprecated functions
TeoZosa Aug 18, 2021
7cace77
:memo: Add docstring for `reverse_sub_list()` driver fn
TeoZosa Aug 18, 2021
9c0399d
:white_check_mark: Add trivial `reverse_sub_list()` driver fn test
TeoZosa Aug 18, 2021
621ee39
:recycle: Make `merge_alt()` divide & conquer step self-recursive
TeoZosa Aug 18, 2021
621f44f
:alien: Add optional `do_iterative_merge` parameter
TeoZosa Aug 18, 2021
931a4fa
:recycle: Dynamically generate test list nodes
TeoZosa Aug 18, 2021
e08eabe
:white_check_mark: Test internal recursive merge implementation
TeoZosa Aug 18, 2021
d1bbd8a
:recycle: Refactor variadic arguments into positional arguments
TeoZosa Aug 18, 2021
1dca01a
:white_check_mark: Add missing subtraction functionality test
TeoZosa Aug 18, 2021
9a99475
:white_check_mark: Add missing edge case tests
TeoZosa Aug 18, 2021
2e24b25
:art: Assign left & right chars at start of iteration
TeoZosa Aug 18, 2021
2265dfa
:chart_with_upwards_trend: Exclude intentional structurally partial b…
TeoZosa Aug 18, 2021
ff73186
:construction_worker: Fail coverage runs under 100% code coverage
TeoZosa Aug 18, 2021
e901b79
:sparkles: Add `_clear_cache` class method
TeoZosa Aug 18, 2021
97973c2
:white_check_mark: Fix `climb_stairs_tabulate()` tests
TeoZosa Aug 18, 2021
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
10 changes: 9 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,14 @@ docs = [
[tool.coverage.run]
branch = true
concurrency = ["multiprocessing"]
omit =[
# Private (unversioned) files
"pytudes/_2021/google_kickstart/*",
"pytudes/_2021/miscellany/network_partition_hashing/*",
"pytudes/_2021/miscellany/python_dict_implementation/*",
"pytudes/_2021/miscellany/sorting/bitonic_mergesort/*",
"pytudes/_2021/miscellany/technical_interviews/*",
]
parallel = true
source = ["pytudes"]

Expand All @@ -106,7 +114,7 @@ exclude_lines =[
"raise NotImplementedError",
"if __name__ == .__main__.:",
]
fail_under = 0
fail_under = 100
show_missing = true
skip_covered = true

Expand Down
2 changes: 1 addition & 1 deletion pytudes/_2021/coderbyte/math_challenge__hard.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ def MathChallenge_cases(rpn_expression: str) -> int:
processed_token = left_operand - right_operand
elif char == "*":
processed_token = left_operand * right_operand
elif char == "/":
else: # char == "/"
processed_token = int(
left_operand / right_operand
) # truncate fractions
Expand Down
5 changes: 5 additions & 0 deletions pytudes/_2021/coderbyte/string_challenge__medium.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ def StringChallenge(strParam: str) -> str:
'threefour'
>>> # 34

>>> # Empty string (degenerate case)
>>> StringChallenge("")
'zero'
>>> # 0

"""
if not strParam:
return "zero"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@
- Linked List
- Blind 75

Examples:
>>> assert (reverse_sub_list(None,0,0) is None)

"""

from pytudes._2021.utils.linked_list import NodeType, convert_list_to_linked_list


def reverse_sub_list(head: NodeType, p: int, q: int) -> NodeType:
"""Driver method for `_reverse_sub_list()`"""
return _reverse_sub_list(head, p, q)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def binary_search(find_last_pos: bool) -> int:

if check_left():
end = mid - 1
elif check_right():
elif check_right(): # pragma: no branch
start = mid + 1

# first or last pos of val_idx if val exists, -1 otherwise
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,10 @@ def find_string_anagrams(input_str: str, pattern: str) -> list[str]:

## SLIDING ##
for window_end in range(len(input_str)):
left_char, right_char = input_str[window_start], input_str[window_end]

## EXPANSION ##
if (right_char := input_str[window_end]) in pattern_char_count:
if right_char in pattern_char_count: # pragma: no branch
pattern_char_count[right_char] -= 1 # Decrement the character count
if pattern_char_count[right_char] == 0:
num_fully_matched_chars += 1 # Increment the matched count
Expand All @@ -73,7 +74,7 @@ def find_string_anagrams(input_str: str, pattern: str) -> list[str]:

## CONTRACTION ##
if get_curr_win_size() == len(pattern):
if (left_char := input_str[window_start]) in pattern_char_count:
if left_char in pattern_char_count: # pragma: no branch
if pattern_char_count[left_char] == 0:
num_fully_matched_chars -= 1 # Decrement the matched count
pattern_char_count[left_char] += 1 # Re-increment the character count
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ def print_orders(prerequisites: list[list[int]], num_tasks: int):
[1, 3, 0, 4, 2, 5]
[1, 3, 2, 0, 5, 4]
[1, 3, 2, 0, 4, 5]
>>> print_orders([[0, 1], [1, 2]], num_tasks=0)
False

"""
if num_tasks <= 0:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,17 +121,22 @@ def merge_k_lists_heap(lists: list[ListNodeType]) -> ListNodeType:
return head.next


def _merge_k_lists(lists: list[ListNodeType]) -> ListNodeType:
def _merge_k_lists(
lists: list[ListNodeType], do_iterative_merge: bool = True
) -> ListNodeType:
"""Merge a list of linked-lists into a single, sorted linked-list

Args:
lists: array of linked-lists, each sorted in ascending order.
do_iterative_merge: flag to specify which internal merge implementation to use.

Returns: a single merged, sorted linked-list

Examples:
>>> list_nodes = [convert_to_listnode(sublist) for sublist in [[1,4,5],[1,3,4],[2,6]]]
>>> _merge_k_lists(list_nodes).as_list()
>>> def get_test_list_nodes(): return [convert_to_listnode(sublist) for sublist in [[1,4,5],[1,3,4],[2,6]]]
>>> _merge_k_lists(get_test_list_nodes()).as_list()
[1, 1, 2, 3, 4, 4, 5, 6]
>>> _merge_k_lists(get_test_list_nodes(), do_iterative_merge=False).as_list()
[1, 1, 2, 3, 4, 4, 5, 6]
>>> _merge_k_lists([[]])
[]
Expand All @@ -149,23 +154,22 @@ def _merge_k_lists(lists: list[ListNodeType]) -> ListNodeType:

mid = len(lists) // 2
left, right = _merge_k_lists(lists[:mid]), _merge_k_lists(lists[mid:])
return merge(left, right)
return merge(left, right, do_iterative_merge=do_iterative_merge)


def merge(*args):
default = False
return merge_default(*args) if default else merge_alt(*args)
def merge(left, right, do_iterative_merge: bool):
return merge_default(left, right) if do_iterative_merge else merge_alt(left, right)


def merge_alt(left, right):
if not (left and right):
# return the non-None ListNode, if one exists
return left or right
elif left.val < right.val:
left.next = merge_default(left.next, right)
left.next = merge_alt(left.next, right)
return left
else:
right.next = merge_default(left, right.next)
right.next = merge_alt(left, right.next)
return right


Expand Down
4 changes: 2 additions & 2 deletions pytudes/_2021/leetcode/easy/_204__count_primes.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def count_primes(n: int) -> int:
# (since (√n)^2 = n ≤ n), so we terminate the outer loop when num > √n

for num in range(2, int(n ** 0.5) + 1):
if is_prime[num]:
if is_prime[num]: # pragma: no branch
# Mark all multiples of `num` as non-prime,
# starting from num^2 (since the other multiples of `num`
# will have already been marked as non-prime in previous iterations)
Expand Down Expand Up @@ -128,7 +128,7 @@ def count_primes_separate_loop_for_2(n: int) -> int:
is_prime[multiple_of_num] = False

for num in range(3, int(n ** 0.5) + 1):
if is_prime[num]:
if is_prime[num]: # pragma: no branch

for multiple_of_num in range(num ** 2, len(is_prime), 2 * num):
is_prime[multiple_of_num] = False
Expand Down
8 changes: 4 additions & 4 deletions pytudes/_2021/leetcode/hard/_224_basic_calculator.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ def move_greater_or_equal_precedence_operators_in_curr_scope_to_rpn_stack() -> N
if operator_precedence[token] >= operator_precedence[last_operator]:
rpn_tokens.append(intermediate_operator_tokens.pop())
else:
if last_operator == "(": # Current scope is now empty
# Discard open parenthesis belonging to current scope which no
# longer contains operators
if last_operator == "(": # pragma: no branch
# Current scope is now empty; discard open parenthesis belonging to
# current scope which no longer contains operators
intermediate_operator_tokens.pop()
break

Expand All @@ -73,7 +73,7 @@ def move_greater_or_equal_precedence_operators_in_curr_scope_to_rpn_stack() -> N

# Build number from multi-digit strings
if token.isdigit():
if num is None:
if num is None: # pragma: no branch
num = 0
num = num * 10 + int(token)

Expand Down
4 changes: 3 additions & 1 deletion pytudes/_2021/leetcode/hard/_227_basic_calculator_ii.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ def calculate_separate_operator_conditionals(expression: str) -> int:
1
>>> calculate_separate_operator_conditionals(" 3+5 / 2 ")
5
>>> calculate_separate_operator_conditionals(" 3 - 1/2 ")
3

"""

Expand All @@ -110,7 +112,7 @@ def calculate_separate_operator_conditionals(expression: str) -> int:
# Perform '*', '/' against last stored number to respect operator precedence
elif last_operator == "*":
new_operand = operand_stack.pop() * curr_num
elif last_operator == "/":
else: # last_operator == "/"
new_operand = int(operand_stack.pop() / curr_num) # truncate towards 0

operand_stack.append(new_operand)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ def compute_median_binary_search(
# S[S_left_idx] ≤ L[L_right_idx]
# L[L_left_idx] ≤ S[S_right_idx]
# (i.e. monotonically increasing)
while S_start_idx <= S_end_idx:
while S_start_idx <= S_end_idx: # pragma: no branch
S_right_idx = (S_start_idx + S_end_idx) // 2
S_left_idx = S_right_idx - 1

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -343,78 +343,6 @@ def compute_possible_permutations(sub_permutation):

return res

# return compute_possible_permutations_accumulate([], nums_counts, len(nums), res=[])
# return dfs_accumulate([], nums_counts, len(nums), res=[])
# return dfs([], nums_counts, len(nums))


def compute_possible_permutations_accumulate(
sub_permutation, nums_counts, len_nums, res
):
# BASE CASE
if len(sub_permutation) == len_nums:
res.append(sub_permutation)
return res

for curr_num in nums_counts:

# If there are remaining instances of `curr_num` left to choose
# => Select curr_num at this position
# and continue building permutation from remaining elements
if nums_counts[curr_num] > 0:
nums_counts[curr_num] -= 1 # Select `curr_num` at current position
compute_possible_permutations_accumulate(
sub_permutation + [curr_num], nums_counts, len_nums, res
)
nums_counts[curr_num] += 1 # Deselect `curr_num` at current position
return res


def dfs_accumulate(perm, nums_counts, len_nums, acc):
# BASE CASE
if len(perm) == len_nums:
acc.append(perm.copy())
else:
for curr_num in nums_counts:
# If there are remaining instances of `curr_num` left to choose
if nums_counts[curr_num] > 0:

# Select `curr_num` at current position
perm.append(curr_num)
nums_counts[curr_num] -= 1

# Building remainder of permutation from remaining elements
dfs_accumulate(perm, nums_counts, len_nums, acc)

# Deselect `curr_num` at current position
nums_counts[curr_num] += 1
perm.pop()

return acc


def dfs(perm, nums_counts, len_nums):
# BASE CASE
if len(perm) == len_nums:
return [perm]

res = []
for curr_num in nums_counts:

# If there are remaining instances of `curr_num` left to choose
if nums_counts[curr_num] > 0:

# Select `curr_num` at current position
nums_counts[curr_num] -= 1

# Building remainder of permutation from remaining elements
res += dfs(perm + [curr_num], nums_counts, len_nums)

# Deselect `curr_num` at current position
nums_counts[curr_num] += 1

return res


def permute_unique_backtrack_stack(nums: list[int]) -> list[list[int]]:
"""Compute all the *unique* permutations of the elements in a given input array
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def next_greatest_letter(letters: list[str], target: str) -> str:
mid_idx = start_idx + (end_idx - start_idx) // 2
if letters[mid_idx] <= target:
start_idx = mid_idx + 1
elif letters[mid_idx] > target:
elif letters[mid_idx] > target: # pragma: no branch
end_idx = mid_idx - 1

# If start_idx pointer exceeded the array bounds, this implies that
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ def single_number_hashtable(nums: list[int]) -> int:
4
>>> single_number_hashtable([1])
1
>>> single_number_hashtable([1,1])
Traceback (most recent call last):
...
ValueError: No element in `nums` appears exactly once.

"""

Expand All @@ -85,3 +89,5 @@ def single_number_hashtable(nums: list[int]) -> int:
for num, count in num_counts.items():
if count == 1:
return num
else:
raise ValueError("No element in `nums` appears exactly once.")
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,12 @@ class instantiants, using a dict (as opposed to a list) offers

"""

memo = {i: i for i in range(3)} # base case for [0,2]
_base_cases_memo = {i: i for i in range(3)} # base case for [0,2]
memo = _base_cases_memo.copy()

@classmethod
def _clear_cache(cls):
cls.memo = cls._base_cases_memo.copy()

def climbStairs(self, n: int) -> int:
"""Alias to `climb_stairs_memoize`"""
Expand Down Expand Up @@ -86,7 +91,8 @@ def climb_stairs_tabulate(self, n: int) -> int:

Args:
n: The number of stairs to climb

Examples:
>>> Solution()._clear_cache() # reset cache for valid method testing
>>> Solution().climb_stairs_tabulate(2)
2
>>> Solution().climb_stairs_tabulate(3)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ def bisect_right(nums, val, start_idx, end_idx):
mid_idx = start_idx + (end_idx - start_idx) // 2
if nums[mid_idx] <= val:
start_idx = mid_idx + 1
elif nums[mid_idx] > val:
elif nums[mid_idx] > val: # pragma: no branch
end_idx = mid_idx - 1
return start_idx

Expand All @@ -204,6 +204,6 @@ def bisect_left(nums, val, start_idx, end_idx):
mid_idx = start_idx + (end_idx - start_idx) // 2
if nums[mid_idx] < val:
start_idx = mid_idx + 1
elif nums[mid_idx] >= val:
elif nums[mid_idx] >= val: # pragma: no branch
end_idx = mid_idx - 1
return end_idx
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ def eval_RPN_classic(tokens: list[str]) -> int:
new_operand = left_operand - right_operand
elif token == "*":
new_operand = left_operand * right_operand
elif token == "/":
else: # token == "/"
# Integer division is truncated toward zero.
new_operand = int(left_operand / right_operand)
else:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ def swap_elements(i, j) -> None:
get_distance = lambda i: sum(coord ** 2 for coord in points[i])

## BASE CASE ##
while start < end:
while start < end: # pragma: no branch

## INITIALIZE VARS ##
pivot_idx = random.randint(start, end) # inclusive range
Expand Down
Loading