From 024685a07c5233c82a1017af75b682f4bbce3ba4 Mon Sep 17 00:00:00 2001 From: podhmo Date: Sun, 21 Oct 2018 15:42:01 +0900 Subject: [PATCH 1/3] test start --- dictknife/tests/test_soft.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 dictknife/tests/test_soft.py diff --git a/dictknife/tests/test_soft.py b/dictknife/tests/test_soft.py new file mode 100644 index 00000000..090b317b --- /dev/null +++ b/dictknife/tests/test_soft.py @@ -0,0 +1,23 @@ +import unittest + + +class Tests(unittest.TestCase): + def _callFUT(self, x): + from dictknife.sort import sort_flexibly + return sort_flexibly(x) + + def test_it(self): + from collections import namedtuple + C = namedtuple("C", "msg, input, expected") + candidates = [ + C(msg="int", input=1, expected=1), + C(msg="str", input="foo", expected="foo"), + ] + for c in candidates: + with self.subTest(msg=c.msg): + got = self._callFUT(c.input) + self.assertEqual(got, c.expected) + + +if __name__ == "__main__": + unittest.main() From 78e69dd84a01ebbcccf7a0c0aa4bd7e24dc76b18 Mon Sep 17 00:00:00 2001 From: podhmo Date: Thu, 25 Oct 2018 03:26:52 +0900 Subject: [PATCH 2/3] hmm --- dictknife/sort.py | 137 +++++++++++++++++++++++++++++++++++ dictknife/tests/test_soft.py | 23 ------ dictknife/tests/test_sort.py | 121 +++++++++++++++++++++++++++++++ 3 files changed, 258 insertions(+), 23 deletions(-) create mode 100644 dictknife/sort.py delete mode 100644 dictknife/tests/test_soft.py create mode 100644 dictknife/tests/test_sort.py diff --git a/dictknife/sort.py b/dictknife/sort.py new file mode 100644 index 00000000..90be448e --- /dev/null +++ b/dictknife/sort.py @@ -0,0 +1,137 @@ +from collections import defaultdict +from .langhelpers import reify + +# key order (alphabetical, order) +# value order (str, int) + + +def sort_flexibly(x): + if hasattr(x, "__next__"): + return _unwrap(_wrap(list(x))) + return _unwrap(_wrap(x)) + + +def _wrap(ob): + if isinstance(ob, (list, tuple)): + ks = set() + wobs = [] + for x in ob: + wob = _wrap(x) + if hasattr(wob, "update_keys"): + wob.update_keys(ks) + wobs.append(wob) + return _Collection(wobs) + elif hasattr(ob, "keys"): + wob = ob.__class__() + for k in sorted(ob): + wob[k] = _wrap(ob[k]) + return _Dict(wob) + else: + return _Atom(ob) + + +def _unwrap(wob): + return wob.unwrap() + + +# uid (, , , ) +ONE_0 = 0 +MIXED_0 = 1 + +STR_1 = 0 +NUM_1 = 1 +ANY_1 = 2 +EMPTY_1 = 3 +MISSING_1 = 4 + +PRIMITIVE_2 = 0 +DICT_2 = 1 +COLLECTION_2 = 2 + + +class _Atom: + def __init__(self, value): + self.value = value + + @reify + def uid(self): + v = self.value + if isinstance(v, str): + return (ONE_0, STR_1, PRIMITIVE_2, v) + elif isinstance(v, (float, int)): + return (ONE_0, NUM_1, PRIMITIVE_2, v) + elif v is None: + return (ONE_0, EMPTY_1, PRIMITIVE_2, v) + else: + return (ONE_0, ANY_1, PRIMITIVE_2, v) + + def unwrap(self): + return self.value + + +class _Collection: + def __init__(self, value): + self.value = value + + @reify + def uid(self): + if not self.value: + return (MIXED_0, EMPTY_1, COLLECTION_2, None) + self.value = sorted(self.value, key=lambda x: x.uid) + uids = [x.uid for x in self.value] + r = [ + ONE_0 if len(set((uid[0], uid[1]) for uid in uids)) == 1 else MIXED_0, + uids[0][1], + COLLECTION_2, + ] + for uid in uids: + r.extend(uid) + return tuple(r) + + def unwrap(self): + self.uid # xxx: for sort + return [x.unwrap() for x in self.value] + + +class _Dict: + def __init__(self, value): + self.value = value + self.keys = None + + def update_keys(self, keys): + keys.update(self.value.keys()) + self.keys = keys + + @reify + def uid(self): + if not self.value: + return (MIXED_0, EMPTY_1, DICT_2, None) + d = defaultdict(list) + vs = {} + for k in self.keys or self.value.keys(): + uid = self.value.get(k, _MISSING).uid + d[(uid[0], uid[1], uid[2])].append(k) + vs[k] = uid[3:] + + if len(d) == 1: + uid = next(iter(d)) + r = [ONE_0, uid[1], DICT_2] + else: + r = [MIXED_0, ANY_1, DICT_2] + for uid_prefix, ks in sorted(d.items()): + for k in ks: + r.extend(uid_prefix) + r.extend(vs.get(k)) + return tuple(r) + + def unwrap(self): + self.uid # xxx + + d = self.value + for k in list(d.keys()): + d[k] = d[k].unwrap() + return d + + +class _MISSING: + uid = (MIXED_0, MISSING_1, PRIMITIVE_2, None) diff --git a/dictknife/tests/test_soft.py b/dictknife/tests/test_soft.py deleted file mode 100644 index 090b317b..00000000 --- a/dictknife/tests/test_soft.py +++ /dev/null @@ -1,23 +0,0 @@ -import unittest - - -class Tests(unittest.TestCase): - def _callFUT(self, x): - from dictknife.sort import sort_flexibly - return sort_flexibly(x) - - def test_it(self): - from collections import namedtuple - C = namedtuple("C", "msg, input, expected") - candidates = [ - C(msg="int", input=1, expected=1), - C(msg="str", input="foo", expected="foo"), - ] - for c in candidates: - with self.subTest(msg=c.msg): - got = self._callFUT(c.input) - self.assertEqual(got, c.expected) - - -if __name__ == "__main__": - unittest.main() diff --git a/dictknife/tests/test_sort.py b/dictknife/tests/test_sort.py new file mode 100644 index 00000000..0bdcda64 --- /dev/null +++ b/dictknife/tests/test_sort.py @@ -0,0 +1,121 @@ +import unittest +from collections import OrderedDict, namedtuple + + +class WrapTests(unittest.TestCase): + def _callFUT(self, x): + from dictknife.sort import _wrap + return _wrap(x) + + def test_it(self): + C = namedtuple("C", "input, expected") + # yapf: disable + candidates = [ + C(input=[1, 2, 10], expected=[1, 2, 10]), + C(input=[1, 2, "10"], expected=["10", 1, 2]), + C(input=["1", "2", "10"], expected=["1", "10", "2"]), + C(input=["2", 1, None], expected=["2", 1, None]), + C(input=[1, [1]], expected=[1, [1]]), + C(input=[1, [1], ["1"]], expected=[["1"], 1, [1]]), + C(input=[1, [1], [[4, 2, 3], [1, 2, 3]], ["1"]], expected=[["1"], 1, [1], [[1, 2, 3], [2, 3, 4, ]]]), + C(input=[1, [], {}], expected=[1, {}, []]), + C(input=[1, [1], ["1"], "1", {"name": "foo"}, {"age": 10}], expected=["1", {"name": "foo"}, ["1"], 1, {"age": 10}, [1]]), + C( + input=[{"name": "x"}, {}, {"name": "x", "age": 20}, {"name": "x", "age": 10}, {"name": "x", "age": None}], + expected=[{"name": "x"}, {"name": "x", "age": 10}, {"name": "x", "age": 20}, {"name": "x", "age": None}, {}], + ) + ] + # yapf: enable + for c in candidates: + with self.subTest(msg=str(c.input)): + ws = [self._callFUT(x) for x in c.input] + got = [x.unwrap() for x in sorted(ws, key=lambda x: x.uid)] + self.assertEqual(got, c.expected) + + +class Tests(unittest.TestCase): + maxDiff = None + + def _callFUT(self, x): + from dictknife.sort import sort_flexibly + return sort_flexibly(x) + + def test_it(self): + C = namedtuple("C", "msg, input, expected") + # yapf: disable + candidates = [ + C(msg="int", input=1, expected=1), + C(msg="str", input="foo", expected="foo"), + C(msg="list, primitive", input=[1, 3, 2], expected=[1, 2, 3]), + C(msg="dict, primitive", + input=OrderedDict([("name", "foo"), ("age", 20)]), + expected=OrderedDict([("age", 20), ("name", "foo")])), + C(msg="list, dict", + input=[ + {"name": "foo", "age": 10}, + {"name": "bar", "age": 9}, + {"name": "z", "age": 1}, + ], expected=[ + {"name": "bar", "age": 9}, + {"name": "foo", "age": 10}, + {"name": "z", "age": 1}, + ]), + C(msg="list, dict, missing", + input=[ + {"name": "x"}, + {"name": "y", "age": 1}, + {"name": "y", "age": None}, + {"name": "y"}, + ], expected=[ + {"name": "x"}, + {"name": "y", "age": 1}, + {"name": "y", "age": None}, + {"name": "y"}, + ]), + C(msg="list, dict, nested", + input=[ + { + "dimensions": [{"name": "x"}, {"name": "y"}, {"name": "z"}], + "value": 0, + }, + { + "dimensions": [{"name": "a"}, {"name": "y"}, {"name": "z"}], + "value": 1000, + }, + { + "dimensions": [{"name": "x"}, {"name": "a"}, {"name": "z"}], + "value": 2000, + }, + { + "dimensions": [{"name": "x"}, {"name": "y"}, {"name": "a"}], + "value": 3000, + }, + ], expected=[ + { + "dimensions": [{"name": "a"}, {"name": "x"}, {"name": "y"}], + "value": 3000, + }, + { + "dimensions": [{"name": "a"}, {"name": "x"}, {"name": "z"}], + "value": 2000, + }, + { + "dimensions": [{"name": "a"}, {"name": "y"}, {"name": "z"}], + "value": 1000, + }, + { + "dimensions": [{"name": "x"}, {"name": "y"}, {"name": "z"}], + "value": 0, + }, + ]), + # nested + ] + # yapf: enable + for c in candidates: + with self.subTest(msg=c.msg): + got = self._callFUT(c.input) + self.assertEqual(got, c.expected) + + +if __name__ == "__main__": + unittest.main() From 9805576c82bac519c31e8aa959033e79698028cf Mon Sep 17 00:00:00 2001 From: podhmo Date: Thu, 25 Oct 2018 14:03:06 +0900 Subject: [PATCH 3/3] hmm --- dictknife/sort.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dictknife/sort.py b/dictknife/sort.py index 90be448e..4ac1e7e4 100644 --- a/dictknife/sort.py +++ b/dictknife/sort.py @@ -57,7 +57,7 @@ def __init__(self, value): def uid(self): v = self.value if isinstance(v, str): - return (ONE_0, STR_1, PRIMITIVE_2, v) + return (ONE_0, STR_1, PRIMITIVE_2, v.lower()) elif isinstance(v, (float, int)): return (ONE_0, NUM_1, PRIMITIVE_2, v) elif v is None: @@ -108,7 +108,7 @@ def uid(self): return (MIXED_0, EMPTY_1, DICT_2, None) d = defaultdict(list) vs = {} - for k in self.keys or self.value.keys(): + for k in sorted(self.keys or self.value.keys()): uid = self.value.get(k, _MISSING).uid d[(uid[0], uid[1], uid[2])].append(k) vs[k] = uid[3:]