Skip to content
Snippets Groups Projects
Unverified Commit 9928abd4 authored by Björn Ludwig's avatar Björn Ludwig
Browse files

wip(chars): finalize new chars and associated tests

parent 96ac13b9
No related branches found
No related tags found
1 merge request!1Introduce Chars
This commit is part of merge request !1. Comments created here will be created in the context of that merge request.
...@@ -10,13 +10,28 @@ from ..type_aliases import Bigram, CharTuple ...@@ -10,13 +10,28 @@ from ..type_aliases import Bigram, CharTuple
class Chars: class Chars:
"""A unified interface to a collection of characters and corresponding bigrams
Parameters
----------
chars : str or CharTupel, optional
A string of concatenated (special) characters or a CharTupel of single
characters, that are supposed to be considered. Defaults to the most common
letters, numbers and punctuation in German texts.
"""
_chars: str _chars: str
_monos: CharTuple
_bis: Tuple[Bigram]
def __init__(self, chars: Optional[Union[str, CharTuple]] = None): def __init__(self, chars: Optional[Union[str, CharTuple]] = None):
if chars is None: if chars is None:
self._chars = ( self._chars = (
"""abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890,""" string.ascii_lowercase
r"""üöäÜÖÄß.:;-–…_[](){}^\/#!?+<>&$|~`*=%"'@""" + string.ascii_uppercase
+ string.digits
+ string.punctuation
+ "üöäÜÖÄß–…"
) )
else: else:
self.chars = chars self.chars = chars
...@@ -31,11 +46,33 @@ class Chars: ...@@ -31,11 +46,33 @@ class Chars:
self._chars = chars self._chars = chars
else: # isinstance(chars, CharTuple): else: # isinstance(chars, CharTuple):
self._chars = "".join(char for char in chars) self._chars = "".join(char for char in chars)
try:
del self._monos
except AttributeError:
pass
try:
del self._bis
except AttributeError:
pass
@property @property
def char_tuple(self) -> CharTuple: def monos(self) -> CharTuple:
return self._str2char_tuple(self.chars) try:
return self._monos
except AttributeError:
self._monos = self._str2char_tuple(self.chars)
return self._monos
@staticmethod @staticmethod
def _str2char_tuple(string: str) -> CharTuple: def _str2char_tuple(char_str: str) -> CharTuple:
return tuple(char for char in string) return tuple(char for char in char_str)
@property
def bis(self):
try:
return self._bis
except AttributeError:
self._bis = tuple(
"".join(bigram_tuple) for bigram_tuple in product(self.chars, repeat=2)
)
return self._bis
...@@ -47,8 +47,8 @@ class KeyboardOptimization: ...@@ -47,8 +47,8 @@ class KeyboardOptimization:
chars: CharTuple chars: CharTuple
keys: KeyTuple keys: KeyTuple
def __init__(self, chars: CharTuple, keys: KeyTuple): def __init__(self, chars: Chars, poss: PosTuple):
assert len(chars) == len(keys) assert len(chars.monos) == len(poss)
self.chars = chars self.chars = chars
self.keys = keys self.keys = keys
self.char_key_assigns: LinVars = {} self.char_key_assigns: LinVars = {}
...@@ -73,7 +73,7 @@ class KeyboardOptimization: ...@@ -73,7 +73,7 @@ class KeyboardOptimization:
self.quad_char_key_costs = quad_char_key_costs self.quad_char_key_costs = quad_char_key_costs
constr = {} constr = {}
for char in self.chars: for char in self.chars.monos:
self.model.addCons( self.model.addCons(
quicksum(self.char_key_assigns[char, key] for key in self.keys) == 1, quicksum(self.char_key_assigns[char, key] for key in self.keys) == 1,
f"AllCharacterAssignedOnce({char})", f"AllCharacterAssignedOnce({char})",
...@@ -81,8 +81,8 @@ class KeyboardOptimization: ...@@ -81,8 +81,8 @@ class KeyboardOptimization:
for (key, key_2) in self.key_pairs: for (key, key_2) in self.key_pairs:
self.model.addCons( self.model.addCons(
quicksum( quicksum(
self.quad_char_key_assigns[char, char_2, key, key_2] self.quad_char_pos_assigns[char, char_2, key, key_2]
for char_2 in self.chars for char_2 in self.chars.monos
if char_2 != char if char_2 != char
) )
<= self.char_key_assigns[char, key], <= self.char_key_assigns[char, key],
...@@ -90,15 +90,15 @@ class KeyboardOptimization: ...@@ -90,15 +90,15 @@ class KeyboardOptimization:
) )
self.model.addCons( self.model.addCons(
quicksum( quicksum(
self.quad_char_key_assigns[char_2, char, key_2, key] self.quad_char_pos_assigns[char_2, char, key_2, key]
for char_2 in self.chars for char_2 in self.chars.monos
if char_2 != char if char_2 != char
) )
<= self.char_key_assigns[char, key], <= self.char_key_assigns[char, key],
f"QuadCharacterAssignedLEQThanSecondPosition({char},{key_2},{key})", f"QuadCharacterAssignedLEQThanSecondPosition({char},{key_2},{key})",
) )
for char_2 in self.chars: for char_2 in self.chars.monos:
for key in self.keys: for key in self.poss:
if char_2 != char: if char_2 != char:
self.model.addCons( self.model.addCons(
quicksum( quicksum(
...@@ -199,7 +199,7 @@ class KeyboardOptimization: ...@@ -199,7 +199,7 @@ class KeyboardOptimization:
"""An iterator for quadruples of character pairs and corresponding key pairs""" """An iterator for quadruples of character pairs and corresponding key pairs"""
flattened_tuple_of_quads = chain.from_iterable( flattened_tuple_of_quads = chain.from_iterable(
chain.from_iterable( chain.from_iterable(
product(permutations(self.chars, 2), permutations(self.keys, 2)) product(permutations(self.chars.monos, 2), permutations(self.poss, 2))
) )
) )
iter_of_quads = (iter(flattened_tuple_of_quads),) * 4 iter_of_quads = (iter(flattened_tuple_of_quads),) * 4
......
...@@ -15,6 +15,8 @@ function at the very bottom of this file to change inputs. ...@@ -15,6 +15,8 @@ function at the very bottom of this file to change inputs.
from ilp_keyboard_layout_optimization.ilp import KeyboardOptimization from ilp_keyboard_layout_optimization.ilp import KeyboardOptimization
from ilp_keyboard_layout_optimization.type_aliases import LinCosts, QuadCosts from ilp_keyboard_layout_optimization.type_aliases import LinCosts, QuadCosts
from src.ilp_keyboard_layout_optimization.data_aquisition.chars import Chars
def prepare_costs( def prepare_costs(
optimization_problem: KeyboardOptimization, optimization_problem: KeyboardOptimization,
...@@ -73,7 +75,7 @@ def prepare_costs( ...@@ -73,7 +75,7 @@ def prepare_costs(
if __name__ == "__main__": if __name__ == "__main__":
test_chars = ("a", "e", "i", "u", "n", "r", "t", "d") test_chars = Chars(("a", "e", "i", "u", "n", "r", "t", "d"))
test_poss = ( test_poss = (
"left_pinky_home", "left_pinky_home",
"left_ring_home", "left_ring_home",
......
...@@ -16,15 +16,14 @@ def test_chars_chars_type(): ...@@ -16,15 +16,14 @@ def test_chars_chars_type():
assert isinstance(Chars().chars, str) assert isinstance(Chars().chars, str)
def test_chars_char_tuple_type(): def test_chars_monos_type():
assert isinstance(Chars().char_tuple, CharTuple.__origin__) assert isinstance(Chars().monos, CharTuple.__origin__)
@given(hst.tuples(hst.text(min_size=1))) @given(hst.lists(hst.characters(), min_size=1))
@settings(deadline=None) @settings(deadline=None)
# TODO Fix this test, it is not generating what it is supposed to
def test_chars_input_tuple(char_tuple): def test_chars_input_tuple(char_tuple):
assert Chars(char_tuple).chars == "".join(char_tuple) assert Chars(tuple(char_tuple)).chars == "".join(char_tuple)
@given(hst.text(min_size=1)) @given(hst.text(min_size=1))
...@@ -52,6 +51,7 @@ def test_chars_default(): ...@@ -52,6 +51,7 @@ def test_chars_default():
general_punc = _get_unicode_chars((0x2013, 0x2026)) general_punc = _get_unicode_chars((0x2013, 0x2026))
extended_alphabet = basic_latin + latin_1_supp + general_punc extended_alphabet = basic_latin + latin_1_supp + general_punc
for char in extended_alphabet: for char in extended_alphabet:
assert char in all_test_chars
all_test_chars.remove(char) all_test_chars.remove(char)
assert not all_test_chars assert not all_test_chars
...@@ -60,6 +60,89 @@ def _get_unicode_chars(code_point_range: Iterable) -> List[str]: ...@@ -60,6 +60,89 @@ def _get_unicode_chars(code_point_range: Iterable) -> List[str]:
return [chr(char) for char in code_point_range] return [chr(char) for char in code_point_range]
@given(hst.text(min_size=1)) def test_chars_monograms():
def test_chars_monograms(char_string): assert Chars().monos
pass
def test_chars_monograms_multiple_times():
test_chars = Chars()
first_time_monos = test_chars.monos
second_time_monos = test_chars.monos
assert first_time_monos == second_time_monos
@given(hst.lists(hst.characters(), min_size=3))
def test_chars_monograms_after_resetting(char_tuple):
first_test_chars = Chars(char_tuple)
assert first_test_chars.monos == tuple(char_tuple)
first_test_chars.chars = char_tuple[1:-1]
assert first_test_chars.monos == tuple(char_tuple[1:-1])
def test_chars_bigrams():
assert Chars().bis
def test_chars_bigrams_length():
for bigram in Chars().bis:
assert len(bigram) == 2
def test_bigrams_default():
all_test_bigrams = list(Chars("1234").bis)
actual_bigrams = (
"11",
"12",
"21",
"13",
"31",
"14",
"41",
"22",
"23",
"32",
"24",
"42",
"33",
"34",
"44",
"43",
)
for bigram in actual_bigrams:
assert bigram in all_test_bigrams
all_test_bigrams.remove(bigram)
assert not all_test_bigrams
def test_chars_bigrams_multiple_times():
test_chars = Chars()
first_time_bis = test_chars.bis
second_time_bis = test_chars.bis
assert first_time_bis == second_time_bis
def test_chars_bigrams_after_resetting():
test_chars = Chars("12")
first_bigram_list = list(test_chars.bis)
first_actual_bigrams = (
"11",
"12",
"21",
"22",
)
for bigram in first_actual_bigrams:
assert bigram in first_bigram_list
first_bigram_list.remove(bigram)
assert not first_bigram_list
test_chars.chars = "23"
second_bigram_list = list(test_chars.bis)
second_actual_bigrams = (
"22",
"23",
"32",
"33",
)
for bigram in second_actual_bigrams:
assert bigram in second_bigram_list
second_bigram_list.remove(bigram)
assert not second_bigram_list
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment