├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── go.mod
├── go.sum
├── intervalset
├── intervalset.go
├── intervalset_immutable.go
└── intervalset_test.go
├── modinterval
├── modinterval.go
└── modinterval_test.go
└── timespanset
├── doc_test.go
├── timespanset.go
├── timespanset_interval.go
└── timespanset_test.go
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to Contribute
2 |
3 | We'd love to accept your patches and contributions to this project. There are
4 | just a few small guidelines you need to follow.
5 |
6 | ## Contributor License Agreement
7 |
8 | Contributions to this project must be accompanied by a Contributor License
9 | Agreement. You (or your employer) retain the copyright to your contribution,
10 | this simply gives us permission to use and redistribute your contributions as
11 | part of the project. Head over to to see
12 | your current agreements on file or to sign a new one.
13 |
14 | You generally only need to submit a CLA once, so if you've already submitted one
15 | (even if it was for a different project), you probably don't need to do it
16 | again.
17 |
18 | ## Code reviews
19 |
20 | All submissions, including submissions by project members, require review. We
21 | use GitHub pull requests for this purpose. Consult
22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
23 | information on using pull requests.
24 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # go-intervals
2 |
3 | go-intervals is a library for performing set operations on 1-dimensional
4 | intervals, such as time ranges.
5 |
6 | Example usage:
7 |
8 | ```go
9 | var tz = func() *time.Location {
10 | x, err := time.LoadLocation("PST8PDT")
11 | if err != nil {
12 | panic(fmt.Errorf("timezone not available: %v", err))
13 | }
14 | return x
15 | }()
16 |
17 | type span struct {
18 | start, end time.Time
19 | }
20 | week1 := &span{
21 | time.Date(2015, time.June, 1, 0, 0, 0, 0, tz),
22 | time.Date(2015, time.June, 8, 0, 0, 0, 0, tz),
23 | }
24 | week2 := &span{
25 | time.Date(2015, time.June, 8, 0, 0, 0, 0, tz),
26 | time.Date(2015, time.June, 15, 0, 0, 0, 0, tz),
27 | }
28 | week3 := &span{
29 | time.Date(2015, time.June, 15, 0, 0, 0, 0, tz),
30 | time.Date(2015, time.June, 22, 0, 0, 0, 0, tz),
31 | }
32 |
33 | set := timespanset.Empty()
34 | fmt.Printf("Empty set: %s\n", set)
35 |
36 | set.Insert(week1.start, week3.end)
37 | fmt.Printf("Week 1-3: %s\n", set)
38 |
39 | set2 := timespanset.Empty()
40 | set2.Insert(week2.start, week2.end)
41 | set.Sub(set2)
42 | fmt.Printf("Week 1-3 minus week 2: %s\n", set)
43 | ```
44 |
45 | produces
46 |
47 | Empty set: {}
48 | Week 1-3: {[2015-06-01 00:00:00 -0700 PDT, 2015-06-22 00:00:00 -0700 PDT)}
49 | Week 1-3 minus week 2: {[2015-06-01 00:00:00 -0700 PDT, 2015-06-08 00:00:00 -0700 PDT), [2015-06-15 00:00:00 -0700 PDT, 2015-06-22 00:00:00 -0700 PDT)}
50 |
51 | ## Notes
52 |
53 | - The intervalset.Set implementation's efficiency could be improved. Insertion
54 | is best- and worse-case O(n). It could be O(log(n)).
55 |
56 | - The library's types and interfaces are still evolving, so expect breaking
57 | changes.
58 |
59 | ## Disclaimer
60 |
61 | This is not an official Google product.
62 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/google/go-intervals
2 |
3 | // 1.12 was not chosen for any particular reason. This library was written in
4 | // 2017 and should work with fairly old Go releases.
5 | go 1.12
6 |
7 | require github.com/google/go-cmp v0.5.9
8 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
2 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
3 |
--------------------------------------------------------------------------------
/intervalset/intervalset.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // Package intervalset provides an abtraction for dealing with sets of
16 | // 1-dimensional spans, such as sets of time ranges. The Set type provides set
17 | // arithmetic and enumeration methods based on an Interval interface.
18 | //
19 | // DISCLAIMER: This library is not yet stable, so expect breaking changes.
20 | package intervalset
21 |
22 | import (
23 | "fmt"
24 | "sort"
25 | "strings"
26 | )
27 |
28 | // Interval is the interface for a continuous or discrete span. The interval is
29 | // assumed to be inclusive of the starting point and exclusive of the ending
30 | // point.
31 | //
32 | // All methods in the interface are non-destructive: Calls to the methods should
33 | // not modify the interval. Furthermore, the implementation assumes an interval
34 | // will not be mutated by user code, either.
35 | type Interval interface {
36 | // Intersect returns the intersection of an interval with another
37 | // interval. The function may panic if the other interval is incompatible.
38 | Intersect(Interval) Interval
39 |
40 | // Before returns true if the interval is completely before another interval.
41 | Before(Interval) bool
42 |
43 | // IsZero returns true for the zero value of an interval.
44 | IsZero() bool
45 |
46 | // Bisect returns two intervals, one on the lower side of x and one on the
47 | // upper side of x, corresponding to the subtraction of x from the original
48 | // interval. The returned intervals are always within the range of the
49 | // original interval.
50 | Bisect(x Interval) (Interval, Interval)
51 |
52 | // Adjoin returns the union of two intervals, if the intervals are exactly
53 | // adjacent, or the zero interval if they are not.
54 | Adjoin(Interval) Interval
55 |
56 | // Encompass returns an interval that covers the exact extents of two
57 | // intervals.
58 | Encompass(Interval) Interval
59 | }
60 |
61 | // Set is a set of interval objects used for
62 | type Set struct {
63 | //non-overlapping intervals
64 | intervals []Interval
65 | // factory is needed when the extents of the empty set are needed.
66 | factory intervalFactory
67 | }
68 |
69 | // SetInput is an interface implemented by Set and ImmutableSet. It is used when
70 | // one of these types type must take a set as an argument.
71 | type SetInput interface {
72 | // Extent returns the Interval defined by the minimum and maximum values of
73 | // the set.
74 | Extent() Interval
75 |
76 | // IntervalsBetween iterates over the intervals within extents set and calls f
77 | // with each. If f returns false, iteration ceases.
78 | //
79 | // Any interval within the set that overlaps partially with extents is truncated
80 | // before being passed to f.
81 | IntervalsBetween(extents Interval, f IntervalReceiver)
82 | }
83 |
84 | // NewSet returns a new set given a sorted slice of intervals. This function
85 | // panics if the intervals are not sorted.
86 | func NewSet(intervals []Interval) *Set {
87 | return NewSetV1(intervals, oldBehaviorFactory.makeZero)
88 | }
89 |
90 | // NewSetV1 returns a new set given a sorted slice of intervals. This function
91 | // panics if the intervals are not sorted.
92 | //
93 | // NewSetV1 will be renamed and will replace NewSet in the v1 release.
94 | func NewSetV1(intervals []Interval, makeZero func() Interval) *Set {
95 | if err := CheckSorted(intervals); err != nil {
96 | panic(err)
97 | }
98 | return &Set{intervals, makeIntervalFactor(makeZero)}
99 | }
100 |
101 | // CheckSorted checks that interval[i+1] is not before interval[i] for all
102 | // relevant elements of the input slice. Nil is returned when len(intervals) is
103 | // 0 or 1.
104 | func CheckSorted(intervals []Interval) error {
105 | for i := 0; i < len(intervals)-1; i++ {
106 | if !intervals[i].Before(intervals[i+1]) {
107 | return fmt.Errorf("!intervals[%d].Before(intervals[%d]) for %s, %s", i, i+1, intervals[i], intervals[i+1])
108 | }
109 | }
110 | return nil
111 | }
112 |
113 | // Empty returns a new, empty set of intervals.
114 | func Empty() *Set {
115 | return EmptyV1(oldBehaviorFactory.makeZero)
116 | }
117 |
118 | // EmptyV1 returns a new, empty set of intervals using the semantics of the V1
119 | // API, which will require a factory method for construction of an empty interval.
120 | func EmptyV1(makeZero func() Interval) *Set {
121 | return &Set{nil, makeIntervalFactor(makeZero)}
122 | }
123 |
124 | // Copy returns a copy of a set that may be mutated without affecting the original.
125 | func (s *Set) Copy() *Set {
126 | return &Set{append([]Interval(nil), s.intervals...), s.factory}
127 | }
128 |
129 | // String returns a human-friendly representation of the set.
130 | func (s *Set) String() string {
131 | var strs []string
132 | for _, x := range s.intervals {
133 | strs = append(strs, fmt.Sprintf("%s", x))
134 | }
135 | return fmt.Sprintf("{%s}", strings.Join(strs, ", "))
136 | }
137 |
138 | // Extent returns the Interval defined by the minimum and maximum values of the
139 | // set.
140 | func (s *Set) Extent() Interval {
141 | if len(s.intervals) == 0 {
142 | return s.factory.makeZero()
143 | }
144 | return s.intervals[0].Encompass(s.intervals[len(s.intervals)-1])
145 | }
146 |
147 | // Add adds all the elements of another set to this set.
148 | func (s *Set) Add(b SetInput) {
149 | // Deal with nil extent. See https://github.com/google/go-intervals/issues/6.
150 | bExtent := b.Extent()
151 | if bExtent == nil {
152 | return // no changes needed
153 | }
154 |
155 | // Loop through the intervals of x
156 | b.IntervalsBetween(bExtent, func(x Interval) bool {
157 | s.insert(x)
158 | return true
159 | })
160 | }
161 |
162 | // Contains reports whether an interval is entirely contained by the set.
163 | func (s *Set) Contains(ival Interval) bool {
164 | // Loop through the intervals of x
165 | next := s.iterator(ival, true)
166 | for setInterval := next(); setInterval != nil; setInterval = next() {
167 | left, right := ival.Bisect(setInterval)
168 | if !left.IsZero() {
169 | return false
170 | }
171 | ival = right
172 | }
173 | return ival.IsZero()
174 | }
175 |
176 | // adjoinOrAppend adds an interval to the end of intervals unless that value
177 | // directly adjoins the last element of intervals, in which case the last
178 | // element will be replaced by the adjoined interval.
179 | func adjoinOrAppend(intervals []Interval, x Interval) []Interval {
180 | lastIndex := len(intervals) - 1
181 | if lastIndex == -1 {
182 | return append(intervals, x)
183 | }
184 | adjoined := intervals[lastIndex].Adjoin(x)
185 | if adjoined.IsZero() {
186 | return append(intervals, x)
187 | }
188 | intervals[lastIndex] = adjoined
189 | return intervals
190 | }
191 |
192 | func (s *Set) insert(insertion Interval) {
193 | if s.Contains(insertion) {
194 | return
195 | }
196 | // TODO(reddaly): Something like Java's ArrayList would allow both O(log(n))
197 | // insertion and O(log(n)) lookup. For now, we have O(log(n)) lookup and O(n)
198 | // insertion.
199 | var newIntervals []Interval
200 | push := func(x Interval) {
201 | newIntervals = adjoinOrAppend(newIntervals, x)
202 | }
203 | inserted := false
204 | for _, x := range s.intervals {
205 | if inserted {
206 | push(x)
207 | continue
208 | }
209 | if insertion.Before(x) {
210 | push(insertion)
211 | push(x)
212 | inserted = true
213 | continue
214 | }
215 | // [===left===)[==x===)[===right===)
216 | left, right := insertion.Bisect(x)
217 | if !left.IsZero() {
218 | push(left)
219 | }
220 | push(x)
221 | // Replace the interval being inserted with the remaining portion of the
222 | // interval to be inserted.
223 | if right.IsZero() {
224 | inserted = true
225 | } else {
226 | insertion = right
227 | }
228 | }
229 | if !inserted {
230 | push(insertion)
231 | }
232 | s.intervals = newIntervals
233 | }
234 |
235 | // Sub destructively modifies the set by subtracting b.
236 | func (s *Set) Sub(b SetInput) {
237 | extent := s.Extent()
238 | // Deal with nil extent. See https://github.com/google/go-intervals/issues/6.
239 | if extent == nil {
240 | // Set is already empty, no changes necessary.
241 | return
242 | }
243 | var newIntervals []Interval
244 | push := func(x Interval) {
245 | newIntervals = adjoinOrAppend(newIntervals, x)
246 | }
247 | nextX := s.iterator(extent, true)
248 | nextY, cancel := setIntervalIterator(b, extent)
249 | defer cancel()
250 |
251 | x := nextX()
252 | y := nextY()
253 | for x != nil {
254 | // If y == nil, all of the remaining intervals in A are to the right of B,
255 | // so just yield them.
256 | if y == nil {
257 | push(x)
258 | x = nextX()
259 | continue
260 | }
261 | // Split x into parts left and right of y.
262 | // The diagrams below show the bisection results for various situations.
263 | // if left.IsZero() && !right.IsZero()
264 | // xxx
265 | // y1y1 y2y2 y3 y4y4
266 | // xxx
267 | // or
268 | // xxxxxxxxxxxx
269 | // y1y1 y2y2 y3 y4y4
270 | //
271 | // if !left.IsZero() && !right.IsZero()
272 | // x1x1x1x1x1
273 | // y1 y2
274 | //
275 | // if left.IsZero() && right.IsZero()
276 | // x1x1x1x1 x2x2x2
277 | // y1y1y1y1y1y1y1
278 | //
279 | // if !left.IsZero() && right.IsZero()
280 | // x1x1 x2
281 | // y1y1y1y1
282 | left, right := x.Bisect(y)
283 |
284 | // If the left side of x is non-zero, it can definitely be pushed to the
285 | // resulting interval set since no subsequent y value will intersect it.
286 | // The sequences look something like
287 | // x1x1x1x1x1 OR x1x1x1 x2
288 | // y1 y2 y1y1y1
289 | // left = x1x1 x1x1x1
290 | // right = x1x1 {zero}
291 | if !left.IsZero() {
292 | push(left)
293 | }
294 |
295 | if !right.IsZero() {
296 | // If the right side of x is non-zero:
297 | // 1) Right is the remaining portion of x that needs to be pushed.
298 | x = right
299 | // 2) It's not possible for current y to intersect it, so advance y. It's
300 | // possible nextY() will intersect it, so don't push yet.
301 | y = nextY()
302 | } else {
303 | // There's nothing left of x to push, so advance x.
304 | x = nextX()
305 | }
306 | }
307 |
308 | // Setting s.intervals is the only side effect in this function.
309 | s.intervals = newIntervals
310 | }
311 |
312 | // intersectionIterator returns a function that yields intervals that are
313 | // members of the intersection of s and b, in increasing order.
314 | func (s *Set) intersectionIterator(b SetInput) (iter func() Interval, cancel func()) {
315 | return intervalMapperToIterator(func(f IntervalReceiver) {
316 | sExtent, bExtent := s.Extent(), b.Extent()
317 | // Deal with nil extent. See https://github.com/google/go-intervals/issues/6.
318 | if sExtent == nil || bExtent == nil {
319 | // IF either set is already empty, the intersection is empty. This
320 | // voids a panic below where a valid Interval is needed for each
321 | // extent.
322 | return
323 | }
324 | nextX := s.iterator(bExtent, true)
325 | nextY, cancel := setIntervalIterator(b, sExtent)
326 | defer cancel()
327 |
328 | x := nextX()
329 | y := nextY()
330 | // Loop through corresponding intervals of S and B.
331 | // If y == nil, all of the remaining intervals in S are to the right of B.
332 | // If x == nil, all of the remaining intervals in B are to the right of S.
333 | for x != nil && y != nil {
334 | if x.Before(y) {
335 | x = nextX()
336 | continue
337 | }
338 | if y.Before(x) {
339 | y = nextY()
340 | continue
341 | }
342 | xyIntersect := x.Intersect(y)
343 | if !xyIntersect.IsZero() {
344 | if !f(xyIntersect) {
345 | return
346 | }
347 | _, right := x.Bisect(y)
348 | if !right.IsZero() {
349 | x = right
350 | } else {
351 | x = nextX()
352 | }
353 | }
354 | }
355 | })
356 | }
357 |
358 | // Intersect destructively modifies the set by intersectin it with b.
359 | func (s *Set) Intersect(b SetInput) {
360 | iter, cancel := s.intersectionIterator(b)
361 | defer cancel()
362 | var newIntervals []Interval
363 | for x := iter(); x != nil; x = iter() {
364 | newIntervals = append(newIntervals, x)
365 | }
366 | s.intervals = newIntervals
367 | }
368 |
369 | // searchLow returns the first index in s.intervals that is not before x.
370 | func (s *Set) searchLow(x Interval) int {
371 | return sort.Search(len(s.intervals), func(i int) bool {
372 | return !s.intervals[i].Before(x)
373 | })
374 | }
375 |
376 | // searchLow returns the index of the first interval in s.intervals that is
377 | // entirely after x.
378 | func (s *Set) searchHigh(x Interval) int {
379 | return sort.Search(len(s.intervals), func(i int) bool {
380 | return x.Before(s.intervals[i])
381 | })
382 | }
383 |
384 | // iterator returns a function that yields elements of the set in order.
385 | //
386 | // The function returned will return nil when finished iterating.
387 | func (s *Set) iterator(extents Interval, forward bool) func() Interval {
388 | low, high := s.searchLow(extents), s.searchHigh(extents)
389 |
390 | i, stride := low, 1
391 | if !forward {
392 | i, stride = high-1, -1
393 | }
394 |
395 | return func() Interval {
396 | if i < 0 || i >= len(s.intervals) {
397 | return nil
398 | }
399 | x := s.intervals[i]
400 | i += stride
401 | return x
402 | }
403 | }
404 |
405 | // IntervalReceiver is a function used for iterating over a set of intervals. It
406 | // takes the start and end times and returns true if the iteration should
407 | // continue.
408 | type IntervalReceiver func(Interval) bool
409 |
410 | // IntervalsBetween iterates over the intervals within extents set and calls f
411 | // with each. If f returns false, iteration ceases.
412 | //
413 | // Any interval within the set that overlaps partially with extents is truncated
414 | // before being passed to f.
415 | func (s *Set) IntervalsBetween(extents Interval, f IntervalReceiver) {
416 | // Begin = first index in s.intervals that is not before extents.
417 | begin := sort.Search(len(s.intervals), func(i int) bool {
418 | return !s.intervals[i].Before(extents)
419 | })
420 |
421 | // TODO(reddaly): Optimize this by performing a binary search for the ending
422 | // point.
423 | for _, interval := range s.intervals[begin:] {
424 | // If the interval is after the extents, there will be no more overlap, so
425 | // break out of the loop.
426 | if extents.Before(interval) {
427 | break
428 | }
429 | portionOfInterval := extents.Intersect(interval)
430 | if portionOfInterval.IsZero() {
431 | continue
432 | }
433 |
434 | if !f(portionOfInterval) {
435 | return
436 | }
437 | }
438 | }
439 |
440 | // Intervals iterates over all the intervals within the set and calls f with
441 | // each one. If f returns false, iteration ceases.
442 | func (s *Set) Intervals(f IntervalReceiver) {
443 | for _, interval := range s.intervals {
444 | if !f(interval) {
445 | return
446 | }
447 | }
448 | }
449 |
450 | // AllIntervals returns an ordered slice of all the intervals in the set.
451 | func (s *Set) AllIntervals() []Interval {
452 | return append(make([]Interval, 0, len(s.intervals)), s.intervals...)
453 | }
454 |
455 | // ImmutableSet returns an immutable copy of this set.
456 | func (s *Set) ImmutableSet() *ImmutableSet {
457 | return NewImmutableSet(s.AllIntervals())
458 | }
459 |
460 | // mapFn reports true if an iteration should continue. It is called on values of
461 | // a collection.
462 | type mapFn func(interface{}) bool
463 |
464 | // mapFn calls mapFn for each member of a collection.
465 | type mapperFn func(mapFn)
466 |
467 | // iteratorFn returns the next item in an iteration or the zero value. The
468 | // second return value indicates whether the first return value is a member of
469 | // the collection.
470 | type iteratorFn func() (interface{}, bool)
471 |
472 | // generatorFn returns an iterator.
473 | type generatorFn func() iteratorFn
474 |
475 | // cancelFn should be called to clean up the goroutine that would otherwise leak.
476 | type cancelFn func()
477 |
478 | // mapperToIterator returns an iteratorFn version of a mappingFn. The second
479 | // return value must be called at the end of iteration, or the underlying
480 | // goroutine will leak.
481 | func mapperToIterator(m mapperFn) (iteratorFn, cancelFn) {
482 | generatedValues := make(chan interface{}, 1)
483 | stopCh := make(chan interface{}, 1)
484 | go func() {
485 | m(func(obj interface{}) bool {
486 | select {
487 | case <-stopCh:
488 | return false
489 | case generatedValues <- obj:
490 | return true
491 | }
492 | })
493 | close(generatedValues)
494 | }()
495 | iter := func() (interface{}, bool) {
496 | value, ok := <-generatedValues
497 | return value, ok
498 | }
499 | return iter, func() {
500 | stopCh <- nil
501 | }
502 | }
503 |
504 | func intervalMapperToIterator(mapper func(IntervalReceiver)) (iter func() Interval, cancel func()) {
505 | genericMapper := func(m mapFn) {
506 | mapper(func(ival Interval) bool {
507 | return m(ival)
508 | })
509 | }
510 |
511 | genericIter, cancel := mapperToIterator(genericMapper)
512 | return func() Interval {
513 | genericVal, iterationEnded := genericIter()
514 | if !iterationEnded {
515 | return nil
516 | }
517 | ival, ok := genericVal.(Interval)
518 | if !ok {
519 | panic("unexpected value type, internal error")
520 | }
521 | return ival
522 | }, cancel
523 | }
524 |
525 | func setIntervalIterator(s SetInput, extent Interval) (iter func() Interval, cancel func()) {
526 | return intervalMapperToIterator(func(f IntervalReceiver) {
527 | s.IntervalsBetween(extent, f)
528 | })
529 | }
530 |
531 | // oldBehaviorFactory returns a nil interval. This was used before
532 | // construction of a Set/ImmutableSet required passing in a factory method for
533 | // creating a zero interval object.
534 | var oldBehaviorFactory = makeIntervalFactor(func() Interval { return nil })
535 |
536 | // intervalFactory is used to construct a zero-value interval. The zero value
537 | // interval may be different for different types of intervals, so a factory is
538 | // sometimes needed to write generic algorithms about intervals.
539 | type intervalFactory struct {
540 | makeZero func() Interval
541 | }
542 |
543 | func makeIntervalFactor(makeZero func() Interval) intervalFactory {
544 | return intervalFactory{makeZero}
545 | }
546 |
--------------------------------------------------------------------------------
/intervalset/intervalset_immutable.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package intervalset
16 |
17 | // ImmutableSet is a set of interval objects. It provides various set theory
18 | // operations.
19 | type ImmutableSet struct {
20 | set *Set
21 | }
22 |
23 | // NewImmutableSet returns a new set given a sorted slice of intervals. This
24 | // function panics if the intervals are not sorted.
25 | func NewImmutableSet(intervals []Interval) *ImmutableSet {
26 | return NewImmutableSetV1(intervals, oldBehaviorFactory.makeZero)
27 | }
28 |
29 | // NewImmutableSetV1 returns a new set given a sorted slice of intervals. This
30 | // function panics if the intervals are not sorted.
31 | func NewImmutableSetV1(intervals []Interval, makeZero func() Interval) *ImmutableSet {
32 | return &ImmutableSet{NewSetV1(intervals, makeZero)}
33 | }
34 |
35 | // String returns a human-friendly representation of the set.
36 | func (s *ImmutableSet) String() string {
37 | return s.set.String()
38 | }
39 |
40 | // Extent returns the Interval defined by the minimum and maximum values of the
41 | // set.
42 | func (s *ImmutableSet) Extent() Interval {
43 | return s.set.Extent()
44 | }
45 |
46 | // Contains reports whether an interval is entirely contained by the set.
47 | func (s *ImmutableSet) Contains(ival Interval) bool {
48 | return s.set.Contains(ival)
49 | }
50 |
51 | // Union returns a set with the contents of this set and another set.
52 | func (s *ImmutableSet) Union(b SetInput) *ImmutableSet {
53 | union := s.set.Copy()
54 | union.Add(b)
55 | return &ImmutableSet{union}
56 | }
57 |
58 | // Sub returns a set without the intervals of another set.
59 | func (s *ImmutableSet) Sub(b SetInput) *ImmutableSet {
60 | x := s.set.Copy()
61 | x.Sub(b)
62 | return &ImmutableSet{x}
63 | }
64 |
65 | // Intersect returns the intersection of two sets.
66 | func (s *ImmutableSet) Intersect(b SetInput) *ImmutableSet {
67 | x := s.set.Copy()
68 | x.Intersect(b)
69 | return &ImmutableSet{x}
70 | }
71 |
72 | // IntervalsBetween iterates over the intervals within extents set and calls f
73 | // with each. If f returns false, iteration ceases.
74 | //
75 | // Any interval within the set that overlaps partially with extents is truncated
76 | // before being passed to f.
77 | func (s *ImmutableSet) IntervalsBetween(extents Interval, f IntervalReceiver) {
78 | s.set.IntervalsBetween(extents, f)
79 | }
80 |
81 | // Intervals iterates over all the intervals within the set and calls f with
82 | // each one. If f returns false, iteration ceases.
83 | func (s *ImmutableSet) Intervals(f IntervalReceiver) {
84 | s.set.Intervals(f)
85 | }
86 |
--------------------------------------------------------------------------------
/intervalset/intervalset_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 | package intervalset
15 |
16 | import (
17 | "fmt"
18 | "reflect"
19 | "testing"
20 | )
21 |
22 | type span struct {
23 | min, max int
24 | }
25 |
26 | // case returns a *span from an Interval interface, or it panics.
27 | func cast(i Interval) *span {
28 | x, ok := i.(*span)
29 | if !ok {
30 | panic(fmt.Errorf("interval must be an span: %v", i))
31 | }
32 | return x
33 | }
34 |
35 | // zero returns the zero value for span.
36 | func zero() *span {
37 | return &span{}
38 | }
39 |
40 | func min(a, b int) int {
41 | if a < b {
42 | return a
43 | }
44 | return b
45 | }
46 |
47 | func max(a, b int) int {
48 | if a > b {
49 | return a
50 | }
51 | return b
52 | }
53 |
54 | func (s *span) String() string {
55 | return fmt.Sprintf("[%d, %d)", s.min, s.max)
56 | }
57 |
58 | func (s *span) Equal(t *span) bool {
59 | return s.min == t.min && s.max == t.max
60 | }
61 |
62 | // Intersect returns the intersection of an interval with another
63 | // interval. The function may panic if the other interval is incompatible.
64 | func (s *span) Intersect(tInt Interval) Interval {
65 | t := cast(tInt)
66 | result := &span{
67 | max(s.min, t.min),
68 | min(s.max, t.max),
69 | }
70 | if result.min < result.max {
71 | return result
72 | }
73 | return zero()
74 | }
75 |
76 | // Before returns true if the interval is completely before another interval.
77 | func (s *span) Before(tInt Interval) bool {
78 | t := cast(tInt)
79 | return s.max <= t.min
80 | }
81 |
82 | // IsZero returns true for the zero value of an interval.
83 | func (s *span) IsZero() bool {
84 | return s.min == 0 && s.max == 0
85 | }
86 |
87 | // Bisect returns two intervals, one on either lower side of x and one on the
88 | // upper side of x, corresponding to the subtraction of x from the original
89 | // interval. The returned intervals are always within the range of the
90 | // original interval.
91 | func (s *span) Bisect(tInt Interval) (Interval, Interval) {
92 | intersection := cast(s.Intersect(tInt))
93 | if intersection.IsZero() {
94 | if s.Before(tInt) {
95 | return s, zero()
96 | }
97 | return zero(), s
98 | }
99 | maybeZero := func(min, max int) *span {
100 | if min == max {
101 | return zero()
102 | }
103 | return &span{min, max}
104 | }
105 | return maybeZero(s.min, intersection.min), maybeZero(intersection.max, s.max)
106 |
107 | }
108 |
109 | // Adjoin returns the union of two intervals, if the intervals are exactly
110 | // adjacent, or the zero interval if they are not.
111 | func (s *span) Adjoin(tInt Interval) Interval {
112 | t := cast(tInt)
113 | if s.max == t.min {
114 | return &span{s.min, t.max}
115 | }
116 | if t.max == s.min {
117 | return &span{t.min, s.max}
118 | }
119 | return zero()
120 | }
121 |
122 | // Encompass returns an interval that covers the exact extents of two
123 | // intervals.
124 | func (s *span) Encompass(tInt Interval) Interval {
125 | t := cast(tInt)
126 | return &span{min(s.min, t.min), max(s.max, t.max)}
127 | }
128 |
129 | func TestExtent(t *testing.T) {
130 | x := &span{20, 40}
131 | y := &span{60, 100}
132 |
133 | ival := NewSet([]Interval{x, y})
134 | if got, want := cast(ival.Extent()), (&span{20, 100}); !got.Equal(want) {
135 | t.Errorf("Extent() = %v, want %v", got, want)
136 | }
137 | }
138 |
139 | func allIntervals(s SetInput) []*span {
140 | result := []*span{}
141 | s.IntervalsBetween(s.Extent(), func(x Interval) bool {
142 | result = append(result, cast(x))
143 | return true
144 | })
145 | return result
146 | }
147 |
148 | func TestAdd(t *testing.T) {
149 | x := NewSet([]Interval{&span{20, 40}})
150 | y := NewSet([]Interval{&span{60, 111}})
151 |
152 | if got, want := cast(x.Extent()), (&span{20, 40}); !got.Equal(want) {
153 | t.Errorf("Extent() = %v, want %v", got, want)
154 | }
155 |
156 | if got, want := cast(y.Extent()), (&span{60, 111}); !got.Equal(want) {
157 | t.Errorf("Extent() = %v, want %v", got, want)
158 | }
159 |
160 | x.Add(y)
161 |
162 | if got, want := cast(x.Extent()), (&span{20, 111}); !got.Equal(want) {
163 | t.Errorf("Extent() = %v, want %v", got, want)
164 | }
165 |
166 | for _, tt := range []struct {
167 | name string
168 | a *Set
169 | b SetInput
170 | want []*span
171 | }{
172 | {
173 | "empty + empty = empty",
174 | NewSet([]Interval{}),
175 | NewImmutableSet([]Interval{}),
176 | []*span{},
177 | },
178 | {
179 | "empty + [30,111) = [30, 111)",
180 | NewSet([]Interval{}),
181 | NewImmutableSet([]Interval{&span{30, 111}}),
182 | []*span{
183 | {30, 111},
184 | },
185 | },
186 | {
187 | "[20, 40) + empty = [20, 40)",
188 | NewSet([]Interval{&span{20, 40}}),
189 | NewImmutableSet([]Interval{}),
190 | []*span{
191 | {20, 40},
192 | },
193 | },
194 | {
195 | "[20, 40) + [60,111)",
196 | NewSet([]Interval{&span{20, 40}}),
197 | NewSet([]Interval{&span{60, 111}}),
198 | []*span{
199 | {20, 40},
200 | {60, 111},
201 | },
202 | },
203 | {
204 | "[20, 40) + [30,111) = [20, 111)",
205 | NewSet([]Interval{&span{20, 40}}),
206 | NewImmutableSet([]Interval{&span{30, 111}}),
207 | []*span{
208 | {20, 111},
209 | },
210 | },
211 | } {
212 | u := NewImmutableSet(tt.a.AllIntervals()).Union(tt.b)
213 | tt.a.Add(tt.b)
214 | if got := allIntervals(tt.a); !reflect.DeepEqual(got, tt.want) {
215 | t.Errorf("%s: got %v, want %v", tt.name, got, tt.want)
216 | }
217 | if got := allIntervals(u); !reflect.DeepEqual(got, tt.want) {
218 | t.Errorf("%s: [ImmutableSet] got %v, want %v", tt.name, got, tt.want)
219 | }
220 | }
221 | }
222 |
223 | func TestSub(t *testing.T) {
224 | for _, tt := range []struct {
225 | name string
226 | a *Set
227 | b SetInput
228 | want []*span
229 | }{
230 | {
231 | "empty - empty = empty",
232 | NewSet([]Interval{}),
233 | NewImmutableSet([]Interval{}),
234 | []*span{},
235 | },
236 | {
237 | "empty - [30,111) = empty",
238 | NewSet([]Interval{}),
239 | NewImmutableSet([]Interval{&span{30, 111}}),
240 | []*span{},
241 | },
242 | {
243 | "[20, 40) - empty = [20, 40)",
244 | NewSet([]Interval{&span{20, 40}}),
245 | NewImmutableSet([]Interval{}),
246 | []*span{
247 | {20, 40},
248 | },
249 | },
250 | {
251 | "[20, 40) - [30,111)",
252 | NewSet([]Interval{&span{20, 40}}),
253 | NewSet([]Interval{&span{30, 111}}),
254 | []*span{
255 | {20, 30},
256 | },
257 | },
258 | {
259 | "[0, 2) [4, 6) [8, 10) - [1, 2) [5, 6) [9, 10) = [0, 1) [4, 5) [8, 9)",
260 | NewSet([]Interval{&span{0, 2}, &span{4, 6}, &span{8, 10}}),
261 | NewSet([]Interval{&span{1, 2}, &span{5, 6}, &span{9, 10}}),
262 | []*span{{0, 1}, {4, 5}, {8, 9}},
263 | },
264 | {
265 | "[0...3)[10...13)...[90...93) - all odd numbers",
266 | func() *Set {
267 | spans := []Interval{}
268 | for i := 0; i < 100; i += 10 {
269 | spans = append(spans, &span{i, i + 3})
270 | }
271 | return NewSet(spans)
272 | }(),
273 | func() *Set {
274 | spans := []Interval{}
275 | for i := 1; i < 100; i += 2 {
276 | spans = append(spans, &span{i, i + 1})
277 | }
278 | return NewSet(spans)
279 | }(),
280 | func() []*span {
281 | spans := []*span{}
282 | for i := 0; i < 100; i += 10 {
283 | spans = append(spans, &span{i, i + 1}, &span{i + 2, i + 3})
284 | }
285 | return spans
286 | }(),
287 | },
288 | } {
289 | if got := allIntervals(tt.a.ImmutableSet().Sub(tt.b)); !reflect.DeepEqual(got, tt.want) {
290 | t.Errorf("%s: [ImmutableSet] got %v, want %v", tt.name, got, tt.want)
291 | }
292 | tt.a.Sub(tt.b)
293 | if got := allIntervals(tt.a); !reflect.DeepEqual(got, tt.want) {
294 | t.Errorf("%s: got %v, want %v", tt.name, got, tt.want)
295 | }
296 | }
297 | }
298 |
299 | func TestIntersect(t *testing.T) {
300 | for _, tt := range []struct {
301 | name string
302 | a *Set
303 | b SetInput
304 | want []*span
305 | }{
306 | {
307 | "empty intersect empty = empty",
308 | NewSet([]Interval{}),
309 | NewImmutableSet([]Interval{}),
310 | []*span{},
311 | },
312 | {
313 | "empty intersect [30,111) = empty",
314 | NewSet([]Interval{}),
315 | NewImmutableSet([]Interval{&span{30, 111}}),
316 | []*span{},
317 | },
318 | {
319 | "[20, 40) intersect empty = empty",
320 | NewSet([]Interval{&span{20, 40}}),
321 | NewImmutableSet([]Interval{}),
322 | []*span{},
323 | },
324 | {
325 | "[20, 40) intersect [30,111)",
326 | NewSet([]Interval{&span{20, 40}}),
327 | NewSet([]Interval{&span{30, 111}}),
328 | []*span{{30, 40}},
329 | },
330 | {
331 | "[0, 2) [4, 6) [8, 10) intersect [1, 2) [5, 6) [9, 10) = [1, 3) [5, 6) [9, 10)",
332 | NewSet([]Interval{&span{0, 2}, &span{4, 6}, &span{8, 10}}),
333 | NewSet([]Interval{&span{1, 2}, &span{5, 6}, &span{9, 10}}),
334 | []*span{{1, 2}, {5, 6}, {9, 10}},
335 | },
336 | {
337 | "[0, 2) [5, 7) intersect [5, 7) = [1, 2) [5, 6)",
338 | // [01...56...]
339 | // [.12345....]
340 | NewSet([]Interval{&span{0, 2}, &span{5, 7}}),
341 | NewSet([]Interval{&span{1, 6}}),
342 | []*span{{1, 2}, {5, 6}},
343 | },
344 | {
345 | "[0, 2) [5, 7) intersect [5, 7) = [1, 2) [5, 6)",
346 | NewSet([]Interval{&span{1, 6}}),
347 | NewSet([]Interval{&span{0, 2}, &span{5, 7}}),
348 | []*span{{1, 2}, {5, 6}},
349 | },
350 | {
351 | "[0...7)[10...17)...[90...97) intersect (all odd numbers + {4, 14, ... 94})",
352 | func() *Set {
353 | spans := []Interval{}
354 | for i := 0; i < 100; i += 10 {
355 | spans = append(spans, &span{i, i + 7})
356 | }
357 | return NewSet(spans)
358 | }(),
359 | func() *Set {
360 | spans := []Interval{}
361 | for i := 0; i < 100; i += 10 {
362 | spans = append(spans, &span{i + 1, i + 2}, &span{i + 3, i + 6}, &span{i + 7, i + 8}, &span{i + 9, i + 10})
363 | }
364 | return NewSet(spans)
365 | }(),
366 | func() []*span {
367 | spans := []*span{}
368 | for i := 0; i < 100; i += 10 {
369 | spans = append(spans, &span{i + 1, i + 2}, &span{i + 3, i + 6})
370 | }
371 | return spans
372 | }(),
373 | },
374 | } {
375 | if got := allIntervals(tt.a.ImmutableSet().Intersect(tt.b)); !reflect.DeepEqual(got, tt.want) {
376 | t.Errorf("%s: [ImmutableSet] got\n %v, want\n %v", tt.name, got, tt.want)
377 | }
378 | tt.a.Intersect(tt.b)
379 | if got := allIntervals(tt.a); !reflect.DeepEqual(got, tt.want) {
380 | t.Errorf("%s: got\n %v, want\n %v", tt.name, got, tt.want)
381 | }
382 | }
383 | }
384 |
385 | func TestContains(t *testing.T) {
386 | for _, tt := range []struct {
387 | name string
388 | set *Set
389 | elem *span
390 | want bool
391 | }{
392 | {
393 | name: "{} contains empty interval",
394 | set: NewSet([]Interval{}),
395 | elem: &span{},
396 | want: true,
397 | },
398 | {
399 | name: "{} does not contain [30,111)",
400 | set: NewSet([]Interval{}),
401 | elem: &span{30, 111},
402 | want: false,
403 | },
404 | {
405 | name: "[20, 40) contains empty interval",
406 | set: NewSet([]Interval{&span{20, 40}}),
407 | elem: &span{},
408 | want: true,
409 | },
410 | {
411 | name: "{[0, 5), [10, 15)} contains [0, 5)]",
412 | set: NewSet([]Interval{&span{0, 5}, &span{10, 15}}),
413 | elem: &span{0, 5},
414 | want: true,
415 | },
416 | {
417 | name: "{[0, 5), [10, 15)} does not contain [0, 6)]",
418 | set: NewSet([]Interval{&span{0, 5}, &span{10, 15}}),
419 | elem: &span{0, 6},
420 | want: false,
421 | },
422 | } {
423 | if got := tt.set.ImmutableSet().Contains(tt.elem); got != tt.want {
424 | t.Errorf("%s: [ImmutableSet] set.Contains(%s) = %t, want %t", tt.name, tt.elem, got, tt.want)
425 | }
426 | if got := tt.set.Contains(tt.elem); got != tt.want {
427 | t.Errorf("%s: set.Contains(%s) = %t, want %t", tt.name, tt.elem, got, tt.want)
428 | }
429 | }
430 | }
431 |
--------------------------------------------------------------------------------
/modinterval/modinterval.go:
--------------------------------------------------------------------------------
1 | // Package modinterval provides data structures and functions for working with
2 | // 1-dimensional integer intervals that use modular arithmetic.
3 | //
4 | // See https://fgiesen.wordpress.com/2015/09/24/intervals-in-modular-arithmetic/
5 | // for a discussion of intervals in modular arithmetic.
6 | //
7 | // The core type exported by the library is IntInterval, which is aware of its
8 | // modulus and has several methods for set-like operations, like Contains and
9 | // Expand*.
10 | //
11 | // IntIntervals can also be transformed into standard real-number-based
12 | // intervals; see the RealIntervals() method. An implementation of such
13 | // intervals is provided in this package as well.
14 | package modinterval
15 |
16 | import (
17 | "fmt"
18 | "strings"
19 | )
20 |
21 | // Modulus is a type for working with an integer modulus.
22 | type Modulus int
23 |
24 | // Int returns the modulus as a normal integer.
25 | func (m Modulus) Int() int { return int(m) }
26 |
27 | // GoModulo returns the result of performing the typical Go modulo operation
28 | // (%) on the argument with m as the modulus. For negative arguments, this
29 | // function may return negative values.
30 | func (m Modulus) GoModulo(a int) int { return a % m.Int() }
31 |
32 | // ArrayOffset returns the offset into an array of length m for the given
33 | // position designator according to these rules:
34 | //
35 | // 1) If 0 <= position and position < m, position will be returned as is.
36 | //
37 | // 2) Otherwise, if position > m, (position % m) will be returned.
38 | //
39 | // 3) If position < 0, (position % m) + m will be returned.
40 | //
41 | // Examples:
42 | //
43 | // Modulus(10).ArrayOffset(-1) returns 9.
44 | //
45 | // Modulus(10).ArrayOffset(-11) returns 9.
46 | //
47 | // Modulus(10).ArrayOffset(10) returns 0.
48 | //
49 | // Modulus(10).ArrayOffset(11) returns 1.
50 | //
51 | // Unlike the native Go modulus operator and m.GoModulus(a), ArrayOffset never
52 | // returns negative values.
53 | func (m Modulus) ArrayOffset(position int) int {
54 | maybeNegative := m.GoModulo(position)
55 | if maybeNegative < 0 {
56 | return maybeNegative + m.Int()
57 | }
58 | return maybeNegative
59 | }
60 |
61 | // IntervalSizeForward returns the size of an modular interval that starts at a
62 | // and ends at b using modulus m.
63 | //
64 | // First a and b are normalized using m.ArrayOffset.
65 | //
66 | // If a == b, 0 is returned. If a < b, b - a is returned. Otherwise, b - a + m
67 | // is returned.
68 | func (m Modulus) IntervalSizeForward(a, b int) int {
69 | return forwardDistance(m, m.ArrayOffset(a), m.ArrayOffset(b))
70 | }
71 |
72 | // IntervalSizeMin returns min(IntervalSizeForward(a, b), IntervalSizeForward(b, a)).
73 | func (m Modulus) IntervalSizeMin(a, b int) int {
74 | a = m.ArrayOffset(a)
75 | b = m.ArrayOffset(b)
76 | return intMin(forwardDistance(m, a, b), forwardDistance(m, b, a))
77 | }
78 |
79 | func forwardDistance(m Modulus, a, b int) int {
80 | if b < a {
81 | b += m.Int()
82 | }
83 | return b - a
84 | }
85 |
86 | // IntInterval is an integer interval.
87 | type IntInterval struct {
88 | modulus Modulus
89 | start intPos
90 | size intSize
91 | }
92 |
93 | // FromStartSizeInt returns an IntInterval from a starting location and a size.
94 | //
95 | // If size > m, size is set to m. If size < 0, FromStartSize panics.
96 | //
97 | // The returned interval is guaranteed to return a Start() value equal to
98 | // m.ArrayOffset(start), even if the size of the interval is 0 or >= m.
99 | func FromStartSizeInt(m Modulus, start, size int) IntInterval {
100 | if m < 0 {
101 | panic(fmt.Errorf("invalid modulus = %d is less than 0", m))
102 | }
103 | if size > m.Int() {
104 | size = m.Int()
105 | } else if size < 0 {
106 | panic(fmt.Errorf("invalid size = %d is less than 0", size))
107 | }
108 | return IntInterval{m, intPos(m.ArrayOffset(start)), intSize(size)}
109 | }
110 |
111 | func fromNonemptyStartEnd(m Modulus, start, end int) IntInterval {
112 | start = m.ArrayOffset(start)
113 | end = m.ArrayOffset(end)
114 | if start == end {
115 | return FromStartSizeInt(m, start, m.Int())
116 | }
117 | size := end - start
118 | if size > 0 {
119 | return FromStartSizeInt(m, start, size)
120 | }
121 | return FromStartSizeInt(m, start, m.Int()-start+end)
122 | }
123 |
124 | // String returns a string representation of the interval.
125 | func (iv IntInterval) String() string {
126 | if iv.IsEmpty() {
127 | return fmt.Sprintf("", iv.modulus)
128 | }
129 | var parts []string
130 | for _, part := range iv.RealIntervals() {
131 | parts = append(parts, part.String())
132 | }
133 | return fmt.Sprintf("", iv.modulus, strings.Join(parts, ", "))
134 | }
135 |
136 | // Size returns the number of integers in the interval.
137 | func (iv IntInterval) Size() int {
138 | return iv.size.int()
139 | }
140 |
141 | // Modulus returns the modulus used for the modulo arithmetic assumed by this
142 | // interval.
143 | func (iv IntInterval) Modulus() Modulus {
144 | return iv.modulus
145 | }
146 |
147 | // Start returns the first position in the interval. If the interval is empty,
148 | // the value returned by Start may be non-zero.
149 | func (iv IntInterval) Start() int {
150 | return iv.start.int() // already normalized
151 | }
152 |
153 | // End returns the end position in the interval. If the interval is empty, the
154 | // value returned by End may be non-zero.
155 | //
156 | // End may be less than Start if the interval wraps.
157 | //
158 | // End is equal to start for both the empty set and the complete set.
159 | func (iv IntInterval) End() int {
160 | return iv.modulus.ArrayOffset(iv.start.int() + iv.size.int())
161 | }
162 |
163 | // ExpandStart returns an interval that changes the Start position of the
164 | // interval so that it contains all of the arguments.
165 | //
166 | // Each position designator is transformed by iv.Modulus().ArrayOffset() before
167 | // being considered.
168 | //
169 | // If iv is empty, ExpandStart will use the 'start' parameter passed to
170 | // FromStartSize while expanding the set.
171 | func (iv IntInterval) ExpandStart(positionDesignator ...int) IntInterval {
172 | if iv.IsComplete() || len(positionDesignator) == 0 {
173 | return iv
174 | }
175 |
176 | minStart := iv.start.int()
177 | for _, val := range positionDesignator {
178 | offset := iv.Modulus().ArrayOffset(val)
179 | if offset >= iv.End() {
180 | // Make offset into a negative number for easier comparison... normalized again later.
181 | offset -= iv.Modulus().Int()
182 | }
183 |
184 | minStart = intMin(minStart, offset)
185 | }
186 |
187 | return FromStartSizeInt(iv.Modulus(), minStart, iv.End()-minStart)
188 | }
189 |
190 | // ExpandEnd returns an interval that changes the End position of the
191 | // interval so that it contains all of the arguments.
192 | //
193 | // Each position designator is transformed by iv.Modulus().ArrayOffset() before
194 | // being considered.
195 | //
196 | // If iv is empty, ExpandEnd will use the 'start' parameter passed to
197 | // FromStartSize while expanding the set.
198 | func (iv IntInterval) ExpandEnd(positionDesignator ...int) IntInterval {
199 | if iv.IsComplete() || len(positionDesignator) == 0 {
200 | return iv
201 | }
202 |
203 | origEnd := iv.End()
204 | maxEnd := origEnd
205 | for _, val := range positionDesignator {
206 | minEndToContainPosition := iv.Modulus().ArrayOffset(val + 1)
207 | if minEndToContainPosition < iv.Start() {
208 | // Make offset go beyond the allowable normalized length... normalized again later.
209 | minEndToContainPosition += iv.Modulus().Int()
210 | }
211 |
212 | maxEnd = intMax(maxEnd, minEndToContainPosition)
213 | }
214 |
215 | return FromStartSizeInt(iv.Modulus(), iv.Start(), maxEnd-iv.Start())
216 | }
217 |
218 | // ExpandMinimal returns a new interval that is expanded the minimal possible
219 | // amount so that it will contain all of its arguments.
220 | //
221 | // Each position designator is transformed by iv.Modulus().ArrayOffset() before
222 | // being considered.
223 | //
224 | // If iv is empty, ExpandMinimal will use the 'start' parameter passed to
225 | // FromStartSize while expanding the set.
226 | func (iv IntInterval) ExpandMinimal(positionDesignator ...int) IntInterval {
227 | a := iv.ExpandStart(positionDesignator...)
228 | b := iv.ExpandEnd(positionDesignator...)
229 | if a.Size() > b.Size() {
230 | return b
231 | }
232 | return a
233 | }
234 |
235 | // Contains reports true iff the integer set described by the interval contains
236 | // iv.Modulus().ArrayOffset(positionDesignator).
237 | func (iv IntInterval) Contains(positionDesignator int) bool {
238 | return iv.ContainsExactInt(iv.Modulus().ArrayOffset(positionDesignator))
239 | }
240 |
241 | // ContainsExactInt reports true iff the set described by the interval contains the
242 | // argument. The modulo operation will NOT be applied to the argument.
243 | func (iv IntInterval) ContainsExactInt(i int) bool {
244 | a, b := iv.realIntervals()
245 | return a.Contains(i) || b.Contains(i)
246 | }
247 |
248 | // realIntervals returns two intervals, either of which may be empty.
249 | //
250 | // The first return value always has the same start as iv and has a maximum
251 | // End() value of iv.modulus - 1.
252 | //
253 | // The second return value always has a start value of 0 (or is empty). The
254 | // second return value will be empty if the first return value has a start value
255 | // of 0.
256 | func (iv IntInterval) realIntervals() (sameStart, zeroStart RealIntInterval) {
257 | if iv.IsEmpty() {
258 | return RealIntInterval{}, RealIntInterval{}
259 | }
260 | sameStartSize := iv.Size()
261 | if max := iv.modulus.Int() - iv.Start(); sameStartSize > max {
262 | sameStartSize = max
263 | }
264 | sameStart = RealFromStartSize(iv.Start(), sameStartSize)
265 | zeroStart = RealFromStartSize(0, iv.Size()-sameStartSize)
266 | return sameStart, zeroStart
267 | }
268 |
269 | // IsEmpty returns true if Size() == 0.
270 | func (iv IntInterval) IsEmpty() bool {
271 | return iv.size == 0
272 | }
273 |
274 | // IsComplete returns true if Size() == iv.Modulus().Int().
275 | func (iv IntInterval) IsComplete() bool {
276 | return iv.Size() == iv.modulus.Int()
277 | }
278 |
279 | // EqualSets returns true if the interval contains exactly the same values as
280 | // another interval. The function ignored the modulus of the two intervals.
281 | func (iv IntInterval) EqualSets(other IntInterval) bool {
282 | sizesEqual := iv.Size() == other.Size()
283 | if !sizesEqual {
284 | return false
285 | }
286 | if iv.IsEmpty() {
287 | return true
288 | }
289 | a := iv.normalized()
290 | b := other.normalized()
291 |
292 | return a.Start() == b.Start()
293 | }
294 |
295 | func (iv IntInterval) normalized() IntInterval {
296 | if !iv.IsComplete() {
297 | return iv
298 | }
299 | return FromStartSizeInt(iv.Modulus(), 0, iv.Size())
300 | }
301 |
302 | // RealIntervals returns a set of intervals that together contain exactly the
303 | // same set of integers. The returned slice may be of length 0, 1, or 2.
304 | //
305 | // If the returned slice is of length 1 or 2, the start of the first interval in
306 | // the slice is always equal to iv.Start().
307 | func (iv IntInterval) RealIntervals() []RealIntInterval {
308 | a, b := iv.realIntervals()
309 | if a.IsEmpty() && b.IsEmpty() {
310 | return []RealIntInterval{}
311 | } else if b.IsEmpty() {
312 | return []RealIntInterval{a}
313 | }
314 | return []RealIntInterval{a, b}
315 | }
316 |
317 | // intPos is a position within an interval
318 | type intPos int
319 |
320 | func (p intPos) int() int { return int(p) }
321 |
322 | // intSize is the size of an interval.
323 | type intSize int
324 |
325 | func (p intSize) int() int { return int(p) }
326 |
327 | // boundaries describes both sides of an interval's inclusivity.
328 | type boundaries byte
329 |
330 | const (
331 | // [min, max]
332 | inclusiveInclusive boundaries = iota
333 | // [min, max)
334 | inclusiveExclusive
335 | // (min, max)
336 | exclusiveExclusive
337 |
338 | // [min, max]
339 | closedClosed = inclusiveInclusive
340 | // [min, max)
341 | closedOpen = inclusiveExclusive
342 | // (min, max)
343 | openOpen = exclusiveExclusive
344 | )
345 |
346 | func (b boundaries) formatSimpleInterval(min, max string) string {
347 | switch b {
348 | case inclusiveInclusive:
349 | return fmt.Sprintf("[%s, %s]", min, max)
350 | case inclusiveExclusive:
351 | return fmt.Sprintf("[%s, %s)", min, max)
352 | case exclusiveExclusive:
353 | return fmt.Sprintf("(%s, %s)", min, max)
354 | default:
355 | return fmt.Sprintf("", min, max)
356 | }
357 | }
358 |
359 | // RealIntInterval is an integer interval that does not use modular arithmetic.
360 | //
361 | // This type is used by functions of IntInterval that convert a modular interval
362 | // to zero, one, or two non-modular intervals.
363 | type RealIntInterval struct {
364 | start, size int
365 | }
366 |
367 | // RealEmpty returns the empty RealIntInterval.
368 | func RealEmpty() RealIntInterval { return RealIntInterval{} }
369 |
370 | // RealFromStartSize returns a non-modular interval from the given start and
371 | // size values.
372 | func RealFromStartSize(start, size int) RealIntInterval {
373 | return RealIntInterval{start, size}
374 | }
375 |
376 | // String returns a string representation of the interval. The empty interval
377 | // returns "[empty]".
378 | func (r RealIntInterval) String() string {
379 | return r.format(inclusiveInclusive)
380 | }
381 |
382 | // format uses the given boundary type to format the interval.
383 | func (r RealIntInterval) format(b boundaries) string {
384 | if r.IsEmpty() {
385 | return "[empty]"
386 | }
387 | low, high := func() (int, int) {
388 | switch b {
389 | case inclusiveInclusive:
390 | return r.Start(), r.End() - 1
391 | case inclusiveExclusive:
392 | return r.Start(), r.End()
393 | case exclusiveExclusive:
394 | return r.Start() + 1, r.End()
395 | default:
396 | return r.Start(), r.End()
397 | }
398 | }()
399 | return b.formatSimpleInterval(
400 | fmt.Sprintf("%d", low),
401 | fmt.Sprintf("%d", high))
402 | }
403 |
404 | // IsEmpty reports true iff r.Size() == 0.
405 | func (r RealIntInterval) IsEmpty() bool { return r.Size() == 0 }
406 |
407 | // Size returns the number of integers in the interval
408 | func (r RealIntInterval) Size() int { return r.size }
409 |
410 | // Contains returns true if the argument is within the interval.
411 | func (r RealIntInterval) Contains(i int) bool {
412 | return r.Start() <= i && i < r.End()
413 | }
414 |
415 | // Intersection returns the intersectino of r and another interval.
416 | func (r RealIntInterval) Intersection(other RealIntInterval) RealIntInterval {
417 | start := intMax(r.Start(), other.Start())
418 | end := intMax(r.End(), other.End())
419 | if end <= start {
420 | return RealEmpty()
421 | }
422 | return RealFromStartSize(start, end-start)
423 | }
424 |
425 | // Expand returns an interval that contains the given arguments
426 | func (r RealIntInterval) Expand(value ...int) RealIntInterval {
427 | if len(value) == 0 {
428 | return r
429 | }
430 | min, max := value[0], value[0]
431 | if !r.IsEmpty() {
432 | min, max = r.Start(), r.End()+1
433 | }
434 | for _, v := range value {
435 | if v < min {
436 | min = v
437 | }
438 | if v > max {
439 | max = v
440 | }
441 | }
442 | return RealIntInterval{start: min, size: max - min + 1}
443 | }
444 |
445 | // ContainsInterval returns true if the argument is within the interval.
446 | func (r RealIntInterval) ContainsInterval(other RealIntInterval) bool {
447 | return other.IsEmpty() || (r.Start() <= other.Start() && r.End() >= other.End())
448 | }
449 |
450 | // Start returns the inclusive starting position of the interval. The value
451 | // returned is undefined for an empty interval.
452 | func (r RealIntInterval) Start() int {
453 | return r.start
454 | }
455 |
456 | // End returns the exclusive ending position of the interval. The value returned
457 | // is undefined for an empty interval.
458 | func (r RealIntInterval) End() int {
459 | return r.start + r.size
460 | }
461 |
462 | // Add returns an interval shifted in a positive direction by offset.
463 | func (r RealIntInterval) Add(offset int) RealIntInterval {
464 | return RealFromStartSize(r.Start()+offset, r.Size())
465 | }
466 |
467 | func intMin(a, b int) int {
468 | if a < b {
469 | return a
470 | }
471 | return b
472 | }
473 |
474 | func intMinAll(values ...int) int {
475 | if len(values) == 0 {
476 | panic("cannot take min of no values")
477 | }
478 | best := values[0]
479 | for _, v := range values {
480 | if v < best {
481 | best = v
482 | }
483 | }
484 |
485 | return best
486 | }
487 |
488 | func intMax(a, b int) int {
489 | if a > b {
490 | return a
491 | }
492 | return b
493 | }
494 |
495 | func intMaxAll(values ...int) int {
496 | if len(values) == 0 {
497 | panic("cannot take max of no values")
498 | }
499 | best := values[0]
500 | for _, v := range values {
501 | if v > best {
502 | best = v
503 | }
504 | }
505 |
506 | return best
507 | }
508 |
--------------------------------------------------------------------------------
/modinterval/modinterval_test.go:
--------------------------------------------------------------------------------
1 | package modinterval
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 |
7 | "github.com/google/go-cmp/cmp"
8 | "github.com/google/go-cmp/cmp/cmpopts"
9 | )
10 |
11 | var cmpOpts = []cmp.Option{
12 | cmpopts.EquateEmpty(),
13 | cmp.Transformer("RealIntervalString", func(i RealIntInterval) string {
14 | return i.String()
15 | }),
16 | }
17 |
18 | func ExampleIntInterval_basics() {
19 | fmt.Printf("%s\n", FromStartSizeInt(Modulus(10), 9, 4))
20 | fmt.Printf("interval.Contains(2) = %v\n", FromStartSizeInt(Modulus(10), 9, 4).Contains(2))
21 | fmt.Printf("interval.Contains(9) = %v\n", FromStartSizeInt(Modulus(10), 9, 4).Contains(9))
22 | fmt.Printf("interval.Contains(3) = %v\n", FromStartSizeInt(Modulus(10), 9, 4).Contains(3))
23 | // Output:
24 | //
25 | // interval.Contains(2) = true
26 | // interval.Contains(9) = true
27 | // interval.Contains(3) = false
28 | }
29 |
30 | func ExampleIntInterval_RealIntervals() {
31 | modularInterval := FromStartSizeInt(Modulus(6), 5, 4)
32 | realIntervals := modularInterval.RealIntervals()
33 | fmt.Printf("%s\n", modularInterval)
34 | fmt.Printf(".RealIntervals()[0] = %s\n", realIntervals[0])
35 | fmt.Printf(".RealIntervals()[1] = %s\n", realIntervals[1])
36 | // Output:
37 | //
38 | // .RealIntervals()[0] = [5, 5]
39 | // .RealIntervals()[1] = [0, 2]
40 | }
41 |
42 | func ExampleIntInterval_RealIntervals_empty() {
43 | modularInterval := FromStartSizeInt(Modulus(100), 5, 0)
44 | fmt.Printf("{empty}.RealIntervals() has length %d", len(modularInterval.RealIntervals()))
45 | // Output:
46 | // {empty}.RealIntervals() has length 0
47 | }
48 |
49 | func ExampleIntInterval_RealIntervals_nonwrapping() {
50 | m := FromStartSizeInt(Modulus(100), 5, 20)
51 | r := m.RealIntervals()
52 | fmt.Printf(".RealIntervals() has length %d: %s", len(r), r[0])
53 | // Output:
54 | // .RealIntervals() has length 1: [5, 24]
55 | }
56 |
57 | func ExampleModulus_ArrayOffset() {
58 | for _, tt := range []struct {
59 | m Modulus
60 | arg int
61 | }{
62 | {10, -1},
63 | {10, -11},
64 | {10, 1},
65 | {10, 10},
66 | {10, 11},
67 | } {
68 | fmt.Printf("Modulus(%d).ArrayOffset(%d) = %d\n", tt.m, tt.arg, tt.m.ArrayOffset(tt.arg))
69 | }
70 | // Output:
71 | // Modulus(10).ArrayOffset(-1) = 9
72 | // Modulus(10).ArrayOffset(-11) = 9
73 | // Modulus(10).ArrayOffset(1) = 1
74 | // Modulus(10).ArrayOffset(10) = 0
75 | // Modulus(10).ArrayOffset(11) = 1
76 | }
77 |
78 | func ExampleModulus_IntervalSizeForward() {
79 | for _, tt := range []struct {
80 | m Modulus
81 | a, b int
82 | }{
83 | {m: 10, a: 10, b: 14},
84 | {m: 10, a: 9, b: 7},
85 | {m: 10, a: 10, b: 100},
86 | } {
87 | m, a, b := tt.m, tt.a, tt.b
88 | fmt.Printf("Modulus(%d).IntervalSizeForward(%d, %d) = %d\n", m, a, b, m.IntervalSizeForward(a, b))
89 | fmt.Printf("Modulus(%d).IntervalSizeMin(%d, %d) = %d\n", m, a, b, m.IntervalSizeMin(a, b))
90 | }
91 | // Output:
92 | // Modulus(10).IntervalSizeForward(10, 14) = 4
93 | // Modulus(10).IntervalSizeMin(10, 14) = 4
94 | // Modulus(10).IntervalSizeForward(9, 7) = 8
95 | // Modulus(10).IntervalSizeMin(9, 7) = 2
96 | // Modulus(10).IntervalSizeForward(10, 100) = 0
97 | // Modulus(10).IntervalSizeMin(10, 100) = 0
98 | }
99 |
100 | func TestIntInterval(t *testing.T) {
101 | type containsCase struct {
102 | arg int
103 | want bool
104 | }
105 | type expandStartCase struct {
106 | args []int
107 | want IntInterval
108 | }
109 |
110 | for _, tt := range []struct {
111 | name string
112 | iv IntInterval
113 | wantString string
114 | wantIsEmpty bool
115 | wantIsComplete bool
116 | wantSize int
117 | wantStart int
118 | wantEnd int
119 | wantRealIntervals []RealIntInterval
120 | containsCases []containsCase
121 | containsExactIntCases []containsCase
122 | expandStartCases []expandStartCase
123 | }{
124 | {
125 | name: "empty",
126 | iv: FromStartSizeInt(10, 0, 0),
127 | wantString: "",
128 | wantSize: 0,
129 | wantIsEmpty: true,
130 | containsExactIntCases: []containsCase{
131 | {arg: 0, want: false},
132 | {arg: -1, want: false},
133 | },
134 | expandStartCases: []expandStartCase{
135 | {
136 | args: []int{3, 1},
137 | want: FromStartSizeInt(10, 1, 9),
138 | },
139 | {
140 | args: []int{1, 3},
141 | want: FromStartSizeInt(10, 1, 9),
142 | },
143 | {
144 | args: []int{},
145 | want: FromStartSizeInt(10, 0, 0),
146 | },
147 | },
148 | },
149 | {
150 | name: "from 3 size 0",
151 | iv: FromStartSizeInt(7, 3, 0),
152 | wantString: "",
153 | wantSize: 0,
154 | wantIsEmpty: true,
155 | wantStart: 3,
156 | wantEnd: 3,
157 | },
158 | {
159 | name: "from 3 size 0 expanded",
160 | iv: FromStartSizeInt(7, 3, 0).ExpandEnd(),
161 | wantString: "",
162 | wantSize: 0,
163 | wantIsEmpty: true,
164 | wantStart: 3,
165 | wantEnd: 3,
166 | },
167 | {
168 | name: "from 3 size 0 expanded 2",
169 | iv: FromStartSizeInt(7, 3, 0).ExpandStart(),
170 | wantString: "",
171 | wantSize: 0,
172 | wantIsEmpty: true,
173 | wantStart: 3,
174 | wantEnd: 3,
175 | },
176 | {
177 | name: "from -1 size 0",
178 | iv: FromStartSizeInt(7, -1, 0),
179 | wantString: "",
180 | wantSize: 0,
181 | wantIsEmpty: true,
182 | wantStart: 6,
183 | wantEnd: 6,
184 | },
185 | {
186 | name: "345",
187 | iv: FromStartSizeInt(10, 3, 3),
188 | wantString: "",
189 | wantSize: 3,
190 | wantIsEmpty: false,
191 | wantStart: 3,
192 | wantEnd: 6,
193 | wantRealIntervals: []RealIntInterval{
194 | RealEmpty().Expand(3, 5),
195 | },
196 | containsExactIntCases: []containsCase{
197 | {arg: 2, want: false},
198 | {arg: 3, want: true},
199 | {arg: 4, want: true},
200 | {arg: 5, want: true},
201 | {arg: 6, want: false},
202 | },
203 | expandStartCases: []expandStartCase{
204 | {args: []int{6}, want: FromStartSizeInt(10, 0, 10)},
205 | {args: []int{-2}, want: FromStartSizeInt(10, 8, 8)},
206 | {args: []int{-1}, want: FromStartSizeInt(10, 9, 7)},
207 | {args: []int{0}, want: FromStartSizeInt(10, 0, 6)},
208 | {args: []int{0, -1, -2, 3, 4, 5, 0}, want: FromStartSizeInt(10, 8, 8)},
209 | },
210 | },
211 | {
212 | name: "90123",
213 | iv: FromStartSizeInt(10, 9, 5),
214 | wantString: "",
215 | wantSize: 5,
216 | wantIsEmpty: false,
217 | wantStart: 9,
218 | wantEnd: 4,
219 | wantRealIntervals: []RealIntInterval{
220 | RealEmpty().Expand(9),
221 | RealEmpty().Expand(0, 1, 2, 3),
222 | },
223 | containsExactIntCases: []containsCase{
224 | {arg: -1, want: false},
225 | {arg: 0, want: true},
226 | {arg: 1, want: true},
227 | {arg: 2, want: true},
228 | {arg: 3, want: true},
229 | {arg: 4, want: false},
230 | {arg: 5, want: false},
231 | {arg: 6, want: false},
232 | {arg: 7, want: false},
233 | {arg: 8, want: false},
234 | {arg: 9, want: true},
235 | {arg: 10, want: false},
236 | },
237 | },
238 | {
239 | name: "complete 012",
240 | iv: FromStartSizeInt(3, 0, 50),
241 | wantString: "",
242 | wantSize: 3,
243 | wantIsEmpty: false,
244 | wantIsComplete: true,
245 | wantStart: 0,
246 | wantEnd: 0,
247 | wantRealIntervals: []RealIntInterval{
248 | RealEmpty().Expand(0, 2),
249 | },
250 | containsExactIntCases: []containsCase{
251 | {arg: -1, want: false},
252 | {arg: 0, want: true},
253 | {arg: 1, want: true},
254 | {arg: 2, want: true},
255 | {arg: 3, want: false},
256 | {arg: 4, want: false},
257 | },
258 | },
259 | {
260 | name: "567 expand start(4, 2)",
261 | iv: FromStartSizeInt(10, 5, 3).ExpandStart(4, 2),
262 | wantString: "",
263 | wantSize: 6,
264 | wantStart: 2,
265 | wantEnd: 8,
266 | wantIsEmpty: false,
267 | wantRealIntervals: []RealIntInterval{
268 | RealFromStartSize(2, 6),
269 | },
270 | },
271 | } {
272 | t.Run(fmt.Sprintf("%s - %s", tt.name, tt.iv.String()), func(t *testing.T) {
273 | if want, got := tt.wantString, tt.iv.String(); got != want {
274 | t.Errorf("String() got %q, want %q", got, want)
275 | }
276 | if want, got := tt.wantIsEmpty, tt.iv.IsEmpty(); got != want {
277 | t.Errorf("IsEmpty() got %v, want %v", got, want)
278 | }
279 | if want, got := tt.wantIsComplete, tt.iv.IsComplete(); got != want {
280 | t.Errorf("IsComplete() got %v, want %v", got, want)
281 | }
282 | if want, got := tt.wantSize, tt.iv.Size(); got != want {
283 | t.Errorf("Size() got %v, want %v", got, want)
284 | }
285 | if want, got := tt.wantStart, tt.iv.Start(); got != want {
286 | t.Errorf("Start() got %v, want %v", got, want)
287 | }
288 | if want, got := tt.wantEnd, tt.iv.End(); got != want {
289 | t.Errorf("End() got %v, want %v", got, want)
290 | }
291 | if diff := cmp.Diff(tt.wantRealIntervals, tt.iv.RealIntervals(), cmpOpts...); diff != "" {
292 | t.Errorf("unexpected diff in RealIntervals() (-want +got):\n%s", diff)
293 | }
294 | for _, ttt := range tt.containsCases {
295 | if want, got := ttt.want, tt.iv.Contains(ttt.arg); got != want {
296 | t.Errorf("Contains(%v) got %v, want %v", ttt.arg, got, want)
297 | }
298 | }
299 | for _, ttt := range tt.containsExactIntCases {
300 | if want, got := ttt.want, tt.iv.ContainsExactInt(ttt.arg); got != want {
301 | t.Errorf("ContainsExactInt(%v) got %v, want %v", ttt.arg, got, want)
302 | }
303 | }
304 | for _, ttt := range tt.expandStartCases {
305 | if want, got := ttt.want, tt.iv.ExpandStart(ttt.args...); !got.EqualSets(want) {
306 | t.Errorf("%v.ExpandStart(%v) got %v, want %v", tt.iv, ttt.args, got, want)
307 | }
308 | }
309 | })
310 | }
311 | }
312 |
313 | func TestIntIntervalEquality(t *testing.T) {
314 | for _, tt := range []struct {
315 | name string
316 | a, b IntInterval
317 | wantEqualSets bool
318 | }{
319 | {
320 | name: "empty",
321 | a: FromStartSizeInt(10, 0, 0),
322 | b: FromStartSizeInt(10, 0, 0),
323 | wantEqualSets: true,
324 | },
325 | {
326 | name: "empty different modulus values",
327 | a: FromStartSizeInt(10, 0, 0),
328 | b: FromStartSizeInt(7, 0, 0),
329 | wantEqualSets: true,
330 | },
331 | {
332 | name: "complete different modulus values",
333 | a: FromStartSizeInt(10, 0, 10),
334 | b: FromStartSizeInt(7, 0, 7),
335 | wantEqualSets: false,
336 | },
337 | {
338 | name: "complete different start values",
339 | a: FromStartSizeInt(7, 4, 7),
340 | b: FromStartSizeInt(7, 0, 7),
341 | wantEqualSets: true,
342 | },
343 | {
344 | name: "ExpandStart order for empty set",
345 | a: FromStartSizeInt(7, 3, 0).ExpandStart(2),
346 | b: FromStartSizeInt(7, 2, 1),
347 | wantEqualSets: true,
348 | },
349 | {
350 | name: "ExpandEnd basic",
351 | a: FromStartSizeInt(7, 0, 1).ExpandEnd(5),
352 | b: FromStartSizeInt(7, 0, 6),
353 | wantEqualSets: true,
354 | },
355 | {
356 | name: "ExpandEnd does nothing when passed no args",
357 | a: FromStartSizeInt(7, 3, 0).ExpandEnd(),
358 | b: FromStartSizeInt(7, 3, 0),
359 | wantEqualSets: true,
360 | },
361 | } {
362 | t.Run(fmt.Sprintf("%s", tt.name), func(t *testing.T) {
363 | if want, got := tt.wantEqualSets, tt.a.EqualSets(tt.b); got != want {
364 | t.Errorf("%s.EqualSets(%s) = %v, want %v", tt.a, tt.b, got, want)
365 | }
366 | })
367 | }
368 | }
369 |
370 | func TestRealIntInterval(t *testing.T) {
371 | type containsCase struct {
372 | arg int
373 | want bool
374 | }
375 | type containsIntervalCase struct {
376 | arg RealIntInterval
377 | want bool
378 | }
379 | type endCase struct {
380 | want int
381 | }
382 | type expandCase struct {
383 | values []int
384 | want RealIntInterval
385 | }
386 | type isEmptyCase struct {
387 | want bool
388 | }
389 | type sizeCase struct {
390 | want int
391 | }
392 | type startCase struct {
393 | want int
394 | }
395 |
396 | for _, tt := range []struct {
397 | name string
398 | iv RealIntInterval
399 | wantString string
400 | wantIsEmpty bool
401 | wantSize int
402 | wantStart int
403 | wantEnd int
404 | containsCases []containsCase
405 | containsIntervalCases []containsIntervalCase
406 | expandCases []expandCase
407 | }{
408 | {
409 | name: "empty",
410 | iv: RealEmpty(),
411 | wantString: "[empty]",
412 | wantSize: 0,
413 | wantIsEmpty: true,
414 | containsCases: []containsCase{
415 | {arg: 0, want: false},
416 | {arg: -1, want: false},
417 | },
418 | },
419 | {
420 | name: "345",
421 | iv: RealFromStartSize(3, 3),
422 | wantString: "[3, 5]",
423 | wantSize: 3,
424 | wantIsEmpty: false,
425 | wantStart: 3,
426 | wantEnd: 6,
427 | containsCases: []containsCase{
428 | {arg: 2, want: false},
429 | {arg: 3, want: true},
430 | {arg: 4, want: true},
431 | {arg: 5, want: true},
432 | {arg: 6, want: false},
433 | },
434 | containsIntervalCases: []containsIntervalCase{
435 | {RealFromStartSize(2, 2), false},
436 | {RealFromStartSize(3, 2), true},
437 | {RealFromStartSize(4, 2), true},
438 | {RealFromStartSize(5, 2), false},
439 | {RealFromStartSize(3, 3), true},
440 | },
441 | },
442 | } {
443 | t.Run(fmt.Sprintf("%s - %s", tt.name, tt.iv.String()), func(t *testing.T) {
444 | if want, got := tt.wantString, tt.iv.String(); got != want {
445 | t.Errorf("String() got %q, want %q", got, want)
446 | }
447 | if want, got := tt.wantIsEmpty, tt.iv.IsEmpty(); got != want {
448 | t.Errorf("IsEmpty() got %v, want %v", got, want)
449 | }
450 | if want, got := tt.wantSize, tt.iv.Size(); got != want {
451 | t.Errorf("Size() got %v, want %v", got, want)
452 | }
453 | if want, got := tt.wantStart, tt.iv.Start(); got != want {
454 | t.Errorf("Start() got %v, want %v", got, want)
455 | }
456 | if want, got := tt.wantEnd, tt.iv.End(); got != want {
457 | t.Errorf("End() got %v, want %v", got, want)
458 | }
459 | for _, ttt := range tt.containsCases {
460 | if want, got := ttt.want, tt.iv.Contains(ttt.arg); got != want {
461 | t.Errorf("Contains(%v) got %v, want %v", ttt.arg, got, want)
462 | }
463 | }
464 | for _, ttt := range tt.containsIntervalCases {
465 | if want, got := ttt.want, tt.iv.ContainsInterval(ttt.arg); got != want {
466 | t.Errorf("ContainsInterval(%v) got %v, want %v", ttt.arg, got, want)
467 | }
468 | }
469 | })
470 | }
471 | }
472 |
--------------------------------------------------------------------------------
/timespanset/doc_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package timespanset_test
16 |
17 | import (
18 | "fmt"
19 | "time"
20 |
21 | "github.com/google/go-intervals/timespanset"
22 | )
23 |
24 | func Example() {
25 | var tz = func() *time.Location {
26 | x, err := time.LoadLocation("PST8PDT")
27 | if err != nil {
28 | panic(fmt.Errorf("timezone not available: %v", err))
29 | }
30 | return x
31 | }()
32 |
33 | type span struct {
34 | start, end time.Time
35 | }
36 | week1 := &span{
37 | time.Date(2015, time.June, 1, 0, 0, 0, 0, tz),
38 | time.Date(2015, time.June, 8, 0, 0, 0, 0, tz),
39 | }
40 | week2 := &span{
41 | time.Date(2015, time.June, 8, 0, 0, 0, 0, tz),
42 | time.Date(2015, time.June, 15, 0, 0, 0, 0, tz),
43 | }
44 | week3 := &span{
45 | time.Date(2015, time.June, 15, 0, 0, 0, 0, tz),
46 | time.Date(2015, time.June, 22, 0, 0, 0, 0, tz),
47 | }
48 |
49 | set := timespanset.Empty()
50 | fmt.Printf("Empty set: %s\n", set)
51 |
52 | set.Insert(week1.start, week3.end)
53 | fmt.Printf("Week 1-3: %s\n", set)
54 |
55 | set2 := timespanset.Empty()
56 | set2.Insert(week2.start, week2.end)
57 | set.Sub(set2)
58 | fmt.Printf("Week 1-3 minus week 2: %s\n", set)
59 | }
60 |
--------------------------------------------------------------------------------
/timespanset/timespanset.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // Package timespanset is a finite set implementation for time spans.
16 | //
17 | // DISCLAIMER: This library is not yet stable, so expect breaking changes.
18 | package timespanset
19 |
20 | import (
21 | "fmt"
22 | "time"
23 |
24 | "github.com/google/go-intervals/intervalset"
25 | )
26 |
27 | // Set is a finite set of time spans. Functions are provided for iterating over the
28 | // spans and performing set operations (intersection, union, subtraction).
29 | //
30 | // This is a time span-specific implemention of intervalset.Set.
31 | type Set struct {
32 | iset *intervalset.Set
33 | }
34 |
35 | // String returns a human readable version of the set.
36 | func (s *Set) String() string {
37 | return s.iset.String()
38 | }
39 |
40 | // Empty returns a new, empty Set.
41 | func Empty() *Set {
42 | return &Set{intervalset.Empty()}
43 | }
44 |
45 | // Copy returns a copy of a set that may be mutated without affecting the original.
46 | func (s *Set) Copy() *Set {
47 | return &Set{s.iset.Copy()}
48 | }
49 |
50 | // Insert adds a single time span into the set.
51 | func (s *Set) Insert(start, end time.Time) {
52 | if end.Before(start) {
53 | panic(fmt.Errorf("start %s before end %s", start, end))
54 | }
55 | s.iset.Add(intervalset.NewSet([]intervalset.Interval{×pan{start, end}}))
56 | }
57 |
58 | // Add performs an in-place union of two sets.
59 | func (s *Set) Add(b *Set) {
60 | s.iset.Add(b.iset)
61 | }
62 |
63 | // Sub performs an in-place subtraction of set b from set a.
64 | func (s *Set) Sub(b *Set) {
65 | s.iset.Sub(b.iset)
66 | }
67 |
68 | // Intersect performs an in-place intersection of sets a and b.
69 | func (s *Set) Intersect(b *Set) {
70 | s.iset.Intersect(b.iset)
71 | }
72 |
73 | // Extent returns the start and end time that defines the entire timespan
74 | // covering the set. The returned times are the zero value for an empty set.
75 | func (s *Set) Extent() (time.Time, time.Time) {
76 | x := s.iset.Extent()
77 | if x == nil {
78 | return time.Time{}, time.Time{}
79 | }
80 | tr := trOrPanic(x)
81 | return tr.start, tr.end
82 | }
83 |
84 | // Empty reports if the extent of the set is zero.
85 | func (s *Set) Empty() bool {
86 | x := s.iset.Extent()
87 | if x == nil {
88 | return true
89 | }
90 | return x.IsZero()
91 | }
92 |
93 | // Contains reports whether a time span is entirely contained within the set.
94 | func (s *Set) Contains(start, end time.Time) bool {
95 | return s.iset.Contains(×pan{start, end})
96 | }
97 |
98 | // IntervalReceiver is a function used for iterating over a set of time
99 | // ranges. It takes the start and end times and returns true if the iteration
100 | // should continue.
101 | type IntervalReceiver func(start, end time.Time) bool
102 |
103 | // ensureNonAdjoining returns a modified version of an IntervalsBetween callback
104 | // function that will always be called with non-adjoining intervals. To do this,
105 | // it returns a function that accumulates adjoining intervals, calling f with
106 | // the combined interval. The second return value is a function that should be
107 | // called after iteration is complete to ensure the last interval is sent to f.
108 | func ensureNonAdjoining(f IntervalReceiver) (IntervalReceiver, func()) {
109 | last := ×pan{}
110 | isDone := false
111 | doneFn := func() {
112 | if isDone {
113 | return
114 | }
115 | if !last.IsZero() {
116 | f(last.start, last.end)
117 | }
118 | }
119 | receiveInterval := func(start, end time.Time) bool {
120 | if isDone {
121 | panic("should not be done")
122 | }
123 | current := ×pan{start, end}
124 | adjoined := last.adjoin(current)
125 | if !adjoined.IsZero() {
126 | // Always continue if this interval adjoins the last one because the next
127 | // may also adjoin.
128 | last = adjoined
129 | return true
130 | }
131 | if !last.IsZero() {
132 | isDone = !f(last.start, last.end)
133 | if isDone {
134 | return false //stop iteration
135 | }
136 | }
137 | last = current
138 | return true // continue iteration
139 | }
140 | return receiveInterval, doneFn
141 | }
142 |
143 | // IntervalsBetween iterates over the time ranges within the set and calls f with the
144 | // start (inclusive) and end (exclusive) of each. If f returns false, iteration
145 | // ceases. Only intervals in between the provided start and end times are
146 | // included.
147 | func (s *Set) IntervalsBetween(start, end time.Time, f IntervalReceiver) {
148 | fPrime, done := ensureNonAdjoining(f)
149 | s.iset.IntervalsBetween(×pan{start, end}, func(x intervalset.Interval) bool {
150 | tr := trOrPanic(x)
151 | return fPrime(tr.start, tr.end)
152 | })
153 | done()
154 | }
155 |
--------------------------------------------------------------------------------
/timespanset/timespanset_interval.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package timespanset
16 |
17 | import (
18 | "fmt"
19 | "time"
20 |
21 | "github.com/google/go-intervals/intervalset"
22 | )
23 |
24 | func min(a, b time.Time) time.Time {
25 | if a.Before(b) {
26 | return a
27 | }
28 | return b
29 | }
30 |
31 | func max(a, b time.Time) time.Time {
32 | if a.After(b) {
33 | return a
34 | }
35 | return b
36 | }
37 |
38 | // timespan is a private implementation of intervalset.Interval.
39 | type timespan struct {
40 | start, end time.Time
41 | }
42 |
43 | func (ts *timespan) String() string {
44 | return fmt.Sprintf("[%s, %s)", ts.start, ts.end)
45 | }
46 |
47 | func (ts *timespan) Equal(b *timespan) bool {
48 | return ts.start.Equal(b.start) && ts.end.Equal(ts.end)
49 | }
50 |
51 | func (ts *timespan) intersect(b *timespan) *timespan {
52 | result := ×pan{
53 | max(ts.start, b.start),
54 | min(ts.end, b.end),
55 | }
56 | if result.start.Before(result.end) {
57 | return result
58 | }
59 | return ×pan{}
60 | }
61 |
62 | func (ts *timespan) IsZero() bool {
63 | return ts.start.IsZero() && ts.end.IsZero()
64 | }
65 |
66 | func trOrPanic(i intervalset.Interval) *timespan {
67 | tr, ok := i.(*timespan)
68 | if !ok {
69 | panic(fmt.Errorf("interval must be a time range: %v", i))
70 | }
71 | return tr
72 | }
73 |
74 | func (ts *timespan) Before(other intervalset.Interval) bool {
75 | return !trOrPanic(other).start.Before(ts.end)
76 | }
77 |
78 | func (ts *timespan) Bisect(other intervalset.Interval) (intervalset.Interval, intervalset.Interval) {
79 | b := trOrPanic(other)
80 | intersection := ts.intersect(b)
81 | if intersection.IsZero() {
82 | if ts.Before(b) {
83 | return ts, ×pan{}
84 | }
85 | return ×pan{}, ts
86 | }
87 | maybeZero := func(start, end time.Time) *timespan {
88 | if start.Equal(end) {
89 | return ×pan{}
90 | }
91 | return ×pan{start, end}
92 | }
93 | return maybeZero(ts.start, intersection.start), maybeZero(intersection.end, ts.end)
94 | }
95 |
96 | func (ts *timespan) Intersect(other intervalset.Interval) intervalset.Interval {
97 | return ts.intersect(trOrPanic(other))
98 | }
99 |
100 | func (ts *timespan) adjoin(b *timespan) *timespan {
101 | if ts.end.Equal(b.start) {
102 | return ×pan{ts.start, b.end}
103 | }
104 | if b.end.Equal(ts.start) {
105 | return ×pan{b.start, ts.end}
106 | }
107 | return ×pan{}
108 | }
109 |
110 | func (ts *timespan) Adjoin(other intervalset.Interval) intervalset.Interval {
111 | return ts.adjoin(trOrPanic(other))
112 | }
113 |
114 | func (ts *timespan) encompass(b *timespan) intervalset.Interval {
115 | return ×pan{min(ts.start, b.start), max(ts.end, b.end)}
116 | }
117 |
118 | func (ts *timespan) Encompass(other intervalset.Interval) intervalset.Interval {
119 | return ts.encompass(trOrPanic(other))
120 | }
121 |
--------------------------------------------------------------------------------
/timespanset/timespanset_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 | package timespanset
15 |
16 | import (
17 | "fmt"
18 | "reflect"
19 | "testing"
20 | "time"
21 | )
22 |
23 | func betweenSlice(set *Set, start, end time.Time) []*timespan {
24 | ivals := []*timespan{}
25 | set.IntervalsBetween(start, end, func(s, e time.Time) bool {
26 | ivals = append(ivals, ×pan{s, e})
27 | return true
28 | })
29 | return ivals
30 | }
31 |
32 | func tz() *time.Location {
33 | x, err := time.LoadLocation("PST8PDT")
34 | if err != nil {
35 | panic(fmt.Errorf("timezone not available: %v", err))
36 | }
37 | return x
38 | }
39 |
40 | var (
41 | // Dates before and after all the other dates.
42 | past = time.Date(1980, time.June, 1, 0, 0, 0, 0, tz())
43 | future = time.Date(2030, time.June, 1, 0, 0, 0, 0, tz())
44 |
45 | week1 = ×pan{
46 | time.Date(2015, time.June, 1, 0, 0, 0, 0, tz()),
47 | time.Date(2015, time.June, 8, 0, 0, 0, 0, tz()),
48 | }
49 | week2 = ×pan{
50 | time.Date(2015, time.June, 8, 0, 0, 0, 0, tz()),
51 | time.Date(2015, time.June, 15, 0, 0, 0, 0, tz()),
52 | }
53 | week3 = ×pan{
54 | time.Date(2015, time.June, 15, 0, 0, 0, 0, tz()),
55 | time.Date(2015, time.June, 22, 0, 0, 0, 0, tz()),
56 | }
57 | )
58 |
59 | func weeks1And3() *Set {
60 | set := Empty()
61 | set.Insert(week1.start, week1.end)
62 | set.Insert(week3.start, week3.end)
63 | return set
64 | }
65 |
66 | func weeks123() *Set {
67 | set := Empty()
68 | set.Insert(week1.start, week1.end)
69 | set.Insert(week3.start, week3.end)
70 | set.Insert(week2.start, week2.end)
71 | return set
72 | }
73 |
74 | func TestIntervalsBetween(t *testing.T) {
75 | {
76 | // Test iterating over a single value.
77 | num := 0
78 | weeks1And3().IntervalsBetween(past, future, func(_, _ time.Time) bool {
79 | num++
80 | return false
81 | })
82 | if got, want := num, 1; got != want {
83 | t.Errorf("want a single result to be returned, got %v", num)
84 | }
85 | }
86 | for _, tt := range []struct {
87 | name string
88 | set *Set
89 | bounds *timespan
90 | want []*timespan
91 | }{
92 | {
93 | name: "entire range overlaps with weeks 1 and 2",
94 | set: weeks1And3(),
95 | bounds: ×pan{past, future},
96 | want: []*timespan{week1, week3},
97 | },
98 | {
99 | name: "[week2.start, week3.end] overlaps with week3",
100 | set: weeks1And3(),
101 | bounds: ×pan{week2.start, week3.end},
102 | want: []*timespan{week3},
103 | },
104 | {
105 | name: "zero overlap with week 2",
106 | set: weeks1And3(),
107 | bounds: week2,
108 | want: []*timespan{},
109 | },
110 | {
111 | name: "[week1.start, week3.end] overlaps with week3",
112 | set: weeks1And3(),
113 | bounds: ×pan{week2.start, week3.end},
114 | want: []*timespan{week3},
115 | },
116 | {
117 | name: "weeks123 should be one continuous range",
118 | set: weeks123(),
119 | bounds: ×pan{past, future},
120 | want: []*timespan{{week1.start, week3.end}},
121 | },
122 | } {
123 | if got := betweenSlice(tt.set, tt.bounds.start, tt.bounds.end); !reflect.DeepEqual(got, tt.want) {
124 | t.Errorf("%s: time ranges between %s = %s, want %s", tt.name, tt.bounds, got, tt.want)
125 | }
126 | }
127 | }
128 |
129 | func TestIntersect(t *testing.T) {
130 | for _, tt := range []struct {
131 | name string
132 | set *Set
133 | bounds *timespan
134 | want []*timespan
135 | }{
136 | {
137 | name: "subtract empty from empty",
138 | set: func() *Set {
139 | w := Empty()
140 | w.Sub(Empty())
141 | return w
142 | }(),
143 | bounds: ×pan{past, future},
144 | want: []*timespan{},
145 | },
146 | {
147 | name: "subtract weeks 1 and 3 from empty",
148 | set: func() *Set {
149 | w := Empty()
150 | w.Sub(weeks1And3())
151 | return w
152 | }(),
153 | bounds: ×pan{past, future},
154 | want: []*timespan{},
155 | },
156 | {
157 | name: "subtract empty from weeks 1 and 3",
158 | set: func() *Set {
159 | w := weeks1And3()
160 | w.Sub(Empty())
161 | return w
162 | }(),
163 | bounds: ×pan{past, future},
164 | want: []*timespan{week1, week3},
165 | },
166 | {
167 | name: "subtract weeks 1 and 3 from weeks123",
168 | set: func() *Set {
169 | w := weeks123()
170 | w.Sub(weeks1And3())
171 | return w
172 | }(),
173 | bounds: ×pan{past, future},
174 | want: []*timespan{week2},
175 | },
176 | {
177 | name: "subtract eternity from weeks 1 and 3",
178 | set: func() *Set {
179 | w := weeks1And3()
180 | eternity := Empty()
181 | eternity.Insert(past, future)
182 | w.Sub(eternity)
183 | return w
184 | }(),
185 | bounds: ×pan{past, future},
186 | want: []*timespan{},
187 | },
188 | } {
189 | if got := betweenSlice(tt.set, tt.bounds.start, tt.bounds.end); !reflect.DeepEqual(got, tt.want) {
190 | t.Errorf("%s: time ranges between %s = %s, want %s", tt.name, tt.bounds, got, tt.want)
191 | }
192 | }
193 | }
194 |
195 | func TestExtent(t *testing.T) {
196 | for _, tt := range []struct {
197 | name string
198 | set *Set
199 | want *timespan
200 | }{
201 | {
202 | name: "weeks123 - weeks13 = week2",
203 | set: func() *Set {
204 | w := weeks123()
205 | w.Sub(weeks1And3())
206 | return w
207 | }(),
208 | want: week2,
209 | },
210 | {
211 | name: "weeks123 - empty set = weeks123",
212 | set: func() *Set {
213 | w := weeks123()
214 | w.Sub(Empty())
215 | return w
216 | }(),
217 | want: ×pan{week1.start, week3.end},
218 | },
219 | {
220 | name: "weeks123 + empty set = weeks123",
221 | set: func() *Set {
222 | w := weeks123()
223 | w.Add(Empty())
224 | return w
225 | }(),
226 | want: ×pan{week1.start, week3.end},
227 | },
228 | {
229 | name: "empty set + weeks123 = weeks123",
230 | set: func() *Set {
231 | w := Empty()
232 | w.Add(weeks123())
233 | return w
234 | }(),
235 | want: ×pan{week1.start, week3.end},
236 | },
237 | {
238 | name: "empty set extent is two zero times",
239 | set: Empty(),
240 | want: ×pan{time.Time{}, time.Time{}},
241 | },
242 | } {
243 | gotStart, gotEnd := tt.set.Extent()
244 |
245 | if got := (×pan{gotStart, gotEnd}); !reflect.DeepEqual(got, tt.want) {
246 | t.Errorf("%s: Extent() = %s, want %s", tt.name, got, tt.want)
247 | }
248 | }
249 | }
250 |
251 | func TestContains(t *testing.T) {
252 | for _, tt := range []struct {
253 | name string
254 | set *Set
255 | elem *timespan
256 | want bool
257 | }{
258 | {
259 | name: "weeks 1 and 3 contain week1",
260 | set: weeks1And3(),
261 | elem: week1,
262 | want: true,
263 | },
264 | } {
265 | if got := tt.set.Contains(tt.elem.start, tt.elem.end); got != tt.want {
266 | t.Errorf("%s: set.Contains(%s) = %t, want %t", tt.name, tt.elem, got, tt.want)
267 | }
268 | }
269 | }
270 |
271 | func benchmarkNewSet(numToCreate, numMembers int, overlapping bool, b *testing.B) {
272 | for n := 0; n < b.N; n++ {
273 | for i := 0; i < numToCreate; i++ {
274 | set := Empty()
275 | for j := 0; j < numMembers; j++ {
276 | if !overlapping {
277 | set.Insert(week1.start, week1.end)
278 | } else {
279 | set.Insert(week1.start.AddDate(0, 0, 7), week1.end.AddDate(0, 0, 7))
280 | }
281 | }
282 | }
283 | }
284 | }
285 |
286 | func TestIntersectCalendar(t *testing.T) {
287 | for _, tt := range []struct {
288 | name string
289 | set *Set
290 | bounds *timespan
291 | want []*timespan
292 | }{
293 | {
294 | name: "Intersect: middays August 7-11, 2017",
295 | set: func() *Set {
296 | weekdays, _ := weekdaysWeekends(2016, 2018)
297 | weekdays.Intersect(middays(2016, 2018))
298 | return weekdays
299 | }(),
300 | bounds: ×pan{
301 | time.Date(2017, time.August, 6, 0, 0, 0, 0, tz()),
302 | time.Date(2017, time.August, 13, 0, 0, 0, 0, tz()),
303 | },
304 | want: []*timespan{
305 | {
306 | time.Date(2017, time.August, 7, 11, 0, 0, 0, tz()),
307 | time.Date(2017, time.August, 7, 13, 0, 0, 0, tz()),
308 | },
309 | {
310 | time.Date(2017, time.August, 8, 11, 0, 0, 0, tz()),
311 | time.Date(2017, time.August, 8, 13, 0, 0, 0, tz()),
312 | },
313 | {
314 | time.Date(2017, time.August, 9, 11, 0, 0, 0, tz()),
315 | time.Date(2017, time.August, 9, 13, 0, 0, 0, tz()),
316 | },
317 | {
318 | time.Date(2017, time.August, 10, 11, 0, 0, 0, tz()),
319 | time.Date(2017, time.August, 10, 13, 0, 0, 0, tz()),
320 | },
321 | {
322 | time.Date(2017, time.August, 11, 11, 0, 0, 0, tz()),
323 | time.Date(2017, time.August, 11, 13, 0, 0, 0, tz()),
324 | },
325 | },
326 | },
327 | {
328 | name: "Sub: middays August 7-9, 2017",
329 | set: func() *Set {
330 | weekdays, _ := weekdaysWeekends(2016, 2018)
331 | weekdays.Sub(middays(2016, 2018))
332 | return weekdays
333 | }(),
334 | bounds: ×pan{
335 | time.Date(2017, time.August, 6, 0, 0, 0, 0, tz()),
336 | time.Date(2017, time.August, 9, 0, 0, 0, 0, tz()),
337 | },
338 | want: []*timespan{
339 | {
340 | time.Date(2017, time.August, 7, 0, 0, 0, 0, tz()),
341 | time.Date(2017, time.August, 7, 11, 0, 0, 0, tz()),
342 | },
343 | {
344 | time.Date(2017, time.August, 7, 13, 0, 0, 0, tz()),
345 | time.Date(2017, time.August, 8, 11, 0, 0, 0, tz()),
346 | },
347 | {
348 | time.Date(2017, time.August, 8, 13, 0, 0, 0, tz()),
349 | time.Date(2017, time.August, 9, 0, 0, 0, 0, tz()),
350 | },
351 | },
352 | },
353 | {
354 | name: "Intersect: middays August 7-11, 2017",
355 | set: func() *Set {
356 | x := middays(2017, 2017)
357 | weekdays, _ := weekdaysWeekends(2016, 2018)
358 | x.Intersect(weekdays)
359 | return x
360 | }(),
361 | bounds: ×pan{
362 | time.Date(2017, time.August, 6, 0, 0, 0, 0, tz()),
363 | time.Date(2017, time.August, 13, 0, 0, 0, 0, tz()),
364 | },
365 | want: []*timespan{
366 | {
367 | time.Date(2017, time.August, 7, 11, 0, 0, 0, tz()),
368 | time.Date(2017, time.August, 7, 13, 0, 0, 0, tz()),
369 | },
370 | {
371 | time.Date(2017, time.August, 8, 11, 0, 0, 0, tz()),
372 | time.Date(2017, time.August, 8, 13, 0, 0, 0, tz()),
373 | },
374 | {
375 | time.Date(2017, time.August, 9, 11, 0, 0, 0, tz()),
376 | time.Date(2017, time.August, 9, 13, 0, 0, 0, tz()),
377 | },
378 | {
379 | time.Date(2017, time.August, 10, 11, 0, 0, 0, tz()),
380 | time.Date(2017, time.August, 10, 13, 0, 0, 0, tz()),
381 | },
382 | {
383 | time.Date(2017, time.August, 11, 11, 0, 0, 0, tz()),
384 | time.Date(2017, time.August, 11, 13, 0, 0, 0, tz()),
385 | },
386 | },
387 | },
388 | } {
389 | if got := betweenSlice(tt.set, tt.bounds.start, tt.bounds.end); !reflect.DeepEqual(got, tt.want) {
390 | t.Errorf("%s: time ranges between %s = %s, want %s", tt.name, tt.bounds, got, tt.want)
391 | }
392 | }
393 | }
394 |
395 | func TestCopyUsingStringEquals(t *testing.T) {
396 | for _, tt := range []struct {
397 | name string
398 | set *Set
399 | }{
400 | {
401 | name: "3 years worth of weekdays 11am-1pm",
402 | set: func() *Set {
403 | weekdays, _ := weekdaysWeekends(2016, 2018)
404 | weekdays.Intersect(middays(2016, 2018))
405 | return weekdays
406 | }(),
407 | },
408 | } {
409 | if got, want := tt.set.Copy().String(), tt.set.String(); got != want {
410 | t.Errorf("%s: copy = %s != %s", tt.name, got, want)
411 | }
412 | }
413 | }
414 |
415 | func weekdaysWeekends(yearStart, yearEnd int) (weekdays, weekends *Set) {
416 | weekdays, weekends = Empty(), Empty()
417 | jan1 := time.Date(yearStart, time.January, 1, 0, 0, 0, 0, tz())
418 | for t := jan1; t.Year() <= yearEnd; t = t.AddDate(0, 0, 1) {
419 | nextDayStart := t.AddDate(0, 0, 1)
420 | switch t.Weekday() {
421 | case time.Saturday, time.Sunday:
422 | weekends.Insert(t, nextDayStart)
423 | default:
424 | weekdays.Insert(t, nextDayStart)
425 | }
426 | }
427 | return
428 | }
429 |
430 | func middays(yearStart, yearEnd int) *Set {
431 | middays := Empty()
432 | jan1 := time.Date(yearStart, time.January, 1, 0, 0, 0, 0, tz())
433 | for t := jan1; t.Year() <= yearEnd; t = t.AddDate(0, 0, 1) {
434 | middays.Insert(
435 | time.Date(t.Year(), t.Month(), t.Day(), 11, 0, 0, 0, tz()),
436 | time.Date(t.Year(), t.Month(), t.Day(), 13, 0, 0, 0, tz()))
437 | }
438 | return middays
439 | }
440 |
--------------------------------------------------------------------------------