boris-behav-obs 9.7.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of boris-behav-obs might be problematic. Click here for more details.
- boris/__init__.py +26 -0
- boris/__main__.py +25 -0
- boris/about.py +143 -0
- boris/add_modifier.py +635 -0
- boris/add_modifier_ui.py +303 -0
- boris/advanced_event_filtering.py +455 -0
- boris/analysis_plugins/__init__.py +0 -0
- boris/analysis_plugins/_latency.py +59 -0
- boris/analysis_plugins/irr_cohen_kappa.py +109 -0
- boris/analysis_plugins/irr_cohen_kappa_with_modifiers.py +112 -0
- boris/analysis_plugins/irr_weighted_cohen_kappa.py +157 -0
- boris/analysis_plugins/irr_weighted_cohen_kappa_with_modifiers.py +162 -0
- boris/analysis_plugins/list_of_dataframe_columns.py +22 -0
- boris/analysis_plugins/number_of_occurences.py +22 -0
- boris/analysis_plugins/number_of_occurences_by_independent_variable.py +54 -0
- boris/analysis_plugins/time_budget.py +61 -0
- boris/behav_coding_map_creator.py +1110 -0
- boris/behavior_binary_table.py +305 -0
- boris/behaviors_coding_map.py +239 -0
- boris/boris_cli.py +340 -0
- boris/cmd_arguments.py +49 -0
- boris/coding_pad.py +280 -0
- boris/config.py +785 -0
- boris/config_file.py +356 -0
- boris/connections.py +409 -0
- boris/converters.py +333 -0
- boris/converters_ui.py +225 -0
- boris/cooccurence.py +250 -0
- boris/core.py +5901 -0
- boris/core_qrc.py +15958 -0
- boris/core_ui.py +1107 -0
- boris/db_functions.py +324 -0
- boris/dev.py +134 -0
- boris/dialog.py +1108 -0
- boris/duration_widget.py +238 -0
- boris/edit_event.py +245 -0
- boris/edit_event_ui.py +233 -0
- boris/event_operations.py +1040 -0
- boris/events_cursor.py +61 -0
- boris/events_snapshots.py +596 -0
- boris/exclusion_matrix.py +141 -0
- boris/export_events.py +1006 -0
- boris/export_observation.py +1203 -0
- boris/external_processes.py +332 -0
- boris/geometric_measurement.py +941 -0
- boris/gui_utilities.py +135 -0
- boris/image_overlay.py +72 -0
- boris/import_observations.py +242 -0
- boris/ipc_mpv.py +325 -0
- boris/irr.py +634 -0
- boris/latency.py +244 -0
- boris/measurement_widget.py +161 -0
- boris/media_file.py +115 -0
- boris/menu_options.py +213 -0
- boris/modifier_coding_map_creator.py +1013 -0
- boris/modifiers_coding_map.py +157 -0
- boris/mpv.py +2016 -0
- boris/mpv2.py +2193 -0
- boris/observation.py +1453 -0
- boris/observation_operations.py +2538 -0
- boris/observation_ui.py +679 -0
- boris/observations_list.py +337 -0
- boris/otx_parser.py +442 -0
- boris/param_panel.py +201 -0
- boris/param_panel_ui.py +305 -0
- boris/player_dock_widget.py +198 -0
- boris/plot_data_module.py +536 -0
- boris/plot_events.py +634 -0
- boris/plot_events_rt.py +237 -0
- boris/plot_spectrogram_rt.py +316 -0
- boris/plot_waveform_rt.py +230 -0
- boris/plugins.py +431 -0
- boris/portion/__init__.py +31 -0
- boris/portion/const.py +95 -0
- boris/portion/dict.py +365 -0
- boris/portion/func.py +52 -0
- boris/portion/interval.py +581 -0
- boris/portion/io.py +181 -0
- boris/preferences.py +510 -0
- boris/preferences_ui.py +770 -0
- boris/project.py +2007 -0
- boris/project_functions.py +2041 -0
- boris/project_import_export.py +1096 -0
- boris/project_ui.py +794 -0
- boris/qrc_boris.py +10389 -0
- boris/qrc_boris5.py +2579 -0
- boris/select_modifiers.py +312 -0
- boris/select_observations.py +210 -0
- boris/select_subj_behav.py +286 -0
- boris/state_events.py +197 -0
- boris/subjects_pad.py +106 -0
- boris/synthetic_time_budget.py +290 -0
- boris/time_budget_functions.py +1136 -0
- boris/time_budget_widget.py +1039 -0
- boris/transitions.py +365 -0
- boris/utilities.py +1810 -0
- boris/version.py +24 -0
- boris/video_equalizer.py +159 -0
- boris/video_equalizer_ui.py +248 -0
- boris/video_operations.py +310 -0
- boris/view_df.py +104 -0
- boris/view_df_ui.py +75 -0
- boris/write_event.py +538 -0
- boris_behav_obs-9.7.7.dist-info/METADATA +139 -0
- boris_behav_obs-9.7.7.dist-info/RECORD +109 -0
- boris_behav_obs-9.7.7.dist-info/WHEEL +5 -0
- boris_behav_obs-9.7.7.dist-info/entry_points.txt +2 -0
- boris_behav_obs-9.7.7.dist-info/licenses/LICENSE.TXT +674 -0
- boris_behav_obs-9.7.7.dist-info/top_level.txt +1 -0
boris/portion/const.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import enum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Bound(enum.Enum):
|
|
5
|
+
"""
|
|
6
|
+
Bound types, either CLOSED for inclusive, or OPEN for exclusive.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
CLOSED = True
|
|
10
|
+
OPEN = False
|
|
11
|
+
|
|
12
|
+
def __bool__(self):
|
|
13
|
+
raise ValueError("The truth value of a bound is ambiguous.")
|
|
14
|
+
|
|
15
|
+
def __invert__(self):
|
|
16
|
+
return Bound.CLOSED if self is Bound.OPEN else Bound.OPEN
|
|
17
|
+
|
|
18
|
+
def __str__(self):
|
|
19
|
+
return self.name
|
|
20
|
+
|
|
21
|
+
def __repr__(self):
|
|
22
|
+
return self.name
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class _Singleton:
|
|
26
|
+
__instance = None
|
|
27
|
+
|
|
28
|
+
def __new__(cls, *args, **kwargs):
|
|
29
|
+
if not cls.__instance:
|
|
30
|
+
cls.__instance = super(_Singleton, cls).__new__(cls)
|
|
31
|
+
return cls.__instance
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class _PInf(_Singleton):
|
|
35
|
+
"""
|
|
36
|
+
Represent positive infinity.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __neg__(self):
|
|
40
|
+
return _NInf()
|
|
41
|
+
|
|
42
|
+
def __lt__(self, o):
|
|
43
|
+
return False
|
|
44
|
+
|
|
45
|
+
def __le__(self, o):
|
|
46
|
+
return isinstance(o, _PInf)
|
|
47
|
+
|
|
48
|
+
def __gt__(self, o):
|
|
49
|
+
return not isinstance(o, _PInf)
|
|
50
|
+
|
|
51
|
+
def __ge__(self, o):
|
|
52
|
+
return True
|
|
53
|
+
|
|
54
|
+
def __eq__(self, o):
|
|
55
|
+
return isinstance(o, _PInf)
|
|
56
|
+
|
|
57
|
+
def __repr__(self):
|
|
58
|
+
return "+inf"
|
|
59
|
+
|
|
60
|
+
def __hash__(self):
|
|
61
|
+
return hash(float("+inf"))
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class _NInf(_Singleton):
|
|
65
|
+
"""
|
|
66
|
+
Represent negative infinity.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
def __neg__(self):
|
|
70
|
+
return _PInf()
|
|
71
|
+
|
|
72
|
+
def __lt__(self, o):
|
|
73
|
+
return not isinstance(o, _NInf)
|
|
74
|
+
|
|
75
|
+
def __le__(self, o):
|
|
76
|
+
return True
|
|
77
|
+
|
|
78
|
+
def __gt__(self, o):
|
|
79
|
+
return False
|
|
80
|
+
|
|
81
|
+
def __ge__(self, o):
|
|
82
|
+
return isinstance(o, _NInf)
|
|
83
|
+
|
|
84
|
+
def __eq__(self, o):
|
|
85
|
+
return isinstance(o, _NInf)
|
|
86
|
+
|
|
87
|
+
def __repr__(self):
|
|
88
|
+
return "-inf"
|
|
89
|
+
|
|
90
|
+
def __hash__(self):
|
|
91
|
+
return hash(float("-inf"))
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# Positive infinity
|
|
95
|
+
inf = _PInf()
|
boris/portion/dict.py
ADDED
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
from .const import Bound
|
|
2
|
+
from .interval import Interval, singleton
|
|
3
|
+
|
|
4
|
+
from collections.abc import MutableMapping, Mapping
|
|
5
|
+
|
|
6
|
+
from sortedcontainers import SortedDict
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _sort(i):
|
|
10
|
+
# Sort by lower bound, closed first
|
|
11
|
+
return (i[0].lower, i[0].left is Bound.OPEN)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class IntervalDict(MutableMapping):
|
|
15
|
+
"""
|
|
16
|
+
An IntervalDict is a dict-like data structure that maps from intervals to data,
|
|
17
|
+
where keys can be single values or Interval instances.
|
|
18
|
+
|
|
19
|
+
When keys are Interval instances, its behaviour merely corresponds to
|
|
20
|
+
range queries and it returns IntervalDict instances corresponding to the
|
|
21
|
+
subset of values covered by the given interval. If no matching value is
|
|
22
|
+
found, an empty IntervalDict is returned.
|
|
23
|
+
When keys are "single values", its behaviour corresponds to the one of Python
|
|
24
|
+
built-in dict. When no matchin value is found, a KeyError is raised.
|
|
25
|
+
|
|
26
|
+
Note that this class does not aim to have the best performance, but is
|
|
27
|
+
provided mainly for convenience. Its performance mainly depends on the
|
|
28
|
+
number of distinct values (not keys) that are stored.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
__slots__ = ("_storage",)
|
|
32
|
+
|
|
33
|
+
def __init__(self, mapping_or_iterable=None):
|
|
34
|
+
"""
|
|
35
|
+
Return a new IntervalDict.
|
|
36
|
+
|
|
37
|
+
If no argument is given, an empty IntervalDict is created. If an argument
|
|
38
|
+
is given, and is a mapping object (e.g., another IntervalDict), an
|
|
39
|
+
new IntervalDict with the same key-value pairs is created. If an
|
|
40
|
+
iterable is provided, it has to be a list of (key, value) pairs.
|
|
41
|
+
|
|
42
|
+
:param mapping_or_iterable: optional mapping or iterable.
|
|
43
|
+
"""
|
|
44
|
+
self._storage = SortedDict(_sort) # Mapping from intervals to values
|
|
45
|
+
|
|
46
|
+
if mapping_or_iterable is not None:
|
|
47
|
+
self.update(mapping_or_iterable)
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def _from_items(cls, items):
|
|
51
|
+
"""
|
|
52
|
+
Fast creation of an IntervalDict with the provided items.
|
|
53
|
+
|
|
54
|
+
The items have to satisfy the two following properties: (1) all keys
|
|
55
|
+
must be disjoint intervals and (2) all values must be distinct.
|
|
56
|
+
|
|
57
|
+
:param items: list of (key, value) pairs.
|
|
58
|
+
:return: an IntervalDict
|
|
59
|
+
"""
|
|
60
|
+
d = cls()
|
|
61
|
+
for key, value in items:
|
|
62
|
+
d._storage[key] = value
|
|
63
|
+
|
|
64
|
+
return d
|
|
65
|
+
|
|
66
|
+
def clear(self):
|
|
67
|
+
"""
|
|
68
|
+
Remove all items from the IntervalDict.
|
|
69
|
+
"""
|
|
70
|
+
self._storage.clear()
|
|
71
|
+
|
|
72
|
+
def copy(self):
|
|
73
|
+
"""
|
|
74
|
+
Return a shallow copy.
|
|
75
|
+
|
|
76
|
+
:return: a shallow copy.
|
|
77
|
+
"""
|
|
78
|
+
return IntervalDict._from_items(self.items())
|
|
79
|
+
|
|
80
|
+
def get(self, key, default=None):
|
|
81
|
+
"""
|
|
82
|
+
Return the values associated to given key.
|
|
83
|
+
|
|
84
|
+
If the key is a single value, it returns a single value (if it exists) or
|
|
85
|
+
the default value. If the key is an Interval, it returns a new IntervalDict
|
|
86
|
+
restricted to given interval. In that case, the default value is used to
|
|
87
|
+
"fill the gaps" (if any) w.r.t. given key.
|
|
88
|
+
|
|
89
|
+
:param key: a single value or an Interval instance.
|
|
90
|
+
:param default: default value (default to None).
|
|
91
|
+
:return: an IntervalDict, or a single value if key is not an Interval.
|
|
92
|
+
"""
|
|
93
|
+
if isinstance(key, Interval):
|
|
94
|
+
d = self[key]
|
|
95
|
+
d[key - d.domain()] = default
|
|
96
|
+
return d
|
|
97
|
+
else:
|
|
98
|
+
try:
|
|
99
|
+
return self[key]
|
|
100
|
+
except KeyError:
|
|
101
|
+
return default
|
|
102
|
+
|
|
103
|
+
def find(self, value):
|
|
104
|
+
"""
|
|
105
|
+
Return a (possibly empty) Interval i such that self[i] = value, and
|
|
106
|
+
self[~i] != value.
|
|
107
|
+
|
|
108
|
+
:param value: value to look for.
|
|
109
|
+
:return: an Interval instance.
|
|
110
|
+
"""
|
|
111
|
+
return Interval(*(i for i, v in self._storage.items() if v == value))
|
|
112
|
+
|
|
113
|
+
def items(self):
|
|
114
|
+
"""
|
|
115
|
+
Return a view object on the contained items sorted by key
|
|
116
|
+
(see https://docs.python.org/3/library/stdtypes.html#dict-views).
|
|
117
|
+
|
|
118
|
+
:return: a view object.
|
|
119
|
+
"""
|
|
120
|
+
return self._storage.items()
|
|
121
|
+
|
|
122
|
+
def keys(self):
|
|
123
|
+
"""
|
|
124
|
+
Return a view object on the contained keys (sorted)
|
|
125
|
+
(see https://docs.python.org/3/library/stdtypes.html#dict-views).
|
|
126
|
+
|
|
127
|
+
:return: a view object.
|
|
128
|
+
"""
|
|
129
|
+
return self._storage.keys()
|
|
130
|
+
|
|
131
|
+
def values(self):
|
|
132
|
+
"""
|
|
133
|
+
Return a view object on the contained values sorted by key
|
|
134
|
+
(see https://docs.python.org/3/library/stdtypes.html#dict-views).
|
|
135
|
+
|
|
136
|
+
:return: a view object.
|
|
137
|
+
"""
|
|
138
|
+
return self._storage.values()
|
|
139
|
+
|
|
140
|
+
def domain(self):
|
|
141
|
+
"""
|
|
142
|
+
Return an Interval corresponding to the domain of this IntervalDict.
|
|
143
|
+
|
|
144
|
+
:return: an Interval.
|
|
145
|
+
"""
|
|
146
|
+
return Interval(*self._storage.keys())
|
|
147
|
+
|
|
148
|
+
def pop(self, key, default=None):
|
|
149
|
+
"""
|
|
150
|
+
Remove key and return the corresponding value if key is not an Interval.
|
|
151
|
+
If key is an interval, it returns an IntervalDict instance.
|
|
152
|
+
|
|
153
|
+
This method combines self[key] and del self[key]. If a default value
|
|
154
|
+
is provided and is not None, it uses self.get(key, default) instead of
|
|
155
|
+
self[key].
|
|
156
|
+
|
|
157
|
+
:param key: a single value or an Interval instance.
|
|
158
|
+
:param default: optional default value.
|
|
159
|
+
:return: an IntervalDict, or a single value if key is not an Interval.
|
|
160
|
+
"""
|
|
161
|
+
if default is None:
|
|
162
|
+
value = self[key]
|
|
163
|
+
del self[key]
|
|
164
|
+
return value
|
|
165
|
+
else:
|
|
166
|
+
value = self.get(key, default)
|
|
167
|
+
try:
|
|
168
|
+
del self[key]
|
|
169
|
+
except KeyError:
|
|
170
|
+
pass
|
|
171
|
+
return value
|
|
172
|
+
|
|
173
|
+
def popitem(self):
|
|
174
|
+
"""
|
|
175
|
+
Remove and return some (key, value) pair as a 2-tuple.
|
|
176
|
+
Raise KeyError if D is empty.
|
|
177
|
+
|
|
178
|
+
:return: a (key, value) pair.
|
|
179
|
+
"""
|
|
180
|
+
return self._storage.popitem()
|
|
181
|
+
|
|
182
|
+
def setdefault(self, key, default=None):
|
|
183
|
+
"""
|
|
184
|
+
Return given key. If it does not exist, set its value to default and
|
|
185
|
+
return it.
|
|
186
|
+
|
|
187
|
+
:param key: a single value or an Interval instance.
|
|
188
|
+
:param default: default value (default to None).
|
|
189
|
+
:return: an IntervalDict, or a single value if key is not an Interval.
|
|
190
|
+
"""
|
|
191
|
+
if isinstance(key, Interval):
|
|
192
|
+
value = self.get(key, default)
|
|
193
|
+
self.update(value)
|
|
194
|
+
return value
|
|
195
|
+
else:
|
|
196
|
+
try:
|
|
197
|
+
return self[key]
|
|
198
|
+
except KeyError:
|
|
199
|
+
self[key] = default
|
|
200
|
+
return default
|
|
201
|
+
|
|
202
|
+
def update(self, mapping_or_iterable):
|
|
203
|
+
"""
|
|
204
|
+
Update current IntervalDict with provided values.
|
|
205
|
+
|
|
206
|
+
If a mapping is provided, it must map Interval instances to values (e.g., another
|
|
207
|
+
IntervalDict). If an iterable is provided, it must consist of a list of
|
|
208
|
+
(key, value) pairs.
|
|
209
|
+
|
|
210
|
+
:param mapping_or_iterable: mapping or iterable.
|
|
211
|
+
"""
|
|
212
|
+
if isinstance(mapping_or_iterable, Mapping):
|
|
213
|
+
data = mapping_or_iterable.items()
|
|
214
|
+
else:
|
|
215
|
+
data = mapping_or_iterable
|
|
216
|
+
|
|
217
|
+
for i, v in data:
|
|
218
|
+
self[i] = v
|
|
219
|
+
|
|
220
|
+
def combine(self, other, how):
|
|
221
|
+
"""
|
|
222
|
+
Return a new IntervalDict that combines the values from current and
|
|
223
|
+
provided ones.
|
|
224
|
+
|
|
225
|
+
If d = d1.combine(d2, f), then d contains (1) all values from d1 whose
|
|
226
|
+
keys do not intersect the ones of d2, (2) all values from d2 whose keys
|
|
227
|
+
do not intersect the ones of d1, and (3) f(x, y) for x in d1, y in d2 for
|
|
228
|
+
intersecting keys.
|
|
229
|
+
|
|
230
|
+
:param other: another IntervalDict instance.
|
|
231
|
+
:param how: a function of two parameters that combines values.
|
|
232
|
+
:return: a new IntervalDict instance.
|
|
233
|
+
"""
|
|
234
|
+
new_items = []
|
|
235
|
+
|
|
236
|
+
dom1, dom2 = self.domain(), other.domain()
|
|
237
|
+
|
|
238
|
+
new_items.extend(self[dom1 - dom2].items())
|
|
239
|
+
new_items.extend(other[dom2 - dom1].items())
|
|
240
|
+
|
|
241
|
+
intersection = dom1 & dom2
|
|
242
|
+
d1, d2 = self[intersection], other[intersection]
|
|
243
|
+
|
|
244
|
+
for i1, v1 in d1.items():
|
|
245
|
+
for i2, v2 in d2.items():
|
|
246
|
+
if i1.overlaps(i2):
|
|
247
|
+
i = i1 & i2
|
|
248
|
+
v = how(v1, v2)
|
|
249
|
+
new_items.append((i, v))
|
|
250
|
+
|
|
251
|
+
return IntervalDict(new_items)
|
|
252
|
+
|
|
253
|
+
def as_dict(self):
|
|
254
|
+
"""
|
|
255
|
+
Return the content as a classical Python dict.
|
|
256
|
+
|
|
257
|
+
:return: a Python dict.
|
|
258
|
+
"""
|
|
259
|
+
return dict(self._storage)
|
|
260
|
+
|
|
261
|
+
def __getitem__(self, key):
|
|
262
|
+
if isinstance(key, Interval):
|
|
263
|
+
items = []
|
|
264
|
+
for i, v in self._storage.items():
|
|
265
|
+
intersection = key & i
|
|
266
|
+
if not intersection.empty:
|
|
267
|
+
items.append((intersection, v))
|
|
268
|
+
return IntervalDict._from_items(items)
|
|
269
|
+
else:
|
|
270
|
+
for i, v in self._storage.items():
|
|
271
|
+
if key in i:
|
|
272
|
+
return v
|
|
273
|
+
raise KeyError(key)
|
|
274
|
+
|
|
275
|
+
def __setitem__(self, key, value):
|
|
276
|
+
interval = key if isinstance(key, Interval) else singleton(key)
|
|
277
|
+
|
|
278
|
+
if interval.empty:
|
|
279
|
+
return
|
|
280
|
+
|
|
281
|
+
removed_keys = []
|
|
282
|
+
added_items = []
|
|
283
|
+
|
|
284
|
+
found = False
|
|
285
|
+
for i, v in self._storage.items():
|
|
286
|
+
if value == v:
|
|
287
|
+
found = True
|
|
288
|
+
# Extend existing key
|
|
289
|
+
removed_keys.append(i)
|
|
290
|
+
added_items.append((i | interval, v))
|
|
291
|
+
elif i.overlaps(interval):
|
|
292
|
+
# Reduce existing key
|
|
293
|
+
remaining = i - interval
|
|
294
|
+
removed_keys.append(i)
|
|
295
|
+
if not remaining.empty:
|
|
296
|
+
added_items.append((remaining, v))
|
|
297
|
+
|
|
298
|
+
if not found:
|
|
299
|
+
added_items.append((interval, value))
|
|
300
|
+
|
|
301
|
+
# Update storage accordingly
|
|
302
|
+
for key in removed_keys:
|
|
303
|
+
self._storage.pop(key)
|
|
304
|
+
|
|
305
|
+
for key, value in added_items:
|
|
306
|
+
self._storage[key] = value
|
|
307
|
+
|
|
308
|
+
def __delitem__(self, key):
|
|
309
|
+
interval = key if isinstance(key, Interval) else singleton(key)
|
|
310
|
+
|
|
311
|
+
if interval.empty:
|
|
312
|
+
return
|
|
313
|
+
|
|
314
|
+
removed_keys = []
|
|
315
|
+
added_items = []
|
|
316
|
+
|
|
317
|
+
found = False
|
|
318
|
+
for i, v in self._storage.items():
|
|
319
|
+
if i.overlaps(interval):
|
|
320
|
+
found = True
|
|
321
|
+
remaining = i - interval
|
|
322
|
+
removed_keys.append(i)
|
|
323
|
+
if not remaining.empty:
|
|
324
|
+
added_items.append((remaining, v))
|
|
325
|
+
|
|
326
|
+
if not found and not isinstance(key, Interval):
|
|
327
|
+
raise KeyError(key)
|
|
328
|
+
|
|
329
|
+
# Update storage accordingly
|
|
330
|
+
for key in removed_keys:
|
|
331
|
+
self._storage.pop(key)
|
|
332
|
+
|
|
333
|
+
for key, value in added_items:
|
|
334
|
+
self._storage[key] = value
|
|
335
|
+
|
|
336
|
+
def __or__(self, other):
|
|
337
|
+
d = self.copy()
|
|
338
|
+
d.update(other)
|
|
339
|
+
return d
|
|
340
|
+
|
|
341
|
+
def __ior__(self, other):
|
|
342
|
+
self.update(other)
|
|
343
|
+
return self
|
|
344
|
+
|
|
345
|
+
def __iter__(self):
|
|
346
|
+
return iter(self._storage)
|
|
347
|
+
|
|
348
|
+
def __len__(self):
|
|
349
|
+
return len(self._storage)
|
|
350
|
+
|
|
351
|
+
def __contains__(self, key):
|
|
352
|
+
return key in self.domain()
|
|
353
|
+
|
|
354
|
+
def __repr__(self):
|
|
355
|
+
return "{}{}{}".format(
|
|
356
|
+
"{",
|
|
357
|
+
", ".join("{!r}: {!r}".format(i, v) for i, v in self.items()),
|
|
358
|
+
"}",
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
def __eq__(self, other):
|
|
362
|
+
if isinstance(other, IntervalDict):
|
|
363
|
+
return self.as_dict() == other.as_dict()
|
|
364
|
+
else:
|
|
365
|
+
return NotImplemented
|
boris/portion/func.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import operator
|
|
2
|
+
from functools import partial
|
|
3
|
+
|
|
4
|
+
from .const import inf
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def iterate(interval, step, *, base=None, reverse=False):
|
|
8
|
+
"""
|
|
9
|
+
Iterate on the (discrete) values of given interval.
|
|
10
|
+
|
|
11
|
+
This function returns a (lazy) iterator over the values of given interval,
|
|
12
|
+
starting from its lower bound and ending on its upper bound (if interval is not open).
|
|
13
|
+
Each returned value merely corresponds to lower + i * step, where "step" defines
|
|
14
|
+
the step between consecutive values.
|
|
15
|
+
It also accepts a callable that is used to compute the next possible
|
|
16
|
+
value based on the current one.
|
|
17
|
+
|
|
18
|
+
When a non-atomic interval is provided, this function chains the iterators obtained
|
|
19
|
+
by calling itself on the underlying atomic intervals.
|
|
20
|
+
|
|
21
|
+
The values returned by the iterator can be aligned with a base value with the "base" parameter.
|
|
22
|
+
This parameter must be a callable that accepts the lower bound of the (atomic) interval as
|
|
23
|
+
input, and returns the first value that needs to be considered for the iteration.
|
|
24
|
+
By default, the identity function is used. If reverse=True, then the upper bound will be
|
|
25
|
+
passed instead of the lower one.
|
|
26
|
+
|
|
27
|
+
:param interval: an interval.
|
|
28
|
+
:param step: step between values, or a callable that returns the next value.
|
|
29
|
+
:param base: a callable that accepts a bound and returns an initial value to consider.
|
|
30
|
+
:param reverse: set to True for descending order.
|
|
31
|
+
:return: a lazy iterator.
|
|
32
|
+
"""
|
|
33
|
+
if base is None:
|
|
34
|
+
base = lambda x: x
|
|
35
|
+
|
|
36
|
+
exclude = operator.lt if not reverse else operator.gt
|
|
37
|
+
include = operator.le if not reverse else operator.ge
|
|
38
|
+
step = step if callable(step) else partial(operator.add, step)
|
|
39
|
+
|
|
40
|
+
value = base(interval.lower if not reverse else interval.upper)
|
|
41
|
+
if (value == -inf and not reverse) or (value == inf and reverse):
|
|
42
|
+
raise ValueError("Cannot start iteration with infinity.")
|
|
43
|
+
|
|
44
|
+
for i in interval if not reverse else reversed(interval):
|
|
45
|
+
value = base(i.lower if not reverse else i.upper)
|
|
46
|
+
|
|
47
|
+
while exclude(value, i):
|
|
48
|
+
value = step(value)
|
|
49
|
+
|
|
50
|
+
while include(value, i):
|
|
51
|
+
yield value
|
|
52
|
+
value = step(value)
|