#ifndef SCIQLOP_ARRAYDATA_H #define SCIQLOP_ARRAYDATA_H #include "Data/ArrayDataIterator.h" #include #include #include #include #include template class ArrayData; using DataContainer = QVector; namespace arraydata_detail { /// Struct used to sort ArrayData template struct Sort { static std::shared_ptr > sort(const DataContainer &data, int nbComponents, const std::vector &sortPermutation) { return std::make_shared >( SortUtils::sort(data, nbComponents, sortPermutation), nbComponents); } }; /// Specialization for uni-dimensional ArrayData template <> struct Sort<1> { static std::shared_ptr > sort(const DataContainer &data, int nbComponents, const std::vector &sortPermutation) { Q_UNUSED(nbComponents) return std::make_shared >(SortUtils::sort(data, 1, sortPermutation)); } }; template class IteratorValue; template struct IteratorValueBuilder { }; template struct IteratorValueBuilder { using DataContainerIterator = DataContainer::const_iterator; static void swap(IteratorValue &o1, IteratorValue &o2) {} }; template struct IteratorValueBuilder { using DataContainerIterator = DataContainer::iterator; static void swap(IteratorValue &o1, IteratorValue &o2) { for (auto i = 0; i < o1.m_NbComponents; ++i) { std::iter_swap(o1.m_It + i, o2.m_It + i); } } }; template class IteratorValue : public ArrayDataIteratorValue::Impl { public: friend class ArrayData; friend class IteratorValueBuilder; using DataContainerIterator = typename IteratorValueBuilder::DataContainerIterator; template > explicit IteratorValue(const DataContainer &container, int nbComponents, bool begin) : m_It{begin ? container.cbegin() : container.cend()}, m_NbComponents{nbComponents} { } template > explicit IteratorValue(DataContainer &container, int nbComponents, bool begin) : m_It{begin ? container.begin() : container.end()}, m_NbComponents{nbComponents} { } IteratorValue(const IteratorValue &other) = default; std::unique_ptr clone() const override { return std::make_unique >(*this); } bool equals(const ArrayDataIteratorValue::Impl &other) const override try { const auto &otherImpl = dynamic_cast(other); return std::tie(m_It, m_NbComponents) == std::tie(otherImpl.m_It, otherImpl.m_NbComponents); } catch (const std::bad_cast &) { return false; } void next() override { std::advance(m_It, m_NbComponents); } void prev() override { std::advance(m_It, -m_NbComponents); } double at(int componentIndex) const override { return *(m_It + componentIndex); } double first() const override { return *m_It; } double min() const override { auto values = this->values(); auto end = values.cend(); auto it = std::min_element(values.cbegin(), end, [](const auto &v1, const auto &v2) { return SortUtils::minCompareWithNaN(v1, v2); }); return it != end ? *it : std::numeric_limits::quiet_NaN(); } double max() const override { auto values = this->values(); auto end = values.cend(); auto it = std::max_element(values.cbegin(), end, [](const auto &v1, const auto &v2) { return SortUtils::maxCompareWithNaN(v1, v2); }); return it != end ? *it : std::numeric_limits::quiet_NaN(); } QVector values() const override { auto result = QVector{}; for (auto i = 0; i < m_NbComponents; ++i) { result.push_back(*(m_It + i)); } return result; } void swap(ArrayDataIteratorValue::Impl &other) override { auto &otherImpl = dynamic_cast(other); IteratorValueBuilder::swap(*this, otherImpl); } private: DataContainerIterator m_It; int m_NbComponents; }; } // namespace arraydata_detail /** * @brief The ArrayData class represents a dataset for a data series. * * A dataset can be unidimensional or two-dimensional. This property is determined by the Dim * template-parameter. In a case of a two-dimensional dataset, each dataset component has the same * number of values * * @tparam Dim the dimension of the ArrayData (one or two) * @sa IDataSeries */ template class ArrayData { public: // ///// // // Ctors // // ///// // /** * Ctor for a unidimensional ArrayData * @param data the data the ArrayData will hold */ template > explicit ArrayData(DataContainer data) : m_Data{std::move(data)}, m_NbComponents{1} { } /** * Ctor for a two-dimensional ArrayData. The number of components (number of lines) must be * greater than 2 and must be a divisor of the total number of data in the vector * @param data the data the ArrayData will hold * @param nbComponents the number of components * @throws std::invalid_argument if the number of components is less than 2 or is not a divisor * of the size of the data */ template > explicit ArrayData(DataContainer data, int nbComponents) : m_Data{std::move(data)}, m_NbComponents{nbComponents} { if (nbComponents < 2) { throw std::invalid_argument{ QString{"A multidimensional ArrayData must have at least 2 components (found: %1)"} .arg(nbComponents) .toStdString()}; } if (m_Data.size() % m_NbComponents != 0) { throw std::invalid_argument{QString{ "The number of components (%1) is inconsistent with the total number of data (%2)"} .arg(m_Data.size(), nbComponents) .toStdString()}; } } /// Copy ctor explicit ArrayData(const ArrayData &other) { QReadLocker otherLocker{&other.m_Lock}; m_Data = other.m_Data; m_NbComponents = other.m_NbComponents; } // /////////////// // // General methods // // /////////////// // /** * Merges into the array data an other array data. The two array datas must have the same number * of components so the merge can be done * @param other the array data to merge with * @param prepend if true, the other array data is inserted at the beginning, otherwise it is * inserted at the end */ void add(const ArrayData &other, bool prepend = false) { QWriteLocker locker{&m_Lock}; QReadLocker otherLocker{&other.m_Lock}; if (m_NbComponents != other.componentCount()) { return; } if (prepend) { auto otherDataSize = other.m_Data.size(); m_Data.insert(m_Data.begin(), otherDataSize, 0.); for (auto i = 0; i < otherDataSize; ++i) { m_Data.replace(i, other.m_Data.at(i)); } } else { m_Data.append(other.m_Data); } } void clear() { QWriteLocker locker{&m_Lock}; m_Data.clear(); } int componentCount() const noexcept { return m_NbComponents; } /// @return the size (i.e. number of values) of a single component /// @remarks in a case of a two-dimensional ArrayData, each component has the same size int size() const { QReadLocker locker{&m_Lock}; return m_Data.size() / m_NbComponents; } std::shared_ptr > sort(const std::vector &sortPermutation) { QReadLocker locker{&m_Lock}; return arraydata_detail::Sort::sort(m_Data, m_NbComponents, sortPermutation); } // ///////// // // Iterators // // ///////// // ArrayDataIterator begin() { return ArrayDataIterator{ ArrayDataIteratorValue{std::make_unique >( m_Data, m_NbComponents, true)}}; } ArrayDataIterator end() { return ArrayDataIterator{ ArrayDataIteratorValue{std::make_unique >( m_Data, m_NbComponents, false)}}; } ArrayDataIterator cbegin() const { return ArrayDataIterator{ ArrayDataIteratorValue{std::make_unique >( m_Data, m_NbComponents, true)}}; } ArrayDataIterator cend() const { return ArrayDataIterator{ ArrayDataIteratorValue{std::make_unique >( m_Data, m_NbComponents, false)}}; } void erase(ArrayDataIterator first, ArrayDataIterator last) { auto firstImpl = dynamic_cast *>(first->impl()); auto lastImpl = dynamic_cast *>(last->impl()); if (firstImpl && lastImpl) { m_Data.erase(firstImpl->m_It, lastImpl->m_It); } } /// 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 * @remarks index must be a valid position */ double at(int index) const noexcept { QReadLocker locker{&m_Lock}; return m_Data.at(index); } // ///////////// // // 1-dim methods // // ///////////// // /** * @return the data as a vector, as a const reference * @remarks this method is only available for a unidimensional ArrayData */ template > const QVector &cdata() const noexcept { QReadLocker locker{&m_Lock}; return m_Data; } private: DataContainer m_Data; /// Number of components (lines). Is always 1 in a 1-dim ArrayData int m_NbComponents; mutable QReadWriteLock m_Lock; }; #endif // SCIQLOP_ARRAYDATA_H