diff --git a/core/include/Data/DataSeries.h b/core/include/Data/DataSeries.h index 2d5ebd8..22c4a79 100644 --- a/core/include/Data/DataSeries.h +++ b/core/include/Data/DataSeries.h @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -87,6 +88,8 @@ private: */ template class SCIQLOP_CORE_EXPORT DataSeries : public IDataSeries { + friend class DataSeriesMergeHelper; + public: /// @sa IDataSeries::xAxisData() std::shared_ptr > xAxisData() override { return m_XAxisData; } @@ -118,6 +121,8 @@ public: m_ValuesData->clear(); } + bool isEmpty() const noexcept { return m_XAxisData->size() == 0; } + /// Merges into the data series an other data series /// @remarks the data series to merge with is cleared after the operation void merge(IDataSeries *dataSeries) override @@ -126,49 +131,7 @@ public: lockWrite(); if (auto other = dynamic_cast *>(dataSeries)) { - const auto &otherXAxisData = other->xAxisData()->cdata(); - const auto &xAxisData = m_XAxisData->cdata(); - - // As data series are sorted, we can improve performances of merge, by call the sort - // method only if the two data series overlap. - if (!otherXAxisData.empty()) { - auto firstValue = otherXAxisData.front(); - auto lastValue = otherXAxisData.back(); - - auto xAxisDataBegin = xAxisData.cbegin(); - auto xAxisDataEnd = xAxisData.cend(); - - bool prepend; - bool sortNeeded; - - if (std::lower_bound(xAxisDataBegin, xAxisDataEnd, firstValue) == xAxisDataEnd) { - // Other data series if after data series - prepend = false; - sortNeeded = false; - } - else if (std::upper_bound(xAxisDataBegin, xAxisDataEnd, lastValue) - == xAxisDataBegin) { - // Other data series if before data series - prepend = true; - sortNeeded = false; - } - else { - // The two data series overlap - prepend = false; - sortNeeded = true; - } - - // Makes the merge - m_XAxisData->add(*other->xAxisData(), prepend); - m_ValuesData->add(*other->valuesData(), prepend); - - if (sortNeeded) { - sort(); - } - } - - // Clears the other data series - other->clear(); + DataSeriesMergeHelper::merge(*other, *this); } else { qCWarning(LOG_DataSeries()) diff --git a/core/include/Data/DataSeriesMergeHelper.h b/core/include/Data/DataSeriesMergeHelper.h new file mode 100644 index 0000000..0f45a02 --- /dev/null +++ b/core/include/Data/DataSeriesMergeHelper.h @@ -0,0 +1,132 @@ +#ifndef SCIQLOP_DATASERIESMERGEHELPER_H +#define SCIQLOP_DATASERIESMERGEHELPER_H + +template +class DataSeries; + +namespace detail { + +/** + * Scope that can be used for a merge operation + * @tparam FEnd the type of function that will be executed at the end of the scope + */ +template +struct MergeScope { + explicit MergeScope(FEnd end) : m_End{end} {} + virtual ~MergeScope() noexcept { m_End(); } + FEnd m_End; +}; + +/** + * Creates a scope for merge operation + * @tparam end the function executed at the end of the scope + */ +template +MergeScope scope(FEnd end) +{ + return MergeScope{end}; +} + +/** + * Enum used to position a data series relative to another during a merge operation + */ +enum class MergePosition { LOWER_THAN, GREATER_THAN, EQUAL, OVERLAP }; + +/** + * Computes the position of the first data series relative to the second data series + * @param lhs the first data series + * @param rhs the second data series + * @return the merge position computed + * @remarks the data series must not be empty + */ +template +MergePosition mergePosition(DataSeries &lhs, DataSeries &rhs) +{ + Q_ASSERT(!lhs.isEmpty() && !rhs.isEmpty()); + + // Case lhs < rhs + auto lhsLast = --lhs.cend(); + auto rhsFirst = rhs.cbegin(); + if (lhsLast->x() < rhsFirst->x()) { + return MergePosition::LOWER_THAN; + } + + // Case lhs > rhs + auto lhsFirst = lhs.cbegin(); + auto rhsLast = --rhs.cend(); + if (lhsFirst->x() > rhsLast->x()) { + return MergePosition::GREATER_THAN; + } + + // Other cases + auto equal = std::equal(lhs.cbegin(), lhs.cend(), rhs.cbegin(), rhs.cend(), + [](const auto &it1, const auto &it2) { + return it1.x() == it2.x() && it1.values() == it2.values(); + }); + return equal ? MergePosition::EQUAL : MergePosition::OVERLAP; +} + +} // namespace detail + + +/// Helper used to merge two DataSeries +/// @sa DataSeries +struct DataSeriesMergeHelper { + /// Merges the source data series into the dest data series. Data of the source data series are + /// consumed + template + static void merge(DataSeries &source, DataSeries &dest) + { + // Creates a scope to clear source data series at the end of the merge + auto _ = detail::scope([&source]() { source.clear(); }); + + // Case : source data series is empty -> no merge is made + if (source.isEmpty()) { + return; + } + + // Case : dest data series is empty -> we simply swap the data + if (dest.isEmpty()) { + std::swap(dest.m_XAxisData, source.m_XAxisData); + std::swap(dest.m_ValuesData, source.m_ValuesData); + return; + } + + // Gets the position of the source in relation to the destination + auto sourcePosition = detail::mergePosition(source, dest); + + switch (sourcePosition) { + case detail::MergePosition::LOWER_THAN: + case detail::MergePosition::GREATER_THAN: { + auto prepend = sourcePosition == detail::MergePosition::LOWER_THAN; + dest.m_XAxisData->add(*source.m_XAxisData, prepend); + dest.m_ValuesData->add(*source.m_ValuesData, prepend); + break; + } + case detail::MergePosition::EQUAL: + // the data series equal each other : no merge made + break; + case detail::MergePosition::OVERLAP: { + // the two data series overlap : merge is made + auto temp = dest.clone(); + if (auto tempSeries = dynamic_cast *>(temp.get())) { + // Makes the merge : + // - Data are sorted by x-axis values + // - If two entries are in the source range and the other range, only one entry + // is retained as result + // - The results are stored directly in the data series + dest.clear(); + std::set_union( + tempSeries->cbegin(), tempSeries->cend(), source.cbegin(), source.cend(), + std::back_inserter(dest), + [](const auto &it1, const auto &it2) { return it1.x() < it2.x(); }); + } + break; + } + default: + Q_ASSERT(false); + } + } +}; + +#endif // SCIQLOP_DATASERIESMERGEHELPER_H