This repository has been archived by the owner on Apr 4, 2024. It is now read-only.
forked from diffplug/selfie
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
247 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"java.compile.nullAnalysis.mode": "automatic" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
from collections.abc import Set, Iterator, Mapping | ||
from typing import List, TypeVar, Union | ||
from abc import abstractmethod, ABC | ||
|
||
T = TypeVar('T') | ||
V = TypeVar('V') | ||
K = TypeVar('K') | ||
|
||
class ListBackedSet(Set[T], ABC): | ||
@abstractmethod | ||
def __len__(self) -> int: ... | ||
|
||
@abstractmethod | ||
def __getitem__(self, index: Union[int, slice]) -> Union[T, List[T]]: ... | ||
|
||
def __contains__(self, item: object) -> bool: | ||
for i in range(len(self)): | ||
if self[i] == item: | ||
return True | ||
return False | ||
|
||
class ArraySet(ListBackedSet[K]): | ||
__data: List[K] | ||
|
||
def __init__(self, data: List[K]): | ||
raise NotImplementedError("Use ArraySet.empty() instead") | ||
|
||
@classmethod | ||
def __create(cls, data: List[K]) -> 'ArraySet[K]': | ||
# Create a new instance without calling __init__ | ||
instance = super().__new__(cls) | ||
instance.__data = data | ||
return instance | ||
|
||
def __iter__(self) -> Iterator[K]: | ||
return iter(self.__data) | ||
|
||
@classmethod | ||
def empty(cls) -> 'ArraySet[K]': | ||
if not hasattr(cls, '__EMPTY'): | ||
cls.__EMPTY = cls([]) | ||
return cls.__EMPTY | ||
|
||
def __len__(self) -> int: | ||
return len(self.__data) | ||
|
||
def __getitem__(self, index: Union[int, slice]) -> Union[K, List[K]]: | ||
if isinstance(index, int): | ||
return self.__data[index] | ||
elif isinstance(index, slice): | ||
return self.__data[index] | ||
else: | ||
raise TypeError("Invalid argument type.") | ||
|
||
def plusOrThis(self, element: K) -> 'ArraySet[K]': | ||
# TODO: use binary search, and also special sort order for strings | ||
if element in self.__data: | ||
return self | ||
else: | ||
new_data = self.__data[:] | ||
new_data.append(element) | ||
new_data.sort() # type: ignore[reportOperatorIssue] | ||
return ArraySet.__create(new_data) | ||
|
||
|
||
class ArrayMap(Mapping[K, V]): | ||
def __init__(self, data: list): | ||
# TODO: hide this constructor as done in ArraySet | ||
self.__data = data | ||
|
||
@classmethod | ||
def empty(cls) -> 'ArrayMap[K, V]': | ||
if not hasattr(cls, '__EMPTY'): | ||
cls.__EMPTY = cls([]) | ||
return cls.__EMPTY | ||
|
||
def __getitem__(self, key: K) -> V: | ||
index = self.__binary_search_key(key) | ||
if index >= 0: | ||
return self.__data[2 * index + 1] | ||
raise KeyError(key) | ||
|
||
def __iter__(self) -> Iterator[K]: | ||
return (self.__data[i] for i in range(0, len(self.__data), 2)) | ||
|
||
def __len__(self) -> int: | ||
return len(self.__data) // 2 | ||
|
||
def __binary_search_key(self, key: K) -> int: | ||
# TODO: special sort order for strings | ||
low, high = 0, (len(self.__data) // 2) - 1 | ||
while low <= high: | ||
mid = (low + high) // 2 | ||
mid_key = self.__data[2 * mid] | ||
if mid_key < key: | ||
low = mid + 1 | ||
elif mid_key > key: | ||
high = mid - 1 | ||
else: | ||
return mid | ||
return -(low + 1) | ||
|
||
def plus(self, key: K, value: V) -> 'ArrayMap[K, V]': | ||
index = self.__binary_search_key(key) | ||
if index >= 0: | ||
raise ValueError("Key already exists") | ||
insert_at = -(index + 1) | ||
new_data = self.__data[:] | ||
new_data[insert_at * 2:insert_at * 2] = [key, value] | ||
return ArrayMap(new_data) | ||
|
||
def minus_sorted_indices(self, indicesToRemove: List[int]) -> 'ArrayMap[K, V]': | ||
if not indicesToRemove: | ||
return self | ||
newData = [] | ||
for i in range(0, len(self.__data), 2): | ||
if i // 2 not in indicesToRemove: | ||
newData.extend(self.__data[i:i + 2]) | ||
return ArrayMap(newData) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
import pytest | ||
from selfie_lib.ArrayMap import ArrayMap | ||
|
||
def assertEmpty(map): | ||
assert len(map) == 0 | ||
assert list(map.keys()) == [] | ||
assert list(map.values()) == [] | ||
assert list(map.items()) == [] | ||
with pytest.raises(KeyError): | ||
_ = map["key"] | ||
assert map == {} | ||
assert map == ArrayMap.empty() | ||
|
||
def assertSingle(map, key, value): | ||
assert len(map) == 1 | ||
assert set(map.keys()) == {key} | ||
assert list(map.values()) == [value] | ||
assert set(map.items()) == {(key, value)} | ||
assert map[key] == value | ||
with pytest.raises(KeyError): | ||
_ = map[key + "blah"] | ||
assert map == {key: value} | ||
assert map == ArrayMap.empty().plus(key, value) | ||
|
||
def assertDouble(map, key1, value1, key2, value2): | ||
assert len(map) == 2 | ||
assert set(map.keys()) == {key1, key2} | ||
assert list(map.values()) == [value1, value2] | ||
assert set(map.items()) == {(key1, value1), (key2, value2)} | ||
assert map[key1] == value1 | ||
assert map[key2] == value2 | ||
with pytest.raises(KeyError): | ||
_ = map[key1 + "blah"] | ||
assert map == {key1: value1, key2: value2} | ||
assert map == {key2: value2, key1: value1} | ||
assert map == ArrayMap.empty().plus(key1, value1).plus(key2, value2) | ||
assert map == ArrayMap.empty().plus(key2, value2).plus(key1, value1) | ||
|
||
def assertTriple(map, key1, value1, key2, value2, key3, value3): | ||
assert len(map) == 3 | ||
assert set(map.keys()) == {key1, key2, key3} | ||
assert list(map.values()) == [value1, value2, value3] | ||
assert set(map.items()) == {(key1, value1), (key2, value2), (key3, value3)} | ||
assert map[key1] == value1 | ||
assert map[key2] == value2 | ||
assert map[key3] == value3 | ||
with pytest.raises(KeyError): | ||
_ = map[key1 + "blah"] | ||
assert map == {key1: value1, key2: value2, key3: value3} | ||
assert map == ArrayMap.empty().plus(key1, value1).plus(key2, value2).plus(key3, value3) | ||
|
||
def test_empty(): | ||
assertEmpty(ArrayMap.empty()) | ||
|
||
def test_single(): | ||
empty = ArrayMap.empty() | ||
single = empty.plus("one", "1") | ||
assertEmpty(empty) | ||
assertSingle(single, "one", "1") | ||
|
||
def test_double(): | ||
empty = ArrayMap.empty() | ||
single = empty.plus("one", "1") | ||
double = single.plus("two", "2") | ||
assertEmpty(empty) | ||
assertSingle(single, "one", "1") | ||
assertDouble(double, "one", "1", "two", "2") | ||
assertDouble(single.plus("a", "sorted"), "a", "sorted", "one", "1") | ||
|
||
with pytest.raises(ValueError) as context: | ||
single.plus("one", "2") | ||
assert str(context.value) == "Key already exists" | ||
|
||
def test_triple(): | ||
triple = ArrayMap.empty().plus("1", "one").plus("2", "two").plus("3", "three") | ||
assertTriple(triple, "1", "one", "2", "two", "3", "three") | ||
|
||
def test_multi(): | ||
test_triple() # Calling another test function directly is unusual but works | ||
triple = ArrayMap.empty().plus("2", "two").plus("3", "three").plus("1", "one") | ||
assertTriple(triple, "1", "one", "2", "two", "3", "three") | ||
triple = ArrayMap.empty().plus("3", "three").plus("1", "one").plus("2", "two") | ||
assertTriple(triple, "1", "one", "2", "two", "3", "three") | ||
|
||
def test_minus_sorted_indices(): | ||
initial_map = ArrayMap.empty().plus("1", "one").plus("2", "two").plus("3", "three").plus("4", "four") | ||
modified_map = initial_map.minus_sorted_indices([1, 3]) | ||
assert len(modified_map) == 2 | ||
assert list(modified_map.keys()) == ["1", "3"] | ||
assert list(modified_map.values()) == ["one", "three"] | ||
with pytest.raises(KeyError): | ||
_ = modified_map["2"] | ||
with pytest.raises(KeyError): | ||
_ = modified_map["4"] | ||
assert modified_map == {"1": "one", "3": "three"} | ||
|
||
def test_plus_with_existing_keys(): | ||
map_with_duplicates = ArrayMap.empty().plus("a", "alpha").plus("b", "beta") | ||
with pytest.raises(ValueError): | ||
map_with_duplicates.plus("a", "new alpha") | ||
updated_map = map_with_duplicates.plus("c", "gamma") | ||
assert len(updated_map) == 3 | ||
assert updated_map["a"] == "alpha" | ||
assert updated_map["b"] == "beta" | ||
assert updated_map["c"] == "gamma" | ||
modified_map = map_with_duplicates.minus_sorted_indices([0]).plus("a", "updated alpha") | ||
assert len(modified_map) == 2 | ||
assert modified_map["a"] == "updated alpha" | ||
assert modified_map["b"] == "beta" | ||
|
||
def test_map_length(): | ||
map = ArrayMap.empty() | ||
assert len(map) == 0, "Length should be 0 for an empty map" | ||
map = map.plus("key1", "value1") | ||
assert len(map) == 1, "Length should be 1 after adding one item" | ||
map = map.plus("key2", "value2") | ||
assert len(map) == 2, "Length should be 2 after adding another item" | ||
map = map.plus("key3", "value3") | ||
assert len(map) == 3, "Length should be 3 after adding a third item" | ||
map = map.minus_sorted_indices([1]) | ||
assert len(map) == 2, "Length should be 2 after removing one item" | ||
map = map.minus_sorted_indices([0]) | ||
assert len(map) == 1, "Length should be 1 after removing another item" | ||
map = map.minus_sorted_indices([0]) | ||
assert len(map) == 0, "Length should be 0 after removing all items" |