-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbaselib.py
1566 lines (1203 loc) · 39.7 KB
/
baselib.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
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# pylint: disable=C0103, too-few-public-methods, locally-disabled, consider-using-enumerate,
# stop-iteration-return, simplifiable-if-statement, stop-iteration-return,
# too-many-return-statements, consider-using-f-string
# unused-variable
"""Decorators, base classes and misc functions
for manipulatin other base classes.
Stick list/tuple/dic functions in here.
"""
import traceback as _traceback
from datetime import timedelta as _timedelta
from datetime import datetime as _datetime
import sys as _sys
import inspect as _inspect
import itertools as _itertools
import pickle as _pickle
import collections as _collections
import dateutil as _dateutil
import random as _random
import operator as _operator
from copy import deepcopy as _deepcopy
from enum import Enum as _Enum
import ast as _ast
import numpy as _np
# region enums
class eDictMatch(_Enum):
"""do dictionaries match
"""
Exact = 0 # every element matches in both
Subset = 1 # one dic is a subset of the other
Superset = 2 # dic is a superset of the other
Intersects = 3 # some elements match
Disjoint = 4 # No match
# endregion
# region Decorators
class classproperty(property):
"""class prop decorator, used
to enable class level properties"""
def __get__(self, obj, objtype=None):
return super(classproperty, self).__get__(objtype)
def __set__(self, obj, value):
super(classproperty, self).__set__(type(obj), value)
def __delete__(self, obj):
super(classproperty, self).__delete__(type(obj))
# endregion
# region classes misc
class Switch:
""" Replicates the C switch statement
if case(): # default, could also just omit condition or 'if True'
print "something else!"
Credit:
http://code.activestate.com/recipes/410692/.
Examples:
>>> v = 'ten'
>>> for case in Switch(v):
>>> if case('one'):
>>> print 1
>>> if case('two'):
>>> print 2
>>> if case('ten'):
>>> print 10
>>> if case('eleven'):
>>> print 11
10
"""
def __init__(self, value):
self.value = value
self.fall = False
def __iter__(self):
"""Return the match method once, then stop"""
yield self.match
raise StopIteration
def match(self, *args):
"""Indicate whether or not to enter a case suite"""
if self.fall or not args:
return True
if self.value in args: # changed for v1.5, see below
self.fall = True
return True
return False
def dt_week_start_end(dt: (str, _datetime)) -> tuple[_datetime, _datetime]:
"""
Get start and end date of the week in which dt falls
Args:
dt (str, datetime): A datetime, date or string representation of a date in iso formay (yyyy-mm-dd)
Returns:
tuple[_datetime, _datetime]: A tuple of week start, week end datetimes
Notes:
Monday based start week
Examples:
ISO date as string
>>> dt_week_start_end('2022-12-25')
datetime.datetime(2022, 12, 23, 0, 0), datetime.datetime(2022, 12, 30, 0, 0) # noqa
passing a datetime instance
>>> dt_week_start_end(datetime.strptime('2022-12-23', '%Y-%m-%d')) # noqa
datetime.datetime(2022, 12, 23, 0, 0), datetime.datetime(2022, 12, 30, 0, 0) # noqa
"""
if isinstance(dt, str):
dt = _datetime.strptime(dt, '%Y-%m-%d')
return dt - _timedelta(days=dt.weekday()), dt - _timedelta(days=dt.weekday()) + _timedelta(days=6)
def date_to_datetime(dt):
"""
Converts a date instance to a datetime
Credited to https://stackoverflow.com/a/1937636/5585800
Args:
dt (date instance): An instance of datetime.date
Returns:
datetime instance
"""
return _datetime.combine(dt.today(), _datetime.min.time())
class TimeDelta(_timedelta):
"""
Subclasses datetime.timedelta, adding several methods
to get total time diff in mins, hours or seconds.
Also supports friendly print.
Methods:
__str__: Overridden __str__ class, with friendly time print
as_mins: Time diff in mins
as_hours: Time diff in hours
as_seconds: Time diff in seconds
TimeDelta: Create an instance from a timedelta instance
Notes:
If timedelta is provided, all other arguments are ignored at initialisation.
Examples:
Use as basic timedelta
>>> datetime.now() - TimeDelta(days=2) # noqa
1.1234
Time conversion to different units
>>> TimeDelta(days=8).as_hours
192.0
Credit:
Partial credit to https://stackoverflow.com/a/61883517/5585800
"""
def __str__(self):
_times = super(TimeDelta, self).__str__().split(':')
if "," in _times[0]:
_hour = int(_times[0].split(',')[-1].strip())
if _hour:
_times[0] += " hours" if _hour > 1 else " hour"
else:
_times[0] = _times[0].split(',')[0]
else:
_hour = int(_times[0].strip())
if _hour:
_times[0] += " hours" if _hour > 1 else " hour"
else:
_times[0] = ""
_min = int(_times[1])
if _min:
_times[1] += " minutes" if _min > 1 else " minute"
else:
_times[1] = ""
_sec = int(_times[2])
if _sec:
_times[2] += " seconds" if _sec > 1 else " second"
else:
_times[2] = ""
return ", ".join([i for i in _times if i]).strip(" ,").title()
@property
def as_mins(self) -> float:
"""Difference in minutes
Returns:
float: Diff in minutes
"""
return self.as_seconds/60
@property
def as_hours(self) -> float:
"""
Diff in hours
Returns:
float: diff in hours
"""
return self.as_mins/60
@property
def as_days(self) -> float:
""" Diff in days
Returns:
float: diff in days
"""
return self.as_hours/24
@property
def as_seconds(self) -> float:
"""Diff in seconds
Returns:
float: Diff in seconds
"""
return self.seconds + (self.microseconds/1e6) + self.days * 86400
# region statics
@staticmethod
def now():
return _datetime.now()
@staticmethod
def TimeDelta(timedelta: _timedelta):
"""
Args:
timedelta (datetime.timedelta): A timedelta class used to create a TimeDelta subclass
Returns:
TimeDelta: A TimeDelta instance
Notes:
This method created as issue with overriding and super(ing) to timedelta.__init__
Could probably resolve at some point, but this will do for now.
"""
return TimeDelta(timedelta.days, timedelta.seconds, timedelta.microseconds)
# endregion
# endregion
# region dict classes
class odict(_collections.OrderedDict):
"""subclass OrderedDict to support item retreival by index
d = _baselib.odict()
d[1] = '12'
"""
def getbyindex(self, ind):
"""(int)->tuple
Retrieve dictionary key-value pair as a tuple using
the integer index
"""
items = list(self.items())
return items[ind]
# For convieniance, and dont want to break compatibility by renaming odict
DictOrdered = odict
class DictAttributed(dict):
"""
Small wrapper around dict, which exposes the dict keys as instance fields
Credit: https://stackoverflow.com/a/1639632/6494418
Examples:
>>> D = DictAttributed({"hello": 1, "world": 2, "cat": {"dog": 5}})
>>> print(D.cat, D.cat.dog, D.cat.items())
{'dog': 5}, 5, dict_items([('dog', 5)])
"""
def __getattr__(self, name):
return self[name] if not isinstance(self[name], dict) else DictAttributed(self[name])
class dictp(dict):
"""allow values to be accessed with partial key match
dic = {'abc':1}
d = dictp(dic)
print(d['a']) # returns 1
"""
def __getitem__(self, partial_key):
key = ''
keys = [k for k in self.keys() if partial_key in k and k.startswith(partial_key)]
if keys:
if len(keys) > 1:
raise KeyError('Partial key matched more than 1 element')
key = keys[0] if keys else None
return self.get(key)
def getp(self, partial_key, d=None):
"""(str, any)->dict item
Support partial key matches,
return d if key not found
"""
keys = [k for k in self.keys() if partial_key in k and k.startswith(partial_key)]
if keys:
if len(keys) > 1:
raise KeyError('Partial key matched more than 1 element')
key = keys[0] if keys else None
if key is None:
return d
return self.get(key)
return d
# For convieniance, and dont want to break compatibility by renaming dictp
DictPartialKeyMatches = dictp
class DictKwarg(dict):
"""Dictionary wrapper adding the method kwargs.
Methods:
kwargs (dict): Yields keword arguments constructed from the key values (lists) on a 'row like' basis
Raises:
ValueError: If the len of the lists do not all match or if the values were not of type list or tuple
Examples:
Yield args as kword like dict
>>> dK = DictKwarg({'age': [75, 20], 'hair': ['bald', 'blonde']})
>>> for d in dK.kwargs():
>>> print(d)
{'age':12, 'hair':'bald'}
{'age':20, 'hair':'blonde'}
"""
def kwargs(self) -> dict:
"""
Yield the kword arguments as a dict
Returns:
dict: A single row from the dict used to instantiate the class instance
Examples:
See the class documentation
"""
for v in self.values():
if not isinstance(v, (list, tuple)):
raise ValueError('DictKwarg expects all values to be lists or tuples. Got type %s' % type(v))
lens = [len(v) for v in self.values()]
out = dict()
if len(set(lens)) != 1:
raise ValueError('All dictionary lists should be the same length.')
for i in range(lens[0]):
for k in self.keys():
out[k] = self[k][i]
yield out
class DictList(DictKwarg):
"""Easy support for dictionary values as lists.
Methods:
keys_from_item: Get a list of all keys where the dictionary values (which are lists) contain a specified value
kwargs (dict): Yields keword arguments constructed from the key values (lists) on a 'row like' basis
Notes:
kwargs method is supported by inheriting from DictKwarg.
Examples:
>>> d = DictList()
>>> d['test'] = 1
>>> d['test'] = 2
>>> d['test'] = 3
>>> d
{'test': [1, 2, 3]}
>>> d['other'] = 100
>>> d
{'test': [1, 2, 3], 'other': [100]}
Yield args as kword like dict
>>> dK = DictList({'age': [75, 20], 'hair': ['bald', 'blonde']})
>>> for d in dK.kwargs():
>>> print(d)
{'age':12, 'hair':'bald'}
{'age':20, 'hair':'blonde'}
"""
def __setitem__(self, key, value):
try:
self[key]
except KeyError:
super(DictList, self).__setitem__(key, [])
self[key].append(value)
def keys_from_item(self, v) -> (list, None):
"""
Get the key(s) that contains the value v
Args:
v : A value, expected to be in some subset of dictionary values
Returns:
None: v not in any of the dict values
list: List of keys
Examples:
>>> DL = DictList({'a':[1,2,3], 'b':[1,10], 'c':[100,120]})
>>> DL.keys_from_item(1)
['a', 'b']
>>> DL.keys_from_item(100)
['c']
>>> DL.keys_from_item(-10)
None
"""
out = list()
for k, vv in self.items():
if v in list_flatten(vv):
out += [k]
if out:
return out # noqa
return None
def as_dict(self):
"""
Get as inbuilt dict. Just returns dict(self)
Returns: dict
"""
return dict(self)
# endregion
# region dictionaries
def dic_filter_by_keys(d: dict, keys: (list, tuple)) -> dict:
"""
Filter a dictionary by a list of keys.
No filter is applied if "not keys" evaliates to true.
Args:
d (dict): Target dict to apply filter
keys (list, tuple): iterable of keys to remove from dict
Returns:
dict: the dictionary sans all keys in keys
Notes:
Returns d if "not keys" evaluates to True
Examples:
>>> dic_filter_by_keys({'a':1, 'b':2}, ['a'])
{'b':2}
"""
if not keys:
return d
return {key: d[key] for key in keys}
def dic_filter_by_values(d: dict, v: (list, tuple)) -> dict:
"""
Filter a dictionary by a list of its values.
Args:
d (dict): Target dict to apply filter
v (list, tuple): iterable of values to remove from dict
Returns:
dict: the dictionary sans all members whose value is in v
Notes:
Returns d if "not v" evaluates to True
Examples:
>>> dic_filter_by_values({'a':1, 'b':2}, [1])
{2:2}
"""
if not v:
return d
return {itm[0]: itm[1] for itm in d if itm[1] in v}
def dic_merge_two(x_, y):
"""Given two dicts, merge them into a new dict as a shallow copy."""
z = x_.copy()
z.update(y)
return z
def dic_key_with_max_val(d):
"""(dict)->value
Get key with largest value
Examples:
>>> dic_key_with_max_val({'a':12, 'b':100, 'x':-1})
'b'
"""
return max(d, key=lambda key: d[key])
def dic_expand_keys_to_list(d: dict[str:list]) -> list[list]:
"""
Args:
d (dict): A dictionary where keys are non-iterables and value are iterables (list)
Returns:
list: A nested list. See examples
Examples:
>>> dic_expand_to_list({'a':[1, 3]. 'b':['x', 'y']})
[['a', 1], ['a', 3], ['b', 'x'], ['b', 'y']]
"""
lst = []
for k, v in d.items():
for s in v:
lst += [[k, s]]
return lst
def dic_value_counts(d: dict) -> dict:
"""
Args:
d (dict): Get count of values in dict d.
Returns:
dict: {'value1': n1, 'value2': n2, 'value3': n3}
Examples:
>>> dic_value_counts(({'a':'given', 'b':'given', 'c':'bad','d':'good' }))
{'given': 2, 'bad': 1, 'good': 1}
"""
return dict(_collections.Counter(d.values()))
def dic_vals_same_len(d) -> bool:
"""
Check if the values in dictionary d have the same length.
Args:
d: dictionary to check
Returns:
bool: True of all same length, else false.
Examples:
>>> dic_vals_same_len({'a':[1,2,3,4,5], 'b':[1,2,3,4,5]})
True
>>> dic_vals_same_len({'a':[1,2,3,4,5], 'b':[2,3,4,5]})
False
"""
b = len(set([len(itm[1]) for itm in d.items()])) == 1
return b
def dic_sort_by_val(d: dict, as_dict: bool = False) -> (list, dict):
"""
Sort a dictionary by the values, returning as a list or dict
Args:
d (dict): dictionary
as_dict (bool): return as dictionary rather than list
Returns:
list: list of tuples, [(k1, v1), (k2, v2), ...]
dict: Sorted dictionary
Examples:
>>> dic_sort_by_val({1:1, 2:10, 3:22, 4:1.03})
[(1, 1), (4, 1.03), (2, 10), (3, 22)]
"""
if as_dict:
return dict(sorted(d.items(), key=lambda item: item[1])) # noqa
return sorted(d.items(), key=_operator.itemgetter(1))
def dic_sort_by_key(d: dict, as_dict: bool = False) -> (list, dict):
"""(dict) -> list
Sort a dictionary by its keys, returning as a list or dict
Args:
d (dict): dictionary
as_dict (bool): return as dictionary rather than list
Returns:
list: list of tuples, [(k1, v1), (k2, v2), ...]
dict: Sorted dictionary
Examples:
>>> dic_sort_by_key({1:1, 4:10, 3:22, 2:1.03})
[(1,1), (2,1.03), (3,22), (4,10)]
"""
if as_dict:
return dict(sorted(d.items())) # noqa
return sorted(d.items(), key=_operator.itemgetter(0))
def dic_match(a: dict, b: dict) -> ("eDictMatch", None):
"""(dict, dict) -> Enum:eDictMatch
Compares dictionary a to dictionary b.
Args:
a (dict): Dictionary, compared to b
b (dict): Dictionary, which a is compared to
Returns:
eDictMatch: A member of eDictMatch.
None: If a or b were not dicts
Notes:
eDictMatch is defined as follows,
Exact = 0 # every element matches in both
Subset = 1 # a is a subset of b
Superset = 2 # a is a superset b
Intersects = 3 # some elements match
Disjoint = 4 # No match
Examples:
>>> dic_match({'a':1}, {'a':1, 'b':2})
eDictMatch.Subset
>>> dic_match({'a':1, 'b':2}}, {'a':1, 'b':2})
eDictMatch.Exact
"""
if not isinstance(a, dict) or not isinstance(a, dict):
return None # noqa
unmatched = False
matched = False
b_lst = list(b.items())
a_lst = list(a.items())
if len(a_lst) < len(b_lst):
for i in a_lst:
if i in b_lst:
matched = True
else:
unmatched = True
if matched and unmatched:
return eDictMatch.Intersects
if not matched:
return eDictMatch.Disjoint
if not unmatched:
return eDictMatch.Subset
return None # noqa
if len(a_lst) > len(b_lst):
for i in b_lst:
if i in a_lst:
matched = True
else:
unmatched = True
if matched and unmatched:
return eDictMatch.Intersects
if not matched:
return eDictMatch.Disjoint
if not unmatched:
return eDictMatch.Superset
return None # noqa
# same length
for i in b_lst:
if i in a_lst:
matched = True
else:
unmatched = True
if matched and unmatched:
return eDictMatch.Intersects
if not matched:
return eDictMatch.Disjoint
if not unmatched:
return eDictMatch.Exact
return None # noqa
# endregion
# region lists
def list_filter_by_list(a: list, filt: list) -> list:
"""
Filter the values in a list which contain substrings in another list.
Args:
a (list): The list to filter
filt (list): The list to use to filter values in a
Returns:
list: List a, filtered by wildcard matches of filt[n] in a
Notes:
Is case sensitive.
Useful for doing things like filtering file lists by a list of partial matches.
Returns a if "not a" evaluates to True
Examples:
>>> list_filter_by_list(['aaa','bbb', 'ccc'], ['a', 'bb'])
['aaa', 'bbb']
"""
if not filt or not a:
return a
filt = lambda fcs, match: [s for s in fcs if [z for z in match if z in s]]
return filt(a, filt)
def list_delete_value_pairs(list_a: list, list_b: list, match_value=0) -> None:
"""(list,list,str|number) -> void
Given two lists, removes matching values pairs occuring
at same index location. By Ref
Args:
list_a (list): first list
list_b (list): second list
match_value (any): value to match (and delete if matched)
Returns:
None: This method is BY REF
Notes:
List arguments are **By Ref**
Examples:
>>> a = [1, 0, 2, 0]; b = [2, 0, 2, 1]
>>> list_delete_value_pairs(a, b)
>>> a, b
([1, 2, 0], [2, 2, 1])
"""
for ind, value in reversed(list(enumerate(list_a))):
if value == match_value and list_b[ind] == match_value:
del list_a[ind]
del list_b[ind]
def list_index(list_, val):
"""(list, <anything>) -> int|None
Safely returns the list index which
matches val, else None
Parameters:
list_: a list
val: the value to find in list
Returns:
None if the item not found, else the index of the item in list
Examples:
>>> list_index([1,2,3], 2)
1
>>> list_index([1,2,3], 5)
None
"""
return list_.index(val) if val in list_ else None
def list_get_dups(lst: list, thresh: int = 2, value_list_only: bool = False) -> (list, dict):
"""
Get a dictionary containing dups in list where
the key is the duplicate value, and the value is the
duplicate nr.
Args:
lst (list): The list to check
thresh (int): Duplicate threshhold count, i.e. only consider an value a duplicate if it has >= thresh occurences
value_list_only (bool): Just return values that are duplicated in a list
Returns:
list: If value_list_only is True. An empty list is returned if there on no duplicates
dict: If value_list_only is False. Keys are the duplicate values, values are the count of duplicates. Returns empty dict of no duplicates.
Examples:
Default behaviour, all duplicates\n
>>> list_get_dups([1,1,2,3,4,4,4])
{1:2, 4:3}
Only count duplicates with 3 or more occurences
>>> list_get_dups([1,2,3,4,4], thresh=3)
{}
Unique list of the values with duplicates
>>> list_get_dups([1, 1, 2, 3, 4, 4, 4, 5, 5, 5], thresh=3, value_list_only=True)
[4, 5]
"""
my_dict = {i: lst.count(i) for i in lst}
out_ = dict(my_dict) # create a copy
for k, v in my_dict.items():
if v < thresh:
del (out_[k])
if value_list_only:
return list(out_.keys())
return out_ # noqa
def list_add_elementwise(lsts):
"""lists->list
Add lists elementwise.
lsts:
a list of lists with the same nr of elements
Returns:
list with summed elements
Examples:
>>> list_add_elementwise([[1, 2], [1, 2]])
[2, 4]
"""
return list(map(sum, zip(*lsts)))
def list_most_common(L, force_to_string=False):
"""(list, bool)->str|int|float
Find most _common value in list
force_to_string:
make everything a string, use if list
has mixed types
"""
if force_to_string:
Ll = [str(s) for s in L]
else:
Ll = L.copy()
SL = sorted((x, i) for i, x in enumerate(Ll)) # noqa
groups = _itertools.groupby(SL, key=_operator.itemgetter(0))
def _auxfun(g):
_, iterable = g
count = 0
min_index = len(Ll)
for _, where in iterable:
count += 1
min_index = min(min_index, where)
return count, -min_index
return max(groups, key=_auxfun)[0]
def list_batch(lst: list, batch_sz: int) -> list:
"""
Yields sublists from list of length "batch_sz"
Args:
lst: list to batch
batch_sz: sublist length
Yields:
lists of size "batch_sz", except ofcourse for (potentially) the first or last
"""
for i in range(0, len(lst), batch_sz):
yield lst[i:i + batch_sz]
def lists_match(a: list, b: list, ignore_case: bool = False) -> bool:
"""
Check if the contents of two list match.
Args:
a (list): First list
b (list): Second list
ignore_case (bool): Ignore case
Returns:
bool: If all elements of two lists match, ignoring order
Notes:
Considers unique values only, i.e. calls list(set()) on each list
prior to comparison.
Examples:
>>> lists_match(['a', 'c', 'D'], ['c','d','d','a'], ignore_case=True)
True
>>> lists_match(['a', 'c', 'D'], ['c','d','d'], ignore_case=True)
False
"""
c = list(set(a))
d = list(set(b))
if ignore_case:
return list_sym_diff(list(map(str.lower, c)), list(map(str.lower, d)))['a_and_b'] == len(c) == len(d)
return list_sym_diff(c, d)['a_and_b'] == len(c) == len(d)
def lists_merge(first_has_priority=True, *args):
"""merge lists, filling in blanks according to
first_has_priority
Args:
first_has_priority (bool): prioritise first to last, otherwise other way
*args (any): lists as args
Raises:
ValueError: If lists are of different lengths
Returns:
list: single list
Examples:
>>> lists_merge(True, [1,'' ,None,4], ['a',(),3,''], [1,'AZ','','']) # noqa
[1, 'AZ', 3, 4]
"""
if len(set([len(itm) for itm in args])) != 1:
raise ValueError('All lists must be the same length')
lsts = _deepcopy(args)
if not first_has_priority:
lsts.reverse() # noqa
d = {k: None for k in range(len(args[0]))}
for lst in lsts:
for i, v in enumerate(lst):
if not d[i] and v not in (None, '', (), [], {}): # don't ignore 0's
d[i] = v
return d.values()
def lists_remove_empty_pairs(list1, list2):
"""(list|tuple, list|tuple) -> list, list, list
Zip through datasets (pairwise),
make sure both are non-empty; erase if empty.
Returns:
list1: non-empty corresponding pairs in list1
list2: non-empty corresponding pairs in list2
list3: list of original indices prior to erasing of empty pairs
"""
xs, ys, posns = [], [], []
for i in range(len(list1)):
if list1[i] and list2[i]:
xs.append(list1[i])
ys.append(list2[i])
posns.append(i)
return xs, ys, posns
def depth(iter_):
"""(List|Tuple) -> int
Depth of a list or tuple.
Returns 0 of l is and empty list or
tuple.
"""
if isinstance(iter_, (list, tuple)):
if iter_:
d = lambda L: isinstance(L, (list, tuple)) and max(map(d, L)) + 1
else:
return 0
else:
s = 'Depth takes a list or a tuple but got a %s' % (type(iter_))
raise ValueError(s)
return d(iter_)
list_depth = depth # convieniance
# also in stringslib
def list_member_in_str(s: str, match: (str, tuple, list), ignore_case: bool = False) -> bool:
"""
Check if any member of an iterable is IN s
Args:
s (str): string to check list items against
match (list, str, tuple): items to check for being IN s
ignore_case (bool): make check case insensitive
Returns:
bool: True if match in [ [],(),None,'',0 ] or if any item in member is IN s else False
Notes:
Also see list_member_in_str2, which returns what the match was made on
"""
s = str(s) # let it work with floats & ints
if not match: return True # everything is a match if we have nothing to match to
if not isinstance(match, (list, tuple, set, map)):
return str(match) in s
if ignore_case: s = s.lower()
for m in match: