Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions include/roaring/containers/containers.h
Original file line number Diff line number Diff line change
Expand Up @@ -2224,6 +2224,58 @@ static inline int container_rank(const container_t *c, uint8_t type,
return false;
}

// number of values in [min, max], with min <= max <= 0xFFFF
static inline int container_range_cardinality(const container_t *c,
uint8_t type, uint32_t min,
uint32_t max) {
c = container_unwrap_shared(c, &type);
switch (type) {
case BITSET_CONTAINER_TYPE: {
const bitset_container_t *bitset = const_CAST_bitset(c);
// For a wide range, count the values outside it instead.
if (max - min >= (1 << 15) &&
bitset->cardinality != BITSET_UNKNOWN_CARDINALITY) {
int outside = 0;
if (min > 0) {
outside +=
bitset_lenrange_cardinality(bitset->words, 0, min - 1);
}
if (max < 0xFFFF) {
outside += bitset_lenrange_cardinality(
bitset->words, max + 1, 0xFFFF - max - 1);
}
return bitset->cardinality - outside;
}
return bitset_lenrange_cardinality(bitset->words, min, max - min);
}
case ARRAY_CONTAINER_TYPE: {
const array_container_t *array = const_CAST_array(c);
int below =
min > 0 ? array_container_rank(array, (uint16_t)(min - 1)) : 0;
return array_container_rank(array, (uint16_t)max) - below;
}
case RUN_CONTAINER_TYPE: {
const run_container_t *run = const_CAST_run(c);
int sum = 0;
for (int32_t i = 0; i < run->n_runs; i++) {
uint32_t start = run->runs[i].value;
uint32_t end = start + run->runs[i].length;
if (start > max) break;
if (end < min) continue;
sum += (int)((end < max ? end : max) -
(start > min ? start : min) + 1);
}
return sum;
}
default:
assert(false);
roaring_unreachable;
}
assert(false);
roaring_unreachable;
return 0;
}

// bulk version of container_rank(); return number of consumed elements
static inline uint32_t container_rank_many(const container_t *c, uint8_t type,
uint64_t start_rank,
Expand Down
148 changes: 49 additions & 99 deletions src/roaring.c
Original file line number Diff line number Diff line change
Expand Up @@ -1558,18 +1558,23 @@ uint64_t roaring_bitmap_range_cardinality_closed(const roaring_bitmap_t *r,

uint64_t card = 0;

// In the first and last containers, container_range_cardinality() counts
// the values in the range: in a bitset, subtracting ranks would count
// every value below the range, twice.
int i = ra_get_index(ra, minhb);
if (i >= 0) {
if (minhb == maxhb) {
card += container_rank(ra->containers[i], ra->typecodes[i],
range_end & 0xffff);
} else {
return (uint64_t)container_range_cardinality(
ra->containers[i], ra->typecodes[i], range_start & 0xffff,
range_end & 0xffff);
}
if ((range_start & 0xffff) == 0) {
card +=
container_get_cardinality(ra->containers[i], ra->typecodes[i]);
}
if ((range_start & 0xffff) != 0) {
card -= container_rank(ra->containers[i], ra->typecodes[i],
(range_start & 0xffff) - 1);
} else {
card +=
container_range_cardinality(ra->containers[i], ra->typecodes[i],
range_start & 0xffff, 0xffff);
}
i++;
} else {
Expand All @@ -1582,8 +1587,8 @@ uint64_t roaring_bitmap_range_cardinality_closed(const roaring_bitmap_t *r,
card +=
container_get_cardinality(ra->containers[i], ra->typecodes[i]);
} else if (key == maxhb) {
card += container_rank(ra->containers[i], ra->typecodes[i],
range_end & 0xffff);
card += container_range_cardinality(
ra->containers[i], ra->typecodes[i], 0, range_end & 0xffff);
break;
} else {
break;
Expand Down Expand Up @@ -2272,34 +2277,6 @@ bool roaring_bitmap_is_subset(const roaring_bitmap_t *r1,
return false;
}

static void insert_flipped_container(roaring_array_t *ans_arr,
const roaring_array_t *x1_arr, uint16_t hb,
uint16_t lb_start, uint16_t lb_end) {
const int i = ra_get_index(x1_arr, hb);
const int j = ra_get_index(ans_arr, hb);
uint8_t ctype_in, ctype_out;
container_t *flipped_container = NULL;
if (i >= 0) {
container_t *container_to_flip =
ra_get_container_at_index(x1_arr, (uint16_t)i, &ctype_in);
flipped_container =
container_not_range(container_to_flip, ctype_in, (uint32_t)lb_start,
(uint32_t)(lb_end + 1), &ctype_out);

if (container_nonzero_cardinality(flipped_container, ctype_out))
ra_insert_new_key_value_at(ans_arr, -j - 1, hb, flipped_container,
ctype_out);
else {
container_free(flipped_container, ctype_out);
}
} else {
flipped_container = container_range_of_ones(
(uint32_t)lb_start, (uint32_t)(lb_end + 1), &ctype_out);
ra_insert_new_key_value_at(ans_arr, -j - 1, hb, flipped_container,
ctype_out);
}
}

static void inplace_flip_container(roaring_array_t *x1_arr, uint16_t hb,
uint16_t lb_start, uint16_t lb_end) {
const int i = ra_get_index(x1_arr, hb);
Expand Down Expand Up @@ -2327,31 +2304,6 @@ static void inplace_flip_container(roaring_array_t *x1_arr, uint16_t hb,
}
}

static void insert_fully_flipped_container(roaring_array_t *ans_arr,
const roaring_array_t *x1_arr,
uint16_t hb) {
const int i = ra_get_index(x1_arr, hb);
const int j = ra_get_index(ans_arr, hb);
uint8_t ctype_in, ctype_out;
container_t *flipped_container = NULL;
if (i >= 0) {
container_t *container_to_flip =
ra_get_container_at_index(x1_arr, (uint16_t)i, &ctype_in);
flipped_container =
container_not(container_to_flip, ctype_in, &ctype_out);
if (container_nonzero_cardinality(flipped_container, ctype_out))
ra_insert_new_key_value_at(ans_arr, -j - 1, hb, flipped_container,
ctype_out);
else {
container_free(flipped_container, ctype_out);
}
} else {
flipped_container = container_range_of_ones(0U, 0x10000U, &ctype_out);
ra_insert_new_key_value_at(ans_arr, -j - 1, hb, flipped_container,
ctype_out);
}
}

static void inplace_fully_flip_container(roaring_array_t *x1_arr, uint16_t hb) {
const int i = ra_get_index(x1_arr, hb);
uint8_t ctype_in, ctype_out;
Expand Down Expand Up @@ -2399,45 +2351,43 @@ roaring_bitmap_t *roaring_bitmap_flip_closed(const roaring_bitmap_t *x1,
roaring_bitmap_t *ans = roaring_bitmap_create();
roaring_bitmap_set_copy_on_write(ans, is_cow(x1));

uint16_t hb_start = (uint16_t)(range_start >> 16);
const uint16_t lb_start = (uint16_t)range_start; // & 0xFFFF;
uint16_t hb_end = (uint16_t)(range_end >> 16);
const uint16_t lb_end = (uint16_t)range_end; // & 0xFFFF;

ra_append_copies_until(&ans->high_low_container, &x1->high_low_container,
hb_start, is_cow(x1));
if (hb_start == hb_end) {
insert_flipped_container(&ans->high_low_container,
&x1->high_low_container, hb_start, lb_start,
lb_end);
} else {
// start and end containers are distinct
if (lb_start > 0) {
// handle first (partial) container
insert_flipped_container(&ans->high_low_container,
&x1->high_low_container, hb_start,
lb_start, 0xFFFF);
++hb_start; // for the full containers. Can't wrap.
}

if (lb_end != 0xFFFF) --hb_end; // later we'll handle the partial block

for (uint32_t hb = hb_start; hb <= hb_end; ++hb) {
insert_fully_flipped_container(&ans->high_low_container,
&x1->high_low_container,
(uint16_t)hb);
}

// handle a partial final container
if (lb_end != 0xFFFF) {
insert_flipped_container(&ans->high_low_container,
&x1->high_low_container, hb_end + 1, 0,
lb_end);
++hb_end;
const roaring_array_t *x1_arr = &x1->high_low_container;
roaring_array_t *ans_arr = &ans->high_low_container;
const uint32_t hb_start = range_start >> 16;
const uint32_t hb_end = range_end >> 16;

ra_append_copies_until(ans_arr, x1_arr, (uint16_t)hb_start, is_cow(x1));
// We visit the keys of the range and the containers of x1 together, in
// order, appending the flipped containers to ans. Index i is that of the
// first container of x1 not visited yet.
int32_t i = ans_arr->size;
for (uint32_t hb = hb_start; hb <= hb_end; ++hb) {
const uint32_t lb_start = hb == hb_start ? (range_start & 0xFFFF) : 0;
const uint32_t lb_end = hb == hb_end ? (range_end & 0xFFFF) : 0xFFFF;
uint8_t ctype_out;
container_t *flipped;
if (i < x1_arr->size && x1_arr->keys[i] == hb) {
const container_t *c = x1_arr->containers[i];
const uint8_t ctype_in = x1_arr->typecodes[i];
++i;
if (lb_start == 0 && lb_end == 0xFFFF) {
// A full container flips to nothing.
if (container_is_full(c, ctype_in)) continue;
flipped = container_not(c, ctype_in, &ctype_out);
} else {
flipped = container_not_range(c, ctype_in, lb_start, lb_end + 1,
&ctype_out);
}
if (!container_nonzero_cardinality(flipped, ctype_out)) {
container_free(flipped, ctype_out);
continue;
}
} else {
flipped = container_range_of_ones(lb_start, lb_end + 1, &ctype_out);
}
ra_append(ans_arr, (uint16_t)hb, flipped, ctype_out);
}
ra_append_copies_after(&ans->high_low_container, &x1->high_low_container,
hb_end, is_cow(x1));
ra_append_copy_range(ans_arr, x1_arr, i, x1_arr->size, is_cow(x1));
return ans;
}

Expand Down
51 changes: 51 additions & 0 deletions tests/toplevel_unit.c
Original file line number Diff line number Diff line change
Expand Up @@ -7276,6 +7276,56 @@ DEFINE_TEST(test_flip_to_empty_is_valid) {
roaring_bitmap_free(r);
}

// Range cardinality and flip, against a plain bit array, on chunks holding
// every kind of container: missing, array, bitset, full, one run, many runs.
DEFINE_TEST(test_range_cardinality_and_flip_by_container) {
enum { NCHUNKS = 7, UNIVERSE = NCHUNKS << 16 };
uint8_t *bits = (uint8_t *)calloc(UNIVERSE, 1);
for (uint32_t i = 0; i < 65536; i += 97) bits[(1 << 16) + i] = 1;
for (uint32_t i = 0; i < 65536; i += 3) bits[(2 << 16) + i] = 1;
for (uint32_t i = 0; i < 65536; i++) bits[(3 << 16) + i] = 1;
for (uint32_t i = 1000; i < 50000; i++) bits[(4 << 16) + i] = 1;
for (uint32_t i = 0; i < 65536; i += 1000) {
for (uint32_t j = i; j < i + 300 && j < 65536; j++) {
bits[(5 << 16) + j] = 1;
}
}
for (uint32_t i = 0; i < 65536; i++) bits[(6 << 16) + i] = 1;
roaring_bitmap_t *r = roaring_bitmap_create();
for (uint32_t v = 0; v < UNIVERSE; v++) {
if (bits[v]) roaring_bitmap_add(r, v);
}
roaring_bitmap_run_optimize(r);
// Ends inside chunks, on chunk boundaries, and wide ranges in a chunk.
const uint32_t points[] = {0, 1, 777, 32768, 40000, 65535,
65536, 65537, 99999, 131072, 150000, 196608,
196609, 230000, 262143, 262144, 300000, 327680,
393215, 393216, 400000, 458751, UNIVERSE};
const size_t npoints = sizeof(points) / sizeof(points[0]);
for (size_t a = 0; a < npoints; a++) {
for (size_t b = a; b < npoints; b++) {
uint64_t start = points[a], end = points[b];
uint64_t expected = 0;
for (uint64_t v = start; v < end; v++) expected += bits[v];
assert_true(roaring_bitmap_range_cardinality(r, start, end) ==
expected);
roaring_bitmap_t *f = roaring_bitmap_flip(r, start, end);
assert_bitmap_validate(f);
assert_true(roaring_bitmap_get_cardinality(f) ==
roaring_bitmap_get_cardinality(r) - expected +
(end - start - expected));
for (uint32_t v = 0; v < UNIVERSE; v += 61) {
bool flipped = v >= start && v < end;
assert_true(roaring_bitmap_contains(f, v) ==
(bits[v] != flipped));
}
roaring_bitmap_free(f);
}
}
roaring_bitmap_free(r);
free(bits);
}

/* ---- 32-bit set operations ---- */

// Java containerSharingWithXor / testAstesana
Expand Down Expand Up @@ -8657,6 +8707,7 @@ int main() {
cmocka_unit_test(test_minimum_maximum_extremes),
cmocka_unit_test(test_minimal_run_point_mutations),
cmocka_unit_test(test_flip_to_empty_is_valid),
cmocka_unit_test(test_range_cardinality_and_flip_by_container),
cmocka_unit_test(test_inplace_ops_do_not_share_mutated_containers),
cmocka_unit_test(test_results_do_not_share_containers_with_inputs),
cmocka_unit_test(test_xor_many_drops_cancelled_containers),
Expand Down
Loading