1*9e94795aSAndroid Build Coastguard Worker# Copyright (C) 2014 The Android Open Source Project 2*9e94795aSAndroid Build Coastguard Worker# 3*9e94795aSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 4*9e94795aSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 5*9e94795aSAndroid Build Coastguard Worker# You may obtain a copy of the License at 6*9e94795aSAndroid Build Coastguard Worker# 7*9e94795aSAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 8*9e94795aSAndroid Build Coastguard Worker# 9*9e94795aSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 10*9e94795aSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 11*9e94795aSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*9e94795aSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 13*9e94795aSAndroid Build Coastguard Worker# limitations under the License. 14*9e94795aSAndroid Build Coastguard Worker 15*9e94795aSAndroid Build Coastguard Workerfrom __future__ import print_function 16*9e94795aSAndroid Build Coastguard Worker 17*9e94795aSAndroid Build Coastguard Workerimport heapq 18*9e94795aSAndroid Build Coastguard Workerimport itertools 19*9e94795aSAndroid Build Coastguard Worker 20*9e94795aSAndroid Build Coastguard Worker 21*9e94795aSAndroid Build Coastguard Worker__all__ = ["RangeSet"] 22*9e94795aSAndroid Build Coastguard Worker 23*9e94795aSAndroid Build Coastguard Worker 24*9e94795aSAndroid Build Coastguard Workerclass RangeSet(object): 25*9e94795aSAndroid Build Coastguard Worker """A RangeSet represents a set of non-overlapping ranges on integers. 26*9e94795aSAndroid Build Coastguard Worker 27*9e94795aSAndroid Build Coastguard Worker Attributes: 28*9e94795aSAndroid Build Coastguard Worker monotonic: Whether the input has all its integers in increasing order. 29*9e94795aSAndroid Build Coastguard Worker extra: A dict that can be used by the caller, e.g. to store info that's 30*9e94795aSAndroid Build Coastguard Worker only meaningful to caller. 31*9e94795aSAndroid Build Coastguard Worker """ 32*9e94795aSAndroid Build Coastguard Worker 33*9e94795aSAndroid Build Coastguard Worker def __init__(self, data=None): 34*9e94795aSAndroid Build Coastguard Worker self.monotonic = False 35*9e94795aSAndroid Build Coastguard Worker self._extra = {} 36*9e94795aSAndroid Build Coastguard Worker if isinstance(data, str): 37*9e94795aSAndroid Build Coastguard Worker self._parse_internal(data) 38*9e94795aSAndroid Build Coastguard Worker elif data: 39*9e94795aSAndroid Build Coastguard Worker assert len(data) % 2 == 0 40*9e94795aSAndroid Build Coastguard Worker self.data = tuple(self._remove_pairs(data)) 41*9e94795aSAndroid Build Coastguard Worker self.monotonic = all(x < y for x, y in zip(self.data, self.data[1:])) 42*9e94795aSAndroid Build Coastguard Worker else: 43*9e94795aSAndroid Build Coastguard Worker self.data = () 44*9e94795aSAndroid Build Coastguard Worker 45*9e94795aSAndroid Build Coastguard Worker def __iter__(self): 46*9e94795aSAndroid Build Coastguard Worker for i in range(0, len(self.data), 2): 47*9e94795aSAndroid Build Coastguard Worker yield self.data[i:i+2] 48*9e94795aSAndroid Build Coastguard Worker 49*9e94795aSAndroid Build Coastguard Worker def __eq__(self, other): 50*9e94795aSAndroid Build Coastguard Worker return self.data == other.data 51*9e94795aSAndroid Build Coastguard Worker 52*9e94795aSAndroid Build Coastguard Worker def __ne__(self, other): 53*9e94795aSAndroid Build Coastguard Worker return self.data != other.data 54*9e94795aSAndroid Build Coastguard Worker 55*9e94795aSAndroid Build Coastguard Worker def __bool__(self): 56*9e94795aSAndroid Build Coastguard Worker return bool(self.data) 57*9e94795aSAndroid Build Coastguard Worker 58*9e94795aSAndroid Build Coastguard Worker # Python 2 uses __nonzero__, while Python 3 uses __bool__. 59*9e94795aSAndroid Build Coastguard Worker __nonzero__ = __bool__ 60*9e94795aSAndroid Build Coastguard Worker 61*9e94795aSAndroid Build Coastguard Worker def __str__(self): 62*9e94795aSAndroid Build Coastguard Worker if not self.data: 63*9e94795aSAndroid Build Coastguard Worker return "empty" 64*9e94795aSAndroid Build Coastguard Worker else: 65*9e94795aSAndroid Build Coastguard Worker return self.to_string() 66*9e94795aSAndroid Build Coastguard Worker 67*9e94795aSAndroid Build Coastguard Worker def __repr__(self): 68*9e94795aSAndroid Build Coastguard Worker return '<RangeSet("' + self.to_string() + '")>' 69*9e94795aSAndroid Build Coastguard Worker 70*9e94795aSAndroid Build Coastguard Worker @property 71*9e94795aSAndroid Build Coastguard Worker def extra(self): 72*9e94795aSAndroid Build Coastguard Worker return self._extra 73*9e94795aSAndroid Build Coastguard Worker 74*9e94795aSAndroid Build Coastguard Worker @classmethod 75*9e94795aSAndroid Build Coastguard Worker def parse(cls, text): 76*9e94795aSAndroid Build Coastguard Worker """Parses a text string into a RangeSet. 77*9e94795aSAndroid Build Coastguard Worker 78*9e94795aSAndroid Build Coastguard Worker The input text string consists of a space-separated list of blocks and 79*9e94795aSAndroid Build Coastguard Worker ranges, e.g. "10-20 30 35-40". Ranges are interpreted to include both their 80*9e94795aSAndroid Build Coastguard Worker ends (so the above example represents 18 individual blocks). Returns a 81*9e94795aSAndroid Build Coastguard Worker RangeSet object. 82*9e94795aSAndroid Build Coastguard Worker 83*9e94795aSAndroid Build Coastguard Worker If the input has all its blocks in increasing order, then the 'monotonic' 84*9e94795aSAndroid Build Coastguard Worker attribute of the returned RangeSet will be set to True. For example the 85*9e94795aSAndroid Build Coastguard Worker input "10-20 30" is monotonic, but the input "15-20 30 10-14" is not, even 86*9e94795aSAndroid Build Coastguard Worker though they represent the same set of blocks (and the two RangeSets will 87*9e94795aSAndroid Build Coastguard Worker compare equal with ==). 88*9e94795aSAndroid Build Coastguard Worker """ 89*9e94795aSAndroid Build Coastguard Worker return cls(text) 90*9e94795aSAndroid Build Coastguard Worker 91*9e94795aSAndroid Build Coastguard Worker @classmethod 92*9e94795aSAndroid Build Coastguard Worker def parse_raw(cls, text): 93*9e94795aSAndroid Build Coastguard Worker """Parse a string generated by RangeSet.to_string_raw(). 94*9e94795aSAndroid Build Coastguard Worker 95*9e94795aSAndroid Build Coastguard Worker >>> RangeSet.parse_raw(RangeSet("0-9").to_string_raw()) 96*9e94795aSAndroid Build Coastguard Worker <RangeSet("0-9")> 97*9e94795aSAndroid Build Coastguard Worker """ 98*9e94795aSAndroid Build Coastguard Worker 99*9e94795aSAndroid Build Coastguard Worker raw = [int(i) for i in text.split(',')] 100*9e94795aSAndroid Build Coastguard Worker assert raw[0] == len(raw[1:]), "Invalid raw string." 101*9e94795aSAndroid Build Coastguard Worker 102*9e94795aSAndroid Build Coastguard Worker return cls(data=raw[1:]) 103*9e94795aSAndroid Build Coastguard Worker 104*9e94795aSAndroid Build Coastguard Worker def _parse_internal(self, text): 105*9e94795aSAndroid Build Coastguard Worker data = [] 106*9e94795aSAndroid Build Coastguard Worker last = -1 107*9e94795aSAndroid Build Coastguard Worker monotonic = True 108*9e94795aSAndroid Build Coastguard Worker for p in text.split(): 109*9e94795aSAndroid Build Coastguard Worker if "-" in p: 110*9e94795aSAndroid Build Coastguard Worker s, e = (int(x) for x in p.split("-")) 111*9e94795aSAndroid Build Coastguard Worker data.append(s) 112*9e94795aSAndroid Build Coastguard Worker data.append(e+1) 113*9e94795aSAndroid Build Coastguard Worker if last <= s <= e: 114*9e94795aSAndroid Build Coastguard Worker last = e 115*9e94795aSAndroid Build Coastguard Worker else: 116*9e94795aSAndroid Build Coastguard Worker monotonic = False 117*9e94795aSAndroid Build Coastguard Worker else: 118*9e94795aSAndroid Build Coastguard Worker s = int(p) 119*9e94795aSAndroid Build Coastguard Worker data.append(s) 120*9e94795aSAndroid Build Coastguard Worker data.append(s+1) 121*9e94795aSAndroid Build Coastguard Worker if last <= s: 122*9e94795aSAndroid Build Coastguard Worker last = s+1 123*9e94795aSAndroid Build Coastguard Worker else: 124*9e94795aSAndroid Build Coastguard Worker monotonic = False 125*9e94795aSAndroid Build Coastguard Worker data.sort() 126*9e94795aSAndroid Build Coastguard Worker self.data = tuple(self._remove_pairs(data)) 127*9e94795aSAndroid Build Coastguard Worker self.monotonic = monotonic 128*9e94795aSAndroid Build Coastguard Worker 129*9e94795aSAndroid Build Coastguard Worker @staticmethod 130*9e94795aSAndroid Build Coastguard Worker def _remove_pairs(source): 131*9e94795aSAndroid Build Coastguard Worker """Remove consecutive duplicate items to simplify the result. 132*9e94795aSAndroid Build Coastguard Worker 133*9e94795aSAndroid Build Coastguard Worker [1, 2, 2, 5, 5, 10] will become [1, 10].""" 134*9e94795aSAndroid Build Coastguard Worker last = None 135*9e94795aSAndroid Build Coastguard Worker for i in source: 136*9e94795aSAndroid Build Coastguard Worker if i == last: 137*9e94795aSAndroid Build Coastguard Worker last = None 138*9e94795aSAndroid Build Coastguard Worker else: 139*9e94795aSAndroid Build Coastguard Worker if last is not None: 140*9e94795aSAndroid Build Coastguard Worker yield last 141*9e94795aSAndroid Build Coastguard Worker last = i 142*9e94795aSAndroid Build Coastguard Worker if last is not None: 143*9e94795aSAndroid Build Coastguard Worker yield last 144*9e94795aSAndroid Build Coastguard Worker 145*9e94795aSAndroid Build Coastguard Worker def to_string(self): 146*9e94795aSAndroid Build Coastguard Worker out = [] 147*9e94795aSAndroid Build Coastguard Worker for i in range(0, len(self.data), 2): 148*9e94795aSAndroid Build Coastguard Worker s, e = self.data[i:i+2] 149*9e94795aSAndroid Build Coastguard Worker if e == s+1: 150*9e94795aSAndroid Build Coastguard Worker out.append(str(s)) 151*9e94795aSAndroid Build Coastguard Worker else: 152*9e94795aSAndroid Build Coastguard Worker out.append(str(s) + "-" + str(e-1)) 153*9e94795aSAndroid Build Coastguard Worker return " ".join(out) 154*9e94795aSAndroid Build Coastguard Worker 155*9e94795aSAndroid Build Coastguard Worker def to_string_raw(self): 156*9e94795aSAndroid Build Coastguard Worker assert self.data 157*9e94795aSAndroid Build Coastguard Worker return str(len(self.data)) + "," + ",".join(str(i) for i in self.data) 158*9e94795aSAndroid Build Coastguard Worker 159*9e94795aSAndroid Build Coastguard Worker def union(self, other): 160*9e94795aSAndroid Build Coastguard Worker """Return a new RangeSet representing the union of this RangeSet 161*9e94795aSAndroid Build Coastguard Worker with the argument. 162*9e94795aSAndroid Build Coastguard Worker 163*9e94795aSAndroid Build Coastguard Worker >>> RangeSet("10-19 30-34").union(RangeSet("18-29")) 164*9e94795aSAndroid Build Coastguard Worker <RangeSet("10-34")> 165*9e94795aSAndroid Build Coastguard Worker >>> RangeSet("10-19 30-34").union(RangeSet("22 32")) 166*9e94795aSAndroid Build Coastguard Worker <RangeSet("10-19 22 30-34")> 167*9e94795aSAndroid Build Coastguard Worker """ 168*9e94795aSAndroid Build Coastguard Worker out = [] 169*9e94795aSAndroid Build Coastguard Worker z = 0 170*9e94795aSAndroid Build Coastguard Worker for p, d in heapq.merge(zip(self.data, itertools.cycle((+1, -1))), 171*9e94795aSAndroid Build Coastguard Worker zip(other.data, itertools.cycle((+1, -1)))): 172*9e94795aSAndroid Build Coastguard Worker if (z == 0 and d == 1) or (z == 1 and d == -1): 173*9e94795aSAndroid Build Coastguard Worker out.append(p) 174*9e94795aSAndroid Build Coastguard Worker z += d 175*9e94795aSAndroid Build Coastguard Worker return RangeSet(data=out) 176*9e94795aSAndroid Build Coastguard Worker 177*9e94795aSAndroid Build Coastguard Worker def intersect(self, other): 178*9e94795aSAndroid Build Coastguard Worker """Return a new RangeSet representing the intersection of this 179*9e94795aSAndroid Build Coastguard Worker RangeSet with the argument. 180*9e94795aSAndroid Build Coastguard Worker 181*9e94795aSAndroid Build Coastguard Worker >>> RangeSet("10-19 30-34").intersect(RangeSet("18-32")) 182*9e94795aSAndroid Build Coastguard Worker <RangeSet("18-19 30-32")> 183*9e94795aSAndroid Build Coastguard Worker >>> RangeSet("10-19 30-34").intersect(RangeSet("22-28")) 184*9e94795aSAndroid Build Coastguard Worker <RangeSet("")> 185*9e94795aSAndroid Build Coastguard Worker """ 186*9e94795aSAndroid Build Coastguard Worker out = [] 187*9e94795aSAndroid Build Coastguard Worker z = 0 188*9e94795aSAndroid Build Coastguard Worker for p, d in heapq.merge(zip(self.data, itertools.cycle((+1, -1))), 189*9e94795aSAndroid Build Coastguard Worker zip(other.data, itertools.cycle((+1, -1)))): 190*9e94795aSAndroid Build Coastguard Worker if (z == 1 and d == 1) or (z == 2 and d == -1): 191*9e94795aSAndroid Build Coastguard Worker out.append(p) 192*9e94795aSAndroid Build Coastguard Worker z += d 193*9e94795aSAndroid Build Coastguard Worker return RangeSet(data=out) 194*9e94795aSAndroid Build Coastguard Worker 195*9e94795aSAndroid Build Coastguard Worker def subtract(self, other): 196*9e94795aSAndroid Build Coastguard Worker """Return a new RangeSet representing subtracting the argument 197*9e94795aSAndroid Build Coastguard Worker from this RangeSet. 198*9e94795aSAndroid Build Coastguard Worker 199*9e94795aSAndroid Build Coastguard Worker >>> RangeSet("10-19 30-34").subtract(RangeSet("18-32")) 200*9e94795aSAndroid Build Coastguard Worker <RangeSet("10-17 33-34")> 201*9e94795aSAndroid Build Coastguard Worker >>> RangeSet("10-19 30-34").subtract(RangeSet("22-28")) 202*9e94795aSAndroid Build Coastguard Worker <RangeSet("10-19 30-34")> 203*9e94795aSAndroid Build Coastguard Worker """ 204*9e94795aSAndroid Build Coastguard Worker 205*9e94795aSAndroid Build Coastguard Worker out = [] 206*9e94795aSAndroid Build Coastguard Worker z = 0 207*9e94795aSAndroid Build Coastguard Worker for p, d in heapq.merge(zip(self.data, itertools.cycle((+1, -1))), 208*9e94795aSAndroid Build Coastguard Worker zip(other.data, itertools.cycle((-1, +1)))): 209*9e94795aSAndroid Build Coastguard Worker if (z == 0 and d == 1) or (z == 1 and d == -1): 210*9e94795aSAndroid Build Coastguard Worker out.append(p) 211*9e94795aSAndroid Build Coastguard Worker z += d 212*9e94795aSAndroid Build Coastguard Worker return RangeSet(data=out) 213*9e94795aSAndroid Build Coastguard Worker 214*9e94795aSAndroid Build Coastguard Worker def overlaps(self, other): 215*9e94795aSAndroid Build Coastguard Worker """Returns true if the argument has a nonempty overlap with this 216*9e94795aSAndroid Build Coastguard Worker RangeSet. 217*9e94795aSAndroid Build Coastguard Worker 218*9e94795aSAndroid Build Coastguard Worker >>> RangeSet("10-19 30-34").overlaps(RangeSet("18-32")) 219*9e94795aSAndroid Build Coastguard Worker True 220*9e94795aSAndroid Build Coastguard Worker >>> RangeSet("10-19 30-34").overlaps(RangeSet("22-28")) 221*9e94795aSAndroid Build Coastguard Worker False 222*9e94795aSAndroid Build Coastguard Worker """ 223*9e94795aSAndroid Build Coastguard Worker 224*9e94795aSAndroid Build Coastguard Worker # This is like intersect, but we can stop as soon as we discover the 225*9e94795aSAndroid Build Coastguard Worker # output is going to be nonempty. 226*9e94795aSAndroid Build Coastguard Worker z = 0 227*9e94795aSAndroid Build Coastguard Worker for _, d in heapq.merge(zip(self.data, itertools.cycle((+1, -1))), 228*9e94795aSAndroid Build Coastguard Worker zip(other.data, itertools.cycle((+1, -1)))): 229*9e94795aSAndroid Build Coastguard Worker if (z == 1 and d == 1) or (z == 2 and d == -1): 230*9e94795aSAndroid Build Coastguard Worker return True 231*9e94795aSAndroid Build Coastguard Worker z += d 232*9e94795aSAndroid Build Coastguard Worker return False 233*9e94795aSAndroid Build Coastguard Worker 234*9e94795aSAndroid Build Coastguard Worker def size(self): 235*9e94795aSAndroid Build Coastguard Worker """Returns the total size of the RangeSet (ie, how many integers 236*9e94795aSAndroid Build Coastguard Worker are in the set). 237*9e94795aSAndroid Build Coastguard Worker 238*9e94795aSAndroid Build Coastguard Worker >>> RangeSet("10-19 30-34").size() 239*9e94795aSAndroid Build Coastguard Worker 15 240*9e94795aSAndroid Build Coastguard Worker """ 241*9e94795aSAndroid Build Coastguard Worker 242*9e94795aSAndroid Build Coastguard Worker total = 0 243*9e94795aSAndroid Build Coastguard Worker for i, p in enumerate(self.data): 244*9e94795aSAndroid Build Coastguard Worker if i % 2: 245*9e94795aSAndroid Build Coastguard Worker total += p 246*9e94795aSAndroid Build Coastguard Worker else: 247*9e94795aSAndroid Build Coastguard Worker total -= p 248*9e94795aSAndroid Build Coastguard Worker return total 249*9e94795aSAndroid Build Coastguard Worker 250*9e94795aSAndroid Build Coastguard Worker def map_within(self, other): 251*9e94795aSAndroid Build Coastguard Worker """'other' should be a subset of 'self'. Returns a RangeSet 252*9e94795aSAndroid Build Coastguard Worker representing what 'other' would get translated to if the integers 253*9e94795aSAndroid Build Coastguard Worker of 'self' were translated down to be contiguous starting at zero. 254*9e94795aSAndroid Build Coastguard Worker 255*9e94795aSAndroid Build Coastguard Worker >>> RangeSet("0-9").map_within(RangeSet("3-4")) 256*9e94795aSAndroid Build Coastguard Worker <RangeSet("3-4")> 257*9e94795aSAndroid Build Coastguard Worker >>> RangeSet("10-19").map_within(RangeSet("13-14")) 258*9e94795aSAndroid Build Coastguard Worker <RangeSet("3-4")> 259*9e94795aSAndroid Build Coastguard Worker >>> RangeSet("10-19 30-39").map_within(RangeSet("17-19 30-32")) 260*9e94795aSAndroid Build Coastguard Worker <RangeSet("7-12")> 261*9e94795aSAndroid Build Coastguard Worker >>> RangeSet("10-19 30-39").map_within(RangeSet("12-13 17-19 30-32")) 262*9e94795aSAndroid Build Coastguard Worker <RangeSet("2-3 7-12")> 263*9e94795aSAndroid Build Coastguard Worker """ 264*9e94795aSAndroid Build Coastguard Worker 265*9e94795aSAndroid Build Coastguard Worker out = [] 266*9e94795aSAndroid Build Coastguard Worker offset = 0 267*9e94795aSAndroid Build Coastguard Worker start = None 268*9e94795aSAndroid Build Coastguard Worker for p, d in heapq.merge(zip(self.data, itertools.cycle((-5, +5))), 269*9e94795aSAndroid Build Coastguard Worker zip(other.data, itertools.cycle((-1, +1)))): 270*9e94795aSAndroid Build Coastguard Worker if d == -5: 271*9e94795aSAndroid Build Coastguard Worker start = p 272*9e94795aSAndroid Build Coastguard Worker elif d == +5: 273*9e94795aSAndroid Build Coastguard Worker offset += p-start 274*9e94795aSAndroid Build Coastguard Worker start = None 275*9e94795aSAndroid Build Coastguard Worker else: 276*9e94795aSAndroid Build Coastguard Worker out.append(offset + p - start) 277*9e94795aSAndroid Build Coastguard Worker return RangeSet(data=out) 278*9e94795aSAndroid Build Coastguard Worker 279*9e94795aSAndroid Build Coastguard Worker def extend(self, n): 280*9e94795aSAndroid Build Coastguard Worker """Extend the RangeSet by 'n' blocks. 281*9e94795aSAndroid Build Coastguard Worker 282*9e94795aSAndroid Build Coastguard Worker The lower bound is guaranteed to be non-negative. 283*9e94795aSAndroid Build Coastguard Worker 284*9e94795aSAndroid Build Coastguard Worker >>> RangeSet("0-9").extend(1) 285*9e94795aSAndroid Build Coastguard Worker <RangeSet("0-10")> 286*9e94795aSAndroid Build Coastguard Worker >>> RangeSet("10-19").extend(15) 287*9e94795aSAndroid Build Coastguard Worker <RangeSet("0-34")> 288*9e94795aSAndroid Build Coastguard Worker >>> RangeSet("10-19 30-39").extend(4) 289*9e94795aSAndroid Build Coastguard Worker <RangeSet("6-23 26-43")> 290*9e94795aSAndroid Build Coastguard Worker >>> RangeSet("10-19 30-39").extend(10) 291*9e94795aSAndroid Build Coastguard Worker <RangeSet("0-49")> 292*9e94795aSAndroid Build Coastguard Worker """ 293*9e94795aSAndroid Build Coastguard Worker out = self 294*9e94795aSAndroid Build Coastguard Worker for i in range(0, len(self.data), 2): 295*9e94795aSAndroid Build Coastguard Worker s, e = self.data[i:i+2] 296*9e94795aSAndroid Build Coastguard Worker s1 = max(0, s - n) 297*9e94795aSAndroid Build Coastguard Worker e1 = e + n 298*9e94795aSAndroid Build Coastguard Worker out = out.union(RangeSet(str(s1) + "-" + str(e1-1))) 299*9e94795aSAndroid Build Coastguard Worker return out 300*9e94795aSAndroid Build Coastguard Worker 301*9e94795aSAndroid Build Coastguard Worker def first(self, n): 302*9e94795aSAndroid Build Coastguard Worker """Return the RangeSet that contains at most the first 'n' integers. 303*9e94795aSAndroid Build Coastguard Worker 304*9e94795aSAndroid Build Coastguard Worker >>> RangeSet("0-9").first(1) 305*9e94795aSAndroid Build Coastguard Worker <RangeSet("0")> 306*9e94795aSAndroid Build Coastguard Worker >>> RangeSet("10-19").first(5) 307*9e94795aSAndroid Build Coastguard Worker <RangeSet("10-14")> 308*9e94795aSAndroid Build Coastguard Worker >>> RangeSet("10-19").first(15) 309*9e94795aSAndroid Build Coastguard Worker <RangeSet("10-19")> 310*9e94795aSAndroid Build Coastguard Worker >>> RangeSet("10-19 30-39").first(3) 311*9e94795aSAndroid Build Coastguard Worker <RangeSet("10-12")> 312*9e94795aSAndroid Build Coastguard Worker >>> RangeSet("10-19 30-39").first(15) 313*9e94795aSAndroid Build Coastguard Worker <RangeSet("10-19 30-34")> 314*9e94795aSAndroid Build Coastguard Worker >>> RangeSet("10-19 30-39").first(30) 315*9e94795aSAndroid Build Coastguard Worker <RangeSet("10-19 30-39")> 316*9e94795aSAndroid Build Coastguard Worker >>> RangeSet("0-9").first(0) 317*9e94795aSAndroid Build Coastguard Worker <RangeSet("")> 318*9e94795aSAndroid Build Coastguard Worker """ 319*9e94795aSAndroid Build Coastguard Worker 320*9e94795aSAndroid Build Coastguard Worker if self.size() <= n: 321*9e94795aSAndroid Build Coastguard Worker return self 322*9e94795aSAndroid Build Coastguard Worker 323*9e94795aSAndroid Build Coastguard Worker out = [] 324*9e94795aSAndroid Build Coastguard Worker for s, e in self: 325*9e94795aSAndroid Build Coastguard Worker if e - s >= n: 326*9e94795aSAndroid Build Coastguard Worker out += (s, s+n) 327*9e94795aSAndroid Build Coastguard Worker break 328*9e94795aSAndroid Build Coastguard Worker else: 329*9e94795aSAndroid Build Coastguard Worker out += (s, e) 330*9e94795aSAndroid Build Coastguard Worker n -= e - s 331*9e94795aSAndroid Build Coastguard Worker return RangeSet(data=out) 332*9e94795aSAndroid Build Coastguard Worker 333*9e94795aSAndroid Build Coastguard Worker def next_item(self): 334*9e94795aSAndroid Build Coastguard Worker """Return the next integer represented by the RangeSet. 335*9e94795aSAndroid Build Coastguard Worker 336*9e94795aSAndroid Build Coastguard Worker >>> list(RangeSet("0-9").next_item()) 337*9e94795aSAndroid Build Coastguard Worker [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 338*9e94795aSAndroid Build Coastguard Worker >>> list(RangeSet("10-19 3-5").next_item()) 339*9e94795aSAndroid Build Coastguard Worker [3, 4, 5, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] 340*9e94795aSAndroid Build Coastguard Worker >>> list(RangeSet("10-19 3 5 7").next_item()) 341*9e94795aSAndroid Build Coastguard Worker [3, 5, 7, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] 342*9e94795aSAndroid Build Coastguard Worker """ 343*9e94795aSAndroid Build Coastguard Worker for s, e in self: 344*9e94795aSAndroid Build Coastguard Worker for element in range(s, e): 345*9e94795aSAndroid Build Coastguard Worker yield element 346*9e94795aSAndroid Build Coastguard Worker 347*9e94795aSAndroid Build Coastguard Worker 348*9e94795aSAndroid Build Coastguard Workerif __name__ == "__main__": 349*9e94795aSAndroid Build Coastguard Worker import doctest 350*9e94795aSAndroid Build Coastguard Worker doctest.testmod() 351