-
Notifications
You must be signed in to change notification settings - Fork 30
Expand file tree
/
Copy pathutils.py
More file actions
96 lines (72 loc) · 3.72 KB
/
utils.py
File metadata and controls
96 lines (72 loc) · 3.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
"""Utility functions for DiffSync library.
Copyright (c) 2020-2021 Network To Code, LLC <[email protected]>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
from collections import OrderedDict
from typing import Callable, Dict, Generic, Iterator, List, Optional, TypeVar
SPACE = " "
BRANCH = "│ "
TEE = "├── "
LAST = "└── "
T = TypeVar("T")
K = TypeVar("K")
V = TypeVar("V")
def intersection(lst1: List[T], lst2: List[T]) -> List[T]:
"""Calculate the intersection of two lists, with ordering based on the first list."""
lst3 = [value for value in lst1 if value in lst2]
return lst3
def symmetric_difference(lst1: List[T], lst2: List[T]) -> List[T]:
"""Calculate the symmetric difference of two lists."""
# Type hinting this correct is kind of hard to do. _Probably_ the solution is using typing.Protocol to ensure the
# set members support the ^ operator / set.symmetric_difference, but I decided this wasn't worth figuring out.
return sorted(set(lst1) ^ set(lst2)) # type: ignore[type-var]
class OrderedDefaultDict(OrderedDict, Generic[K, V]):
"""A combination of collections.OrderedDict and collections.DefaultDict behavior."""
def __init__(self, dict_type: Callable[[], V] = dict) -> None: # type: ignore[assignment]
"""Create a new OrderedDefaultDict."""
self.factory = dict_type
super().__init__(self)
def __missing__(self, key: K) -> V:
"""When trying to access a nonexistent key, initialize the key value based on the internal factory."""
self[key] = value = self.factory()
return value
# from: https://stackoverflow.com/questions/72618673/list-directory-tree-structure-in-python-from-a-list-of-path-file
def _tree(data: Dict, prefix: str = "") -> Iterator[str]:
"""Given a dictionary will yield a visual tree structure.
A recursive generator, given a dictionary will yield a visual tree structure line by line
with each line prefixed by the same characters.
"""
# contents each get pointers that are ├── with a final └── :
pointers = [TEE] * (len(data) - 1) + [LAST]
for pointer, path in zip(pointers, data):
yield prefix + pointer + path
if isinstance(data[path], dict): # extend the prefix and recurse:
extension = BRANCH if pointer == TEE else SPACE
# i.e. SPACE because LAST, └── , above so no more |
yield from _tree(data[path], prefix=prefix + extension)
def tree_string(data: Dict, root: str) -> str:
"""String wrapper around `_tree` function to add header and provide tree view of a dictionary."""
return "\n".join([root, *_tree(data)])
def set_key(data: Dict, keys: List) -> None:
"""Set a nested dictionary key given a list of keys."""
current_level = data
for key in keys:
current_level = current_level.setdefault(key, {})
def get_path(nested_dict: Dict, search_value: str) -> Optional[List]:
"""Find the path of keys in a dictionary, given a single unique value."""
for key in nested_dict.keys():
if key == search_value:
return [key]
if isinstance(nested_dict[key], dict):
path = get_path(nested_dict[key], search_value)
if path is not None:
return [key] + path
return None