Skip to content
This repository was archived by the owner on Apr 4, 2024. It is now read-only.

Commit 757e0fe

Browse files
authored
ArrayMap.py (#17)
2 parents 80cb448 + e891d43 commit 757e0fe

File tree

3 files changed

+247
-0
lines changed

3 files changed

+247
-0
lines changed

.vscode/settings.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"java.compile.nullAnalysis.mode": "automatic"
3+
}
+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
from collections.abc import Set, Iterator, Mapping
2+
from typing import List, TypeVar, Union
3+
from abc import abstractmethod, ABC
4+
5+
T = TypeVar('T')
6+
V = TypeVar('V')
7+
K = TypeVar('K')
8+
9+
class ListBackedSet(Set[T], ABC):
10+
@abstractmethod
11+
def __len__(self) -> int: ...
12+
13+
@abstractmethod
14+
def __getitem__(self, index: Union[int, slice]) -> Union[T, List[T]]: ...
15+
16+
def __contains__(self, item: object) -> bool:
17+
for i in range(len(self)):
18+
if self[i] == item:
19+
return True
20+
return False
21+
22+
class ArraySet(ListBackedSet[K]):
23+
__data: List[K]
24+
25+
def __init__(self, data: List[K]):
26+
raise NotImplementedError("Use ArraySet.empty() instead")
27+
28+
@classmethod
29+
def __create(cls, data: List[K]) -> 'ArraySet[K]':
30+
# Create a new instance without calling __init__
31+
instance = super().__new__(cls)
32+
instance.__data = data
33+
return instance
34+
35+
def __iter__(self) -> Iterator[K]:
36+
return iter(self.__data)
37+
38+
@classmethod
39+
def empty(cls) -> 'ArraySet[K]':
40+
if not hasattr(cls, '__EMPTY'):
41+
cls.__EMPTY = cls([])
42+
return cls.__EMPTY
43+
44+
def __len__(self) -> int:
45+
return len(self.__data)
46+
47+
def __getitem__(self, index: Union[int, slice]) -> Union[K, List[K]]:
48+
if isinstance(index, int):
49+
return self.__data[index]
50+
elif isinstance(index, slice):
51+
return self.__data[index]
52+
else:
53+
raise TypeError("Invalid argument type.")
54+
55+
def plusOrThis(self, element: K) -> 'ArraySet[K]':
56+
# TODO: use binary search, and also special sort order for strings
57+
if element in self.__data:
58+
return self
59+
else:
60+
new_data = self.__data[:]
61+
new_data.append(element)
62+
new_data.sort() # type: ignore[reportOperatorIssue]
63+
return ArraySet.__create(new_data)
64+
65+
66+
class ArrayMap(Mapping[K, V]):
67+
def __init__(self, data: list):
68+
# TODO: hide this constructor as done in ArraySet
69+
self.__data = data
70+
71+
@classmethod
72+
def empty(cls) -> 'ArrayMap[K, V]':
73+
if not hasattr(cls, '__EMPTY'):
74+
cls.__EMPTY = cls([])
75+
return cls.__EMPTY
76+
77+
def __getitem__(self, key: K) -> V:
78+
index = self.__binary_search_key(key)
79+
if index >= 0:
80+
return self.__data[2 * index + 1]
81+
raise KeyError(key)
82+
83+
def __iter__(self) -> Iterator[K]:
84+
return (self.__data[i] for i in range(0, len(self.__data), 2))
85+
86+
def __len__(self) -> int:
87+
return len(self.__data) // 2
88+
89+
def __binary_search_key(self, key: K) -> int:
90+
# TODO: special sort order for strings
91+
low, high = 0, (len(self.__data) // 2) - 1
92+
while low <= high:
93+
mid = (low + high) // 2
94+
mid_key = self.__data[2 * mid]
95+
if mid_key < key:
96+
low = mid + 1
97+
elif mid_key > key:
98+
high = mid - 1
99+
else:
100+
return mid
101+
return -(low + 1)
102+
103+
def plus(self, key: K, value: V) -> 'ArrayMap[K, V]':
104+
index = self.__binary_search_key(key)
105+
if index >= 0:
106+
raise ValueError("Key already exists")
107+
insert_at = -(index + 1)
108+
new_data = self.__data[:]
109+
new_data[insert_at * 2:insert_at * 2] = [key, value]
110+
return ArrayMap(new_data)
111+
112+
def minus_sorted_indices(self, indicesToRemove: List[int]) -> 'ArrayMap[K, V]':
113+
if not indicesToRemove:
114+
return self
115+
newData = []
116+
for i in range(0, len(self.__data), 2):
117+
if i // 2 not in indicesToRemove:
118+
newData.extend(self.__data[i:i + 2])
119+
return ArrayMap(newData)
+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import pytest
2+
from selfie_lib.ArrayMap import ArrayMap
3+
4+
def assertEmpty(map):
5+
assert len(map) == 0
6+
assert list(map.keys()) == []
7+
assert list(map.values()) == []
8+
assert list(map.items()) == []
9+
with pytest.raises(KeyError):
10+
_ = map["key"]
11+
assert map == {}
12+
assert map == ArrayMap.empty()
13+
14+
def assertSingle(map, key, value):
15+
assert len(map) == 1
16+
assert set(map.keys()) == {key}
17+
assert list(map.values()) == [value]
18+
assert set(map.items()) == {(key, value)}
19+
assert map[key] == value
20+
with pytest.raises(KeyError):
21+
_ = map[key + "blah"]
22+
assert map == {key: value}
23+
assert map == ArrayMap.empty().plus(key, value)
24+
25+
def assertDouble(map, key1, value1, key2, value2):
26+
assert len(map) == 2
27+
assert set(map.keys()) == {key1, key2}
28+
assert list(map.values()) == [value1, value2]
29+
assert set(map.items()) == {(key1, value1), (key2, value2)}
30+
assert map[key1] == value1
31+
assert map[key2] == value2
32+
with pytest.raises(KeyError):
33+
_ = map[key1 + "blah"]
34+
assert map == {key1: value1, key2: value2}
35+
assert map == {key2: value2, key1: value1}
36+
assert map == ArrayMap.empty().plus(key1, value1).plus(key2, value2)
37+
assert map == ArrayMap.empty().plus(key2, value2).plus(key1, value1)
38+
39+
def assertTriple(map, key1, value1, key2, value2, key3, value3):
40+
assert len(map) == 3
41+
assert set(map.keys()) == {key1, key2, key3}
42+
assert list(map.values()) == [value1, value2, value3]
43+
assert set(map.items()) == {(key1, value1), (key2, value2), (key3, value3)}
44+
assert map[key1] == value1
45+
assert map[key2] == value2
46+
assert map[key3] == value3
47+
with pytest.raises(KeyError):
48+
_ = map[key1 + "blah"]
49+
assert map == {key1: value1, key2: value2, key3: value3}
50+
assert map == ArrayMap.empty().plus(key1, value1).plus(key2, value2).plus(key3, value3)
51+
52+
def test_empty():
53+
assertEmpty(ArrayMap.empty())
54+
55+
def test_single():
56+
empty = ArrayMap.empty()
57+
single = empty.plus("one", "1")
58+
assertEmpty(empty)
59+
assertSingle(single, "one", "1")
60+
61+
def test_double():
62+
empty = ArrayMap.empty()
63+
single = empty.plus("one", "1")
64+
double = single.plus("two", "2")
65+
assertEmpty(empty)
66+
assertSingle(single, "one", "1")
67+
assertDouble(double, "one", "1", "two", "2")
68+
assertDouble(single.plus("a", "sorted"), "a", "sorted", "one", "1")
69+
70+
with pytest.raises(ValueError) as context:
71+
single.plus("one", "2")
72+
assert str(context.value) == "Key already exists"
73+
74+
def test_triple():
75+
triple = ArrayMap.empty().plus("1", "one").plus("2", "two").plus("3", "three")
76+
assertTriple(triple, "1", "one", "2", "two", "3", "three")
77+
78+
def test_multi():
79+
test_triple() # Calling another test function directly is unusual but works
80+
triple = ArrayMap.empty().plus("2", "two").plus("3", "three").plus("1", "one")
81+
assertTriple(triple, "1", "one", "2", "two", "3", "three")
82+
triple = ArrayMap.empty().plus("3", "three").plus("1", "one").plus("2", "two")
83+
assertTriple(triple, "1", "one", "2", "two", "3", "three")
84+
85+
def test_minus_sorted_indices():
86+
initial_map = ArrayMap.empty().plus("1", "one").plus("2", "two").plus("3", "three").plus("4", "four")
87+
modified_map = initial_map.minus_sorted_indices([1, 3])
88+
assert len(modified_map) == 2
89+
assert list(modified_map.keys()) == ["1", "3"]
90+
assert list(modified_map.values()) == ["one", "three"]
91+
with pytest.raises(KeyError):
92+
_ = modified_map["2"]
93+
with pytest.raises(KeyError):
94+
_ = modified_map["4"]
95+
assert modified_map == {"1": "one", "3": "three"}
96+
97+
def test_plus_with_existing_keys():
98+
map_with_duplicates = ArrayMap.empty().plus("a", "alpha").plus("b", "beta")
99+
with pytest.raises(ValueError):
100+
map_with_duplicates.plus("a", "new alpha")
101+
updated_map = map_with_duplicates.plus("c", "gamma")
102+
assert len(updated_map) == 3
103+
assert updated_map["a"] == "alpha"
104+
assert updated_map["b"] == "beta"
105+
assert updated_map["c"] == "gamma"
106+
modified_map = map_with_duplicates.minus_sorted_indices([0]).plus("a", "updated alpha")
107+
assert len(modified_map) == 2
108+
assert modified_map["a"] == "updated alpha"
109+
assert modified_map["b"] == "beta"
110+
111+
def test_map_length():
112+
map = ArrayMap.empty()
113+
assert len(map) == 0, "Length should be 0 for an empty map"
114+
map = map.plus("key1", "value1")
115+
assert len(map) == 1, "Length should be 1 after adding one item"
116+
map = map.plus("key2", "value2")
117+
assert len(map) == 2, "Length should be 2 after adding another item"
118+
map = map.plus("key3", "value3")
119+
assert len(map) == 3, "Length should be 3 after adding a third item"
120+
map = map.minus_sorted_indices([1])
121+
assert len(map) == 2, "Length should be 2 after removing one item"
122+
map = map.minus_sorted_indices([0])
123+
assert len(map) == 1, "Length should be 1 after removing another item"
124+
map = map.minus_sorted_indices([0])
125+
assert len(map) == 0, "Length should be 0 after removing all items"

0 commit comments

Comments
 (0)