├── 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 | --------------------------------------------------------------------------------