sdata 1.5.0__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.
- sdata/__init__.py +140 -0
- sdata/base.py +684 -0
- sdata/contrib/__init__.py +0 -0
- sdata/contrib/piexif/__init__.py +12 -0
- sdata/contrib/piexif/_common.py +94 -0
- sdata/contrib/piexif/_dump.py +346 -0
- sdata/contrib/piexif/_exceptions.py +2 -0
- sdata/contrib/piexif/_exif.py +638 -0
- sdata/contrib/piexif/_insert.py +60 -0
- sdata/contrib/piexif/_load.py +270 -0
- sdata/contrib/piexif/_remove.py +56 -0
- sdata/contrib/piexif/_transplant.py +45 -0
- sdata/contrib/piexif/_webp.py +240 -0
- sdata/contrib/piexif/helper.py +66 -0
- sdata/contrib/ranger/__init__.py +8 -0
- sdata/contrib/ranger/collections/__init__.py +0 -0
- sdata/contrib/ranger/collections/rangebucketmap.py +346 -0
- sdata/contrib/ranger/collections/rangemap.py +396 -0
- sdata/contrib/ranger/collections/rangeset.py +483 -0
- sdata/contrib/ranger/range/__init__.py +0 -0
- sdata/contrib/ranger/range/cut.py +285 -0
- sdata/contrib/ranger/range/range.py +644 -0
- sdata/contrib/sobol_seq.py +394 -0
- sdata/contrib/sortedcontainers/LICENSE +13 -0
- sdata/contrib/sortedcontainers/__init__.py +74 -0
- sdata/contrib/sortedcontainers/sorteddict.py +789 -0
- sdata/contrib/sortedcontainers/sortedlist.py +2646 -0
- sdata/contrib/sortedcontainers/sortedset.py +733 -0
- sdata/contrib/sqlitedict.py +598 -0
- sdata/contrib/timeflake/__init__.py +40 -0
- sdata/contrib/timeflake/flake.py +69 -0
- sdata/contrib/timeflake/utils.py +47 -0
- sdata/deprecated/__init__.py +10 -0
- sdata/deprecated/data.py +1503 -0
- sdata/did/__init__.py +129 -0
- sdata/did/_http.py +67 -0
- sdata/did/base58btc.py +49 -0
- sdata/did/did_github.py +152 -0
- sdata/did/did_peer4.py +155 -0
- sdata/did/did_web.py +222 -0
- sdata/did/diddoc.py +75 -0
- sdata/did/ed25519.py +225 -0
- sdata/did/eddsa.py +107 -0
- sdata/did/errors.py +40 -0
- sdata/did/issue_vc.py +193 -0
- sdata/did/jose.py +126 -0
- sdata/did/keys.py +104 -0
- sdata/did/utils_didkey.py +161 -0
- sdata/did/verify_vp.py +158 -0
- sdata/doe.py +101 -0
- sdata/dtypes.py +510 -0
- sdata/experiments/__init__.py +331 -0
- sdata/experiments/blob0.py +131 -0
- sdata/experiments/file.py +42 -0
- sdata/experiments/filegroup.py +144 -0
- sdata/experiments/image_old.py +313 -0
- sdata/experiments/ks2.py +15 -0
- sdata/experiments/lapshear.py +8 -0
- sdata/experiments/material.py +7 -0
- sdata/experiments/parameters_deprecated.py +394 -0
- sdata/experiments/workbook.py +226 -0
- sdata/format.py +125 -0
- sdata/imagemeta.py +533 -0
- sdata/interactive.py +118 -0
- sdata/iolib/__init__.py +51 -0
- sdata/iolib/hashtree.py +264 -0
- sdata/iolib/json1sqlitestore.py +554 -0
- sdata/iolib/jsonsqlitestore.py +704 -0
- sdata/iolib/pud.py +242 -0
- sdata/iolib/reader.py +262 -0
- sdata/iolib/writer.py +419 -0
- sdata/metadata.py +882 -0
- sdata/parameter.py +414 -0
- sdata/schema.py +273 -0
- sdata/sclass/__init__.py +111 -0
- sdata/sclass/bfo_classes.py +362 -0
- sdata/sclass/blob.py +362 -0
- sdata/sclass/content.py +87 -0
- sdata/sclass/dataframe.py +1251 -0
- sdata/sclass/dataframegroup.py +187 -0
- sdata/sclass/dependency_graph.py +85 -0
- sdata/sclass/filereference.py +124 -0
- sdata/sclass/image.py +218 -0
- sdata/sclass/lazy_namespace.py +155 -0
- sdata/sclass/process.py +383 -0
- sdata/sclass/process_graph.py +221 -0
- sdata/sclass/zipfile.py +135 -0
- sdata/semantic.py +346 -0
- sdata/suuid.py +136 -0
- sdata/timestamp.py +336 -0
- sdata/tools.py +14 -0
- sdata/units.py +602 -0
- sdata/vocab.py +149 -0
- sdata-1.5.0.dist-info/METADATA +264 -0
- sdata-1.5.0.dist-info/RECORD +98 -0
- sdata-1.5.0.dist-info/WHEEL +5 -0
- sdata-1.5.0.dist-info/licenses/LICENSE-MIT +21 -0
- sdata-1.5.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
from bisect import bisect_left
|
|
2
|
+
from collections import deque
|
|
3
|
+
from sdata.contrib.ranger.range.range import Range
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class RangeSet(object):
|
|
7
|
+
""" Class used to represent a set of non-overlapping ranges of the
|
|
8
|
+
same type. If a range is added that is connected to another range
|
|
9
|
+
already in the set, those ranges are merged. Otherwise, it is added as
|
|
10
|
+
a new range in the set
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, ranges=None):
|
|
14
|
+
""" Instantiates the RangeSet
|
|
15
|
+
|
|
16
|
+
Parameters
|
|
17
|
+
----------
|
|
18
|
+
ranges : List of Range objects
|
|
19
|
+
Ranges to add to the Set
|
|
20
|
+
"""
|
|
21
|
+
## Holds lower and upper cut points of ranges
|
|
22
|
+
self.lower_cuts = []
|
|
23
|
+
self.upper_cuts = []
|
|
24
|
+
## Holds the range objects in the set
|
|
25
|
+
self.ranges = []
|
|
26
|
+
if ranges is not None:
|
|
27
|
+
for aRange in ranges:
|
|
28
|
+
self.add(aRange)
|
|
29
|
+
|
|
30
|
+
def __repr__(self):
|
|
31
|
+
if len(self) < 6:
|
|
32
|
+
return "RangeSet(%s)" % ", ".join(map(str, self.ranges))
|
|
33
|
+
else:
|
|
34
|
+
return "RangeSet(%s, ..., %s)" % (", ".join(map(str, self.ranges[:2])),
|
|
35
|
+
", ".join(map(str, self.ranges[-2:])))
|
|
36
|
+
|
|
37
|
+
def __len__(self):
|
|
38
|
+
return len(self.ranges)
|
|
39
|
+
|
|
40
|
+
def __iter__(self):
|
|
41
|
+
return iter(self.ranges)
|
|
42
|
+
|
|
43
|
+
def __eq__(self, other):
|
|
44
|
+
if not isinstance(other, RangeSet):
|
|
45
|
+
return False
|
|
46
|
+
elif len(self) != len(other):
|
|
47
|
+
return False
|
|
48
|
+
else:
|
|
49
|
+
for r1, r2 in zip(self.ranges, other.ranges):
|
|
50
|
+
if r1 != r2: return False
|
|
51
|
+
return True
|
|
52
|
+
|
|
53
|
+
def __ne__(self, other):
|
|
54
|
+
return not self.__eq__(other)
|
|
55
|
+
|
|
56
|
+
def add(self, a_range):
|
|
57
|
+
""" Adds a range to the range set. If this range is not connected
|
|
58
|
+
to any current ranges, it will place the new range on its own. If
|
|
59
|
+
there is a connection, any connected ranges will be merged into a single
|
|
60
|
+
range
|
|
61
|
+
|
|
62
|
+
Parameters
|
|
63
|
+
----------
|
|
64
|
+
a_range : A Range object
|
|
65
|
+
The Range to add to the RangeSet
|
|
66
|
+
|
|
67
|
+
Raises
|
|
68
|
+
------
|
|
69
|
+
ValueError
|
|
70
|
+
If adding range of type not compatible with previously
|
|
71
|
+
added ranges
|
|
72
|
+
TypeError:
|
|
73
|
+
If not adding a Range
|
|
74
|
+
"""
|
|
75
|
+
if not isinstance(a_range, Range):
|
|
76
|
+
raise TypeError("a_range is not a Range")
|
|
77
|
+
elif a_range.is_empty():
|
|
78
|
+
# Skip if this is an empty range
|
|
79
|
+
return
|
|
80
|
+
# Check for compatibility of types if necessary
|
|
81
|
+
if len(self) > 0:
|
|
82
|
+
if not (issubclass(a_range.lower_cut.the_type,
|
|
83
|
+
self.ranges[0].lower_cut.the_type) or \
|
|
84
|
+
issubclass(self.ranges[0].lower_cut.the_type,
|
|
85
|
+
a_range.lower_cut.the_type)):
|
|
86
|
+
raise ValueError("Range not compatible with previously added ranges")
|
|
87
|
+
# Get the insertion point (where the lower bound should go), should
|
|
88
|
+
# this range be added on its own
|
|
89
|
+
lower_ind = bisect_left(self.lower_cuts, a_range.lower_cut)
|
|
90
|
+
if len(self) == 0:
|
|
91
|
+
# Add on its own if there is nothing in the list
|
|
92
|
+
self.ranges.append(a_range)
|
|
93
|
+
self.lower_cuts.append(a_range.lower_cut)
|
|
94
|
+
self.upper_cuts.append(a_range.upper_cut)
|
|
95
|
+
elif len(self) == lower_ind:
|
|
96
|
+
if not a_range.is_connected(self.ranges[max(lower_ind - 1, 0)]):
|
|
97
|
+
# Add on its own if not connected to previous and last
|
|
98
|
+
self.ranges.insert(lower_ind, a_range)
|
|
99
|
+
self.lower_cuts.insert(lower_ind, a_range.lower_cut)
|
|
100
|
+
self.upper_cuts.insert(lower_ind, a_range.upper_cut)
|
|
101
|
+
else:
|
|
102
|
+
# If connected with the range below, replace with new range
|
|
103
|
+
new_lower_cut = min(a_range.lower_cut,
|
|
104
|
+
self.lower_cuts[max(lower_ind - 1, 0)])
|
|
105
|
+
new_upper_cut = max(a_range.upper_cut,
|
|
106
|
+
self.upper_cuts[max(lower_ind - 1, 0)])
|
|
107
|
+
new_range = Range(new_lower_cut, new_upper_cut)
|
|
108
|
+
self.ranges[-1] = new_range
|
|
109
|
+
self.lower_cuts[-1] = new_lower_cut
|
|
110
|
+
self.upper_cuts[-1] = new_upper_cut
|
|
111
|
+
elif not any((a_range.is_connected(self.ranges[max(lower_ind - 1, 0)]),
|
|
112
|
+
a_range.is_connected(self.ranges[lower_ind]))):
|
|
113
|
+
# Add on its own if not connected
|
|
114
|
+
self.ranges.insert(lower_ind, a_range)
|
|
115
|
+
self.lower_cuts.insert(lower_ind, a_range.lower_cut)
|
|
116
|
+
self.upper_cuts.insert(lower_ind, a_range.upper_cut)
|
|
117
|
+
elif a_range.is_connected(self.ranges[max(lower_ind - 1, 0)]):
|
|
118
|
+
# If connected with range below
|
|
119
|
+
new_lower_cut = min(self.lower_cuts[max(lower_ind - 1, 0)],
|
|
120
|
+
a_range.lower_cut)
|
|
121
|
+
new_upper_cut = max(a_range.upper_cut,
|
|
122
|
+
self.upper_cuts[max(lower_ind - 1, 0)])
|
|
123
|
+
remove_count = 1
|
|
124
|
+
if len(self) == (lower_ind):
|
|
125
|
+
# If hitting the last range, take the maximum uppercut
|
|
126
|
+
new_upper_cut = max(new_upper_cut, self.upper_cuts[max(lower_ind - 1, 0)])
|
|
127
|
+
else:
|
|
128
|
+
# If not hitting the last range, go find the upper cut
|
|
129
|
+
for i in range(max(1, lower_ind), len(self)):
|
|
130
|
+
if a_range.is_connected(self.ranges[i]):
|
|
131
|
+
new_upper_cut = max(new_upper_cut, self.upper_cuts[i])
|
|
132
|
+
remove_count += 1
|
|
133
|
+
else:
|
|
134
|
+
break
|
|
135
|
+
# Make the new range
|
|
136
|
+
new_range = Range(new_lower_cut, new_upper_cut)
|
|
137
|
+
# Get rid of all overlapping ranges
|
|
138
|
+
for i in range(remove_count):
|
|
139
|
+
self.ranges.pop(max(lower_ind - 1, 0))
|
|
140
|
+
self.lower_cuts.pop(max(lower_ind - 1, 0))
|
|
141
|
+
self.upper_cuts.pop(max(lower_ind - 1, 0))
|
|
142
|
+
# Add the new range
|
|
143
|
+
self.ranges.insert(max(lower_ind - 1, 0), new_range)
|
|
144
|
+
self.lower_cuts.insert(max(lower_ind - 1, 0), new_range.lower_cut)
|
|
145
|
+
self.upper_cuts.insert(max(lower_ind - 1, 0), new_range.upper_cut)
|
|
146
|
+
elif a_range.is_connected(self.ranges[lower_ind]):
|
|
147
|
+
# If connected with the range above
|
|
148
|
+
new_lower_cut = min(a_range.lower_cut, self.lower_cuts[lower_ind])
|
|
149
|
+
new_upper_cut = max(a_range.upper_cut, self.upper_cuts[lower_ind])
|
|
150
|
+
remove_count = 0
|
|
151
|
+
if len(self) == (lower_ind + 1):
|
|
152
|
+
# If hitting the last range, you're done
|
|
153
|
+
remove_count += 1
|
|
154
|
+
else:
|
|
155
|
+
# Go find the upper cut
|
|
156
|
+
for i in range(lower_ind, len(self)):
|
|
157
|
+
if a_range.is_connected(self.ranges[i]):
|
|
158
|
+
new_upper_cut = max(new_upper_cut, self.upper_cuts[i])
|
|
159
|
+
remove_count += 1
|
|
160
|
+
else:
|
|
161
|
+
break
|
|
162
|
+
# Make the new range
|
|
163
|
+
new_range = Range(new_lower_cut, new_upper_cut)
|
|
164
|
+
# Remove the overlapping ranges
|
|
165
|
+
for i in range(remove_count):
|
|
166
|
+
self.ranges.pop(lower_ind)
|
|
167
|
+
self.lower_cuts.pop(lower_ind)
|
|
168
|
+
self.upper_cuts.pop(lower_ind)
|
|
169
|
+
# Add the new range
|
|
170
|
+
self.ranges.insert(lower_ind, new_range)
|
|
171
|
+
self.lower_cuts.insert(lower_ind, new_range.lower_cut)
|
|
172
|
+
self.upper_cuts.insert(lower_ind, new_range.upper_cut)
|
|
173
|
+
|
|
174
|
+
def contains(self, val):
|
|
175
|
+
""" Returns true if any of the ranges fully enclose the given
|
|
176
|
+
value, which can be a single value or a Range object
|
|
177
|
+
|
|
178
|
+
Parameters
|
|
179
|
+
----------
|
|
180
|
+
val : A single value or a Range object
|
|
181
|
+
|
|
182
|
+
Raises
|
|
183
|
+
------
|
|
184
|
+
ValueError
|
|
185
|
+
If the value type not compatible with the ranges
|
|
186
|
+
|
|
187
|
+
Returns
|
|
188
|
+
-------
|
|
189
|
+
true if any of the ranges fully enclose the given value
|
|
190
|
+
"""
|
|
191
|
+
if len(self) == 0: return False
|
|
192
|
+
# Get the index+1 of the highest lower cut <= to the value or its
|
|
193
|
+
# lower cutpoint and check if the value contained
|
|
194
|
+
if isinstance(val, Range):
|
|
195
|
+
lower_ind = max(bisect_left(self.lower_cuts, val.lower_cut), 0)
|
|
196
|
+
if lower_ind >= len(self.lower_cuts):
|
|
197
|
+
return self.ranges[lower_ind - 1].encloses(val)
|
|
198
|
+
elif val.lower_cut != self.lower_cuts[lower_ind]:
|
|
199
|
+
return self.ranges[lower_ind - 1].encloses(val)
|
|
200
|
+
else:
|
|
201
|
+
return self.ranges[lower_ind].encloses(val)
|
|
202
|
+
else:
|
|
203
|
+
lower_ind = max(bisect_left(self.lower_cuts, val) - 1, 0)
|
|
204
|
+
return self.ranges[lower_ind].contains(val)
|
|
205
|
+
|
|
206
|
+
def difference(self, otherSet):
|
|
207
|
+
""" Creates a new RangeSet in which all elements in another RangeSet
|
|
208
|
+
are taken out of this RangeSet
|
|
209
|
+
|
|
210
|
+
Parameters
|
|
211
|
+
----------
|
|
212
|
+
otherSet : RangeSet object
|
|
213
|
+
The RangeSet used for this difference
|
|
214
|
+
|
|
215
|
+
Raises
|
|
216
|
+
------
|
|
217
|
+
TypeError
|
|
218
|
+
If the object passed in is not a RangeSet
|
|
219
|
+
ValueError
|
|
220
|
+
If the value type of the ranges in the other set not compatible
|
|
221
|
+
with the range's values
|
|
222
|
+
|
|
223
|
+
Returns
|
|
224
|
+
-------
|
|
225
|
+
RangeSet consisting of the difference of the two sets
|
|
226
|
+
"""
|
|
227
|
+
if not isinstance(otherSet, RangeSet):
|
|
228
|
+
raise TypeError("other_set is not a RangeSet")
|
|
229
|
+
new_set = RangeSet()
|
|
230
|
+
for add_range in self.ranges:
|
|
231
|
+
if otherSet.overlaps(add_range):
|
|
232
|
+
# Determine where overlap occurs
|
|
233
|
+
other_lower_ind = max(bisect_left(otherSet.lower_cuts,
|
|
234
|
+
add_range.lower_cut) - 1, 0)
|
|
235
|
+
other_upper_ind = bisect_left(otherSet.lower_cuts,
|
|
236
|
+
add_range.upper_cut)
|
|
237
|
+
new_lower_cut = add_range.lower_cut
|
|
238
|
+
new_upper_cut = add_range.upper_cut
|
|
239
|
+
add = True
|
|
240
|
+
for i in range(other_lower_ind, other_upper_ind):
|
|
241
|
+
try:
|
|
242
|
+
# Get the intersection of the ranges
|
|
243
|
+
intersect = add_range.intersection(otherSet.ranges[i])
|
|
244
|
+
if not intersect.is_empty():
|
|
245
|
+
if add_range == intersect:
|
|
246
|
+
add = False
|
|
247
|
+
break
|
|
248
|
+
elif add_range.lower_cut == intersect.lower_cut:
|
|
249
|
+
# If equal on the left cutpoint, subtract out left
|
|
250
|
+
# part
|
|
251
|
+
new_lower_cut = intersect.upper_cut
|
|
252
|
+
add_range = Range(new_lower_cut, new_upper_cut)
|
|
253
|
+
elif add_range.upper_cut == intersect.upper_cut:
|
|
254
|
+
# If equal on right cutpoint, subtract out right
|
|
255
|
+
# part
|
|
256
|
+
new_upper_cut = intersect.lower_cut
|
|
257
|
+
add_range = Range(add_range.lower_cut, new_upper_cut)
|
|
258
|
+
else:
|
|
259
|
+
# If in the middle, split into two parts and
|
|
260
|
+
# add the lower one immediately
|
|
261
|
+
new_set.add(Range(new_lower_cut,
|
|
262
|
+
intersect.lower_cut))
|
|
263
|
+
new_lower_cut = intersect.upper_cut
|
|
264
|
+
new_upper_cut = add_range.upper_cut
|
|
265
|
+
add_range = Range(new_lower_cut, new_upper_cut)
|
|
266
|
+
except ValueError:
|
|
267
|
+
continue
|
|
268
|
+
if add:
|
|
269
|
+
new_set.add(Range(new_lower_cut, new_upper_cut))
|
|
270
|
+
else:
|
|
271
|
+
new_set.add(add_range)
|
|
272
|
+
return new_set
|
|
273
|
+
|
|
274
|
+
def intersection(self, other_set):
|
|
275
|
+
""" Creates a new RangeSet that is the intersection of this and
|
|
276
|
+
another RangeSet
|
|
277
|
+
|
|
278
|
+
Parameters
|
|
279
|
+
----------
|
|
280
|
+
other_set : RangeSet object
|
|
281
|
+
The RangeSet used for this intersection
|
|
282
|
+
|
|
283
|
+
Raises
|
|
284
|
+
------
|
|
285
|
+
TypeError
|
|
286
|
+
If the object passed in is not a RangeSet
|
|
287
|
+
ValueError
|
|
288
|
+
If the value type of the ranges in the other set not compatible
|
|
289
|
+
with the range's values
|
|
290
|
+
|
|
291
|
+
Returns
|
|
292
|
+
-------
|
|
293
|
+
RangeSet consisting of the intersection of the two sets
|
|
294
|
+
"""
|
|
295
|
+
if not isinstance(other_set, RangeSet):
|
|
296
|
+
raise TypeError("other_set is not a RangeSet")
|
|
297
|
+
new_set = RangeSet()
|
|
298
|
+
for addRange in self.ranges:
|
|
299
|
+
if other_set.overlaps(addRange):
|
|
300
|
+
# Determine where overlap occurs
|
|
301
|
+
other_lower_ind = max(bisect_left(other_set.lower_cuts,
|
|
302
|
+
addRange.lower_cut) - 1, 0)
|
|
303
|
+
other_upper_ind = bisect_left(other_set.lower_cuts,
|
|
304
|
+
addRange.upper_cut)
|
|
305
|
+
for i in range(other_lower_ind, other_upper_ind):
|
|
306
|
+
# Get the intersection of the ranges
|
|
307
|
+
try:
|
|
308
|
+
intersect = addRange.intersection(other_set.ranges[i])
|
|
309
|
+
# Add intersection if there is any overlap
|
|
310
|
+
if not intersect.is_empty():
|
|
311
|
+
new_set.add(intersect)
|
|
312
|
+
except ValueError:
|
|
313
|
+
continue
|
|
314
|
+
return new_set
|
|
315
|
+
|
|
316
|
+
def overlaps(self, val):
|
|
317
|
+
""" Returns true if any of the ranges at least partially overlap
|
|
318
|
+
the given value, which can be a single value or a Range object
|
|
319
|
+
|
|
320
|
+
Parameters
|
|
321
|
+
----------
|
|
322
|
+
val : A single value or a Range object
|
|
323
|
+
|
|
324
|
+
Raises:
|
|
325
|
+
-------
|
|
326
|
+
ValueError
|
|
327
|
+
If the value type not compatible with the ranges
|
|
328
|
+
|
|
329
|
+
Returns
|
|
330
|
+
-------
|
|
331
|
+
true if any of the ranges fully enclose the given value
|
|
332
|
+
"""
|
|
333
|
+
if len(self) == 0: return False
|
|
334
|
+
# Get the index+1 of the highest lower cut <= to the value or its
|
|
335
|
+
# lower cutpoint and check if the value overlaps
|
|
336
|
+
if isinstance(val, Range):
|
|
337
|
+
lower_ind = bisect_left(self.lower_cuts, val.lower_cut) - 1
|
|
338
|
+
upper_ind = bisect_left(self.lower_cuts, val.upper_cut)
|
|
339
|
+
for i in range(lower_ind, upper_ind):
|
|
340
|
+
if val.is_connected(self.ranges[i]):
|
|
341
|
+
if not self.ranges[i].intersection(val).is_empty():
|
|
342
|
+
return True
|
|
343
|
+
return False
|
|
344
|
+
else:
|
|
345
|
+
lower_ind = bisect_left(self.lower_cuts, val) - 1
|
|
346
|
+
return self.ranges[lower_ind].contains(val)
|
|
347
|
+
|
|
348
|
+
def remove(self, a_range):
|
|
349
|
+
""" Removes a range from the range set.
|
|
350
|
+
|
|
351
|
+
Parameters
|
|
352
|
+
----------
|
|
353
|
+
a_range : A Range object
|
|
354
|
+
The Range to remove from the RangeSet
|
|
355
|
+
|
|
356
|
+
Raises
|
|
357
|
+
------
|
|
358
|
+
ValueError
|
|
359
|
+
If removing range of type not compatible with previously
|
|
360
|
+
added ranges
|
|
361
|
+
TypeError
|
|
362
|
+
If not a Range
|
|
363
|
+
"""
|
|
364
|
+
if not isinstance(a_range, Range):
|
|
365
|
+
raise TypeError("a_range is not a Range")
|
|
366
|
+
elif a_range.is_empty():
|
|
367
|
+
# Skip if this is an empty range
|
|
368
|
+
return
|
|
369
|
+
# Check for compatibility of types if necessary
|
|
370
|
+
if len(self) > 0:
|
|
371
|
+
if not (issubclass(a_range.lower_cut.the_type,
|
|
372
|
+
self.ranges[0].lower_cut.the_type) or issubclass(self.ranges[0].lower_cut.the_type,
|
|
373
|
+
a_range.lower_cut.the_type)):
|
|
374
|
+
raise ValueError("Range not compatible with previously added ranges")
|
|
375
|
+
# Check if the range actually overlaps with this set
|
|
376
|
+
if not self.overlaps(a_range):
|
|
377
|
+
return
|
|
378
|
+
else:
|
|
379
|
+
# There's some overlap, so deal with that
|
|
380
|
+
# Determine where overlap occurs
|
|
381
|
+
ovlap_lower_ind = max(bisect_left(self.lower_cuts, a_range.lower_cut) - 1, 0)
|
|
382
|
+
ovlap_upper_ind = bisect_left(self.lower_cuts, a_range.upper_cut)
|
|
383
|
+
# Create queue of indices marked for removal
|
|
384
|
+
remove_ranges = deque()
|
|
385
|
+
# Create queue of ranges to add
|
|
386
|
+
add_ranges = deque()
|
|
387
|
+
for i in range(ovlap_lower_ind, ovlap_upper_ind):
|
|
388
|
+
try:
|
|
389
|
+
# Get intersection of the ranges
|
|
390
|
+
intersect = a_range.intersection(self.ranges[i])
|
|
391
|
+
if not intersect.is_empty():
|
|
392
|
+
if intersect == self.ranges[i]:
|
|
393
|
+
# Mark range for removal
|
|
394
|
+
remove_ranges.append(i)
|
|
395
|
+
elif self.lower_cuts[i] == intersect.lower_cut:
|
|
396
|
+
# If equal on the left cutpoint, subtract out left
|
|
397
|
+
# part
|
|
398
|
+
self.lower_cuts[i] = intersect.upper_cut
|
|
399
|
+
self.ranges[i] = Range(intersect.upper_cut, self.upper_cuts[i])
|
|
400
|
+
elif self.upper_cuts[i] == intersect.upper_cut:
|
|
401
|
+
# If equal on right cutpoint, subtract out right
|
|
402
|
+
# part
|
|
403
|
+
self.upper_cuts[i] = intersect.lower_cut
|
|
404
|
+
self.ranges[i] = Range(self.lower_cuts[i], intersect.lower_cut)
|
|
405
|
+
else:
|
|
406
|
+
# If in the middle, split into two parts, putting both into
|
|
407
|
+
# add queue and placing the old range index into the removal
|
|
408
|
+
# queue
|
|
409
|
+
add_ranges.append(Range(self.lower_cuts[i], intersect.lower_cut))
|
|
410
|
+
add_ranges.append(Range(intersect.upper_cut, self.upper_cuts[i]))
|
|
411
|
+
remove_ranges.append(i)
|
|
412
|
+
except ValueError:
|
|
413
|
+
# Continue if no overlap with this range
|
|
414
|
+
continue
|
|
415
|
+
# Remove any ranges that are marked for removal
|
|
416
|
+
while len(remove_ranges) > 0:
|
|
417
|
+
remove_ind = remove_ranges.pop()
|
|
418
|
+
self.ranges.pop(remove_ind)
|
|
419
|
+
self.lower_cuts.pop(remove_ind)
|
|
420
|
+
self.upper_cuts.pop(remove_ind)
|
|
421
|
+
# Add any ranges that need to be added
|
|
422
|
+
while len(add_ranges) > 0:
|
|
423
|
+
self.add(add_ranges.pop())
|
|
424
|
+
|
|
425
|
+
def union(self, other_set):
|
|
426
|
+
""" Creates a new RangeSet that is the union of this set and
|
|
427
|
+
another RangeSet object
|
|
428
|
+
|
|
429
|
+
Parameters
|
|
430
|
+
----------
|
|
431
|
+
other_set : RangeSet object
|
|
432
|
+
The RangeSet used for the union
|
|
433
|
+
|
|
434
|
+
Raises
|
|
435
|
+
------
|
|
436
|
+
TypeError
|
|
437
|
+
If the object passed in is not a RangeSet
|
|
438
|
+
ValueError
|
|
439
|
+
If the value type of the set not compatible with the ranges
|
|
440
|
+
|
|
441
|
+
Returns
|
|
442
|
+
-------
|
|
443
|
+
RangeSet consisting of union of two sets
|
|
444
|
+
"""
|
|
445
|
+
if not isinstance(other_set, RangeSet):
|
|
446
|
+
raise TypeError("other_set is not a RangeSet")
|
|
447
|
+
return RangeSet(set(self.ranges + other_set.ranges))
|
|
448
|
+
|
|
449
|
+
def which_overlaps(self, val):
|
|
450
|
+
""" Returns which of the Ranges overlap with a single value or
|
|
451
|
+
Range object
|
|
452
|
+
|
|
453
|
+
Parameters
|
|
454
|
+
----------
|
|
455
|
+
val : A single value or a Range object
|
|
456
|
+
|
|
457
|
+
Raises:
|
|
458
|
+
-------
|
|
459
|
+
ValueError
|
|
460
|
+
If the value type not compatible with the ranges
|
|
461
|
+
|
|
462
|
+
Returns
|
|
463
|
+
-------
|
|
464
|
+
set of ranges overlapping with the value
|
|
465
|
+
"""
|
|
466
|
+
if len(self) == 0: return False
|
|
467
|
+
# Get the index+1 of the highest lower cut <= to the value or its
|
|
468
|
+
# lower cutpoint and check if the value overlaps. If it does, add
|
|
469
|
+
# to set
|
|
470
|
+
overlap_set = set()
|
|
471
|
+
if isinstance(val, Range):
|
|
472
|
+
lower_ind = bisect_left(self.lower_cuts, val.lower_cut) - 1
|
|
473
|
+
upper_ind = bisect_left(self.lower_cuts, val.upper_cut)
|
|
474
|
+
for i in range(lower_ind, upper_ind):
|
|
475
|
+
if val.is_connected(self.ranges[i]):
|
|
476
|
+
if not self.ranges[i].intersection(val).is_empty():
|
|
477
|
+
overlap_set.add(self.ranges[i])
|
|
478
|
+
return overlap_set
|
|
479
|
+
else:
|
|
480
|
+
lower_ind = bisect_left(self.lower_cuts, val) - 1
|
|
481
|
+
if self.ranges[lower_ind].contains(val):
|
|
482
|
+
overlap_set.add(self.ranges[lower_ind])
|
|
483
|
+
return overlap_set
|
|
File without changes
|