github.com/grailbio/base@v0.0.11/intervalmap/intervalmap_test.go (about) 1 package intervalmap 2 3 import ( 4 "bytes" 5 "encoding/gob" 6 "fmt" 7 "math/rand" 8 "sort" 9 "strings" 10 "testing" 11 12 "github.com/biogo/store/interval" 13 "github.com/grailbio/testutil/assert" 14 "github.com/grailbio/testutil/expect" 15 ) 16 17 func TestInterval(t *testing.T) { 18 expect.False(t, Interval{1, 1}.Intersects(Interval{1, 2})) 19 expect.True(t, Interval{1, 2}.Intersects(Interval{1, 2})) 20 expect.True(t, Interval{1, 2}.Intersects(Interval{1, 3})) 21 expect.True(t, Interval{1, 2}.Intersects(Interval{-1, 2})) 22 expect.True(t, Interval{1, 2}.Intersects(Interval{-1, 3})) 23 expect.False(t, Interval{1, 2}.Intersects(Interval{2, 3})) 24 expect.False(t, Interval{1, 2}.Intersects(Interval{3, 4})) 25 expect.EQ(t, Interval{1, 2}.Span(Interval{3, 4}), Interval{1, 4}) 26 expect.EQ(t, Interval{1, 4}.Span(Interval{2, 3}), Interval{1, 4}) 27 28 expect.EQ(t, Interval{1, 4}.Span(Interval{3, 2}), Interval{1, 4}) 29 expect.EQ(t, Interval{10, 14}.Span(Interval{3, 2}), Interval{10, 14}) 30 expect.EQ(t, Interval{4, 1}.Span(Interval{2, 3}), Interval{2, 3}) 31 expect.EQ(t, Interval{4, 1}.Span(Interval{12, 13}), Interval{12, 13}) 32 } 33 34 // Sort the given intervals in place. 35 func sortIntervals(matches []Interval) []Interval { 36 sort.Slice(matches, func(i, j int) bool { 37 e0 := matches[i] 38 e1 := matches[j] 39 if e0.Start != e1.Start { 40 return e0.Start < e1.Start 41 } 42 if e0.Limit != e1.Limit { 43 return e0.Limit < e1.Limit 44 } 45 return false 46 }) 47 return matches 48 } 49 50 // slowModel is a slow, simple intervalmap. 51 type slowModel []Interval 52 53 func (m *slowModel) insert(start, limit Key) { 54 *m = append(*m, Interval{start, limit}) 55 } 56 57 func (m slowModel) get(start, limit Key) []Interval { 58 matches := []Interval{} 59 for _, i := range m { 60 if i.Intersects(Interval{start, limit}) { 61 matches = append(matches, i) 62 } 63 } 64 return matches 65 } 66 67 // biogoModel is a intervalmap using biogo inttree. 68 type biogoModel interval.IntTree 69 70 func (m *biogoModel) insert(start, limit Key) { 71 ii := testInterval{ 72 start: start, 73 limit: limit, 74 id: uintptr(((*interval.IntTree)(m)).Len() + 100), 75 } 76 if err := ((*interval.IntTree)(m)).Insert(ii, false); err != nil { 77 panic(err) 78 } 79 } 80 81 func (m *biogoModel) get(start, limit Key) []Interval { 82 matches := []Interval{} 83 for _, match := range ((*interval.IntTree)(m)).Get(testInterval{start: start, limit: limit}) { 84 matches = append(matches, Interval{Start: int64(match.Range().Start), Limit: int64(match.Range().End)}) 85 } 86 return matches 87 } 88 89 func testGet(tree *T, start, limit Key) []Interval { 90 p := []*Entry{} 91 tree.Get(Interval{start, limit}, &p) 92 matches := make([]Interval, len(p)) 93 for i, e := range p { 94 payload := e.Data.(string) 95 if payload != fmt.Sprintf("[%d,%d)", e.Interval.Start, e.Interval.Limit) { 96 panic(e) 97 } 98 matches[i] = e.Interval 99 } 100 return matches 101 } 102 103 func newEntry(start, limit Key) Entry { 104 return Entry{ 105 Interval: Interval{start, limit}, 106 Data: fmt.Sprintf("[%d,%d)", start, limit), 107 } 108 } 109 110 func TestEmpty(t *testing.T) { 111 expect.EQ(t, testGet(New(nil), 1, 2), []Interval{}) 112 } 113 114 func TestSmall(t *testing.T) { 115 tree := New([]Entry{newEntry(1, 2), newEntry(10, 15)}) 116 expect.EQ(t, testGet(tree, -1, 0), []Interval{}) 117 expect.EQ(t, testGet(tree, 0, 2), []Interval{Interval{1, 2}}) 118 expect.EQ(t, testGet(tree, 0, 10), []Interval{Interval{1, 2}}) 119 expect.EQ(t, sortIntervals(testGet(tree, 0, 11)), []Interval{Interval{1, 2}, Interval{10, 15}}) 120 } 121 122 func randInterval(r *rand.Rand, max Key, width float64) (Key, Key) { 123 for { 124 start := Key(r.Intn(int(max))) 125 limit := start + Key(r.ExpFloat64()*width) 126 if start == limit { 127 continue 128 } 129 if start > limit { 130 start, limit = limit, start 131 } 132 return start, limit 133 } 134 } 135 136 func testRandom(t *testing.T, seed int, nElem int, max Key, width float64) { 137 r := rand.New(rand.NewSource(int64(seed))) 138 m0 := slowModel{} 139 m1 := &biogoModel{} 140 141 entries := []Entry{} 142 for i := 0; i < nElem; i++ { 143 start, limit := randInterval(r, max, width) 144 m0.insert(start, limit) 145 m1.insert(start, limit) 146 entries = append(entries, newEntry(start, limit)) 147 } 148 tree := New(entries) 149 tree2 := gobEncodeAndDecode(t, tree) 150 151 for i := 0; i < 1000; i++ { 152 start, limit := randInterval(r, max, width) 153 154 r0 := sortIntervals(m0.get(start, limit)) 155 r1 := sortIntervals(m1.get(start, limit)) 156 result := sortIntervals(testGet(tree, start, limit)) 157 assert.EQ(t, result, r0, "seed=%d, i=%d, search=[%d,%d)", seed, i, start, limit) 158 assert.EQ(t, result, r1, "seed=%d, i=%d, search=[%d,%d)", seed, i, start, limit) 159 assert.EQ(t, result, sortIntervals(testGet(tree2, start, limit))) 160 } 161 } 162 163 func TestRandom0(t *testing.T) { testRandom(t, 0, 128, 1024, 10) } 164 func TestRandom1(t *testing.T) { testRandom(t, 1, 128, 1024, 100) } 165 func TestRandom2(t *testing.T) { testRandom(t, 1, 1000, 8192, 1000) } 166 167 func gobEncodeAndDecode(t *testing.T, tree *T) *T { 168 buf := bytes.Buffer{} 169 e := gob.NewEncoder(&buf) 170 assert.NoError(t, e.Encode(tree)) 171 172 d := gob.NewDecoder(&buf) 173 var tree2 *T 174 assert.NoError(t, d.Decode(&tree2)) 175 return tree2 176 } 177 178 func TestGobEmpty(t *testing.T) { 179 tree := New(nil) 180 tree2 := gobEncodeAndDecode(t, tree) 181 expect.EQ(t, testGet(tree2, 1, 2), []Interval{}) 182 } 183 184 func TestGobSmall(t *testing.T) { 185 tree := gobEncodeAndDecode(t, New([]Entry{newEntry(1, 2), newEntry(10, 15)})) 186 expect.EQ(t, testGet(tree, -1, 0), []Interval{}) 187 expect.EQ(t, testGet(tree, 0, 2), []Interval{Interval{1, 2}}) 188 expect.EQ(t, testGet(tree, 0, 10), []Interval{Interval{1, 2}}) 189 expect.EQ(t, sortIntervals(testGet(tree, 0, 11)), []Interval{Interval{1, 2}, Interval{10, 15}}) 190 } 191 192 func benchmarkRandom(b *testing.B, seed int, nElem int, max Key, width float64) { 193 b.StopTimer() 194 r := rand.New(rand.NewSource(int64(seed))) 195 entries := []Entry{} 196 for i := 0; i < nElem; i++ { 197 start, limit := randInterval(r, max, width) 198 entries = append(entries, newEntry(start, limit)) 199 } 200 tree := New(entries) 201 b.Logf("Tree stats: %+v", tree.Stats()) 202 b.StartTimer() 203 p := []*Entry{} 204 for i := 0; i < b.N; i++ { 205 start, limit := randInterval(r, max, width) 206 p = p[:0] 207 tree.Get(Interval{start, limit}, &p) 208 } 209 } 210 211 func BenchmarkRandom0(b *testing.B) { 212 benchmarkRandom(b, 0, 100, 10000, 10) 213 } 214 215 func BenchmarkRandom1(b *testing.B) { 216 benchmarkRandom(b, 0, 1000, 200000, 100) 217 } 218 219 func BenchmarkRandom2(b *testing.B) { 220 benchmarkRandom(b, 0, 1000, 1000000, 100) 221 } 222 223 type testInterval struct { 224 id uintptr 225 start, limit Key 226 } 227 228 func (i testInterval) Overlap(b interval.IntRange) bool { 229 return i.limit > Key(b.Start) && i.start < Key(b.End) 230 } 231 232 // ID implements interval.IntInterface. 233 func (i testInterval) ID() uintptr { return i.id } 234 235 // Range implements interval.IntInterface. 236 func (i testInterval) Range() interval.IntRange { 237 return interval.IntRange{Start: int(i.start), End: int(i.limit)} 238 } 239 240 // String implements interval.IntInterface 241 func (i testInterval) String() string { return fmt.Sprintf("[%d,%d)#%d", i.start, i.limit, i.id) } 242 243 func benchmarkBiogoRandom(b *testing.B, seed int, nElem int, max Key, width float64) { 244 b.StopTimer() 245 r := rand.New(rand.NewSource(int64(seed))) 246 tree := interval.IntTree{} 247 248 for i := 0; i < nElem; i++ { 249 start, limit := randInterval(r, max, width) 250 ii := testInterval{ 251 start: start, 252 limit: limit, 253 id: uintptr(i), 254 } 255 if err := tree.Insert(ii, false); err != nil { 256 b.Fatal(err) 257 } 258 } 259 b.StartTimer() 260 for i := 0; i < b.N; i++ { 261 start, limit := randInterval(r, max, width) 262 tree.Get(testInterval{start: start, limit: limit}) 263 } 264 } 265 266 func BenchmarkBiogoRandom0(b *testing.B) { 267 benchmarkBiogoRandom(b, 0, 100, 10000, 10) 268 } 269 270 func BenchmarkBiogoRandom1(b *testing.B) { 271 benchmarkBiogoRandom(b, 0, 1000, 200000, 100) 272 } 273 274 func BenchmarkBiogoRandom2(b *testing.B) { 275 benchmarkBiogoRandom(b, 0, 1000, 1000000, 100) 276 } 277 278 func Example() { 279 newEntry := func(start, limit Key) Entry { 280 return Entry{ 281 Interval: Interval{start, limit}, 282 Data: fmt.Sprintf("[%d,%d)", start, limit), 283 } 284 } 285 286 doGet := func(tree *T, start, limit Key) string { 287 matches := []*Entry{} 288 tree.Get(Interval{start, limit}, &matches) 289 s := []string{} 290 for _, m := range matches { 291 s = append(s, m.Data.(string)) 292 } 293 sort.Strings(s) 294 return strings.Join(s, ",") 295 } 296 297 tree := New([]Entry{newEntry(1, 4), newEntry(3, 5), newEntry(6, 7)}) 298 fmt.Println(doGet(tree, 0, 2)) 299 fmt.Println(doGet(tree, 0, 4)) 300 fmt.Println(doGet(tree, 4, 6)) 301 fmt.Println(doGet(tree, 4, 7)) 302 // Output: 303 // [1,4) 304 // [1,4),[3,5) 305 // [3,5) 306 // [3,5),[6,7) 307 } 308 309 // Example_gob is an example of serializing an intervalmap using Gob. 310 func Example_gob() { 311 newEntry := func(start, limit Key) Entry { 312 return Entry{ 313 Interval: Interval{start, limit}, 314 Data: fmt.Sprintf("[%d,%d)", start, limit), 315 } 316 } 317 318 tree := New([]Entry{newEntry(1, 4), newEntry(3, 5), newEntry(6, 7)}) 319 320 buf := bytes.Buffer{} 321 enc := gob.NewEncoder(&buf) 322 if err := enc.Encode(tree); err != nil { 323 panic(err) 324 } 325 dec := gob.NewDecoder(&buf) 326 var tree2 *T 327 if err := dec.Decode(&tree2); err != nil { 328 panic(err) 329 } 330 331 doGet := func(tree *T, start, limit Key) string { 332 matches := []*Entry{} 333 tree.Get(Interval{start, limit}, &matches) 334 s := []string{} 335 for _, m := range matches { 336 s = append(s, m.Data.(string)) 337 } 338 sort.Strings(s) 339 return strings.Join(s, ",") 340 } 341 342 fmt.Println(doGet(tree2, 0, 2)) 343 fmt.Println(doGet(tree2, 0, 4)) 344 fmt.Println(doGet(tree2, 4, 6)) 345 fmt.Println(doGet(tree2, 4, 7)) 346 // Output: 347 // [1,4) 348 // [1,4),[3,5) 349 // [3,5) 350 // [3,5),[6,7) 351 }