Files
container/experimental/segmented_merge_test.cpp
T

1036 lines
36 KiB
C++

//////////////////////////////////////////////////////////////////////////////
//
// (C) Copyright Ion Gaztanaga 2025-2026. Distributed under the Boost
// Software License, Version 1.0. (See accompanying file
// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// See http://www.boost.org/libs/container for documentation.
//
//////////////////////////////////////////////////////////////////////////////
#include <boost/container/experimental/segmented_merge.hpp>
#include <boost/core/lightweight_test.hpp>
#include "segmented_test_helper.hpp"
#include <boost/container/vector.hpp>
using namespace boost::container;
void test_merge_segmented_inputs()
{
test_detail::seg_vector<int> sv1;
int a1[] = {1, 3};
int a2[] = {5, 7};
sv1.add_segment_range(a1, a1 + 2);
sv1.add_segment_range(a2, a2 + 2);
test_detail::seg_vector<int> sv2;
int b1[] = {2, 4};
int b2[] = {6, 8};
sv2.add_segment_range(b1, b1 + 2);
sv2.add_segment_range(b2, b2 + 2);
boost::container::vector<int> out(8, 0);
boost::container::vector<int>::iterator r = segmented_merge(
sv1.begin(), sv1.end(), sv2.begin(), sv2.end(), out.begin());
BOOST_TEST_EQ(static_cast<std::size_t>(r - out.begin()), 8u);
for(int i = 0; i < 8; ++i)
BOOST_TEST_EQ(out[static_cast<std::size_t>(i)], i + 1);
}
void test_merge_first_empty()
{
test_detail::seg_vector<int> sv1;
test_detail::seg_vector<int> sv2;
int a[] = {1, 2, 3};
sv2.add_segment_range(a, a + 3);
boost::container::vector<int> out(3, 0);
boost::container::vector<int>::iterator r = segmented_merge(
sv1.begin(), sv1.end(), sv2.begin(), sv2.end(), out.begin());
BOOST_TEST_EQ(static_cast<std::size_t>(r - out.begin()), 3u);
BOOST_TEST_EQ(out[0], 1);
BOOST_TEST_EQ(out[1], 2);
BOOST_TEST_EQ(out[2], 3);
}
void test_merge_second_empty()
{
test_detail::seg_vector<int> sv1;
int a[] = {1, 2, 3};
sv1.add_segment_range(a, a + 3);
test_detail::seg_vector<int> sv2;
boost::container::vector<int> out(3, 0);
boost::container::vector<int>::iterator r = segmented_merge(
sv1.begin(), sv1.end(), sv2.begin(), sv2.end(), out.begin());
BOOST_TEST_EQ(static_cast<std::size_t>(r - out.begin()), 3u);
BOOST_TEST_EQ(out[0], 1);
BOOST_TEST_EQ(out[1], 2);
BOOST_TEST_EQ(out[2], 3);
}
void test_merge_both_empty()
{
test_detail::seg_vector<int> sv1;
test_detail::seg_vector<int> sv2;
boost::container::vector<int> out;
boost::container::vector<int>::iterator r = segmented_merge(
sv1.begin(), sv1.end(), sv2.begin(), sv2.end(), out.begin());
BOOST_TEST(r == out.begin());
}
struct greater_comp
{
bool operator()(int a, int b) const { return a > b; }
};
void test_merge_with_comp()
{
int a[] = {5, 3, 1};
int b[] = {6, 4, 2};
boost::container::vector<int> v1(a, a + 3);
boost::container::vector<int> v2(b, b + 3);
boost::container::vector<int> out(6, 0);
boost::container::vector<int>::iterator r = segmented_merge(
v1.begin(), v1.end(), v2.begin(), v2.end(), out.begin(), greater_comp());
BOOST_TEST_EQ(static_cast<std::size_t>(r - out.begin()), 6u);
BOOST_TEST_EQ(out[0], 6);
BOOST_TEST_EQ(out[1], 5);
BOOST_TEST_EQ(out[2], 4);
BOOST_TEST_EQ(out[3], 3);
BOOST_TEST_EQ(out[4], 2);
BOOST_TEST_EQ(out[5], 1);
}
void test_merge_non_segmented()
{
int a[] = {1, 3, 5};
int b[] = {2, 4, 6};
boost::container::vector<int> v1(a, a + 3);
boost::container::vector<int> v2(b, b + 3);
boost::container::vector<int> out(6, 0);
boost::container::vector<int>::iterator r = segmented_merge(
v1.begin(), v1.end(), v2.begin(), v2.end(), out.begin());
BOOST_TEST_EQ(static_cast<std::size_t>(r - out.begin()), 6u);
for(int i = 0; i < 6; ++i)
BOOST_TEST_EQ(out[static_cast<std::size_t>(i)], i + 1);
}
void test_merge_duplicates()
{
int a[] = {1, 2, 3};
int b[] = {2, 3, 4};
boost::container::vector<int> v1(a, a + 3);
boost::container::vector<int> v2(b, b + 3);
boost::container::vector<int> out(6, 0);
boost::container::vector<int>::iterator r = segmented_merge(
v1.begin(), v1.end(), v2.begin(), v2.end(), out.begin());
BOOST_TEST_EQ(static_cast<std::size_t>(r - out.begin()), 6u);
int expected[] = {1, 2, 2, 3, 3, 4};
for(std::size_t i = 0; i < 6; ++i)
BOOST_TEST_EQ(out[i], expected[i]);
}
void test_merge_sentinel_segmented()
{
test_detail::seg_vector<int> sv1;
int a1[] = {1, 3};
int a2[] = {5, 7};
sv1.add_segment_range(a1, a1 + 2);
sv1.add_segment_range(a2, a2 + 2);
test_detail::seg_vector<int> sv2;
int b1[] = {2, 4};
int b2[] = {6, 8};
sv2.add_segment_range(b1, b1 + 2);
sv2.add_segment_range(b2, b2 + 2);
boost::container::vector<int> out(8, 0);
boost::container::vector<int>::iterator r = segmented_merge(
sv1.begin(), test_detail::make_sentinel(sv1.end()),
sv2.begin(), test_detail::make_sentinel(sv2.end()), out.begin());
BOOST_TEST_EQ(static_cast<std::size_t>(r - out.begin()), 8u);
for(int i = 0; i < 8; ++i)
BOOST_TEST_EQ(out[static_cast<std::size_t>(i)], i + 1);
}
void test_merge_sentinel_non_segmented()
{
int a[] = {1, 3, 5};
int b[] = {2, 4, 6};
boost::container::vector<int> v1(a, a + 3);
boost::container::vector<int> v2(b, b + 3);
boost::container::vector<int> out(6, 0);
boost::container::vector<int>::iterator r = segmented_merge(
v1.begin(), test_detail::make_sentinel(v1.end()),
v2.begin(), test_detail::make_sentinel(v2.end()), out.begin());
BOOST_TEST_EQ(static_cast<std::size_t>(r - out.begin()), 6u);
for(int i = 0; i < 6; ++i)
BOOST_TEST_EQ(out[static_cast<std::size_t>(i)], i + 1);
}
void test_merge_seg2()
{
test_detail::seg2_vector<int> sv2;
int a1[] = {1, 3, 5};
int a2[] = {7, 9};
sv2.add_flat_segment_range(a1, a1 + 3);
sv2.add_flat_segment_range(a2, a2 + 2);
int b[] = {2, 4, 6, 8};
boost::container::vector<int> out(9, 0);
boost::container::vector<int>::iterator r = segmented_merge(
sv2.begin(), sv2.end(), b, b + 4, out.begin());
BOOST_TEST_EQ(static_cast<std::size_t>(r - out.begin()), 9u);
for(int i = 0; i < 9; ++i)
BOOST_TEST_EQ(out[static_cast<std::size_t>(i)], i + 1);
}
// Multi-segmented output with non-segmented inputs. Exercises the
// merge_dst_dispatch(segmented_iterator_tag) walker that walks output
// segments and bounds each merge call to the current segment.
void test_merge_segmented_output()
{
int a[] = {1, 3, 5, 7, 9};
int b[] = {2, 4, 6, 8};
test_detail::seg_vector<int> out;
out.add_segment(3, 0);
out.add_segment(4, 0);
out.add_segment(2, 0);
typedef test_detail::seg_vector<int>::iterator iter_t;
iter_t r = segmented_merge(a, a + 5, b, b + 4, out.begin());
BOOST_TEST(r == out.end());
iter_t it = out.begin();
for(int i = 0; i < 9; ++i, ++it)
BOOST_TEST_EQ(*it, i + 1);
}
// Segmented inputs and segmented output: stresses the per-src1-segment
// walker threading first2/result through the multi-segmented dst walker.
void test_merge_segmented_inputs_and_output()
{
test_detail::seg_vector<int> sv1;
int a1[] = {1, 3};
int a2[] = {5, 7, 9};
sv1.add_segment_range(a1, a1 + 2);
sv1.add_segment_range(a2, a2 + 3);
test_detail::seg_vector<int> sv2;
int b1[] = {2, 4};
int b2[] = {6, 8};
sv2.add_segment_range(b1, b1 + 2);
sv2.add_segment_range(b2, b2 + 2);
test_detail::seg_vector<int> out;
out.add_segment(2, 0);
out.add_segment(3, 0);
out.add_segment(4, 0);
typedef test_detail::seg_vector<int>::iterator iter_t;
iter_t r = segmented_merge(
sv1.begin(), sv1.end(), sv2.begin(), sv2.end(), out.begin());
BOOST_TEST(r == out.end());
iter_t it = out.begin();
for(int i = 0; i < 9; ++i, ++it)
BOOST_TEST_EQ(*it, i + 1);
}
// First source larger than second: forces the first2-exhausted branch
// of merge_dst_dispatch(segmented_iterator_tag) which drains the
// remaining first1 via segmented_copy through the rest of dst.
void test_merge_segmented_output_first_longer()
{
int a[] = {1, 3, 5, 7, 9, 11, 13};
int b[] = {2, 4};
test_detail::seg_vector<int> out;
out.add_segment(2, 0);
out.add_segment(2, 0);
out.add_segment(2, 0);
out.add_segment(3, 0);
typedef test_detail::seg_vector<int>::iterator iter_t;
iter_t r = segmented_merge(a, a + 7, b, b + 2, out.begin());
BOOST_TEST(r == out.end());
int expected[] = {1, 2, 3, 4, 5, 7, 9, 11, 13};
iter_t it = out.begin();
for(std::size_t i = 0; i < 9; ++i, ++it)
BOOST_TEST_EQ(*it, expected[i]);
}
// Second source larger than first: forces the first1-exhausted branch
// where the leaf returns and then the remaining first2 is flushed via
// segmented_copy at the top-level segmented_merge_dispatch.
void test_merge_segmented_output_second_longer()
{
int a[] = {1, 3};
int b[] = {2, 4, 6, 8, 10, 12, 14};
test_detail::seg_vector<int> out;
out.add_segment(3, 0);
out.add_segment(3, 0);
out.add_segment(3, 0);
typedef test_detail::seg_vector<int>::iterator iter_t;
iter_t r = segmented_merge(a, a + 2, b, b + 7, out.begin());
BOOST_TEST(r == out.end());
int expected[] = {1, 2, 3, 4, 6, 8, 10, 12, 14};
iter_t it = out.begin();
for(std::size_t i = 0; i < 9; ++i, ++it)
BOOST_TEST_EQ(*it, expected[i]);
}
// Stress test for the unrolled main loop. With N=64 in both ranges, the
// unrolled fast path of merge_dst_bounded executes at least 16 times and
// the count-based tail handles the remainder. Exercises the various
// dst-flavour code paths (unbounded, bounded RA, segmented).
void test_merge_stress_long_ranges()
{
const int N = 64;
boost::container::vector<int> v1, v2;
v1.reserve(N); v2.reserve(N);
for(int i = 0; i < N; ++i) v1.push_back(2 * i); // 0, 2, 4, ...
for(int i = 0; i < N; ++i) v2.push_back(2 * i + 1); // 1, 3, 5, ...
// Flat dst (RA, unbounded path through dual-RA when sized exactly).
boost::container::vector<int> out_flat(2 * N, -1);
boost::container::vector<int>::iterator r1 = segmented_merge(
v1.begin(), v1.end(), v2.begin(), v2.end(), out_flat.begin());
BOOST_TEST_EQ(static_cast<std::size_t>(r1 - out_flat.begin()),
static_cast<std::size_t>(2 * N));
for(int i = 0; i < 2 * N; ++i)
BOOST_TEST_EQ(out_flat[static_cast<std::size_t>(i)], i);
// Segmented dst with non-uniform segments: exercises unrolled body
// inside the segmented walker (each per-segment call hits the
// unrolled overload with bounded RA dst).
test_detail::seg_vector<int> out_seg;
out_seg.add_segment(13, 0);
out_seg.add_segment(31, 0);
out_seg.add_segment(7, 0);
out_seg.add_segment(2 * N - 13 - 31 - 7, 0);
typedef test_detail::seg_vector<int>::iterator seg_iter_t;
seg_iter_t r2 = segmented_merge(
v1.begin(), v1.end(), v2.begin(), v2.end(), out_seg.begin());
BOOST_TEST(r2 == out_seg.end());
seg_iter_t it = out_seg.begin();
for(int i = 0; i < 2 * N; ++i, ++it)
BOOST_TEST_EQ(*it, i);
}
// Non-segmented first1 + segmented first2 + non-segmented (RA) dst.
// Exercises the new merge_seg2_dispatch walker: each first2 segment is
// fed flat to the bounded leaf, so the dual-RA / unrolled fast paths fire
// per segment rather than degrading to the generic input-iterator merge.
void test_merge_seg2_walker_flat_dst()
{
int a[] = {2, 4, 6, 8, 10};
test_detail::seg_vector<int> sv2;
int b1[] = {1, 3};
int b2[] = {5, 7};
int b3[] = {9, 11, 13};
sv2.add_segment_range(b1, b1 + 2);
sv2.add_segment_range(b2, b2 + 2);
sv2.add_segment_range(b3, b3 + 3);
boost::container::vector<int> out(12, 0);
boost::container::vector<int>::iterator r = segmented_merge(
a, a + 5, sv2.begin(), sv2.end(), out.begin());
BOOST_TEST_EQ(static_cast<std::size_t>(r - out.begin()), 12u);
int expected[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13};
for(std::size_t i = 0; i < 12; ++i)
BOOST_TEST_EQ(out[i], expected[i]);
}
// Non-segmented first1 + segmented first2 + segmented dst.
// Exercises the cross product of the seg2 walker with the seg-dst walker
// inside merge_until_exhausts (segmented dst overload).
void test_merge_seg2_walker_seg_dst()
{
int a[] = {2, 4, 6, 8, 10};
test_detail::seg_vector<int> sv2;
int b1[] = {1, 3};
int b2[] = {5, 7};
int b3[] = {9, 11, 13};
sv2.add_segment_range(b1, b1 + 2);
sv2.add_segment_range(b2, b2 + 2);
sv2.add_segment_range(b3, b3 + 3);
test_detail::seg_vector<int> out;
out.add_segment(3, 0);
out.add_segment(2, 0);
out.add_segment(5, 0);
out.add_segment(2, 0);
typedef test_detail::seg_vector<int>::iterator iter_t;
iter_t r = segmented_merge(a, a + 5, sv2.begin(), sv2.end(), out.begin());
BOOST_TEST(r == out.end());
int expected[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13};
iter_t it = out.begin();
for(std::size_t i = 0; i < 12; ++i, ++it)
BOOST_TEST_EQ(*it, expected[i]);
}
// first1 longer than first2: forces seg2 walker to fully consume all
// first2 segments and then drain the remaining first1 via segmented_copy.
void test_merge_seg2_walker_first1_longer()
{
int a[] = {1, 3, 5, 7, 9, 11, 13, 15, 17};
test_detail::seg_vector<int> sv2;
int b1[] = {2};
int b2[] = {4, 6};
sv2.add_segment_range(b1, b1 + 1);
sv2.add_segment_range(b2, b2 + 2);
boost::container::vector<int> out(12, 0);
boost::container::vector<int>::iterator r = segmented_merge(
a, a + 9, sv2.begin(), sv2.end(), out.begin());
BOOST_TEST_EQ(static_cast<std::size_t>(r - out.begin()), 12u);
int expected[] = {1, 2, 3, 4, 5, 6, 7, 9, 11, 13, 15, 17};
for(std::size_t i = 0; i < 12; ++i)
BOOST_TEST_EQ(out[i], expected[i]);
}
// first1 shorter than first2: first1 exhausts while we are mid-first2-segment.
// Validates that seg2 walker composes the partial-segment first2 position on
// return so the top-level segmented_copy can flush the leftover first2 tail.
void test_merge_seg2_walker_first2_longer()
{
int a[] = {3, 5};
test_detail::seg_vector<int> sv2;
int b1[] = {1, 2};
int b2[] = {4, 6, 8};
int b3[] = {10, 12};
sv2.add_segment_range(b1, b1 + 2);
sv2.add_segment_range(b2, b2 + 3);
sv2.add_segment_range(b3, b3 + 2);
boost::container::vector<int> out(9, 0);
boost::container::vector<int>::iterator r = segmented_merge(
a, a + 2, sv2.begin(), sv2.end(), out.begin());
BOOST_TEST_EQ(static_cast<std::size_t>(r - out.begin()), 9u);
int expected[] = {1, 2, 3, 4, 5, 6, 8, 10, 12};
for(std::size_t i = 0; i < 9; ++i)
BOOST_TEST_EQ(out[i], expected[i]);
}
// Stress: long flat first1 + multi-segmented first2 with non-uniform segments
// + segmented dst. Each first2 segment gets fed flat into the bounded leaf,
// so the unrolled / dual-RA fast paths fire on every segment with size >= 4.
// Total elements 64 + 64 = 128, exercising the unrolled body multiple times.
void test_merge_seg2_walker_stress()
{
const int N1 = 64;
const int N2 = 64;
boost::container::vector<int> v1;
v1.reserve(N1);
for(int i = 0; i < N1; ++i) v1.push_back(2 * i); // 0, 2, 4, ...
test_detail::seg_vector<int> sv2;
int seg_sizes[] = {17, 9, 23, 4, 11}; // sums to 64
int next = 1;
for(std::size_t s = 0; s < sizeof(seg_sizes)/sizeof(seg_sizes[0]); ++s) {
boost::container::vector<int> seg;
seg.reserve(static_cast<std::size_t>(seg_sizes[s]));
for(int j = 0; j < seg_sizes[s]; ++j) {
seg.push_back(next);
next += 2;
}
sv2.add_segment_range(seg.begin(), seg.end());
}
const int total = N1 + N2;
test_detail::seg_vector<int> out;
out.add_segment(11, 0);
out.add_segment(50, 0);
out.add_segment(40, 0);
out.add_segment(total - 11 - 50 - 40, 0);
typedef test_detail::seg_vector<int>::iterator iter_t;
iter_t r = segmented_merge(
v1.begin(), v1.end(), sv2.begin(), sv2.end(), out.begin());
BOOST_TEST(r == out.end());
iter_t it = out.begin();
for(int i = 0; i < total; ++i, ++it)
BOOST_TEST_EQ(*it, i);
}
// Asymmetric stress: src1 and src2 of very different sizes plus pathological
// data distribution where one source dominates a long contiguous run.
// Forces the count-based tail of the unrolled overload to handle the
// remaining > 4 elements once one side dropped below 4 in the main loop.
void test_merge_stress_asymmetric()
{
boost::container::vector<int> v1, v2;
for(int i = 0; i < 5; ++i) v1.push_back(1000 + i); // [1000..1004]
for(int i = 0; i < 50; ++i) v2.push_back(i); // [0..49]
boost::container::vector<int> out(55, -1);
boost::container::vector<int>::iterator r = segmented_merge(
v1.begin(), v1.end(), v2.begin(), v2.end(), out.begin());
BOOST_TEST_EQ(static_cast<std::size_t>(r - out.begin()), 55u);
for(int i = 0; i < 50; ++i)
BOOST_TEST_EQ(out[static_cast<std::size_t>(i)], i);
for(int i = 0; i < 5; ++i)
BOOST_TEST_EQ(out[static_cast<std::size_t>(50 + i)], 1000 + i);
}
//////////////////////////////////////////////////////////////////////////////
// Single-segment coverage.
//
// A range spanning a whole seg_vector never takes the single-segment branch
// of a segment walker, because its end iterator lives in the trailing
// sentinel segment. The tests below build one oversized segment and merge a
// proper sub-range of it, so that segment(first) == segment(last).
//
// Every test pins the complete output sequence, including the slots left
// untouched past the returned iterator, plus the returned position itself.
//
// Shared data: {1, 2, 3, 5, 8} merged with {2, 3, 4, 8, 9} gives
// {1, 2, 2, 3, 3, 4, 5, 8, 8, 9}; the duplicate keys shared by both inputs
// make the take-from-the-first-range-on-ties rule observable.
//////////////////////////////////////////////////////////////////////////////
// M4: single-segment first input, flat second input.
void test_merge_single_segment_input1()
{
test_detail::seg_vector<int> sv1;
int a[] = {-100, 1, 2, 3, 5, 8, 100};
sv1.add_segment_range(a, a + 7);
int b[] = {2, 3, 4, 8, 9};
boost::container::vector<int> out(14, -1);
boost::container::vector<int>::iterator r = segmented_merge(
test_detail::iter_at(sv1, 1), test_detail::iter_at(sv1, 6),
b, b + 5, out.begin());
BOOST_TEST_EQ(static_cast<std::size_t>(r - out.begin()), 10u);
int expected[] = {1, 2, 2, 3, 3, 4, 5, 8, 8, 9, -1, -1, -1, -1};
for(std::size_t i = 0; i < 14; ++i)
BOOST_TEST_EQ(out[i], expected[i]);
}
// M4: flat first input, single-segment second input.
void test_merge_single_segment_input2()
{
int a[] = {1, 2, 3, 5, 8};
test_detail::seg_vector<int> sv2;
int b[] = {-100, 2, 3, 4, 8, 9, 100};
sv2.add_segment_range(b, b + 7);
boost::container::vector<int> out(14, -1);
boost::container::vector<int>::iterator r = segmented_merge(
a, a + 5,
test_detail::iter_at(sv2, 1), test_detail::iter_at(sv2, 6), out.begin());
BOOST_TEST_EQ(static_cast<std::size_t>(r - out.begin()), 10u);
int expected[] = {1, 2, 2, 3, 3, 4, 5, 8, 8, 9, -1, -1, -1, -1};
for(std::size_t i = 0; i < 14; ++i)
BOOST_TEST_EQ(out[i], expected[i]);
}
// M4: both inputs single-segment, both bounded on both sides.
void test_merge_single_segment_both_inputs()
{
test_detail::seg_vector<int> sv1;
int a[] = {-100, 1, 2, 3, 5, 8, 100};
sv1.add_segment_range(a, a + 7);
test_detail::seg_vector<int> sv2;
int b[] = {-100, 2, 3, 4, 8, 9, 100};
sv2.add_segment_range(b, b + 7);
boost::container::vector<int> out(14, -1);
boost::container::vector<int>::iterator r = segmented_merge(
test_detail::iter_at(sv1, 1), test_detail::iter_at(sv1, 6),
test_detail::iter_at(sv2, 1), test_detail::iter_at(sv2, 6), out.begin());
BOOST_TEST_EQ(static_cast<std::size_t>(r - out.begin()), 10u);
int expected[] = {1, 2, 2, 3, 3, 4, 5, 8, 8, 9, -1, -1, -1, -1};
for(std::size_t i = 0; i < 14; ++i)
BOOST_TEST_EQ(out[i], expected[i]);
}
// M1: multi-segment inputs, single-segment segmented output.
void test_merge_single_segment_output()
{
test_detail::seg_vector<int> sv1;
int a1[] = {1, 2};
int a2[] = {3};
int a3[] = {5, 8};
sv1.add_segment_range(a1, a1 + 2);
sv1.add_segment_range(a2, a2 + 1);
sv1.add_segment_range(a3, a3 + 2);
test_detail::seg_vector<int> sv2;
int b1[] = {2, 3};
int b2[] = {4, 8, 9};
sv2.add_segment_range(b1, b1 + 2);
sv2.add_segment_range(b2, b2 + 3);
test_detail::seg_vector<int> out;
out.add_segment(14, -1);
typedef test_detail::seg_vector<int>::iterator iter_t;
iter_t r = segmented_merge(sv1.begin(), sv1.end(), sv2.begin(), sv2.end(), out.begin());
BOOST_TEST(r == test_detail::iter_at(out, 10));
boost::container::vector<int> got = test_detail::flatten_all_ints(out);
BOOST_TEST_EQ(got.size(), 14u);
int expected[] = {1, 2, 2, 3, 3, 4, 5, 8, 8, 9, -1, -1, -1, -1};
for(std::size_t i = 0; i < got.size(); ++i)
BOOST_TEST_EQ(got[i], expected[i]);
}
// M3: single-segment inputs and single-segment output.
void test_merge_single_segment_inputs_and_output()
{
test_detail::seg_vector<int> sv1;
int a[] = {-100, 1, 2, 3, 5, 8, 100};
sv1.add_segment_range(a, a + 7);
test_detail::seg_vector<int> sv2;
int b[] = {-100, 2, 3, 4, 8, 9, 100};
sv2.add_segment_range(b, b + 7);
test_detail::seg_vector<int> out;
out.add_segment(14, -1);
typedef test_detail::seg_vector<int>::iterator iter_t;
iter_t r = segmented_merge(
test_detail::iter_at(sv1, 1), test_detail::iter_at(sv1, 6),
test_detail::iter_at(sv2, 1), test_detail::iter_at(sv2, 6), out.begin());
BOOST_TEST(r == test_detail::iter_at(out, 10));
boost::container::vector<int> got = test_detail::flatten_all_ints(out);
BOOST_TEST_EQ(got.size(), 14u);
int expected[] = {1, 2, 2, 3, 3, 4, 5, 8, 8, 9, -1, -1, -1, -1};
for(std::size_t i = 0; i < got.size(); ++i)
BOOST_TEST_EQ(got[i], expected[i]);
}
// M2: single-segment inputs, multi-segment output. The output segments are
// small enough that the merge crosses two of them and the tail copy of the
// residue crosses a third.
void test_merge_single_segment_inputs_multi_output()
{
test_detail::seg_vector<int> sv1;
int a[] = {-100, 1, 2, 3, 5, 8, 100};
sv1.add_segment_range(a, a + 7);
test_detail::seg_vector<int> sv2;
int b[] = {-100, 2, 3, 4, 8, 9, 100};
sv2.add_segment_range(b, b + 7);
test_detail::seg_vector<int> out;
out.add_segment(3, -1);
out.add_segment(4, -1);
out.add_segment(5, -1);
out.add_segment(2, -1);
typedef test_detail::seg_vector<int>::iterator iter_t;
iter_t r = segmented_merge(
test_detail::iter_at(sv1, 1), test_detail::iter_at(sv1, 6),
test_detail::iter_at(sv2, 1), test_detail::iter_at(sv2, 6), out.begin());
BOOST_TEST(r == test_detail::iter_at(out, 10));
boost::container::vector<int> got = test_detail::flatten_all_ints(out);
BOOST_TEST_EQ(got.size(), 14u);
int expected[] = {1, 2, 2, 3, 3, 4, 5, 8, 8, 9, -1, -1, -1, -1};
for(std::size_t i = 0; i < got.size(); ++i)
BOOST_TEST_EQ(got[i], expected[i]);
}
// The first input is exhausted long before the second, so the whole tail of
// the second input has to be copied through into a single-segment output.
void test_merge_single_segment_first_input_exhausted()
{
test_detail::seg_vector<int> sv1;
int a[] = {-100, 1, 2, 100};
sv1.add_segment_range(a, a + 4);
test_detail::seg_vector<int> sv2;
int b[] = {-100, 3, 4, 5, 6, 7, 100};
sv2.add_segment_range(b, b + 7);
test_detail::seg_vector<int> out;
out.add_segment(10, -1);
typedef test_detail::seg_vector<int>::iterator iter_t;
iter_t r = segmented_merge(
test_detail::iter_at(sv1, 1), test_detail::iter_at(sv1, 3),
test_detail::iter_at(sv2, 1), test_detail::iter_at(sv2, 6), out.begin());
BOOST_TEST(r == test_detail::iter_at(out, 7));
boost::container::vector<int> got = test_detail::flatten_all_ints(out);
BOOST_TEST_EQ(got.size(), 10u);
int expected[] = {1, 2, 3, 4, 5, 6, 7, -1, -1, -1};
for(std::size_t i = 0; i < got.size(); ++i)
BOOST_TEST_EQ(got[i], expected[i]);
}
// Mirror image: the second input is exhausted first and the tail of the
// first input is copied through.
void test_merge_single_segment_second_input_exhausted()
{
test_detail::seg_vector<int> sv1;
int a[] = {-100, 3, 4, 5, 6, 7, 100};
sv1.add_segment_range(a, a + 7);
test_detail::seg_vector<int> sv2;
int b[] = {-100, 1, 2, 100};
sv2.add_segment_range(b, b + 4);
test_detail::seg_vector<int> out;
out.add_segment(10, -1);
typedef test_detail::seg_vector<int>::iterator iter_t;
iter_t r = segmented_merge(
test_detail::iter_at(sv1, 1), test_detail::iter_at(sv1, 6),
test_detail::iter_at(sv2, 1), test_detail::iter_at(sv2, 3), out.begin());
BOOST_TEST(r == test_detail::iter_at(out, 7));
boost::container::vector<int> got = test_detail::flatten_all_ints(out);
BOOST_TEST_EQ(got.size(), 10u);
int expected[] = {1, 2, 3, 4, 5, 6, 7, -1, -1, -1};
for(std::size_t i = 0; i < got.size(); ++i)
BOOST_TEST_EQ(got[i], expected[i]);
}
// S5 and S6 on both inputs: one outer segment holding several inner ones,
// and one outer segment holding exactly one inner segment.
void test_merge_single_segment_seg2_inputs()
{
int a[] = {1, 2, 3, 5, 8};
int b[] = {2, 3, 4, 8, 9};
test_detail::seg2_vector<int> sv1;
test_detail::make_range(sv1, "sm", a, 5, 100);
test_detail::seg2_vector<int> sv2;
test_detail::make_range(sv2, "ss", b, 5, 100);
boost::container::vector<int> out(14, -1);
boost::container::vector<int>::iterator r = segmented_merge(
sv1.begin(), test_detail::iter_at(sv1, 5),
sv2.begin(), test_detail::iter_at(sv2, 5), out.begin());
BOOST_TEST_EQ(static_cast<std::size_t>(r - out.begin()), 10u);
int expected[] = {1, 2, 2, 3, 3, 4, 5, 8, 8, 9, -1, -1, -1, -1};
for(std::size_t i = 0; i < 14; ++i)
BOOST_TEST_EQ(out[i], expected[i]);
}
// A recursively segmented output whose outer level holds a single segment
// with room to spare.
void test_merge_single_segment_seg2_output()
{
int a[] = {1, 2, 3, 5, 8};
int b[] = {2, 3, 4, 8, 9};
test_detail::seg_vector<int> inner;
inner.add_segment(6, -1);
inner.add_segment(8, -1);
test_detail::seg2_vector<int> out;
out.add_segment(inner);
typedef test_detail::seg2_vector<int>::iterator iter_t;
iter_t r = segmented_merge(a, a + 5, b, b + 5, out.begin());
BOOST_TEST(r == test_detail::iter_at(out, 10));
boost::container::vector<int> got = test_detail::flatten_all_ints(out);
BOOST_TEST_EQ(got.size(), 14u);
int expected[] = {1, 2, 2, 3, 3, 4, 5, 8, 8, 9, -1, -1, -1, -1};
for(std::size_t i = 0; i < got.size(); ++i)
BOOST_TEST_EQ(got[i], expected[i]);
}
// Same, but the first outer segment of the output is filled completely, so
// the merge has to carry on into the second outer segment.
void test_merge_single_segment_seg2_output_segment_full()
{
int a[] = {1, 2, 3, 5, 8};
int b[] = {2, 3, 4, 8, 9};
test_detail::seg_vector<int> inner0;
inner0.add_segment(2, -1);
inner0.add_segment(2, -1);
test_detail::seg_vector<int> inner1;
inner1.add_segment(10, -1);
test_detail::seg2_vector<int> out;
out.add_segment(inner0);
out.add_segment(inner1);
typedef test_detail::seg2_vector<int>::iterator iter_t;
iter_t r = segmented_merge(a, a + 5, b, b + 5, out.begin());
BOOST_TEST(r == test_detail::iter_at(out, 10));
boost::container::vector<int> got = test_detail::flatten_all_ints(out);
BOOST_TEST_EQ(got.size(), 14u);
int expected[] = {1, 2, 2, 3, 3, 4, 5, 8, 8, 9, -1, -1, -1, -1};
for(std::size_t i = 0; i < got.size(); ++i)
BOOST_TEST_EQ(got[i], expected[i]);
}
// S3: an empty first input positioned mid-segment; the second input is
// copied through unchanged.
void test_merge_single_segment_empty_input()
{
test_detail::seg_vector<int> sv1;
int a[] = {1, 2, 3, 5, 8, 100};
sv1.add_segment_range(a, a + 6);
int b[] = {2, 3, 4};
boost::container::vector<int> out(6, -1);
typedef test_detail::seg_vector<int>::iterator iter_t;
const iter_t mid = test_detail::iter_at(sv1, 3);
boost::container::vector<int>::iterator r =
segmented_merge(mid, mid, b, b + 3, out.begin());
BOOST_TEST_EQ(static_cast<std::size_t>(r - out.begin()), 3u);
int expected[] = {2, 3, 4, -1, -1, -1};
for(std::size_t i = 0; i < 6; ++i)
BOOST_TEST_EQ(out[i], expected[i]);
}
// S4: single-segment sub-ranges closed by sentinels.
void test_merge_single_segment_sentinel()
{
test_detail::seg_vector<int> sv1;
int a[] = {-100, 1, 2, 3, 5, 8, 100};
sv1.add_segment_range(a, a + 7);
test_detail::seg_vector<int> sv2;
int b[] = {-100, 2, 3, 4, 8, 9, 100};
sv2.add_segment_range(b, b + 7);
boost::container::vector<int> out(14, -1);
boost::container::vector<int>::iterator r = segmented_merge(
test_detail::iter_at(sv1, 1), test_detail::make_sentinel(test_detail::iter_at(sv1, 6)),
test_detail::iter_at(sv2, 1), test_detail::make_sentinel(test_detail::iter_at(sv2, 6)),
out.begin());
BOOST_TEST_EQ(static_cast<std::size_t>(r - out.begin()), 10u);
int expected[] = {1, 2, 2, 3, 3, 4, 5, 8, 8, 9, -1, -1, -1, -1};
for(std::size_t i = 0; i < 14; ++i)
BOOST_TEST_EQ(out[i], expected[i]);
}
// The comparator-taking overload on single-segment inputs and output.
void test_merge_single_segment_with_comp()
{
test_detail::seg_vector<int> sv1;
int a[] = {100, 8, 5, 3, 2, 1, -100};
sv1.add_segment_range(a, a + 7);
test_detail::seg_vector<int> sv2;
int b[] = {100, 9, 8, 4, 3, 2, -100};
sv2.add_segment_range(b, b + 7);
test_detail::seg_vector<int> out;
out.add_segment(14, -1);
typedef test_detail::seg_vector<int>::iterator iter_t;
iter_t r = segmented_merge(
test_detail::iter_at(sv1, 1), test_detail::iter_at(sv1, 6),
test_detail::iter_at(sv2, 1), test_detail::iter_at(sv2, 6),
out.begin(), greater_comp());
BOOST_TEST(r == test_detail::iter_at(out, 10));
boost::container::vector<int> got = test_detail::flatten_all_ints(out);
BOOST_TEST_EQ(got.size(), 14u);
int expected[] = {9, 8, 8, 5, 4, 3, 3, 2, 2, 1, -1, -1, -1, -1};
for(std::size_t i = 0; i < got.size(); ++i)
BOOST_TEST_EQ(got[i], expected[i]);
}
//////////////////////////////////////////////////////////////////////////////
// Shape matrix.
//
// segmented_merge has three independently segmented ranges -- two inputs and
// one destination -- and the destination walker is where both bugs found in
// this algorithm so far have lived. for_each_shape3_all drives the full
// cross product including the 'e' shapes, whose empty segments are the only
// way to reach the sfirst == slast branch of merge_dst_bounded with an empty
// destination segment.
//
// Destination sizes. Two geometries are run for every combination:
//
// * exactly n1 + n2 slots, so the last element of the merge lands on the
// last slot of the destination and every intermediate destination
// segment fills exactly. That is the geometry that hid the last bug:
// merge_dst_bounded handing the original rather than the loop-carried
// input iterators to its final segment call was only observable when an
// intermediate segment filled without either input running out.
// * three slots more, so the destination keeps an unwritten tail that both
// the fill comparison and the guard check have to see intact.
//
// The inputs carry duplicates, within a range and shared between them, so
// that merge's tie-breaking -- take from range 1 when neither compares less
// -- is pinned down rather than left unobserved.
//////////////////////////////////////////////////////////////////////////////
struct merge_shape_check
{
void report(const char* s1, std::size_t n1, const char* s2, std::size_t n2,
const char* s3, std::size_t n3) const
{
BOOST_LIGHTWEIGHT_TEST_OSTREAM
<< " shapes \"" << s1 << "\"(" << n1 << ") / \"" << s2 << "\"(" << n2
<< ") -> \"" << s3 << "\"(" << n3 << ")" << std::endl;
}
template<class C1, class C2, class C3>
void operator()(C1& c1, std::size_t n1, const char* s1,
C2& c2, std::size_t n2, const char* s2,
C3& c3, std::size_t n3, const char* s3) const
{
typedef typename C3::iterator iter3_t;
const boost::container::vector<int> f1 = test_detail::flatten_n_ints(c1, n1);
const boost::container::vector<int> f2 = test_detail::flatten_n_ints(c2, n2);
// Naive reference: the classic two-finger merge, taking from range 1
// whenever the two compare equal.
boost::container::vector<int> ref;
{
std::size_t i = 0, j = 0;
while(i != f1.size() && j != f2.size())
ref.push_back(f2[j] < f1[i] ? f2[j++] : f1[i++]);
for(; i != f1.size(); ++i) ref.push_back(f1[i]);
for(; j != f2.size(); ++j) ref.push_back(f2[j]);
}
const iter3_t r = segmented_merge
( c1.begin(), test_detail::iter_at(c1, n1)
, c2.begin(), test_detail::iter_at(c2, n2)
, c3.begin());
if(!BOOST_TEST(r == test_detail::iter_at(c3, ref.size())))
this->report(s1, n1, s2, n2, s3, n3);
// flatten_n_ints, not flatten_all_ints: the reference answer covers the
// logical range only. The guard past it is checked separately, below.
const boost::container::vector<int> got = test_detail::flatten_n_ints(c3, n3);
if(!BOOST_TEST_EQ(got.size(), n3)) {
this->report(s1, n1, s2, n2, s3, n3);
return;
}
for(std::size_t i = 0; i != n3; ++i) {
const int want = i < ref.size() ? ref[i] : -1;
if(!BOOST_TEST_EQ(got[i], want)) {
this->report(s1, n1, s2, n2, s3, n3);
break;
}
}
// Nothing may be written past the destination's logical end, and
// neither input may be written at all.
if(!BOOST_TEST(test_detail::filler_intact(c3, n3, -999)))
this->report(s1, n1, s2, n2, s3, n3);
if(!BOOST_TEST(test_detail::filler_intact(c1, n1, -999)))
this->report(s1, n1, s2, n2, s3, n3);
if(!BOOST_TEST(test_detail::filler_intact(c2, n2, -999)))
this->report(s1, n1, s2, n2, s3, n3);
}
};
void test_merge_shape_matrix()
{
// Sorted, with duplicates inside each range and shared between them.
const int v1[] = {1, 2, 2, 3, 5, 8};
const int v2[] = {2, 3, 3, 4, 8, 9};
static const std::size_t pairs[][2] =
{ {0u, 0u}, {0u, 3u}, {3u, 0u}, {1u, 1u}, {2u, 4u}, {4u, 2u}, {5u, 6u} };
// 0 fills the destination exactly; 3 leaves an unwritten tail.
static const std::size_t extras[] = { 0u, 3u };
for(std::size_t p = 0; p != sizeof(pairs)/sizeof(pairs[0]); ++p) {
for(std::size_t e = 0; e != sizeof(extras)/sizeof(extras[0]); ++e) {
const std::size_t n1 = pairs[p][0];
const std::size_t n2 = pairs[p][1];
const std::size_t n3 = n1 + n2 + extras[e];
boost::container::vector<int> fill(n3 ? n3 : 1u, -1);
test_detail::for_each_shape3_all<int, int, int>
(v1, n1, v2, n2, &fill[0], n3, -999, merge_shape_check());
}
}
}
int main()
{
test_merge_shape_matrix();
test_merge_segmented_inputs();
test_merge_first_empty();
test_merge_second_empty();
test_merge_both_empty();
test_merge_with_comp();
test_merge_non_segmented();
test_merge_duplicates();
test_merge_sentinel_segmented();
test_merge_sentinel_non_segmented();
test_merge_seg2();
test_merge_segmented_output();
test_merge_segmented_inputs_and_output();
test_merge_segmented_output_first_longer();
test_merge_segmented_output_second_longer();
test_merge_seg2_walker_flat_dst();
test_merge_seg2_walker_seg_dst();
test_merge_seg2_walker_first1_longer();
test_merge_seg2_walker_first2_longer();
test_merge_seg2_walker_stress();
test_merge_stress_long_ranges();
test_merge_stress_asymmetric();
// Single-segment coverage:
test_merge_single_segment_input1();
test_merge_single_segment_input2();
test_merge_single_segment_both_inputs();
test_merge_single_segment_output();
test_merge_single_segment_inputs_and_output();
test_merge_single_segment_inputs_multi_output();
test_merge_single_segment_first_input_exhausted();
test_merge_single_segment_second_input_exhausted();
test_merge_single_segment_seg2_inputs();
test_merge_single_segment_seg2_output();
test_merge_single_segment_seg2_output_segment_full();
test_merge_single_segment_empty_input();
test_merge_single_segment_sentinel();
test_merge_single_segment_with_comp();
return boost::report_errors();
}