From e8cd5c1ba5416536f7096c6e07964d33884b0ead Mon Sep 17 00:00:00 2001 From: hollowcrust <72879387+hollowcrust@users.noreply.github.com> Date: Sat, 14 Oct 2023 22:16:03 +0800 Subject: [PATCH 1/9] Create sparse_table.py --- data_structures/arrays/sparse_table.py | 93 ++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 data_structures/arrays/sparse_table.py diff --git a/data_structures/arrays/sparse_table.py b/data_structures/arrays/sparse_table.py new file mode 100644 index 000000000000..61cd172e5290 --- /dev/null +++ b/data_structures/arrays/sparse_table.py @@ -0,0 +1,93 @@ +""" + Sparse table is a data structure that allows answering range queries on + a static array, i.e. the elements do not change throughout all the queries. + + The implementation below will solve the problem of Range Minimum Query: + Finding the minimum value of a subset [L..R] of a static array. + + Overall time complexity: O(nlogn) + Overall space complexity: O(nlogn) + + Wikipedia link: https://en.wikipedia.org/wiki/Range_minimum_query +""" + + +def build_sparse_table(arr: list[int], n: int) -> list[int]: + """ + Precompute range minimum queries with power of two length + and store the precomputed values in a table. + + >>> build_sparse_table([8, 1, 0, 3, 4, 9, 3], 7) + [[8, 1, 0, 3, 4, 9, 3], [1, 0, 0, 3, 4, 3, 0], [0, 0, 0, 3, 0, 0, 0]] + >>> build_sparse_table([3, 1, 9], 3) + [[3, 1, 9], [1, 1, 0]] + >>> build_sparse_table([], 0) + Traceback (most recent call last): + ... + ValueError: math domain error + """ + import math + + if arr == []: + raise ValueError("math domain error") + + # Initialise lookup table + k = int(math.log2(n)) + 1 + lookup = [[0 for i in range(n)] for j in range(k)] + + for i in range(0, n): + lookup[0][i] = arr[i] + + j = 1 + + while (1 << j) <= n: + + # Compute the minimum value for all intervals with size (2 ** j) + i = 0 + while (i + (1 << j) - 1) < n: + lookup[j][i] = min(lookup[j - 1][i + (1 << (j - 1))], lookup[j - 1][i]) + i += 1 + + j += 1 + + return lookup + + +def query(lookup: list[int], L: int, R: int) -> int: + """ + >>> query(build_sparse_table([8, 1, 0, 3, 4, 9, 3], 7), 0, 4) + 0 + >>> query(build_sparse_table([8, 1, 0, 3, 4, 9, 3], 7), 4, 6) + 3 + >>> query(build_sparse_table([8, 1, 0, 3, 4, 9, 3], 7), 0, 11) + Traceback (most recent call last): + ... + IndexError: list index out of range + + >>> query(build_sparse_table([3, 1, 9], 3), 2, 2) + 9 + >>> query(build_sparse_table([3, 1, 9], 3), 0, 1) + 1 + + >>> query(build_sparse_table([], 0), 0, 0) + Traceback (most recent call last): + ... + ValueError: math domain error + """ + import math + + if lookup == []: + raise ValueError("math domain error") + if L < 0 or R >= len(lookup[0]): + raise IndexError("list index out of range") + + """ + Find the highest power of 2 + that is at least the number of elements in a given range. + """ + j = int(math.log2(R - L + 1)) + return min(lookup[j][R - (1 << j) + 1], lookup[j][L]) + +if __name__ == "__main__": + from doctest import testmod + testmod() From 25d453e4db76aa75ce9e7132d99ec14df5e31f01 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 14 Oct 2023 14:19:42 +0000 Subject: [PATCH 2/9] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- data_structures/arrays/sparse_table.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/data_structures/arrays/sparse_table.py b/data_structures/arrays/sparse_table.py index 61cd172e5290..857f7c7864bf 100644 --- a/data_structures/arrays/sparse_table.py +++ b/data_structures/arrays/sparse_table.py @@ -6,7 +6,7 @@ Finding the minimum value of a subset [L..R] of a static array. Overall time complexity: O(nlogn) - Overall space complexity: O(nlogn) + Overall space complexity: O(nlogn) Wikipedia link: https://en.wikipedia.org/wiki/Range_minimum_query """ @@ -35,13 +35,12 @@ def build_sparse_table(arr: list[int], n: int) -> list[int]: k = int(math.log2(n)) + 1 lookup = [[0 for i in range(n)] for j in range(k)] - for i in range(0, n): - lookup[0][i] = arr[i] + for i in range(0, n): + lookup[0][i] = arr[i] j = 1 - while (1 << j) <= n: - + while (1 << j) <= n: # Compute the minimum value for all intervals with size (2 ** j) i = 0 while (i + (1 << j) - 1) < n: @@ -82,12 +81,14 @@ def query(lookup: list[int], L: int, R: int) -> int: raise IndexError("list index out of range") """ - Find the highest power of 2 + Find the highest power of 2 that is at least the number of elements in a given range. """ - j = int(math.log2(R - L + 1)) - return min(lookup[j][R - (1 << j) + 1], lookup[j][L]) - + j = int(math.log2(R - L + 1)) + return min(lookup[j][R - (1 << j) + 1], lookup[j][L]) + + if __name__ == "__main__": from doctest import testmod - testmod() + + testmod() From e232e37cd103c78ca014b323c2a953bc8c1d5249 Mon Sep 17 00:00:00 2001 From: hollowcrust <72879387+hollowcrust@users.noreply.github.com> Date: Sat, 14 Oct 2023 22:23:30 +0800 Subject: [PATCH 3/9] Descriptive names for variables --- data_structures/arrays/sparse_table.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/data_structures/arrays/sparse_table.py b/data_structures/arrays/sparse_table.py index 857f7c7864bf..f30f120248fd 100644 --- a/data_structures/arrays/sparse_table.py +++ b/data_structures/arrays/sparse_table.py @@ -12,7 +12,7 @@ """ -def build_sparse_table(arr: list[int], n: int) -> list[int]: +def build_sparse_table(arr: list[int], arr_length: int) -> list[int]: """ Precompute range minimum queries with power of two length and store the precomputed values in a table. @@ -32,18 +32,18 @@ def build_sparse_table(arr: list[int], n: int) -> list[int]: raise ValueError("math domain error") # Initialise lookup table - k = int(math.log2(n)) + 1 - lookup = [[0 for i in range(n)] for j in range(k)] + k = int(math.log2(arr_length)) + 1 + lookup = [[0 for i in range(arr_length)] for j in range(k)] - for i in range(0, n): + for i in range(0, arr_length): lookup[0][i] = arr[i] j = 1 - while (1 << j) <= n: + while (1 << j) <= arr_length: # Compute the minimum value for all intervals with size (2 ** j) i = 0 - while (i + (1 << j) - 1) < n: + while (i + (1 << j) - 1) < arr_length: lookup[j][i] = min(lookup[j - 1][i + (1 << (j - 1))], lookup[j - 1][i]) i += 1 @@ -52,7 +52,7 @@ def build_sparse_table(arr: list[int], n: int) -> list[int]: return lookup -def query(lookup: list[int], L: int, R: int) -> int: +def query(lookup: list[int], left_bound: int, right_bound: int) -> int: """ >>> query(build_sparse_table([8, 1, 0, 3, 4, 9, 3], 7), 0, 4) 0 @@ -77,15 +77,15 @@ def query(lookup: list[int], L: int, R: int) -> int: if lookup == []: raise ValueError("math domain error") - if L < 0 or R >= len(lookup[0]): + if left_bound < 0 or right_bound >= len(lookup[0]): raise IndexError("list index out of range") """ Find the highest power of 2 that is at least the number of elements in a given range. """ - j = int(math.log2(R - L + 1)) - return min(lookup[j][R - (1 << j) + 1], lookup[j][L]) + j = int(math.log2(right_bound - left_bound + 1)) + return min(lookup[j][right_bound - (1 << j) + 1], lookup[j][left_bound]) if __name__ == "__main__": From fdba816944a160be91ab19a3202d75eaef9af235 Mon Sep 17 00:00:00 2001 From: hollowcrust <72879387+hollowcrust@users.noreply.github.com> Date: Sat, 14 Oct 2023 22:25:18 +0800 Subject: [PATCH 4/9] Fix ruff check error --- data_structures/arrays/sparse_table.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_structures/arrays/sparse_table.py b/data_structures/arrays/sparse_table.py index f30f120248fd..eb769773627a 100644 --- a/data_structures/arrays/sparse_table.py +++ b/data_structures/arrays/sparse_table.py @@ -35,7 +35,7 @@ def build_sparse_table(arr: list[int], arr_length: int) -> list[int]: k = int(math.log2(arr_length)) + 1 lookup = [[0 for i in range(arr_length)] for j in range(k)] - for i in range(0, arr_length): + for i in range(arr_length): lookup[0][i] = arr[i] j = 1 From 6be290cf0a3091c41dfe757041c45d015a5ab7dd Mon Sep 17 00:00:00 2001 From: hollowcrust <72879387+hollowcrust@users.noreply.github.com> Date: Sat, 14 Oct 2023 22:29:28 +0800 Subject: [PATCH 5/9] Update sparse_table.py --- data_structures/arrays/sparse_table.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data_structures/arrays/sparse_table.py b/data_structures/arrays/sparse_table.py index eb769773627a..3ba40d5f8553 100644 --- a/data_structures/arrays/sparse_table.py +++ b/data_structures/arrays/sparse_table.py @@ -12,7 +12,7 @@ """ -def build_sparse_table(arr: list[int], arr_length: int) -> list[int]: +def build_sparse_table(arr: list[int], arr_length: int) -> list[list[int]]: """ Precompute range minimum queries with power of two length and store the precomputed values in a table. @@ -52,7 +52,7 @@ def build_sparse_table(arr: list[int], arr_length: int) -> list[int]: return lookup -def query(lookup: list[int], left_bound: int, right_bound: int) -> int: +def query(lookup: list[list[int]], left_bound: int, right_bound: int) -> int: """ >>> query(build_sparse_table([8, 1, 0, 3, 4, 9, 3], 7), 0, 4) 0 From 01ee53a7980b8b9540f1a2591e7d26179e267022 Mon Sep 17 00:00:00 2001 From: hollowcrust <72879387+hollowcrust@users.noreply.github.com> Date: Mon, 16 Oct 2023 00:33:56 +0800 Subject: [PATCH 6/9] Add comments, change variable names --- data_structures/arrays/sparse_table.py | 92 +++++++++++++++----------- 1 file changed, 55 insertions(+), 37 deletions(-) diff --git a/data_structures/arrays/sparse_table.py b/data_structures/arrays/sparse_table.py index 3ba40d5f8553..9722a3ebd585 100644 --- a/data_structures/arrays/sparse_table.py +++ b/data_structures/arrays/sparse_table.py @@ -1,9 +1,9 @@ """ Sparse table is a data structure that allows answering range queries on - a static array, i.e. the elements do not change throughout all the queries. + a static number_listay, i.e. the elements do not change throughout all the queries. The implementation below will solve the problem of Range Minimum Query: - Finding the minimum value of a subset [L..R] of a static array. + Finding the minimum value of a subset [L..R] of a static number_listay. Overall time complexity: O(nlogn) Overall space complexity: O(nlogn) @@ -12,80 +12,98 @@ """ -def build_sparse_table(arr: list[int], arr_length: int) -> list[list[int]]: +def build_sparse_table(number_list: list[int]) -> list[list[int]]: """ - Precompute range minimum queries with power of two length - and store the precomputed values in a table. - - >>> build_sparse_table([8, 1, 0, 3, 4, 9, 3], 7) + Precompute range minimum queries with power of two length and store the precomputed + values in a table. + + >>> build_sparse_table([8, 1, 0, 3, 4, 9, 3]) [[8, 1, 0, 3, 4, 9, 3], [1, 0, 0, 3, 4, 3, 0], [0, 0, 0, 3, 0, 0, 0]] - >>> build_sparse_table([3, 1, 9], 3) + >>> build_sparse_table([3, 1, 9]) [[3, 1, 9], [1, 1, 0]] - >>> build_sparse_table([], 0) + >>> build_sparse_table([]) Traceback (most recent call last): ... - ValueError: math domain error + ValueError: empty number list not allowed """ import math - if arr == []: - raise ValueError("math domain error") + if number_list == []: + raise ValueError("empty number list not allowed") + + length = len(number_list) + """ + Initialise sparse_table + + sparse_table[j][i] represents the minimum value of + the subset of length (2 ** j) of number_list, starting from index i. + """ - # Initialise lookup table - k = int(math.log2(arr_length)) + 1 - lookup = [[0 for i in range(arr_length)] for j in range(k)] + # smallest power of 2 subset length that fully covers number_list + row = int(math.log2(length)) + 1 + sparse_table = [[0 for i in range(length)] for j in range(row)] - for i in range(arr_length): - lookup[0][i] = arr[i] + # minimum of subset of length 1 is that value itself + for i, value in enumerate(number_list): + sparse_table[0][i] = value j = 1 - while (1 << j) <= arr_length: - # Compute the minimum value for all intervals with size (2 ** j) + # compute the minimum value for all intervals with size (2 ** j) + while (1 << j) <= length: i = 0 - while (i + (1 << j) - 1) < arr_length: - lookup[j][i] = min(lookup[j - 1][i + (1 << (j - 1))], lookup[j - 1][i]) + # while subset starting from i still have at least (2 ** j) elements + while (i + (1 << j) - 1) < length: + # split range [i, i + 2 ** j] and find minimum of 2 halves + sparse_table[j][i] = min( + sparse_table[j - 1][i + (1 << (j - 1))], + sparse_table[j - 1][i] + ) + i += 1 j += 1 - return lookup + return sparse_table -def query(lookup: list[list[int]], left_bound: int, right_bound: int) -> int: +def query(sparse_table: list[list[int]], left_bound: int, right_bound: int) -> int: """ - >>> query(build_sparse_table([8, 1, 0, 3, 4, 9, 3], 7), 0, 4) + >>> query(build_sparse_table([8, 1, 0, 3, 4, 9, 3]), 0, 4) 0 - >>> query(build_sparse_table([8, 1, 0, 3, 4, 9, 3], 7), 4, 6) + >>> query(build_sparse_table([8, 1, 0, 3, 4, 9, 3]), 4, 6) 3 - >>> query(build_sparse_table([8, 1, 0, 3, 4, 9, 3], 7), 0, 11) + >>> query(build_sparse_table([8, 1, 0, 3, 4, 9, 3]), 0, 11) Traceback (most recent call last): ... IndexError: list index out of range - >>> query(build_sparse_table([3, 1, 9], 3), 2, 2) + >>> query(build_sparse_table([3, 1, 9]), 2, 2) 9 - >>> query(build_sparse_table([3, 1, 9], 3), 0, 1) + >>> query(build_sparse_table([3, 1, 9]), 0, 1) 1 - >>> query(build_sparse_table([], 0), 0, 0) + >>> query(build_sparse_table([]), 0, 0) Traceback (most recent call last): ... - ValueError: math domain error + ValueError: empty number list not allowed """ import math - if lookup == []: - raise ValueError("math domain error") - if left_bound < 0 or right_bound >= len(lookup[0]): + if left_bound < 0 or right_bound >= len(sparse_table[0]): raise IndexError("list index out of range") + # highest subset length of power of 2 that is within range [left_bound, right_bound] + j = int(math.log2(right_bound - left_bound + 1)) + """ - Find the highest power of 2 - that is at least the number of elements in a given range. + minimum of 2 overlapping smaller subsets: [left_bound, left_bound + 2 ** j - 1] + and [right_bound - 2 ** j + 1, right_bound] """ - j = int(math.log2(right_bound - left_bound + 1)) - return min(lookup[j][right_bound - (1 << j) + 1], lookup[j][left_bound]) + return min( + sparse_table[j][right_bound - (1 << j) + 1], + sparse_table[j][left_bound] + ) if __name__ == "__main__": From 60e1a070293adcdec481a213079691b3fd668c23 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 15 Oct 2023 16:34:30 +0000 Subject: [PATCH 7/9] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- data_structures/arrays/sparse_table.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/data_structures/arrays/sparse_table.py b/data_structures/arrays/sparse_table.py index 9722a3ebd585..28a01fffd5b0 100644 --- a/data_structures/arrays/sparse_table.py +++ b/data_structures/arrays/sparse_table.py @@ -16,7 +16,7 @@ def build_sparse_table(number_list: list[int]) -> list[list[int]]: """ Precompute range minimum queries with power of two length and store the precomputed values in a table. - + >>> build_sparse_table([8, 1, 0, 3, 4, 9, 3]) [[8, 1, 0, 3, 4, 9, 3], [1, 0, 0, 3, 4, 3, 0], [0, 0, 0, 3, 0, 0, 0]] >>> build_sparse_table([3, 1, 9]) @@ -56,10 +56,9 @@ def build_sparse_table(number_list: list[int]) -> list[list[int]]: while (i + (1 << j) - 1) < length: # split range [i, i + 2 ** j] and find minimum of 2 halves sparse_table[j][i] = min( - sparse_table[j - 1][i + (1 << (j - 1))], - sparse_table[j - 1][i] + sparse_table[j - 1][i + (1 << (j - 1))], sparse_table[j - 1][i] ) - + i += 1 j += 1 @@ -100,10 +99,7 @@ def query(sparse_table: list[list[int]], left_bound: int, right_bound: int) -> i minimum of 2 overlapping smaller subsets: [left_bound, left_bound + 2 ** j - 1] and [right_bound - 2 ** j + 1, right_bound] """ - return min( - sparse_table[j][right_bound - (1 << j) + 1], - sparse_table[j][left_bound] - ) + return min(sparse_table[j][right_bound - (1 << j) + 1], sparse_table[j][left_bound]) if __name__ == "__main__": From 01be1ead8ddb1a26059513050033e5fdbd8b4204 Mon Sep 17 00:00:00 2001 From: hollowcrust <72879387+hollowcrust@users.noreply.github.com> Date: Mon, 16 Oct 2023 00:38:03 +0800 Subject: [PATCH 8/9] Fix typo --- data_structures/arrays/sparse_table.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data_structures/arrays/sparse_table.py b/data_structures/arrays/sparse_table.py index 28a01fffd5b0..bfbb01c5f536 100644 --- a/data_structures/arrays/sparse_table.py +++ b/data_structures/arrays/sparse_table.py @@ -1,9 +1,9 @@ """ Sparse table is a data structure that allows answering range queries on - a static number_listay, i.e. the elements do not change throughout all the queries. + a static number list, i.e. the elements do not change throughout all the queries. The implementation below will solve the problem of Range Minimum Query: - Finding the minimum value of a subset [L..R] of a static number_listay. + Finding the minimum value of a subset [L..R] of a static number list. Overall time complexity: O(nlogn) Overall space complexity: O(nlogn) From 78c0f81968e3e690ba2de8a1f6cdf8b79e1ee26f Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sun, 15 Oct 2023 22:09:53 +0200 Subject: [PATCH 9/9] Update sparse_table.py --- data_structures/arrays/sparse_table.py | 40 +++++++++----------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/data_structures/arrays/sparse_table.py b/data_structures/arrays/sparse_table.py index bfbb01c5f536..a15d5649e712 100644 --- a/data_structures/arrays/sparse_table.py +++ b/data_structures/arrays/sparse_table.py @@ -10,6 +10,7 @@ Wikipedia link: https://en.wikipedia.org/wiki/Range_minimum_query """ +from math import log2 def build_sparse_table(number_list: list[int]) -> list[list[int]]: @@ -26,27 +27,20 @@ def build_sparse_table(number_list: list[int]) -> list[list[int]]: ... ValueError: empty number list not allowed """ - import math - - if number_list == []: + if not number_list: raise ValueError("empty number list not allowed") length = len(number_list) - """ - Initialise sparse_table - - sparse_table[j][i] represents the minimum value of - the subset of length (2 ** j) of number_list, starting from index i. - """ + # Initialise sparse_table -- sparse_table[j][i] represents the minimum value of the + # subset of length (2 ** j) of number_list, starting from index i. # smallest power of 2 subset length that fully covers number_list - row = int(math.log2(length)) + 1 + row = int(log2(length)) + 1 sparse_table = [[0 for i in range(length)] for j in range(row)] # minimum of subset of length 1 is that value itself for i, value in enumerate(number_list): sparse_table[0][i] = value - j = 1 # compute the minimum value for all intervals with size (2 ** j) @@ -58,11 +52,8 @@ def build_sparse_table(number_list: list[int]) -> list[list[int]]: sparse_table[j][i] = min( sparse_table[j - 1][i + (1 << (j - 1))], sparse_table[j - 1][i] ) - i += 1 - j += 1 - return sparse_table @@ -72,33 +63,27 @@ def query(sparse_table: list[list[int]], left_bound: int, right_bound: int) -> i 0 >>> query(build_sparse_table([8, 1, 0, 3, 4, 9, 3]), 4, 6) 3 - >>> query(build_sparse_table([8, 1, 0, 3, 4, 9, 3]), 0, 11) - Traceback (most recent call last): - ... - IndexError: list index out of range - >>> query(build_sparse_table([3, 1, 9]), 2, 2) 9 >>> query(build_sparse_table([3, 1, 9]), 0, 1) 1 - + >>> query(build_sparse_table([8, 1, 0, 3, 4, 9, 3]), 0, 11) + Traceback (most recent call last): + ... + IndexError: list index out of range >>> query(build_sparse_table([]), 0, 0) Traceback (most recent call last): ... ValueError: empty number list not allowed """ - import math - if left_bound < 0 or right_bound >= len(sparse_table[0]): raise IndexError("list index out of range") # highest subset length of power of 2 that is within range [left_bound, right_bound] - j = int(math.log2(right_bound - left_bound + 1)) + j = int(log2(right_bound - left_bound + 1)) - """ - minimum of 2 overlapping smaller subsets: [left_bound, left_bound + 2 ** j - 1] - and [right_bound - 2 ** j + 1, right_bound] - """ + # minimum of 2 overlapping smaller subsets: + # [left_bound, left_bound + 2 ** j - 1] and [right_bound - 2 ** j + 1, right_bound] return min(sparse_table[j][right_bound - (1 << j) + 1], sparse_table[j][left_bound]) @@ -106,3 +91,4 @@ def query(sparse_table: list[list[int]], left_bound: int, right_bound: int) -> i from doctest import testmod testmod() + print(f"{query(build_sparse_table([3, 1, 9]), 2, 2) = }")