xref: /aosp_15_r20/build/make/tools/releasetools/rangelib.py (revision 9e94795a3d4ef5c1d47486f9a02bb378756cea8a)
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