diff --git a/README.md b/README.md index 8c009a4..58d4e21 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,8 @@ $ pip install py-algorithms - Weighted Union Find - Weighted Union Find with Path Compression + - Longest Consecutive + --- ### Dynamic Programing (DP) diff --git a/py_algorithms/array/longest_consecutive.py b/py_algorithms/array/longest_consecutive.py new file mode 100644 index 0000000..1387b58 --- /dev/null +++ b/py_algorithms/array/longest_consecutive.py @@ -0,0 +1,57 @@ +from typing import List + + +class LongestConsecutive: + @staticmethod + def apply(nums: List[int]) -> int: + return LongestConsecutive.with_sorting(nums) + + @staticmethod + def with_sorting(nums: List[int]): + if not nums: + return 0 + + nums = sorted(nums) + longest_consecutive = 1 + current = 1 + + for i in range(1, len(nums)): + if nums[i] != nums[i - 1]: + if nums[i] == nums[i - 1] + 1: + current += 1 + else: + longest_consecutive = max(current, longest_consecutive) + current = 1 + + return max(current, longest_consecutive) + + @staticmethod + def brute_force(nums: List[int]): + if not nums: + return 0 + + longest_consecutive = 1 + for num in nums: + current = num + current_seq = 1 + + while current + 1 in nums: + current += 1 + current_seq += 1 + + longest_consecutive = max(longest_consecutive, current_seq) + return longest_consecutive + + @staticmethod + def with_set(nums): + xs = set(nums) + known_max = 0 + + for p in xs: + if p - 1 not in xs: + q = p + 1 + while q in xs: + q += 1 + known_max = max(known_max, q - p) + + return known_max diff --git a/py_algorithms/array/median_of_two_sorted_arrays.py b/py_algorithms/array/median_of_two_sorted_arrays.py new file mode 100644 index 0000000..d24bf8e --- /dev/null +++ b/py_algorithms/array/median_of_two_sorted_arrays.py @@ -0,0 +1,59 @@ +from heapq import heappop +from heapq import heappush +from heapq import nsmallest + + +class MedianOfTwoSortedArrays: + @staticmethod + def apply_2(nums1, nums2): + def _push_to_heap(item): + median = _median() + if item > median: + heappush(min_pq, item) + elif item < median: + heappush(max_pq, -1 * item) + else: + heappush(min_pq, item) + + if abs(len(max_pq) - len(min_pq)) > 1: + _rebalance() + + def _rebalance(): + if len(min_pq) > len(max_pq): + heappush(max_pq, -1 * heappop(min_pq)) + else: + heappush(min_pq, -1 * heappop(max_pq)) + + def _median(): + if len(min_pq) > len(max_pq): + return float(nsmallest(1, min_pq)[0]) + elif len(max_pq) > len(min_pq): + return float(-1 * nsmallest(1, max_pq)[0]) + else: + if len(min_pq) == 0 and len(max_pq) == 0: + return 0.0 + a = nsmallest(1, min_pq)[0] + b = -1 * nsmallest(1, max_pq)[0] + return (a + b) / 2 + + max_pq = list() + min_pq = list() + + while len(nums1) > 0 or len(nums2) > 0: + if len(nums1) > 0: + _push_to_heap(nums1.pop()) + + if len(nums2) > 0: + _push_to_heap(nums2.pop()) + + return _median() + + @staticmethod + def apply(nums1, nums2): + arr = sorted(nums1 + nums2) + n = len(arr) + mid = n // 2 + if n % 2 == 0: + return (arr[mid - 1] + arr[mid]) / 2 + else: + return float(arr[mid]) diff --git a/py_algorithms/caches/__init__.py b/py_algorithms/caches/__init__.py new file mode 100644 index 0000000..6e7a89c --- /dev/null +++ b/py_algorithms/caches/__init__.py @@ -0,0 +1,25 @@ +__all__ = [ + 'new_lfu_cache', + 'new_lru_cache' +] + +from .lfu_cache import LfuCache +from .lru_cache import LruCache + + +def new_lfu_cache(size) -> LfuCache: + """ + Factory method + :param size: Size of cache + :return: LfuCache + """ + return LfuCache(size) + + +def new_lru_cache(size) -> LruCache: + """ + Factory method + :param size: Size of cache + :return: LruCache + """ + return LruCache(size) diff --git a/py_algorithms/caches/lfu_cache.py b/py_algorithms/caches/lfu_cache.py new file mode 100644 index 0000000..28307a4 --- /dev/null +++ b/py_algorithms/caches/lfu_cache.py @@ -0,0 +1,55 @@ +from collections import defaultdict + + +class LfuCache: + def __init__(self, capacity): + """ + :type capacity: int + """ + self.n = capacity + self.table = {} + self.counts = {} + self.buckets = defaultdict(list) + self.min = -1 + + def get(self, key): + """ + :type key: int + :rtype: int + """ + if key not in self.table: + return -1 + + count = self.counts[key] + self.counts[key] = count + 1 + self.buckets[count].remove(key) + + if count == self.min and len(self.buckets[count]) == 0: + self.min += 1 + + self.buckets[count + 1].append(key) + + return self.table[key] + + def put(self, key, value): + """ + :type key: int + :type value: int + :rtype: void + """ + if self.n <= 0: + return + + if key in self.table: + self.table[key] = value + self.get(key) + return + + if len(self.table) >= self.n: + to_evict = self.buckets[self.min].pop(0) + del self.table[to_evict] + + self.table[key] = value + self.counts[key] = 1 + self.min = 1 + self.buckets[1].append(key) diff --git a/py_algorithms/caches/lru_cache.py b/py_algorithms/caches/lru_cache.py new file mode 100644 index 0000000..a7b7ede --- /dev/null +++ b/py_algorithms/caches/lru_cache.py @@ -0,0 +1,111 @@ +from collections import OrderedDict +from typing import Any +from typing import Union + + +class LruCache: + class Node: + def __init__(self, key, value): + self.key = key + self.value = value + self.next = None + self.prev = None + + def __init__(self, capacity): + """ + :type capacity: int + """ + self.n = capacity + self.table = {} + self.head = None + self.tail = None + + def get(self, key): + """ + :type key: int + :rtype: int + """ + if key in self.table: + node = self.table[key] + self._remove(node) + self._make_head(node) + return node.value + + return -1 + + def put(self, key, value): + """ + :type key: int + :type value: int + :rtype: void + """ + if key in self.table: + node = self.table.get(key) + node.value = value + self._remove(node) + self._make_head(node) + else: + new_node = self.Node(key, value) + if len(self.table) >= self.n: + del self.table[self.tail.key] + self._remove(self.tail) + self._make_head(new_node) + self.table[key] = new_node + + def _remove(self, node): + if node.prev is not None: + node.prev.next = node.next + else: + self.head = node.next + + if node.next is not None: + node.next.prev = node.prev + else: + self.tail = node.prev + + def _make_head(self, node): + node.next = self.head + node.prev = None + + if self.head is not None: + self.head.prev = node + + self.head = node + + if self.tail is None: + self.tail = self.head + + +class SimpleLruCache: + def __init__(self, capacity): + """ + :type capacity: int + """ + self.capacity = capacity + self.d = OrderedDict() + + def get(self, key) -> Union[Any, int]: + """ + :type key: int + :rtype: int + """ + if key in self.d: + value = self.d[key] + del self.d[key] + self.d[key] = value + return value + else: + return -1 + + def put(self, key, value) -> None: + """ + :type key: int + :type value: int + :rtype: None + """ + if key in self.d: + del self.d[key] + elif len(self.d) == self.capacity: + self.d.popitem(False) + + self.d[key] = value diff --git a/py_algorithms/matrix/__init__.py b/py_algorithms/matrix/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/py_algorithms/strings/path_finder.py b/py_algorithms/matrix/path_finder.py similarity index 100% rename from py_algorithms/strings/path_finder.py rename to py_algorithms/matrix/path_finder.py diff --git a/py_algorithms/strings/word_break.py b/py_algorithms/strings/word_break.py new file mode 100644 index 0000000..63d40e9 --- /dev/null +++ b/py_algorithms/strings/word_break.py @@ -0,0 +1,49 @@ +from typing import List + + +class WordBreak: + @staticmethod + def apply(word: str, voc: List[str]) -> bool: + queue = [0] + visited = [] + + while len(queue) > 0: + start = queue.pop(0) + if start not in visited: + end = start + 1 + while end <= len(word): + if word[start:end] in voc: + queue.append(end) + if end == len(word): + return True + end += 1 + + visited.append(start) + return False + + @staticmethod + def dp_apply(word: str, voc: List[str]) -> bool: + dp = [False for _ in range(len(word))] + length = len(word) + + for i in range(length): + for w in voc: + w_len = len(w) + if w == word[i - w_len + 1:i + 1]: + if dp[i - w_len] or i - w_len == -1: + dp[i] = True + return dp[-1] + + @staticmethod + def dp2_apply(word: str, voc: List[str]) -> bool: + dp = [False for _ in range(len(word) + 1)] + dp[0] = True + + for i in range(1, len(word) + 1): + j = 0 + while j < i: + if dp[j] and word[j:i] in voc: + dp[i] = True + j += 1 + + return dp[-1] diff --git a/tests/py_algorithms/arrays/longest_consecutive_test.py b/tests/py_algorithms/arrays/longest_consecutive_test.py new file mode 100644 index 0000000..d8245a4 --- /dev/null +++ b/tests/py_algorithms/arrays/longest_consecutive_test.py @@ -0,0 +1,28 @@ +import pytest + +from py_algorithms.array.longest_consecutive import LongestConsecutive + + +def _test_case_helper(f, mapping): + for x in mapping: + payload, expected = x + assert expected == f(payload) + + +@pytest.fixture +def cases(): + return [ + ([100, 4, 200, 1, 3, 2], 4), + ([], 0), + ([2, 1, 1, 3], 3)] + + +class TestLongestConsecutive: + def test_apply(self): + _test_case_helper(LongestConsecutive.with_sorting, cases()) + + def test_brute_force(self): + _test_case_helper(LongestConsecutive.brute_force, cases()) + + def test_with_set(self): + _test_case_helper(LongestConsecutive.with_set, cases()) diff --git a/tests/py_algorithms/arrays/median_of_two_sorted_arrays_test.py b/tests/py_algorithms/arrays/median_of_two_sorted_arrays_test.py new file mode 100644 index 0000000..351204d --- /dev/null +++ b/tests/py_algorithms/arrays/median_of_two_sorted_arrays_test.py @@ -0,0 +1,43 @@ +from py_algorithms.array.median_of_two_sorted_arrays import MedianOfTwoSortedArrays + + +class TestMedianOfTwoSortedArrays: + def test_apply(self): + nums1 = [1, 2] + nums2 = [3, 4] + expected_median = 2.5 + f = MedianOfTwoSortedArrays.apply + median = f(nums1, nums2) + assert expected_median == median + + def test_apply2(self): + nums1 = [] + nums2 = [1] + expected_median = 1.0 + f = MedianOfTwoSortedArrays.apply + median = f(nums1, nums2) + assert expected_median == median + + def test_apply3(self): + nums1 = [1, 3] + nums2 = [2] + expected_median = 2.0 + f = MedianOfTwoSortedArrays.apply + median = f(nums1, nums2) + assert expected_median == median + + def test_apply4(self): + nums1 = [] + nums2 = [1, 2, 3, 4] + expected_median = 2.5 + f = MedianOfTwoSortedArrays.apply + median = f(nums1, nums2) + assert expected_median == median + + def test_apply5(self): + nums1 = [] + nums2 = [1, 2, 3, 4, 5] + expected_median = 3 + f = MedianOfTwoSortedArrays.apply + median = f(nums1, nums2) + assert expected_median == median diff --git a/tests/py_algorithms/caches/__init__.py b/tests/py_algorithms/caches/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/py_algorithms/caches/lfu_test.py b/tests/py_algorithms/caches/lfu_test.py new file mode 100644 index 0000000..eec6f66 --- /dev/null +++ b/tests/py_algorithms/caches/lfu_test.py @@ -0,0 +1,27 @@ +from py_algorithms.caches import new_lfu_cache + + +class TestLruCache: + def test_put(self): + cache = new_lfu_cache(2) + cache.put(1, 1) + cache.put(2, 2) + assert cache.get(1) == 1 + cache.put(3, 3) + assert cache.get(2) == -1 + assert cache.get(3) == 3 + cache.put(4, 4) + assert cache.get(1) == -1 + assert cache.get(3) == 3 + assert cache.get(4) == 4 + + def test_put_2(self): + cache = new_lfu_cache(2) + cache.put(2, 1) + cache.put(3, 2) + assert cache.get(3) == 2 + assert cache.get(2) == 1 + cache.put(4, 3) + assert cache.get(2) == 1 + assert cache.get(3) == -1 + assert cache.get(4) == 3 diff --git a/tests/py_algorithms/caches/lru_test.py b/tests/py_algorithms/caches/lru_test.py new file mode 100644 index 0000000..3ae41a1 --- /dev/null +++ b/tests/py_algorithms/caches/lru_test.py @@ -0,0 +1,25 @@ +from py_algorithms.caches import new_lru_cache + + +class TestLruCache: + def test_put(self): + cache = new_lru_cache(3) + cache.put(1, 1) + cache.put(2, 2) + cache.put(3, 3) + cache.put(4, 4) + assert cache.get(2) == 2 + assert cache.head.key == 2 + assert cache.tail.key == 3 + cache.put(5, 5) + assert cache.head.key == 5 + assert cache.tail.key == 4 + assert cache.get(2) is 2 + assert cache.get(4) + assert cache.head.key == 4 + assert cache.tail.key == 5 + cache.put(6, 6) + assert cache.get(3) is -1 + assert cache.head.key == 6 + assert cache.tail.key == 2 + assert cache.get(3) is -1 diff --git a/tests/py_algorithms/strings/word_break_test.py b/tests/py_algorithms/strings/word_break_test.py new file mode 100644 index 0000000..47a9cb1 --- /dev/null +++ b/tests/py_algorithms/strings/word_break_test.py @@ -0,0 +1,10 @@ +from py_algorithms.strings.word_break import WordBreak + + +class TestWordBreak: + def test_apply(self): + impls = [WordBreak.apply, WordBreak.dp_apply, WordBreak.dp2_apply] + voc = ["cat", "cats", "and", "sand", "dog"] + for impl in impls: + is_possible = impl('catsanddog', voc) + assert is_possible is True