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

ArrayMap.py #17

Merged
merged 21 commits into from
Feb 29, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"java.compile.nullAnalysis.mode": "automatic"
}
142 changes: 142 additions & 0 deletions python/selfie-lib/selfie_lib/ArrayMap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
from collections.abc import Set, Sequence, Iterator, Mapping
from typing import List
from functools import cmp_to_key

class ListBackedSet(Set[T], Sequence[T]):
def __getitem__(self, index: int) -> T:
# This method should be implemented by the subclass.
raise NotImplementedError

def __len__(self) -> int:
# This should also be implemented by the subclass to return the number of items.
raise NotImplementedError

def __iter__(self) -> Iterator[T]:
return self.ListBackedSetIterator(self)

class ListBackedSetIterator(Iterator[T]):
def __init__(self, list_backed_set: 'ListBackedSet[T]'):
self.list_backed_set = list_backed_set
self.index = 0

def __next__(self) -> T:
if self.index < len(self.list_backed_set):
result = self.list_backed_set[self.index]
self.index += 1
return result
else:
raise StopIteration

def __contains__(self, item: object) -> bool:
# Efficient implementation of __contains__ should be provided by subclass if needed.
for i in self:
if i == item:
return True
return False

class ArraySet(ListBackedSet[K]):
def __init__(self, data: list):
nedtwigg marked this conversation as resolved.
Show resolved Hide resolved
self.data = data
nedtwigg marked this conversation as resolved.
Show resolved Hide resolved
self.sort_data()

def sort_data(self):
if self.data and isinstance(self.data[0], str):
self.data.sort(key=cmp_to_key(self.string_slash_first_comparator))
else:
self.data.sort()

def string_slash_first_comparator(self, a, b):
# Define sorting where '/' is considered the lowest key
if a == '/':
return -1
elif b == '/':
return 1
else:
return (a > b) - (a < b)

def __len__(self):
return len(self.data)

def __getitem__(self, index: int) -> K:
return self.data[index]

def __contains__(self, item: K) -> bool:
# Implementing binary search for efficiency
left, right = 0, len(self.data) - 1
while left <= right:
mid = (left + right) // 2
if self.data[mid] == item:
return True
elif self.data[mid] < item:
left = mid + 1
else:
right = mid - 1
return False

def plus_or_this(self, key: K) -> 'ArraySet[K]':
# Binary search to find the appropriate index or confirm existence
left, right = 0, len(self.data) - 1
while left <= right:
mid = (left + right) // 2
if self.data[mid] == key:
return self # Key already exists
elif self.data[mid] < key:
left = mid + 1
else:
right = mid - 1

# Key does not exist, insert in the sorted position
new_data = self.data[:left] + [key] + self.data[left:]
return ArraySet(new_data)

class ArrayMap(Mapping[K, V]):
def __init__(self, data: list):
self.data = data
nedtwigg marked this conversation as resolved.
Show resolved Hide resolved

@classmethod
def empty(cls):
return cls([])

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):
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:
nedtwigg marked this conversation as resolved.
Show resolved Hide resolved
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")
else:
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)
87 changes: 87 additions & 0 deletions python/selfie-lib/tests/ArrayMap_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import unittest
from selfie_lib.ArrayMap import ArrayMap

class ArrayMapTest(unittest.TestCase):
nedtwigg marked this conversation as resolved.
Show resolved Hide resolved
def assertEmpty(self, map):
self.assertEqual(len(map), 0)
self.assertEqual(list(map.keys()), [])
self.assertEqual(list(map.values()), [])
self.assertEqual(list(map.items()), [])
with self.assertRaises(KeyError):
_ = map["key"]
self.assertEqual(map, {})
self.assertEqual(map, ArrayMap.empty())

def assertSingle(self, map, key, value):
self.assertEqual(len(map), 1)
self.assertEqual(set(map.keys()), {key})
self.assertEqual(list(map.values()), [value])
self.assertEqual(set(map.items()), {(key, value)})
self.assertEqual(map[key], value)
with self.assertRaises(KeyError):
_ = map[key + "blah"]
self.assertEqual(map, {key: value})
self.assertEqual(map, ArrayMap.empty().plus(key, value))

def assertDouble(self, map, key1, value1, key2, value2):
self.assertEqual(len(map), 2)
self.assertEqual(set(map.keys()), {key1, key2})
self.assertEqual(list(map.values()), [value1, value2])
self.assertEqual(set(map.items()), {(key1, value1), (key2, value2)})
self.assertEqual(map[key1], value1)
self.assertEqual(map[key2], value2)
with self.assertRaises(KeyError):
_ = map[key1 + "blah"]
self.assertEqual(map, {key1: value1, key2: value2})
self.assertEqual(map, {key2: value2, key1: value1})
self.assertEqual(map, ArrayMap.empty().plus(key1, value1).plus(key2, value2))
self.assertEqual(map, ArrayMap.empty().plus(key2, value2).plus(key1, value1))

def assertTriple(self, map, key1, value1, key2, value2, key3, value3):
self.assertEqual(len(map), 3)
self.assertEqual(set(map.keys()), {key1, key2, key3})
self.assertEqual(list(map.values()), [value1, value2, value3])
self.assertEqual(set(map.items()), {(key1, value1), (key2, value2), (key3, value3)})
self.assertEqual(map[key1], value1)
self.assertEqual(map[key2], value2)
self.assertEqual(map[key3], value3)
with self.assertRaises(KeyError):
_ = map[key1 + "blah"]
self.assertEqual(map, {key1: value1, key2: value2, key3: value3})
self.assertEqual(map, ArrayMap.empty().plus(key1, value1).plus(key2, value2).plus(key3, value3))

def test_empty(self):
self.assertEmpty(ArrayMap.empty())

def test_single(self):
empty = ArrayMap.empty()
single = empty.plus("one", "1")
self.assertEmpty(empty)
self.assertSingle(single, "one", "1")

def test_double(self):
empty = ArrayMap.empty()
single = empty.plus("one", "1")
double = single.plus("two", "2")
self.assertEmpty(empty)
self.assertSingle(single, "one", "1")
self.assertDouble(double, "one", "1", "two", "2")
self.assertDouble(single.plus("a", "sorted"), "a", "sorted", "one", "1")

with self.assertRaises(ValueError) as context:
single.plus("one", "2")
self.assertEqual(str(context.exception), "Key already exists")

def test_triple(self):
triple = ArrayMap.empty().plus("1", "one").plus("2", "two").plus("3", "three")
self.assertTriple(triple, "1", "one", "2", "two", "3", "three")

def test_multi(self):
self.test_triple()
triple = ArrayMap.empty().plus("2", "two").plus("3", "three").plus("1", "one")
self.assertTriple(triple, "1", "one", "2", "two", "3", "three")
triple = ArrayMap.empty().plus("3", "three").plus("1", "one").plus("2", "two")
self.assertTriple(triple, "1", "one", "2", "two", "3", "three")

if __name__ == '__main__':
unittest.main()
Loading