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,66 @@
|
|
|
1
|
+
class UserComment:
|
|
2
|
+
#
|
|
3
|
+
# Names of encodings that we publicly support.
|
|
4
|
+
#
|
|
5
|
+
ASCII = 'ascii'
|
|
6
|
+
JIS = 'jis'
|
|
7
|
+
UNICODE = 'unicode'
|
|
8
|
+
ENCODINGS = (ASCII, JIS, UNICODE)
|
|
9
|
+
|
|
10
|
+
#
|
|
11
|
+
# The actual encodings accepted by the standard library differ slightly from
|
|
12
|
+
# the above.
|
|
13
|
+
#
|
|
14
|
+
_JIS = 'shift_jis'
|
|
15
|
+
_UNICODE = 'utf_16_be'
|
|
16
|
+
|
|
17
|
+
_PREFIX_SIZE = 8
|
|
18
|
+
#
|
|
19
|
+
# From Table 9: Character Codes and their Designation
|
|
20
|
+
#
|
|
21
|
+
_ASCII_PREFIX = b'\x41\x53\x43\x49\x49\x00\x00\x00'
|
|
22
|
+
_JIS_PREFIX = b'\x4a\x49\x53\x00\x00\x00\x00\x00'
|
|
23
|
+
_UNICODE_PREFIX = b'\x55\x4e\x49\x43\x4f\x44\x45\x00'
|
|
24
|
+
_UNDEFINED_PREFIX = b'\x00\x00\x00\x00\x00\x00\x00\x00'
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def load(cls, data):
|
|
28
|
+
"""
|
|
29
|
+
Convert "UserComment" value in exif format to str.
|
|
30
|
+
|
|
31
|
+
:param bytes data: "UserComment" value from exif
|
|
32
|
+
:return: u"foobar"
|
|
33
|
+
:rtype: str(Unicode)
|
|
34
|
+
:raises: ValueError if the data does not conform to the EXIF specification,
|
|
35
|
+
or the encoding is unsupported.
|
|
36
|
+
"""
|
|
37
|
+
if len(data) < cls._PREFIX_SIZE:
|
|
38
|
+
raise ValueError('not enough data to decode UserComment')
|
|
39
|
+
prefix = data[:cls._PREFIX_SIZE]
|
|
40
|
+
body = data[cls._PREFIX_SIZE:]
|
|
41
|
+
if prefix == cls._UNDEFINED_PREFIX:
|
|
42
|
+
raise ValueError('prefix is UNDEFINED, unable to decode UserComment')
|
|
43
|
+
try:
|
|
44
|
+
encoding = {
|
|
45
|
+
cls._ASCII_PREFIX: cls.ASCII, cls._JIS_PREFIX: cls._JIS, cls._UNICODE_PREFIX: cls._UNICODE,
|
|
46
|
+
}[prefix]
|
|
47
|
+
except KeyError:
|
|
48
|
+
raise ValueError('unable to determine appropriate encoding')
|
|
49
|
+
return body.decode(encoding, errors='replace')
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def dump(cls, data, encoding="ascii"):
|
|
53
|
+
"""
|
|
54
|
+
Convert str to appropriate format for "UserComment".
|
|
55
|
+
|
|
56
|
+
:param data: Like u"foobar"
|
|
57
|
+
:param str encoding: "ascii", "jis", or "unicode"
|
|
58
|
+
:return: b"ASCII\x00\x00\x00foobar"
|
|
59
|
+
:rtype: bytes
|
|
60
|
+
:raises: ValueError if the encoding is unsupported.
|
|
61
|
+
"""
|
|
62
|
+
if encoding not in cls.ENCODINGS:
|
|
63
|
+
raise ValueError('encoding {!r} must be one of {!r}'.format(encoding, cls.ENCODINGS))
|
|
64
|
+
prefix = {cls.ASCII: cls._ASCII_PREFIX, cls.JIS: cls._JIS_PREFIX, cls.UNICODE: cls._UNICODE_PREFIX}[encoding]
|
|
65
|
+
internal_encoding = {cls.UNICODE: cls._UNICODE, cls.JIS: cls._JIS}.get(encoding, encoding)
|
|
66
|
+
return prefix + data.encode(internal_encoding, errors='replace')
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
__author__ = 'Eli Rodgers-Melnick'
|
|
2
|
+
__license__ = 'BSD'
|
|
3
|
+
|
|
4
|
+
# Make imports
|
|
5
|
+
from sdata.contrib.ranger.range.range import Range
|
|
6
|
+
from sdata.contrib.ranger.collections.rangeset import RangeSet
|
|
7
|
+
from sdata.contrib.ranger.collections.rangemap import RangeMap
|
|
8
|
+
from sdata.contrib.ranger.collections.rangebucketmap import RangeBucketMap
|
|
File without changes
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
from bisect import bisect_left
|
|
2
|
+
from collections import deque
|
|
3
|
+
try: # Python 3.10+ moved ABCs to collections.abc
|
|
4
|
+
from collections.abc import Hashable
|
|
5
|
+
except ImportError: # pragma: no cover - Python < 3.10
|
|
6
|
+
from collections import Hashable
|
|
7
|
+
from sdata.contrib.ranger.collections.rangemap import RangeMap
|
|
8
|
+
from sdata.contrib.ranger.range.range import Range
|
|
9
|
+
from sdata.contrib.ranger.range.cut import Cut
|
|
10
|
+
|
|
11
|
+
class RangeBucketMap(RangeMap):
|
|
12
|
+
""" Class used to represent a mapping of disjoint ranges to sets of items. Ranges
|
|
13
|
+
do not coalesce. However, if a new Range is added over an existing Range, items
|
|
14
|
+
belonging to the existing Range are retained in that Range
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, range_dict=None):
|
|
18
|
+
""" Instantiates a RangeBucketMap
|
|
19
|
+
|
|
20
|
+
Parameters
|
|
21
|
+
----------
|
|
22
|
+
range_dict : Dictionary of Range -> object
|
|
23
|
+
Dictionary to start off the RangeBucketMap with
|
|
24
|
+
"""
|
|
25
|
+
self.recurse_add = False
|
|
26
|
+
super(RangeBucketMap, self).__init__(range_dict)
|
|
27
|
+
|
|
28
|
+
def iteritems(self, start=None, end=None):
|
|
29
|
+
""" Iterates over pairs of (Range, value)
|
|
30
|
+
|
|
31
|
+
Parameters
|
|
32
|
+
----------
|
|
33
|
+
start : comparable, optional
|
|
34
|
+
The starting point for iterating, inclusive
|
|
35
|
+
end : comparable, optional
|
|
36
|
+
The ending point for iterating, inclusive
|
|
37
|
+
|
|
38
|
+
Returns
|
|
39
|
+
-------
|
|
40
|
+
Generator of (Range intersecting [start,end], value), ordered by start point
|
|
41
|
+
"""
|
|
42
|
+
if start is None:
|
|
43
|
+
start = self.lower_cuts[0]
|
|
44
|
+
else:
|
|
45
|
+
start = Cut.below_value(start)
|
|
46
|
+
if end is None:
|
|
47
|
+
end = self.upper_cuts[-1]
|
|
48
|
+
else:
|
|
49
|
+
end = Cut.above_value(end)
|
|
50
|
+
bounding_range = Range(start, end)
|
|
51
|
+
# Get the bounding indices
|
|
52
|
+
ovlapLowerInd = max(bisect_left(self.lower_cuts, start) - 1, 0)
|
|
53
|
+
ovlapUpperInd = bisect_left(self.lower_cuts, end)
|
|
54
|
+
# Create queue of values that need to be generated
|
|
55
|
+
yield_vals = deque()
|
|
56
|
+
# Create dictionary of values to be generated -> indices containing them
|
|
57
|
+
vals_inds_dict = {}
|
|
58
|
+
for i in range(ovlapLowerInd, ovlapUpperInd):
|
|
59
|
+
# Check if anything can be released from the queue
|
|
60
|
+
while len(yield_vals) > 0:
|
|
61
|
+
if vals_inds_dict[yield_vals[0]][-1] < i - 1:
|
|
62
|
+
# Yield the full range, value. Remove value from queue
|
|
63
|
+
val = yield_vals.popleft()
|
|
64
|
+
yield Range(max(self.lower_cuts[vals_inds_dict[val][0]], start),
|
|
65
|
+
min(self.upper_cuts[vals_inds_dict[val][-1]], end)), val
|
|
66
|
+
# Remove value from dict
|
|
67
|
+
del vals_inds_dict[val]
|
|
68
|
+
else:
|
|
69
|
+
break
|
|
70
|
+
try:
|
|
71
|
+
# Get intersection of the ranges
|
|
72
|
+
intersect = bounding_range.intersection(self.ranges[i])
|
|
73
|
+
if not intersect.is_empty():
|
|
74
|
+
# If overlapping with this range, put into queue
|
|
75
|
+
for val in self.items[i]:
|
|
76
|
+
if val not in vals_inds_dict:
|
|
77
|
+
yield_vals.append(val)
|
|
78
|
+
vals_inds_dict[val] = deque()
|
|
79
|
+
vals_inds_dict[val].append(i)
|
|
80
|
+
except ValueError:
|
|
81
|
+
# Continue if no overlap with this range
|
|
82
|
+
continue
|
|
83
|
+
## Yield remaining values
|
|
84
|
+
while len(yield_vals) > 0:
|
|
85
|
+
# Yield the full range, value. Remove value from queue
|
|
86
|
+
val = yield_vals.popleft()
|
|
87
|
+
yield Range(max(self.lower_cuts[vals_inds_dict[val][0]], start),
|
|
88
|
+
min(self.upper_cuts[vals_inds_dict[val][-1]], end)), val
|
|
89
|
+
# Remove value from dict
|
|
90
|
+
del vals_inds_dict[val]
|
|
91
|
+
|
|
92
|
+
def get(self, key):
|
|
93
|
+
""" Get the item(s) corresponding to a given key. The key can be a
|
|
94
|
+
Range or a single value that is within a Range
|
|
95
|
+
|
|
96
|
+
Parameters
|
|
97
|
+
----------
|
|
98
|
+
key : comparable
|
|
99
|
+
A single value or Range object
|
|
100
|
+
|
|
101
|
+
Raises
|
|
102
|
+
------
|
|
103
|
+
KeyError
|
|
104
|
+
If there is no overlap with the key
|
|
105
|
+
ValueError
|
|
106
|
+
If the key type not compatible with the ranges
|
|
107
|
+
|
|
108
|
+
Returns
|
|
109
|
+
-------
|
|
110
|
+
A set containing all overlapping items
|
|
111
|
+
"""
|
|
112
|
+
if not self.overlaps(key):
|
|
113
|
+
self.__missing__(key)
|
|
114
|
+
elif isinstance(key, Range):
|
|
115
|
+
# If this is a single value
|
|
116
|
+
return_set = set()
|
|
117
|
+
# Get the bounding indices
|
|
118
|
+
ovlapLowerInd = max(bisect_left(self.lower_cuts, key.lower_cut) - 1, 0)
|
|
119
|
+
ovlapUpperInd = bisect_left(self.lower_cuts, key.upper_cut)
|
|
120
|
+
for i in range(ovlapLowerInd, ovlapUpperInd):
|
|
121
|
+
try:
|
|
122
|
+
# Get intersection of the ranges
|
|
123
|
+
intersect = key.intersection(self.ranges[i])
|
|
124
|
+
if not intersect.is_empty():
|
|
125
|
+
# If overlapping with this range, put its
|
|
126
|
+
# item in the return set
|
|
127
|
+
return_set = return_set.union(self.items[i])
|
|
128
|
+
except ValueError:
|
|
129
|
+
# Continue if no overlap with this range
|
|
130
|
+
continue
|
|
131
|
+
# Return the set of items
|
|
132
|
+
return return_set
|
|
133
|
+
else:
|
|
134
|
+
# If this is a single value
|
|
135
|
+
# Get the index of the range containing the value
|
|
136
|
+
lower_ind = max(bisect_left(self.lower_cuts, key) - 1, 0)
|
|
137
|
+
# Return the item at that value
|
|
138
|
+
return self.items[lower_ind]
|
|
139
|
+
|
|
140
|
+
def put(self, key, val):
|
|
141
|
+
""" Creates a mapping from a Range to a value, adding to
|
|
142
|
+
any existing values over that Range
|
|
143
|
+
|
|
144
|
+
Parameters
|
|
145
|
+
----------
|
|
146
|
+
key : Range object
|
|
147
|
+
A Range to serve as a key
|
|
148
|
+
val : value, hashable
|
|
149
|
+
Some value that the Range should map to
|
|
150
|
+
|
|
151
|
+
Raises
|
|
152
|
+
------
|
|
153
|
+
TypeError
|
|
154
|
+
If the key is not a Range object or value is not hashable
|
|
155
|
+
"""
|
|
156
|
+
if not isinstance(key, Range):
|
|
157
|
+
raise TypeError("key is not a Range")
|
|
158
|
+
elif not any((isinstance(val, Hashable), self.recurse_add)):
|
|
159
|
+
raise TypeError("value not hashable")
|
|
160
|
+
elif key.is_empty():
|
|
161
|
+
# Skip if this is an empty range
|
|
162
|
+
return
|
|
163
|
+
# Figure out where to the key/value
|
|
164
|
+
if not self.overlaps(key):
|
|
165
|
+
# If this range is completely on its own, just insert
|
|
166
|
+
insert_ind = bisect_left(self.lower_cuts, key.lower_cut)
|
|
167
|
+
self.ranges.insert(insert_ind, key)
|
|
168
|
+
self.lower_cuts.insert(insert_ind, key.lower_cut)
|
|
169
|
+
self.upper_cuts.insert(insert_ind, key.upper_cut)
|
|
170
|
+
if not isinstance(val, set):
|
|
171
|
+
self.items.insert(insert_ind, set([val]))
|
|
172
|
+
else:
|
|
173
|
+
self.items.insert(insert_ind, val)
|
|
174
|
+
return
|
|
175
|
+
else:
|
|
176
|
+
# If this range has some overlap with existing ranges
|
|
177
|
+
ovlap_lower_ind = max(bisect_left(self.lower_cuts, key.lower_cut) - 1, 0)
|
|
178
|
+
ovlap_upper_ind = bisect_left(self.lower_cuts, key.upper_cut)
|
|
179
|
+
# Create queue ranges to add
|
|
180
|
+
add_ranges = deque()
|
|
181
|
+
# Create queue of items to add
|
|
182
|
+
add_items = deque()
|
|
183
|
+
# Keep track of next lower cutpoint to add
|
|
184
|
+
next_lower_cut = key.lower_cut
|
|
185
|
+
for i in range(ovlap_lower_ind, ovlap_upper_ind):
|
|
186
|
+
try:
|
|
187
|
+
# Get intersection of the ranges
|
|
188
|
+
intersect = key.intersection(self.ranges[i])
|
|
189
|
+
if not intersect.is_empty():
|
|
190
|
+
# Add in a Range between the next LowerCut and
|
|
191
|
+
# the beginning of this intersection if necessary
|
|
192
|
+
if next_lower_cut < intersect.lower_cut:
|
|
193
|
+
add_ranges.append(Range(next_lower_cut, intersect.lower_cut))
|
|
194
|
+
add_items.append(val)
|
|
195
|
+
next_lower_cut = intersect.lower_cut
|
|
196
|
+
if intersect == self.ranges[i]:
|
|
197
|
+
## If key encompassing existing Range ##
|
|
198
|
+
# Add item to this range
|
|
199
|
+
self.items[i].add(val)
|
|
200
|
+
# Change the next lower cut
|
|
201
|
+
next_lower_cut = intersect.upper_cut
|
|
202
|
+
elif self.lower_cuts[i] == intersect.lower_cut:
|
|
203
|
+
## If key upper cutpoint enclosed by existing Range ##
|
|
204
|
+
# Add in the rest of the original Range
|
|
205
|
+
if self.upper_cuts[i] > intersect.upper_cut:
|
|
206
|
+
add_ranges.append(Range(intersect.upper_cut,
|
|
207
|
+
self.upper_cuts[i]))
|
|
208
|
+
add_items.append(set(self.items[i]))
|
|
209
|
+
# Define original part to be shorter
|
|
210
|
+
self.upper_cuts[i] = intersect.upper_cut
|
|
211
|
+
self.ranges[i] = Range(self.lower_cuts[i],
|
|
212
|
+
intersect.upper_cut)
|
|
213
|
+
self.items[i].add(val)
|
|
214
|
+
# Change the next lower cut
|
|
215
|
+
next_lower_cut = intersect.upper_cut
|
|
216
|
+
elif self.upper_cuts[i] == intersect.upper_cut:
|
|
217
|
+
## If key lower cutpoint enclosed by existing Range ##
|
|
218
|
+
# Add in the rest of the original Range
|
|
219
|
+
if intersect.lower_cut > self.lower_cuts[i]:
|
|
220
|
+
add_ranges.append(Range(self.lower_cuts[i], intersect.lower_cut))
|
|
221
|
+
add_items.append(set(self.items[i]))
|
|
222
|
+
# Define original part to be shorter
|
|
223
|
+
self.lower_cuts[i] = intersect.lower_cut
|
|
224
|
+
self.ranges[i] = Range(self.lower_cuts[i],
|
|
225
|
+
intersect.upper_cut)
|
|
226
|
+
self.items[i].add(val)
|
|
227
|
+
# Change the next lower cut
|
|
228
|
+
next_lower_cut = intersect.upper_cut
|
|
229
|
+
else:
|
|
230
|
+
# If entire key enclosed by existing Range
|
|
231
|
+
# Add in lower part of original Range
|
|
232
|
+
add_ranges.append(Range(self.lower_cuts[i], intersect.lower_cut))
|
|
233
|
+
add_items.append(set(self.items[i]))
|
|
234
|
+
# Add in upper part of original Range
|
|
235
|
+
add_ranges.append(Range(intersect.upper_cut, self.upper_cuts[i]))
|
|
236
|
+
add_items.append(set(self.items[i]))
|
|
237
|
+
# Define original part to be middle
|
|
238
|
+
self.lower_cuts[i] = intersect.lower_cut
|
|
239
|
+
self.upper_cuts[i] = intersect.upper_cut
|
|
240
|
+
self.ranges[i] = Range(intersect.lower_cut, intersect.upper_cut)
|
|
241
|
+
self.items[i].add(val)
|
|
242
|
+
# Change the next lower cut
|
|
243
|
+
next_lower_cut = intersect.upper_cut
|
|
244
|
+
except ValueError:
|
|
245
|
+
# Continue if no overlap with this range
|
|
246
|
+
continue
|
|
247
|
+
# Put in a last range if necessary
|
|
248
|
+
if next_lower_cut < key.upper_cut:
|
|
249
|
+
add_ranges.append(Range(next_lower_cut, key.upper_cut))
|
|
250
|
+
add_items.append(val)
|
|
251
|
+
# Use recursive call to place the pairs, which now
|
|
252
|
+
# should not overlap with any other ranges
|
|
253
|
+
self.recurse_add = True
|
|
254
|
+
while len(add_ranges) > 0:
|
|
255
|
+
self.put(add_ranges.pop(), add_items.pop())
|
|
256
|
+
self.recurse_add = False
|
|
257
|
+
|
|
258
|
+
def remove(self, a_range):
|
|
259
|
+
""" Removes a range and its value(s) from the range set
|
|
260
|
+
|
|
261
|
+
Parameters
|
|
262
|
+
----------
|
|
263
|
+
a_range : A Range object
|
|
264
|
+
The Range to remove
|
|
265
|
+
|
|
266
|
+
Raises
|
|
267
|
+
------
|
|
268
|
+
ValueError
|
|
269
|
+
If removing range of type not compatible with previously
|
|
270
|
+
added ranges
|
|
271
|
+
TypeError
|
|
272
|
+
If not a Range
|
|
273
|
+
"""
|
|
274
|
+
if not isinstance(a_range, Range):
|
|
275
|
+
raise TypeError("a_range is not a Range")
|
|
276
|
+
elif a_range.is_empty():
|
|
277
|
+
# Skip if this is an empty range
|
|
278
|
+
return
|
|
279
|
+
# Check for compatibility of types if necessary
|
|
280
|
+
if len(self) > 0:
|
|
281
|
+
if not (issubclass(a_range.lower_cut.the_type,
|
|
282
|
+
self.ranges[0].lower_cut.the_type) or \
|
|
283
|
+
issubclass(self.ranges[0].lower_cut.the_type,
|
|
284
|
+
a_range.lower_cut.the_type)):
|
|
285
|
+
raise ValueError("Range not compatible with previously added ranges")
|
|
286
|
+
# Check if the range actually overlaps with the key set
|
|
287
|
+
if not self.overlaps(a_range):
|
|
288
|
+
return
|
|
289
|
+
else:
|
|
290
|
+
# There's some overlap, so deal with that
|
|
291
|
+
# Determine where overlap occurs
|
|
292
|
+
ovlap_lower_ind = max(bisect_left(self.lower_cuts,
|
|
293
|
+
a_range.lower_cut) - 1, 0)
|
|
294
|
+
ovlap_upper_ind = bisect_left(self.lower_cuts, a_range.upper_cut)
|
|
295
|
+
# Create queue of indices marked for removal
|
|
296
|
+
remove_ranges = deque()
|
|
297
|
+
# Create queue of ranges to add
|
|
298
|
+
add_ranges = deque()
|
|
299
|
+
# Create queue of items to add with the add_ranges
|
|
300
|
+
add_items = deque()
|
|
301
|
+
for i in range(ovlap_lower_ind, ovlap_upper_ind):
|
|
302
|
+
try:
|
|
303
|
+
# Get intersection of the ranges
|
|
304
|
+
intersect = a_range.intersection(self.ranges[i])
|
|
305
|
+
if not intersect.is_empty():
|
|
306
|
+
if intersect == self.ranges[i]:
|
|
307
|
+
# Mark range for removal
|
|
308
|
+
remove_ranges.append(i)
|
|
309
|
+
elif self.lower_cuts[i] == intersect.lower_cut:
|
|
310
|
+
# If equal on the left cutpoint, subtract
|
|
311
|
+
# out left part
|
|
312
|
+
self.lower_cuts[i] = intersect.upper_cut
|
|
313
|
+
self.ranges[i] = Range(intersect.upper_cut,
|
|
314
|
+
self.upper_cuts[i])
|
|
315
|
+
elif self.upper_cuts[i] == intersect.upper_cut:
|
|
316
|
+
# If equal on right cutpoint, subtract out
|
|
317
|
+
# right part
|
|
318
|
+
self.upper_cuts[i] = intersect.lower_cut
|
|
319
|
+
self.ranges[i] = Range(self.lower_cuts[i],
|
|
320
|
+
intersect.lower_cut)
|
|
321
|
+
else:
|
|
322
|
+
# If in the middle, split into two parts, putting
|
|
323
|
+
# both in add queue and placing the old range index
|
|
324
|
+
# into the remove queue
|
|
325
|
+
add_ranges.append(Range(self.lower_cuts[i],
|
|
326
|
+
intersect.lower_cut))
|
|
327
|
+
add_items.append(set(self.items[i]))
|
|
328
|
+
add_ranges.append(Range(intersect.upper_cut,
|
|
329
|
+
self.upper_cuts[i]))
|
|
330
|
+
add_items.append(set(self.items[i]))
|
|
331
|
+
remove_ranges.append(i)
|
|
332
|
+
except ValueError:
|
|
333
|
+
# Continue if no overlap with this range
|
|
334
|
+
continue
|
|
335
|
+
# Remove any ranges that are marked for removal
|
|
336
|
+
while len(remove_ranges) > 0:
|
|
337
|
+
remove_ind = remove_ranges.pop()
|
|
338
|
+
self.ranges.pop(remove_ind)
|
|
339
|
+
self.lower_cuts.pop(remove_ind)
|
|
340
|
+
self.upper_cuts.pop(remove_ind)
|
|
341
|
+
self.items.pop(remove_ind)
|
|
342
|
+
# Add any pairs that need to be added
|
|
343
|
+
self.recurse_add = True
|
|
344
|
+
while len(add_ranges) > 0:
|
|
345
|
+
self.put(add_ranges.pop(), add_items.pop())
|
|
346
|
+
self.recurse_add = False
|