diff --git a/core/include/Data/ArrayData.h b/core/include/Data/ArrayData.h index 08bbcb4..6a149e2 100644 --- a/core/include/Data/ArrayData.h +++ b/core/include/Data/ArrayData.h @@ -87,10 +87,9 @@ public: return it != end ? *it : std::numeric_limits::quiet_NaN(); } -private: - std::vector values() const + QVector values() const override { - auto result = std::vector{}; + auto result = QVector{}; for (auto i = 0; i < m_NbComponents; ++i) { result.push_back(*(m_It + i)); } @@ -98,6 +97,7 @@ private: return result; } +private: DataContainer::const_iterator m_It; int m_NbComponents; }; @@ -235,6 +235,17 @@ public: m_Data, m_NbComponents, false)}}; } + /// Inserts at the end of the array data the values passed as a parameter. This + /// method is intended to be used in the context of generating a back insert iterator, or only + /// if it's ensured that the total size of the vector is consistent with the number of + /// components of the array data + /// @param values the values to insert + /// @sa http://en.cppreference.com/w/cpp/iterator/back_inserter + void push_back(const QVector &values) + { + Q_ASSERT(values.size() % m_NbComponents == 0); + m_Data.append(values); + } /** * @return the data at a specified index diff --git a/core/include/Data/ArrayDataIterator.h b/core/include/Data/ArrayDataIterator.h index af46ede..fa86409 100644 --- a/core/include/Data/ArrayDataIterator.h +++ b/core/include/Data/ArrayDataIterator.h @@ -4,6 +4,7 @@ #include "CoreGlobal.h" #include "Data/SqpIterator.h" +#include #include /** @@ -25,6 +26,7 @@ public: virtual double first() const = 0; virtual double min() const = 0; virtual double max() const = 0; + virtual QVector values() const = 0; }; explicit ArrayDataIteratorValue(std::unique_ptr impl); @@ -46,6 +48,8 @@ public: double min() const; /// Gets max value among all components double max() const; + /// Gets all values + QVector values() const; private: std::unique_ptr m_Impl; diff --git a/core/include/Data/DataSeries.h b/core/include/Data/DataSeries.h index c269c22..bfa12c9 100644 --- a/core/include/Data/DataSeries.h +++ b/core/include/Data/DataSeries.h @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -67,6 +68,7 @@ public: double value(int componentIndex) const override { return m_ValuesIt->at(componentIndex); } double minValue() const override { return m_ValuesIt->min(); } double maxValue() const override { return m_ValuesIt->max(); } + QVector values() const override { return m_ValuesIt->values(); } private: ArrayDataIterator m_XIt; @@ -86,7 +88,13 @@ private: */ template class SCIQLOP_CORE_EXPORT DataSeries : public IDataSeries { + friend class DataSeriesMergeHelper; + public: + /// Tag needed to define the push_back() method + /// @sa push_back() + using value_type = DataSeriesIteratorValue; + /// @sa IDataSeries::xAxisData() std::shared_ptr > xAxisData() override { return m_XAxisData; } const std::shared_ptr > xAxisData() const { return m_XAxisData; } @@ -117,6 +125,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 @@ -125,49 +135,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()) @@ -266,6 +234,22 @@ public: virtual void lockWrite() { m_Lock.lockForWrite(); } virtual void unlock() { m_Lock.unlock(); } + // ///// // + // Other // + // ///// // + + /// Inserts at the end of the data series the value of the iterator passed as a parameter. This + /// method is intended to be used in the context of generating a back insert iterator + /// @param iteratorValue the iterator value containing the values to insert + /// @sa http://en.cppreference.com/w/cpp/iterator/back_inserter + /// @sa merge() + /// @sa value_type + void push_back(const value_type &iteratorValue) + { + m_XAxisData->push_back(QVector{iteratorValue.x()}); + m_ValuesData->push_back(iteratorValue.values()); + } + protected: /// Protected ctor (DataSeries is abstract). The vectors must have the same size, otherwise a /// DataSeries with no values will be created. diff --git a/core/include/Data/DataSeriesIterator.h b/core/include/Data/DataSeriesIterator.h index 3489d23..fd859bb 100644 --- a/core/include/Data/DataSeriesIterator.h +++ b/core/include/Data/DataSeriesIterator.h @@ -4,6 +4,7 @@ #include "CoreGlobal.h" #include "Data/SqpIterator.h" +#include #include /** @@ -27,6 +28,7 @@ public: virtual double value(int componentIndex) const = 0; virtual double minValue() const = 0; virtual double maxValue() const = 0; + virtual QVector values() const = 0; }; explicit DataSeriesIteratorValue(std::unique_ptr impl); @@ -50,6 +52,8 @@ public: double minValue() const; /// Gets max of all values data double maxValue() const; + /// Gets all values data + QVector values() const; private: std::unique_ptr m_Impl; 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 diff --git a/core/src/Data/ArrayDataIterator.cpp b/core/src/Data/ArrayDataIterator.cpp index fb13181..cd397c4 100644 --- a/core/src/Data/ArrayDataIterator.cpp +++ b/core/src/Data/ArrayDataIterator.cpp @@ -50,3 +50,8 @@ double ArrayDataIteratorValue::max() const { return m_Impl->max(); } + +QVector ArrayDataIteratorValue::values() const +{ + return m_Impl->values(); +} diff --git a/core/src/Data/DataSeriesIterator.cpp b/core/src/Data/DataSeriesIterator.cpp index 03daf1e..d492b32 100644 --- a/core/src/Data/DataSeriesIterator.cpp +++ b/core/src/Data/DataSeriesIterator.cpp @@ -56,3 +56,8 @@ double DataSeriesIteratorValue::maxValue() const { return m_Impl->maxValue(); } + +QVector DataSeriesIteratorValue::values() const +{ + return m_Impl->values(); +} diff --git a/core/vera-exclusions/exclusions.txt b/core/vera-exclusions/exclusions.txt index 928edb5..5c6690c 100644 --- a/core/vera-exclusions/exclusions.txt +++ b/core/vera-exclusions/exclusions.txt @@ -7,15 +7,21 @@ ArrayDataIterator\.h:\d+:.*IPSIS_S01.* DataSourceItem\.h:\d+:.*IPSIS_S01.* DataSeries\.h:\d+:.*IPSIS_S01.* DataSeriesIterator\.h:\d+:.*IPSIS_S01.* +DataSeriesMergeHelper\.h:\d+:.*IPSIS_S01.* # Ignore false positive relative to a template class +ArrayData\.h:\d+:.*IPSIS_S04_METHOD.*found: push_back ArrayData\.h:\d+:.*IPSIS_S04_VARIABLE.*found: (D) ArrayData\.h:\d+:.*IPSIS_S04_NAMESPACE.*found: (arraydata_detail) ArrayData\.h:\d+:.*IPSIS_S06.*found: (D) ArrayData\.h:\d+:.*IPSIS_S06.*found: (Dim) DataSeries\.h:\d+:.*IPSIS_S04_METHOD.*found: LOG_DataSeries +DataSeries\.h:\d+:.*IPSIS_S04_METHOD.*found: push_back DataSeries\.h:\d+:.*IPSIS_S04_VARIABLE.* DataSeries\.h:\d+:.*IPSIS_S04_NAMESPACE.*found: (dataseries_detail) +DataSeries\.h:\d+:.*IPSIS_S05.* +DataSeries\.h:\d+:.*IPSIS_S06.*found: (value_type) +DataSeries\.h:\d+:.*IPSIS_S06.*found: (DataSeriesIteratorValue) # Ignore false positive relative to iterators SqpIterator\.h:\d+:.*IPSIS_S04_VARIABLE.*found: (forward_iterator_tag)