github.com/prysmaticlabs/prysm@v1.4.4/shared/aggregation/maxcover_test.go (about) 1 package aggregation 2 3 import ( 4 "reflect" 5 "sort" 6 "testing" 7 8 "github.com/prysmaticlabs/go-bitfield" 9 aggtesting "github.com/prysmaticlabs/prysm/shared/aggregation/testing" 10 "github.com/prysmaticlabs/prysm/shared/testutil/assert" 11 ) 12 13 func TestMaxCover_MaxCoverCandidates_filter(t *testing.T) { 14 type args struct { 15 covered bitfield.Bitlist 16 allowOverlaps bool 17 } 18 var problem MaxCoverCandidates 19 tests := []struct { 20 name string 21 cl MaxCoverCandidates 22 args args 23 want *MaxCoverCandidates 24 }{ 25 { 26 name: "nil list", 27 cl: nil, 28 args: args{}, 29 want: &problem, 30 }, 31 { 32 name: "empty list", 33 cl: MaxCoverCandidates{}, 34 args: args{}, 35 want: &MaxCoverCandidates{}, 36 }, 37 { 38 name: "all processed", 39 cl: MaxCoverCandidates{ 40 {0, &bitfield.Bitlist{0b00001010, 0b1}, 2, true}, 41 {2, &bitfield.Bitlist{0b01000010, 0b1}, 2, true}, 42 {3, &bitfield.Bitlist{0b00001010, 0b1}, 2, true}, 43 {4, &bitfield.Bitlist{0b01000010, 0b1}, 2, true}, 44 {4, &bitfield.Bitlist{0b00001010, 0b1}, 2, true}, 45 }, 46 args: args{}, 47 want: &MaxCoverCandidates{}, 48 }, 49 { 50 name: "partially processed", 51 cl: MaxCoverCandidates{ 52 {0, &bitfield.Bitlist{0b00001010, 0b1}, 2, true}, 53 {2, &bitfield.Bitlist{0b01000010, 0b1}, 2, false}, 54 {3, &bitfield.Bitlist{0b00001010, 0b1}, 2, true}, 55 {4, &bitfield.Bitlist{0b01000010, 0b1}, 2, false}, 56 {4, &bitfield.Bitlist{0b00001010, 0b1}, 2, true}, 57 }, 58 args: args{ 59 covered: bitfield.NewBitlist(8), 60 }, 61 want: &MaxCoverCandidates{ 62 {2, &bitfield.Bitlist{0b01000010, 0b1}, 2, false}, 63 {4, &bitfield.Bitlist{0b01000010, 0b1}, 2, false}, 64 }, 65 }, 66 { 67 name: "all overlapping", 68 cl: MaxCoverCandidates{ 69 {0, &bitfield.Bitlist{0b00001010, 0b1}, 2, false}, 70 {2, &bitfield.Bitlist{0b01000010, 0b1}, 2, false}, 71 {3, &bitfield.Bitlist{0b00001010, 0b1}, 2, false}, 72 {4, &bitfield.Bitlist{0b01000010, 0b1}, 2, false}, 73 {4, &bitfield.Bitlist{0b00001010, 0b1}, 2, false}, 74 }, 75 args: args{ 76 covered: aggtesting.BitlistWithAllBitsSet(8), 77 }, 78 want: &MaxCoverCandidates{}, 79 }, 80 { 81 name: "partially overlapping", 82 cl: MaxCoverCandidates{ 83 {0, &bitfield.Bitlist{0b00001010, 0b1}, 2, false}, 84 {2, &bitfield.Bitlist{0b11000010, 0b1}, 2, false}, 85 {3, &bitfield.Bitlist{0b00001010, 0b1}, 2, false}, 86 {4, &bitfield.Bitlist{0b01000011, 0b1}, 2, false}, 87 {4, &bitfield.Bitlist{0b10001010, 0b1}, 2, false}, 88 }, 89 args: args{ 90 covered: bitfield.Bitlist{0b10000001, 0b1}, 91 }, 92 want: &MaxCoverCandidates{ 93 {0, &bitfield.Bitlist{0b00001010, 0b1}, 2, false}, 94 {3, &bitfield.Bitlist{0b00001010, 0b1}, 2, false}, 95 }, 96 }, 97 { 98 name: "overlapping and processed and pending", 99 cl: MaxCoverCandidates{ 100 {0, &bitfield.Bitlist{0b00001010, 0b1}, 2, false}, 101 {2, &bitfield.Bitlist{0b11000010, 0b1}, 2, false}, 102 {3, &bitfield.Bitlist{0b00001010, 0b1}, 2, true}, 103 {4, &bitfield.Bitlist{0b01000011, 0b1}, 2, false}, 104 {4, &bitfield.Bitlist{0b10001010, 0b1}, 2, false}, 105 }, 106 args: args{ 107 covered: bitfield.Bitlist{0b10000001, 0b1}, 108 }, 109 want: &MaxCoverCandidates{ 110 {0, &bitfield.Bitlist{0b00001010, 0b1}, 2, false}, 111 }, 112 }, 113 { 114 name: "overlapping and processed and pending - allow overlaps", 115 cl: MaxCoverCandidates{ 116 {0, &bitfield.Bitlist{0b00001010, 0b1}, 2, false}, 117 {2, &bitfield.Bitlist{0b11000010, 0b1}, 2, false}, 118 {3, &bitfield.Bitlist{0b00001010, 0b1}, 2, true}, 119 {4, &bitfield.Bitlist{0b01000011, 0b1}, 0, false}, 120 {4, &bitfield.Bitlist{0b10001010, 0b1}, 2, false}, 121 }, 122 args: args{ 123 covered: bitfield.Bitlist{0b11111111, 0b1}, 124 allowOverlaps: true, 125 }, 126 want: &MaxCoverCandidates{ 127 {0, &bitfield.Bitlist{0b00001010, 0b1}, 2, false}, 128 {2, &bitfield.Bitlist{0b11000010, 0b1}, 2, false}, 129 {4, &bitfield.Bitlist{0b10001010, 0b1}, 2, false}, 130 }, 131 }, 132 } 133 for _, tt := range tests { 134 t.Run(tt.name, func(t *testing.T) { 135 got, err := tt.cl.filter(tt.args.covered, tt.args.allowOverlaps) 136 if err != nil { 137 t.Error(err) 138 } 139 sort.Slice(*got, func(i, j int) bool { 140 return (*got)[i].key < (*got)[j].key 141 }) 142 sort.Slice(*tt.want, func(i, j int) bool { 143 return (*tt.want)[i].key < (*tt.want)[j].key 144 }) 145 assert.DeepEqual(t, tt.want, got) 146 }) 147 } 148 } 149 150 func TestMaxCover_MaxCoverCandidates_sort(t *testing.T) { 151 var problem MaxCoverCandidates 152 tests := []struct { 153 name string 154 cl MaxCoverCandidates 155 want *MaxCoverCandidates 156 }{ 157 { 158 name: "nil list", 159 cl: nil, 160 want: &problem, 161 }, 162 { 163 name: "empty list", 164 cl: MaxCoverCandidates{}, 165 want: &MaxCoverCandidates{}, 166 }, 167 { 168 name: "single item", 169 cl: MaxCoverCandidates{ 170 {0, &bitfield.Bitlist{}, 5, false}, 171 }, 172 want: &MaxCoverCandidates{ 173 {0, &bitfield.Bitlist{}, 5, false}, 174 }, 175 }, 176 { 177 name: "already sorted", 178 cl: MaxCoverCandidates{ 179 {5, &bitfield.Bitlist{}, 5, false}, 180 {3, &bitfield.Bitlist{}, 4, false}, 181 {4, &bitfield.Bitlist{}, 4, false}, 182 {2, &bitfield.Bitlist{}, 2, false}, 183 {1, &bitfield.Bitlist{}, 1, false}, 184 }, 185 want: &MaxCoverCandidates{ 186 {5, &bitfield.Bitlist{}, 5, false}, 187 {3, &bitfield.Bitlist{}, 4, false}, 188 {4, &bitfield.Bitlist{}, 4, false}, 189 {2, &bitfield.Bitlist{}, 2, false}, 190 {1, &bitfield.Bitlist{}, 1, false}, 191 }, 192 }, 193 { 194 name: "all equal", 195 cl: MaxCoverCandidates{ 196 {0, &bitfield.Bitlist{}, 5, false}, 197 {0, &bitfield.Bitlist{}, 5, false}, 198 {0, &bitfield.Bitlist{}, 5, false}, 199 {0, &bitfield.Bitlist{}, 5, false}, 200 {0, &bitfield.Bitlist{}, 5, false}, 201 }, 202 want: &MaxCoverCandidates{ 203 {0, &bitfield.Bitlist{}, 5, false}, 204 {0, &bitfield.Bitlist{}, 5, false}, 205 {0, &bitfield.Bitlist{}, 5, false}, 206 {0, &bitfield.Bitlist{}, 5, false}, 207 {0, &bitfield.Bitlist{}, 5, false}, 208 }, 209 }, 210 { 211 name: "unsorted", 212 cl: MaxCoverCandidates{ 213 {2, &bitfield.Bitlist{}, 2, false}, 214 {4, &bitfield.Bitlist{}, 4, false}, 215 {3, &bitfield.Bitlist{}, 4, false}, 216 {5, &bitfield.Bitlist{}, 5, false}, 217 {1, &bitfield.Bitlist{}, 1, false}, 218 }, 219 want: &MaxCoverCandidates{ 220 {5, &bitfield.Bitlist{}, 5, false}, 221 {3, &bitfield.Bitlist{}, 4, false}, 222 {4, &bitfield.Bitlist{}, 4, false}, 223 {2, &bitfield.Bitlist{}, 2, false}, 224 {1, &bitfield.Bitlist{}, 1, false}, 225 }, 226 }, 227 } 228 for _, tt := range tests { 229 t.Run(tt.name, func(t *testing.T) { 230 if got := tt.cl.sort(); !reflect.DeepEqual(got, tt.want) { 231 t.Errorf("sort() = %v, want %v", got, tt.want) 232 } 233 }) 234 } 235 } 236 237 func TestMaxCover_MaxCoverCandidates_union(t *testing.T) { 238 tests := []struct { 239 name string 240 cl MaxCoverCandidates 241 want bitfield.Bitlist 242 }{ 243 { 244 name: "nil", 245 cl: nil, 246 want: bitfield.Bitlist(nil), 247 }, 248 { 249 name: "single empty candidate", 250 cl: MaxCoverCandidates{ 251 {0, &bitfield.Bitlist{0b00000000, 0b1}, 0, false}, 252 }, 253 want: bitfield.Bitlist{0b00000000, 0b1}, 254 }, 255 { 256 name: "single full candidate", 257 cl: MaxCoverCandidates{ 258 {0, &bitfield.Bitlist{0b11111111, 0b1}, 8, false}, 259 }, 260 want: aggtesting.BitlistWithAllBitsSet(8), 261 }, 262 { 263 name: "mixed", 264 cl: MaxCoverCandidates{ 265 {1, &bitfield.Bitlist{0b00000000, 0b00001110, 0b00001110, 0b1}, 6, false}, 266 {2, &bitfield.Bitlist{0b00000000, 0b01110000, 0b01110000, 0b1}, 6, false}, 267 {3, &bitfield.Bitlist{0b00000111, 0b10000001, 0b10000000, 0b1}, 6, false}, 268 {4, &bitfield.Bitlist{0b00000000, 0b00000110, 0b00000110, 0b1}, 4, false}, 269 {5, &bitfield.Bitlist{0b10000000, 0b00000001, 0b01100010, 0b1}, 4, false}, 270 {6, &bitfield.Bitlist{0b00001000, 0b00001000, 0b10000010, 0b1}, 4, false}, 271 {7, &bitfield.Bitlist{0b00000000, 0b00000001, 0b11111110, 0b1}, 8, false}, 272 }, 273 want: bitfield.Bitlist{0b10001111, 0b11111111, 0b11111110, 0b1}, 274 }, 275 } 276 for _, tt := range tests { 277 t.Run(tt.name, func(t *testing.T) { 278 if got, err := tt.cl.union(); !reflect.DeepEqual(got, tt.want) || err != nil { 279 t.Errorf("union(), got: %#b, %v, want: %#b", got, err, tt.want) 280 } 281 }) 282 } 283 } 284 285 func TestMaxCover_MaxCoverCandidates_score(t *testing.T) { 286 var problem MaxCoverCandidates 287 tests := []struct { 288 name string 289 cl MaxCoverCandidates 290 uncovered bitfield.Bitlist 291 want *MaxCoverCandidates 292 }{ 293 { 294 name: "nil", 295 cl: nil, 296 want: &problem, 297 }, 298 { 299 name: "uncovered set is empty", 300 cl: MaxCoverCandidates{ 301 {0, &bitfield.Bitlist{0b00000100, 0b1}, 1, false}, 302 {1, &bitfield.Bitlist{0b00011011, 0b1}, 4, false}, 303 {2, &bitfield.Bitlist{0b00011011, 0b1}, 4, false}, 304 {3, &bitfield.Bitlist{0b00000001, 0b1}, 1, false}, 305 {4, &bitfield.Bitlist{0b00011010, 0b1}, 3, false}, 306 }, 307 uncovered: bitfield.NewBitlist(8), 308 want: &MaxCoverCandidates{ 309 {0, &bitfield.Bitlist{0b00000100, 0b1}, 0, false}, 310 {1, &bitfield.Bitlist{0b00011011, 0b1}, 0, false}, 311 {2, &bitfield.Bitlist{0b00011011, 0b1}, 0, false}, 312 {3, &bitfield.Bitlist{0b00000001, 0b1}, 0, false}, 313 {4, &bitfield.Bitlist{0b00011010, 0b1}, 0, false}, 314 }, 315 }, 316 { 317 name: "completely uncovered", 318 cl: MaxCoverCandidates{ 319 {0, &bitfield.Bitlist{0b00000100, 0b1}, 0, false}, 320 {1, &bitfield.Bitlist{0b00011011, 0b1}, 0, false}, 321 {2, &bitfield.Bitlist{0b00011011, 0b1}, 0, false}, 322 {3, &bitfield.Bitlist{0b00000001, 0b1}, 0, false}, 323 {4, &bitfield.Bitlist{0b00011010, 0b1}, 0, false}, 324 }, 325 uncovered: aggtesting.BitlistWithAllBitsSet(8), 326 want: &MaxCoverCandidates{ 327 {0, &bitfield.Bitlist{0b00000100, 0b1}, 1, false}, 328 {1, &bitfield.Bitlist{0b00011011, 0b1}, 4, false}, 329 {2, &bitfield.Bitlist{0b00011011, 0b1}, 4, false}, 330 {3, &bitfield.Bitlist{0b00000001, 0b1}, 1, false}, 331 {4, &bitfield.Bitlist{0b00011010, 0b1}, 3, false}, 332 }, 333 }, 334 { 335 name: "partial uncovered set", 336 cl: MaxCoverCandidates{ 337 {0, &bitfield.Bitlist{0b00000100, 0b1}, 0, false}, 338 {1, &bitfield.Bitlist{0b00011011, 0b1}, 1, false}, 339 {2, &bitfield.Bitlist{0b10011011, 0b1}, 0, false}, 340 {3, &bitfield.Bitlist{0b11111111, 0b1}, 1, false}, 341 {4, &bitfield.Bitlist{0b00011010, 0b1}, 0, false}, 342 }, 343 uncovered: bitfield.Bitlist{0b11010010, 0b1}, 344 want: &MaxCoverCandidates{ 345 {0, &bitfield.Bitlist{0b00000100, 0b1}, 0, false}, 346 {1, &bitfield.Bitlist{0b00011011, 0b1}, 2, false}, 347 {2, &bitfield.Bitlist{0b10011011, 0b1}, 3, false}, 348 {3, &bitfield.Bitlist{0b11111111, 0b1}, 4, false}, 349 {4, &bitfield.Bitlist{0b00011010, 0b1}, 2, false}, 350 }, 351 }, 352 } 353 for _, tt := range tests { 354 t.Run(tt.name, func(t *testing.T) { 355 if got, err := tt.cl.score(tt.uncovered); !reflect.DeepEqual(got, tt.want) || err != nil { 356 t.Errorf("score() = %v, %v, want %v", got, err, tt.want) 357 } 358 }) 359 } 360 } 361 362 func TestMaxCover_MaxCoverProblem_Cover(t *testing.T) { 363 problemSet := func() MaxCoverCandidates { 364 // test vectors originally from: 365 // https://github.com/sigp/lighthouse/blob/master/beacon_node/operation_pool/src/max_cover.rs 366 return MaxCoverCandidates{ 367 {0, &bitfield.Bitlist{0b00000100, 0b1}, 0, false}, 368 {1, &bitfield.Bitlist{0b00011011, 0b1}, 0, false}, 369 {2, &bitfield.Bitlist{0b00011011, 0b1}, 0, false}, 370 {3, &bitfield.Bitlist{0b00000001, 0b1}, 0, false}, 371 {4, &bitfield.Bitlist{0b00011010, 0b1}, 0, false}, 372 } 373 } 374 type args struct { 375 k int 376 candidates MaxCoverCandidates 377 allowOverlaps bool 378 } 379 tests := []struct { 380 name string 381 args args 382 want *Aggregation 383 wantedErr string 384 }{ 385 { 386 name: "nil problem", 387 args: args{}, 388 wantedErr: ErrInvalidMaxCoverProblem.Error(), 389 }, 390 { 391 name: "different bitlengths", 392 args: args{k: 3, candidates: MaxCoverCandidates{ 393 {0, &bitfield.Bitlist{0b00000000, 0b00011111, 0xf1}, 0, false}, 394 {2, &bitfield.Bitlist{0b00000001, 0b11100000, 0b1}, 0, false}, 395 {3, &bitfield.Bitlist{0b00000110, 0b00000000, 0b1}, 0, false}, 396 }}, 397 want: &Aggregation{ 398 Coverage: bitfield.Bitlist{0b00000000, 0b00011111, 0xf1}, 399 Keys: []int{0}, 400 }, 401 }, 402 { 403 name: "k=0", 404 args: args{k: 0, candidates: problemSet()}, 405 want: &Aggregation{ 406 Coverage: bitfield.Bitlist{0b0000000, 0b1}, 407 Keys: []int{}, 408 }, 409 }, 410 { 411 name: "k=1", 412 args: args{k: 1, candidates: problemSet()}, 413 want: &Aggregation{ 414 Coverage: bitfield.Bitlist{0b0011011, 0b1}, 415 Keys: []int{1}, 416 }, 417 }, 418 { 419 name: "k=2", 420 args: args{k: 2, candidates: problemSet()}, 421 want: &Aggregation{ 422 Coverage: bitfield.Bitlist{0b0011111, 0b1}, 423 Keys: []int{1, 0}, 424 }, 425 }, 426 { 427 name: "k=3", 428 args: args{k: 3, candidates: problemSet()}, 429 want: &Aggregation{ 430 Coverage: bitfield.Bitlist{0b0011111, 0b1}, 431 Keys: []int{1, 0}, 432 }, 433 }, 434 { 435 name: "k=5", 436 args: args{k: 5, candidates: problemSet()}, 437 want: &Aggregation{ 438 Coverage: bitfield.Bitlist{0b0011111, 0b1}, 439 Keys: []int{1, 0}, 440 }, 441 }, 442 { 443 name: "k=50", 444 args: args{k: 50, candidates: problemSet()}, 445 want: &Aggregation{ 446 Coverage: bitfield.Bitlist{0b0011111, 0b1}, 447 Keys: []int{1, 0}, 448 }, 449 }, 450 { 451 name: "suboptimal", // Greedy algorithm selects: 0, 2, 3, while 1,4,5 is optimal. 452 args: args{k: 3, candidates: MaxCoverCandidates{ 453 {0, &bitfield.Bitlist{0b00000000, 0b00011111, 0b1}, 0, false}, 454 {2, &bitfield.Bitlist{0b00000001, 0b11100000, 0b1}, 0, false}, 455 {3, &bitfield.Bitlist{0b00000110, 0b00000000, 0b1}, 0, false}, 456 {1, &bitfield.Bitlist{0b00110000, 0b01110000, 0b1}, 0, false}, 457 {4, &bitfield.Bitlist{0b00000110, 0b10001100, 0b1}, 0, false}, 458 {5, &bitfield.Bitlist{0b01001001, 0b00000011, 0b1}, 0, false}, 459 }}, 460 want: &Aggregation{ 461 Coverage: bitfield.Bitlist{0b00000111, 0b11111111, 0b1}, 462 Keys: []int{0, 2, 3}, 463 }, 464 }, 465 { 466 name: "allow overlaps", 467 args: args{k: 5, allowOverlaps: true, candidates: MaxCoverCandidates{ 468 {0, &bitfield.Bitlist{0b00000000, 0b00000001, 0b11111110, 0b1}, 0, false}, 469 {1, &bitfield.Bitlist{0b00000000, 0b00001110, 0b00001110, 0b1}, 0, false}, 470 {2, &bitfield.Bitlist{0b00000000, 0b01110000, 0b01110000, 0b1}, 0, false}, 471 {3, &bitfield.Bitlist{0b00000111, 0b10000001, 0b10000000, 0b1}, 0, false}, 472 {4, &bitfield.Bitlist{0b00000000, 0b00000110, 0b00000110, 0b1}, 0, false}, 473 {5, &bitfield.Bitlist{0b00000000, 0b00000001, 0b01100010, 0b1}, 0, false}, 474 {6, &bitfield.Bitlist{0b00001000, 0b00001000, 0b10000010, 0b1}, 0, false}, 475 }}, 476 want: &Aggregation{ 477 Coverage: bitfield.Bitlist{0b00001111, 0xff, 0b11111110, 0b1}, 478 Keys: []int{0, 3, 1, 2, 6}, 479 }, 480 }, 481 { 482 name: "empty bitlists", 483 args: args{k: 5, allowOverlaps: true, candidates: MaxCoverCandidates{ 484 {1, &bitfield.Bitlist{0b0}, 0, false}, 485 {0, &bitfield.Bitlist{0b00000000, 0b00000000, 0b00000000, 0b1}, 0, false}, 486 {2, &bitfield.Bitlist{0b00000000, 0b00000000, 0b00000000, 0b1}, 0, false}, 487 }}, 488 wantedErr: "empty bitlists: invalid max_cover problem", 489 }, 490 { 491 name: "overlapping solution dropped", 492 args: args{k: 5, allowOverlaps: false, candidates: MaxCoverCandidates{ 493 {0, &bitfield.Bitlist{0b11111111, 0b11000111, 0b11111111, 0b1}, 0, false}, 494 // All remaining bitlists will overlap, so will be dropped. 495 {1, &bitfield.Bitlist{0b11111111, 0b00001100, 0b11111111, 0b1}, 0, false}, 496 {2, &bitfield.Bitlist{0b00000000, 0b01110000, 0b01110000, 0b1}, 0, false}, 497 {3, &bitfield.Bitlist{0b00000111, 0b10000001, 0b10000000, 0b1}, 0, false}, 498 {4, &bitfield.Bitlist{0b00000000, 0b00000110, 0b00000110, 0b1}, 0, false}, 499 {5, &bitfield.Bitlist{0b00000000, 0b00000001, 0b01100010, 0b1}, 0, false}, 500 {6, &bitfield.Bitlist{0b00001000, 0b00001000, 0b10000010, 0b1}, 0, false}, 501 }}, 502 want: &Aggregation{ 503 Coverage: bitfield.Bitlist{0xff, 0b11000111, 0xff, 0b1}, 504 Keys: []int{0}, 505 }, 506 }, 507 } 508 for _, tt := range tests { 509 t.Run(tt.name, func(t *testing.T) { 510 mc := &MaxCoverProblem{ 511 Candidates: tt.args.candidates, 512 } 513 got, err := mc.Cover(tt.args.k, tt.args.allowOverlaps) 514 if tt.wantedErr != "" { 515 assert.ErrorContains(t, tt.wantedErr, err) 516 } else { 517 assert.NoError(t, err) 518 assert.DeepEqual(t, tt.want, got) 519 } 520 }) 521 } 522 } 523 524 func TestMaxCover_MaxCover(t *testing.T) { 525 problemSet := func() []*bitfield.Bitlist64 { 526 return []*bitfield.Bitlist64{ 527 bitfield.NewBitlist64From([]uint64{0b00000100}), 528 bitfield.NewBitlist64From([]uint64{0b00011011}), 529 bitfield.NewBitlist64From([]uint64{0b00011011}), 530 bitfield.NewBitlist64From([]uint64{0b00000001}), 531 bitfield.NewBitlist64From([]uint64{0b00011010}), 532 } 533 } 534 type args struct { 535 k int 536 candidates []*bitfield.Bitlist64 537 allowOverlaps bool 538 } 539 type BitSetAggregation struct { 540 Coverage *bitfield.Bitlist64 541 Keys []int 542 } 543 tests := []struct { 544 name string 545 args args 546 want *BitSetAggregation 547 wantedErr string 548 }{ 549 { 550 name: "nil problem", 551 args: args{}, 552 wantedErr: ErrInvalidMaxCoverProblem.Error(), 553 }, 554 { 555 name: "different bitlengths (pick first, combine with third)", 556 args: args{k: 3, candidates: []*bitfield.Bitlist64{ 557 bitfield.NewBitlist64From([]uint64{0b00000001, 0b11100000, 0b10000000}), 558 bitfield.NewBitlist64From([]uint64{0b00000000, 0b00011111}), 559 bitfield.NewBitlist64From([]uint64{0b00000110, 0b00000000, 0b01000000}), 560 }}, 561 want: &BitSetAggregation{ 562 Coverage: bitfield.NewBitlist64From([]uint64{0b00000111, 0b11100000, 0b11000000}), 563 Keys: []int{0, 2}, 564 }, 565 }, 566 { 567 name: "different bitlengths (pick first, no other combination)", 568 args: args{k: 3, candidates: []*bitfield.Bitlist64{ 569 bitfield.NewBitlist64From([]uint64{0b00000000, 0b00011111}), 570 bitfield.NewBitlist64From([]uint64{0b00000001, 0b11100000, 0b1}), 571 bitfield.NewBitlist64From([]uint64{0b00000110, 0b00000000, 0b1}), 572 }}, 573 want: &BitSetAggregation{ 574 Coverage: bitfield.NewBitlist64From([]uint64{0b00000000, 0b00011111}), 575 Keys: []int{0}, 576 }, 577 }, 578 { 579 name: "k=0", 580 args: args{k: 0, candidates: problemSet()}, 581 want: &BitSetAggregation{ 582 Coverage: bitfield.NewBitlist64From([]uint64{0b0}), 583 Keys: []int{}, 584 }, 585 }, 586 { 587 name: "k=1", 588 args: args{k: 1, candidates: problemSet()}, 589 want: &BitSetAggregation{ 590 Coverage: bitfield.NewBitlist64From([]uint64{0b0011011}), 591 Keys: []int{1}, 592 }, 593 }, 594 { 595 name: "k=2", 596 args: args{k: 2, candidates: problemSet()}, 597 want: &BitSetAggregation{ 598 Coverage: bitfield.NewBitlist64From([]uint64{0b0011111}), 599 Keys: []int{0, 1}, 600 }, 601 }, 602 { 603 name: "k=3", 604 args: args{k: 3, candidates: problemSet()}, 605 want: &BitSetAggregation{ 606 Coverage: bitfield.NewBitlist64From([]uint64{0b0011111}), 607 Keys: []int{0, 1}, 608 }, 609 }, 610 { 611 name: "k=5", 612 args: args{k: 5, candidates: problemSet()}, 613 want: &BitSetAggregation{ 614 Coverage: bitfield.NewBitlist64From([]uint64{0b0011111}), 615 Keys: []int{0, 1}, 616 }, 617 }, 618 { 619 name: "k=50", 620 args: args{k: 50, candidates: problemSet()}, 621 want: &BitSetAggregation{ 622 Coverage: bitfield.NewBitlist64From([]uint64{0b0011111}), 623 Keys: []int{0, 1}, 624 }, 625 }, 626 { 627 name: "suboptimal", // Greedy algorithm selects: 0, 2, 3, while 1,4,5 is optimal. 628 args: args{k: 3, candidates: []*bitfield.Bitlist64{ 629 bitfield.NewBitlist64From([]uint64{0b00000000, 0b00011111}), 630 bitfield.NewBitlist64From([]uint64{0b00000001, 0b11100000}), 631 bitfield.NewBitlist64From([]uint64{0b00000110, 0b00000000}), 632 bitfield.NewBitlist64From([]uint64{0b00110000, 0b01110000}), 633 bitfield.NewBitlist64From([]uint64{0b00000110, 0b10001100}), 634 bitfield.NewBitlist64From([]uint64{0b01001001, 0b00000011}), 635 }}, 636 want: &BitSetAggregation{ 637 Coverage: bitfield.NewBitlist64From([]uint64{0b00000111, 0b11111111}), 638 Keys: []int{0, 1, 2}, 639 }, 640 }, 641 { 642 name: "allow overlaps", 643 args: args{k: 5, allowOverlaps: true, candidates: []*bitfield.Bitlist64{ 644 bitfield.NewBitlist64From([]uint64{0b00000000, 0b00000001, 0b11111110}), 645 bitfield.NewBitlist64From([]uint64{0b00000000, 0b00001110, 0b00001110}), 646 bitfield.NewBitlist64From([]uint64{0b00000000, 0b01110000, 0b01110000}), 647 bitfield.NewBitlist64From([]uint64{0b00000111, 0b10000001, 0b10000000}), 648 bitfield.NewBitlist64From([]uint64{0b00000000, 0b00000110, 0b00000110}), 649 bitfield.NewBitlist64From([]uint64{0b00000000, 0b00000001, 0b01100010}), 650 bitfield.NewBitlist64From([]uint64{0b00001000, 0b00001000, 0b10000010}), 651 }}, 652 want: &BitSetAggregation{ 653 Coverage: bitfield.NewBitlist64From([]uint64{0b00001111, 0xff, 0b11111110}), 654 Keys: []int{0, 1, 2, 3, 6}, 655 }, 656 }, 657 { 658 name: "empty bitlists", 659 args: args{k: 5, allowOverlaps: true, candidates: []*bitfield.Bitlist64{ 660 bitfield.NewBitlist64From([]uint64{}), 661 bitfield.NewBitlist64From([]uint64{0b00000000, 0b00001110, 0b00001110}), 662 bitfield.NewBitlist64From([]uint64{0b00000000, 0b01110000, 0b01110000}), 663 }}, 664 wantedErr: "empty bitlists: invalid max_cover problem", 665 }, 666 } 667 for _, tt := range tests { 668 t.Run(tt.name, func(t *testing.T) { 669 selectedCandidates, coverage, err := MaxCover(tt.args.candidates, tt.args.k, tt.args.allowOverlaps) 670 if tt.wantedErr != "" { 671 assert.ErrorContains(t, tt.wantedErr, err) 672 } else { 673 assert.NoError(t, err) 674 assert.DeepEqual(t, tt.want.Coverage, coverage) 675 selectedKeys := make([]int, selectedCandidates.Count()) 676 selectedCandidates.NoAllocBitIndices(selectedKeys) 677 assert.DeepEqual(t, tt.want.Keys, selectedKeys) 678 } 679 }) 680 } 681 }