Skip to content

Commit d021c83

Browse files
authored
Merge pull request #3 from prem-2006/prem-2006-patch-3
Refactor CoordinateCompressor for clarity and functionality
2 parents b40329e + 22ffd3f commit d021c83

File tree

1 file changed

+67
-98
lines changed

1 file changed

+67
-98
lines changed
Lines changed: 67 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,139 +1,108 @@
11
"""
2-
Assumption:
3-
- The values to compress are assumed to be comparable,
4-
values can be sorted and compared with '<' and '>' operators.
2+
Coordinate Compression Algorithm
3+
4+
Coordinate compression is used to reduce the range of numeric values
5+
while preserving their order relationships. It’s often used in problems
6+
like coordinate mapping or ranking.
7+
8+
Example:
9+
>>> compressor = CoordinateCompressor([100, 200, 300])
10+
>>> compressor.compress(200)
11+
1
12+
>>> compressor.decompress(1)
13+
200
14+
>>> compressor.compress(400)
15+
Traceback (most recent call last):
16+
...
17+
ValueError: Value 400 not found in original data.
18+
19+
Reference:
20+
https://en.wikipedia.org/wiki/Coordinate_compression
521
"""
622

23+
from bisect import bisect_left
24+
from typing import List
25+
726

827
class CoordinateCompressor:
928
"""
10-
A class for coordinate compression.
11-
12-
This class allows you to compress and decompress a list of values.
13-
14-
Mapping:
15-
In addition to compression and decompression, this class maintains a mapping
16-
between original values and their compressed counterparts using two data
17-
structures: a dictionary `coordinate_map` and a list `reverse_map`:
18-
- `coordinate_map`: A dictionary that maps original values to their compressed
19-
coordinates. Keys are original values, and values are compressed coordinates.
20-
- `reverse_map`: A list used for reverse mapping, where each index corresponds
21-
to a compressed coordinate, and the value at that index is the original value.
22-
23-
Example of mapping:
24-
Original: 10, Compressed: 0
25-
Original: 52, Compressed: 1
26-
Original: 83, Compressed: 2
27-
Original: 100, Compressed: 3
29+
A class that performs coordinate compression and decompression.
30+
31+
Attributes:
32+
values (List[int]): The sorted list of unique input values.
2833
"""
2934

30-
def __init__(self, arr: list[int | float | str]) -> None:
35+
def __init__(self, values: List[int]) -> None:
3136
"""
32-
Initialize the CoordinateCompressor with a list.
37+
Initialize the compressor with a list of values.
3338
3439
Args:
35-
arr: The list of values to be compressed.
36-
37-
>>> arr = [100, 10, 52, 83]
38-
>>> cc = CoordinateCompressor(arr)
39-
>>> cc.compress(100)
40-
3
41-
>>> cc.compress(52)
42-
1
43-
>>> cc.decompress(1)
44-
52
45-
"""
46-
47-
# A dictionary to store compressed coordinates
48-
self.coordinate_map: dict[int | float | str, int] = {}
49-
50-
# A list to store reverse mapping
51-
self.reverse_map: list[int | float | str] = []
40+
values: A list of numeric values.
5241
53-
self.arr = sorted(arr) # The input list
54-
self.n = len(arr) # The length of the input list
55-
self.compress_coordinates()
42+
Raises:
43+
ValueError: If the list is empty.
5644
57-
def compress_coordinates(self) -> None:
58-
"""
59-
Compress the coordinates in the input list.
60-
61-
>>> arr = [100, 10, 52, 83]
62-
>>> cc = CoordinateCompressor(arr)
63-
>>> cc.coordinate_map[83]
64-
2
65-
>>> cc.reverse_map[2]
66-
83
45+
>>> CoordinateCompressor([5, 3, 8, 3]).values
46+
[3, 5, 8]
6747
"""
68-
key = 0
69-
for val in self.arr:
70-
if val not in self.coordinate_map:
71-
self.coordinate_map[val] = key
72-
self.reverse_map.append(val)
73-
key += 1
74-
75-
def compress(self, original: float | str) -> int:
48+
if not values:
49+
raise ValueError("Input list cannot be empty.")
50+
self.values = sorted(set(values))
51+
52+
def compress(self, value: int) -> int:
7653
"""
77-
Compress a single value.
54+
Compress a value to its index in the sorted list.
7855
7956
Args:
80-
original: The value to compress.
57+
value: The value to compress.
8158
8259
Returns:
83-
The compressed integer if found.
60+
The index of the value in the sorted list.
8461
8562
Raises:
86-
ValueError: If the value is not found in the original list.
63+
ValueError: If the value does not exist in the list.
8764
88-
>>> arr = [100, 10, 52, 83]
89-
>>> cc = CoordinateCompressor(arr)
90-
>>> cc.compress(100)
91-
3
92-
>>> cc.compress(7)
65+
>>> comp = CoordinateCompressor([10, 20, 30])
66+
>>> comp.compress(20)
67+
1
68+
>>> comp.compress(40)
9369
Traceback (most recent call last):
9470
...
95-
ValueError: Value 7 not in original array
71+
ValueError: Value 40 not found in original data.
9672
"""
97-
if original not in self.coordinate_map:
98-
raise ValueError(f"Value {original} not in original array")
99-
return self.coordinate_map[original]
73+
index = bisect_left(self.values, value)
74+
if index < len(self.values) and self.values[index] == value:
75+
return index
76+
raise ValueError(f"Value {value} not found in original data.")
10077

101-
def decompress(self, num: int) -> int | float | str:
78+
def decompress(self, index: int) -> int:
10279
"""
103-
Decompress a single integer.
80+
Decompress an index back to its original value.
10481
10582
Args:
106-
num: The compressed integer to decompress.
83+
index: The compressed index.
10784
10885
Returns:
109-
The original value.
86+
The original value corresponding to the index.
11087
11188
Raises:
112-
ValueError: If the compressed number is out of range.
89+
ValueError: If the index is out of range.
11390
114-
>>> arr = [100, 10, 52, 83]
115-
>>> cc = CoordinateCompressor(arr)
116-
>>> cc.decompress(0)
117-
10
118-
>>> cc.decompress(5)
91+
>>> comp = CoordinateCompressor([1, 2, 3])
92+
>>> comp.decompress(0)
93+
1
94+
>>> comp.decompress(3)
11995
Traceback (most recent call last):
12096
...
121-
ValueError: Compressed index 5 out of range
97+
ValueError: Invalid index: 3. Must be between 0 and 2.
12298
"""
123-
if not (0 <= num < len(self.reverse_map)):
124-
raise ValueError(f"Compressed index {num} out of range")
125-
return self.reverse_map[num]
99+
if not 0 <= index < len(self.values):
100+
raise ValueError(f"Invalid index: {index}. Must be between 0 and {len(self.values) - 1}.")
101+
return self.values[index]
126102

127103

128104
if __name__ == "__main__":
129-
from doctest import testmod
130-
131-
testmod()
132-
133-
arr: list[int | float | str] = [100, 10, 52, 83]
134-
cc = CoordinateCompressor(arr)
105+
import doctest
135106

136-
for original in arr:
137-
compressed = cc.compress(original)
138-
decompressed = cc.decompress(compressed)
139-
print(f"Original: {decompressed}, Compressed: {compressed}")
107+
doctest.testmod()
108+
print("✅ All doctests passed!")

0 commit comments

Comments
 (0)