-
Notifications
You must be signed in to change notification settings - Fork 1
/
iterables.py
366 lines (260 loc) · 9.84 KB
/
iterables.py
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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
import itertools
class SortedList:
"""
Controls a sorted list in ascending order.
"""
def __init__(self, *objects, getValueFunc=None):
"""
:param objects: Objects to be added instantly
:param function[any] -> float getValueFunc: A function that only takes obj as parameter and returns a float to be used for sorting.
"""
if getValueFunc is None:
getValueFunc = lambda obj: obj
self.getValueFunc = getValueFunc
self.objects = []
self._values = []
self.add(*objects)
def __repr__(self):
return f"<SortedList: {self.objects}>"
def __contains__(self, item):
return item in self.objects
def __iter__(self):
return self.objects.__iter__()
def add(self, *objects):
"""
Add objects to sorted list.
"""
for newObj in objects:
newValue = self.getValueFunc(newObj)
for i, obj in enumerate(self.objects):
value = self._values[i]
if newValue <= value:
index = i
break
else:
index = len(self.objects)
self.objects.insert(index, newObj)
self._values.insert(index, newValue)
def remove(self, *objects):
"""
Remove objects from sorted list.
"""
for removeObj in objects:
if removeObj not in self.objects:
continue
index = self.objects.index(removeObj)
del self.objects[index]
del self._values[index]
def get_keys(obj):
""" Returns an iterator or iterable containing keys of given obj. """
if hasattr(obj, "keys"):
return obj.keys()
else:
return range(len(obj))
def get_values(obj):
""" Returns an iterator or iterable containing values of given obj. """
if hasattr(obj, "values"):
return obj.values()
else:
return iter(obj)
def get_items(obj):
""" Returns an iterator which yields both key and value for any iterable. """
if hasattr(obj, "items"):
return obj.items()
else:
return enumerate(obj)
def is_iterable(obj):
""" Get whether an obj is iterable. """
return hasattr(obj, "__iter__")
def depth(obj):
""" Get depth of an object by recursively checking the first value. """
sentinel = object()
depth = 0
while True:
if not is_iterable(obj=obj):
break
first_value = iter_first_value(iterable=obj, default=sentinel)
if first_value == obj or first_value is sentinel:
break
obj = first_value
depth += 1
return depth
def iter_first_value(iterable, default=None):
""" Get first 'random' value of an iterable or default value. """
for x in iterable:
if hasattr(iterable, "values"):
return iterable[x]
else:
return x
return default
def join_with_str(delimiter, obj):
""" Like str.join() but it casts the values to strings first, also takes dict. """
return delimiter.join([str(value) for value in get_values(obj)])
def extend_list_in_dict(dictionary, key, *args):
""" Add a value to a list inside a dictionary, automatically creates list. """
assert isinstance(dictionary, dict)
if key in dictionary:
assert isinstance(dictionary[key], list)
else:
dictionary[key] = []
dictionary[key].extend(args)
return dictionary
def update_dict_in_dict(dictionary, key, **kwargs):
""" Add a key-value argument to a dict inside a dict, automatically creates dict. """
assert isinstance(dictionary, dict)
if key in dictionary:
assert isinstance(dictionary[key], dict)
else:
dictionary[key] = {}
dictionary[key].update(kwargs)
return dictionary
def get_free_index(dictionary):
""" Get the first free integer index of dictionary starting at 0. """
index = 0
while True:
if index in dictionary:
index += 1
else:
return index
def append_to_dict(dictionary, obj):
""" Puts an object in the lowest free integer index and returns index.
Useful for returning an index that wont change, unlike a list.
Keys can be deleted using the returned index without affecting other values.
:return: Used integer index. """
index = get_free_index(dictionary)
dictionary[index] = obj
return index
def get(iterable, index=None, default=None):
""" Get value of an iterable by index, mainly lists, tuples and sets that don't have a `get` method, works for dict too.
Returns first random value if set, returns default value if not found. """
if isinstance(iterable, set):
try:
return next(iter(iterable))
except StopIteration:
return default
else:
try:
return iterable[index]
except IndexError:
return default
def get_index(iterable, match, default=...):
""" Get the first match' index.
If not found it returns default value if specified, otherwise raises ValueError. """
try:
return next(key for key, value in get_items(iterable) if value == match)
except StopIteration as e:
if default is Ellipsis:
raise ValueError(f"{match} was not found in {iterable}")
else:
return default
def remove(iterable, match):
""" Remove member of an iterable, works for dict, list and set.
Returns True if removed, False if not found. """
if isinstance(iterable, set):
try:
iterable.remove(match)
except KeyError:
return False
else:
return True
else:
index = get_index(iterable, match, sentinel := object())
if index is sentinel:
return False
else:
del iterable[index]
return True
def _get_rows_helper(iterableObj, key=None):
""" Takes an object and returns a list of rows to use for appending.
:param iterableObj: Iterable
:param key: If iterableObj had a key to assigned it it's given here """
row = [key] if key else []
if isinstance(iterableObj, (list, tuple)):
row.extend(iterableObj)
elif isinstance(iterableObj, dict):
for _, value in sorted(iterableObj.items()):
row.append(value)
return row
def get_rows(obj):
""" Get rows as lists in list from a tuple, list or dict (where it discards keys).
All these objects result in [[1, 2, 3], [4, 5, 6]]
| [[1, 2, 3], [4, 5, 6]]
| [{"a": 1, "b": 2, "c": 3}, {"d": 4, "e": 5, "f": 6}]
| {1: {"b": 2, "c": 3}, 4: {"e": 5, "f": 6}}
| {1: [2, 3], 4: [5, 6]}
:param any obj: Iterable (Optionally inside another iterable) or a value for a single cell """
rows = []
if obj is None:
return rows
if is_iterable(obj):
if not len(obj):
return rows
if isinstance(obj, (list, tuple)):
if is_iterable(obj[0]):
for subObj in obj:
rows.append(_get_rows_helper(subObj))
else:
rows.append(_get_rows_helper(obj))
elif isinstance(obj, dict):
if is_iterable(iter_first_value(obj)):
for key, subObj in obj.items():
rows.append(_get_rows_helper(subObj, key))
else:
rows.append(_get_rows_helper(obj))
else:
rows.append([obj])
return rows
def exclusive(dictionary, *keys):
""" Returns a new dictionary without keys. """
return {key: value for key, value in dictionary.items() if key not in keys}
def inclusive(dictionary, *keys):
""" Returns a new dictionary with keys. """
return {key: value for key, value in dictionary.items() if key in keys}
def unique_obj_in_list(list_, obj, active):
""" Controls whether a unique object should be present in a list.
Adds obj to list if active and obj isn't in list.
Removes obj from list if not active and obj in list. """
if active:
if obj not in list_:
list_.append(obj)
else:
if obj in list_:
list_.remove(obj, )
def remove_duplicates(list_, func=None):
""" Remove all duplicates in a list.
Set func to use another value than a self hash for determining if duplicate.
Values must be hashable (If func is None) as they are passed through as dict keys. (Lists work but not Dicts) """
if func is None:
return list(dict.fromkeys(list_))
else:
return list({func(obj): obj for obj in list_}.values())
def combine(**kwargs):
""" Create a list of dicts containing every unique combination from given kwargs.
Values can be iterable or not. """
execLines = []
for i, (key, value) in enumerate(kwargs.items()):
execLines.append(f"{' ' * i * 4}for {key} in (kwargs['{key}'] if is_iterable(kwargs['{key}']) else [kwargs['{key}']]):")
lines = [f"'{key}': {key}" for key in list(kwargs.keys())]
execLines.append(f"{' ' * len(kwargs) * 4}combinations.append({{{', '.join(lines)}}})")
combinations = []
exec("\n".join(execLines))
return [] if combinations == [{}] else combinations
def split_list(func, *args):
""" Split args into one list containing all args where func returned True, and rest in the second one. """
one, two = [], []
for arg in args:
if func(arg):
one.append(arg)
else:
two.append(arg)
return one, two
def pivot_list(list_, index):
""" Return a new altered list where it's first value is the given index for the original list. """
index %= len(list_)
return list_[index:] + list_[:index]
def flatten(list_, gen=False):
""" Flatten a list. """
if gen:
return itertools.chain(*list_)
else:
return list(flatten(list_=list_, gen=True))