-
-
Notifications
You must be signed in to change notification settings - Fork 206
/
util.py
275 lines (220 loc) · 7.45 KB
/
util.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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import re
import sys
from itertools import chain
FORMAT_RE = re.compile(r"\`(\d*)\`")
import time
# A small, simple timing tool
MIN_ELAPSE_REPORT = int(os.environ.get("MIN_ELAPSE_REPORT", "0"))
def timeit(method):
"""Add this as a decorator to time parts of the code.
For example:
@timit
def long_running_function():
...
"""
def timed(*args, **kw):
method_name = method.__name__
# print(f"{date.today()} {method_name} starts")
ts = time.time()
result = method(*args, **kw)
te = time.time()
elapsed = int((te - ts) * 1000)
if elapsed > MIN_ELAPSE_REPORT:
if "log_time" in kw:
name = kw.get("log_name", method.__name__.upper())
kw["log_time"][name] = elapsed
else:
print("%r %2.2f ms" % (method_name, elapsed))
# print(f"{date.today()} {method_name} ends")
return result
return timed
def interpolate_string(text, get_param) -> str:
index = [1]
def get_item(index):
if 1 <= index <= len(args):
return args[index - 1]
else:
return ""
if isinstance(get_param, list):
args = get_param
get_param = get_item
def repl(match):
arg = match.group(1)
if arg == "" or arg == "0":
arg = index[0]
else:
arg = int(arg)
index[0] += 1
param = get_param(arg)
return param
return FORMAT_RE.sub(repl, text)
"""
NOTE: Maybe see
http://www.cosc.canterbury.ac.nz/tad.takaoka/isaac.pdf
resp.
http://www.cosc.canterbury.ac.nz/tad.takaoka/perm.p
for a permutation generating algorithm for multisets.
"""
def permutations(items, without_duplicates=True):
if not items:
yield []
# already_taken = set()
# first yield identical permutation without recursion
yield items
for index in range(len(items)):
item = items[index]
# if item not in already_taken:
for sub in permutations(items[:index] + items[index + 1 :]):
yield [item] + sub
# already_taken.add(item)
def subsets(items, min, max, included=None, less_first=False):
if max is None:
max = len(items)
lengths = list(range(min, max + 1))
if not less_first:
lengths = reversed(lengths)
lengths = list(lengths)
if lengths and lengths[0] == 0:
lengths = lengths[1:] + [0]
def decide(chosen, not_chosen, rest, count):
if count < 0 or len(rest) < count:
return
if count == 0:
yield chosen, list(chain(not_chosen, rest))
elif len(rest) == count:
if included is None or all(item in included for item in rest):
yield list(chain(chosen, rest)), not_chosen
elif rest:
item = rest[0]
if included is None or item in included:
for set in decide(chosen + [item], not_chosen, rest[1:], count - 1):
yield set
for set in decide(chosen, not_chosen + [item], rest[1:], count):
yield set
for length in lengths:
for chosen, not_chosen in decide([], [], items, length):
yield chosen, ([], not_chosen)
def subsets_2(items, min, max, without_duplicates=True):
"""max may only be 1 or None (= infinity).
Respects include property of items
"""
if min <= max == 1:
for index in range(len(items)):
if items[index].include:
yield [items[index]], ([], items[:index] + items[index + 1 :])
if min == 0:
yield [], ([], items)
else:
counts = {}
for item in items:
if item.include:
if item in counts:
counts[item] += 1
else:
counts[item] = 1
already = set()
def decide(chosen, not_chosen, rest):
if not rest:
if len(chosen) >= min:
"""if False and len(chosen) > 1 and (
permutate_until is None or
len(chosen) <= permutate_until):
for perm in permutations(chosen):
yield perm, ([], not_chosen)
else:"""
yield chosen, ([], not_chosen)
else:
if rest[0].include:
for set in decide(chosen + [rest[0]], not_chosen, rest[1:]):
yield set
for set in decide(chosen, not_chosen + [rest[0]], rest[1:]):
yield set
for subset in decide([], [], list(counts.keys())):
t = tuple(subset[0])
if t not in already:
yield subset
already.add(t)
else:
print("already taken")
def subranges(
items, min_count, max, flexible_start=False, included=None, less_first=False
):
# TODO: take into account included
if max is None:
max = len(items)
max = min(max, len(items))
if flexible_start:
starts = list(range(len(items) - max + 1))
else:
starts = (0,)
for start in starts:
lengths = list(range(min_count, max + 1))
if not less_first:
lengths = reversed(lengths)
lengths = list(lengths)
if lengths == [0, 1]:
lengths = [1, 0]
for length in lengths:
yield (
items[start : start + length],
(items[:start], items[start + length :]),
)
def unicode_superscript(value) -> str:
def repl_char(c):
if c == "1":
value = 185
elif c == "2":
value = 178
elif c == "3":
value = 179
elif "0" <= c <= "9":
value = 8304 + (ord(c) - ord("0"))
elif c == "-":
value = 8315
elif c == "(":
value = 8317
elif c == ")":
value = 8318
else:
value = ord(c)
return chr(value)
return "".join(repl_char(c) for c in value)
try:
from inspect import signature
def _python_function_arguments(f):
return signature(f).parameters.keys()
except ImportError: # py2, pypy
from inspect import getargspec
def _python_function_arguments(f):
return getargspec(f).args
if sys.version_info >= (3, 4, 0):
_cython_function_arguments = _python_function_arguments
elif sys.version_info[0] >= 3: # py3.3
def _cython_function_arguments(f):
return f.__code__.co_varnames
else: # py2
def _cython_function_arguments(f):
return f.func_code.co_varnames
def function_arguments(f):
try:
return _python_function_arguments(f)
except (TypeError, ValueError):
return _cython_function_arguments(f)
def robust_min(iterable):
minimum = None
for i in iterable:
if minimum is None or i < minimum:
minimum = i
return minimum
def re_from_keys(d: dict) -> "re":
"""Returns a regex that matches any of the keys of the dictionary"""
# The keys are sorted to prevent shorter keys from obscuring longer keys
# when pattern matching
return re.compile("|".join(sorted(d.keys(), key=lambda k: (-len(k), k))))
def dict_with_escaped_keys(d: dict) -> dict:
"""Takes a dictionary and returns a copy of it where the keys are escaped
with re.escape"""
return {re.escape(k): v for k, v in d.items()}