diff --git a/src/Magnum/MeshTools/RemoveDuplicates.cpp b/src/Magnum/MeshTools/RemoveDuplicates.cpp index 52f30afcb..965ee0a28 100644 --- a/src/Magnum/MeshTools/RemoveDuplicates.cpp +++ b/src/Magnum/MeshTools/RemoveDuplicates.cpp @@ -44,6 +44,43 @@ struct ArrayHash { } }; +std::size_t removeDuplicatesInto(const Containers::StridedArrayView2D& data, const Containers::StridedArrayView1D& indices) { + /* Assuming the second dimension is contiguous so we can calculate the + hashes easily */ + CORRADE_ASSERT(data.empty()[0] || data.isContiguous<1>(), + "MeshTools::removeDuplicatesInto(): second data view dimension is not contiguous", {}); + + const std::size_t dataSize = data.size()[0]; + CORRADE_ASSERT(indices.size() == dataSize, + "MeshTools::removeDuplicatesInto(): output index array has" << indices.size() << "elements but expected" << dataSize, {}); + + /* Table containing index of first occurence for each unique entry. + Reserving more buckets than necessary (i.e. as if each entry was + unique). */ + std::unordered_map, UnsignedInt, ArrayHash, ArrayEqual> table{dataSize}; + + /* Go through all entries */ + for(std::size_t i = 0; i != dataSize; ++i) { + /* Try to insert new entry into the table. The inserted index points + into the original unchanged data array. */ + const Containers::ArrayView entry = data[i].asContiguous(); + const auto result = table.emplace(entry, i); + + /* Put the (either new or already existing) index into the output + index array */ + indices[i] = result.first->second; + } + + CORRADE_INTERNAL_ASSERT(dataSize >= table.size()); + return table.size(); +} + +std::pair, std::size_t> removeDuplicates(const Containers::StridedArrayView2D& data) { + Containers::Array indices{Containers::NoInit, data.size()[0]}; + const std::size_t size = removeDuplicatesInto(data, indices); + return {std::move(indices), size}; +} + std::size_t removeDuplicatesInPlaceInto(const Containers::StridedArrayView2D& data, const Containers::StridedArrayView1D& indices) { /* Assuming the second dimension is contiguous so we can calculate the hashes easily */ @@ -61,11 +98,13 @@ std::size_t removeDuplicatesInPlaceInto(const Containers::StridedArrayView2D entry = data[i].asContiguous(); const auto result = table.emplace(entry, table.size()); - /* Add the (either new or already existing) index into the array */ + /* Put the (either new or already existing) index into the output index + array */ indices[i] = result.first->second; /* If this is a new combination, copy the data to new (earlier) diff --git a/src/Magnum/MeshTools/RemoveDuplicates.h b/src/Magnum/MeshTools/RemoveDuplicates.h index 512f47838..d45debfc7 100644 --- a/src/Magnum/MeshTools/RemoveDuplicates.h +++ b/src/Magnum/MeshTools/RemoveDuplicates.h @@ -56,8 +56,8 @@ namespace Implementation { @brief Remove duplicate data from given array in-place @param[in,out] data Data array, duplicate items will be cut away with order preserved -@return Size of unique prefix in the cleaned up @p data array and the resulting - index array +@return The resulting index array and size of unique prefix in the cleaned up + @p data array @m_since_latest Removes duplicate data from given array by comparing the second dimension of @@ -70,7 +70,11 @@ instead. Usage example: @snippet MagnumMeshTools.cpp removeDuplicates -@see @ref Corrade::Containers::StridedArrayView::isContiguous() +See @ref removeDuplicates(const Containers::StridedArrayView2D&) +for a variant that doesn't modify the input data in any way but instead returns +an index array pointing to original data locations. +@see @ref Corrade::Containers::StridedArrayView::isContiguous(), + @ref removeDuplicatesInPlaceInto() */ MAGNUM_MESHTOOLS_EXPORT std::pair, std::size_t> removeDuplicatesInPlace(const Containers::StridedArrayView2D& data); @@ -84,9 +88,36 @@ MAGNUM_MESHTOOLS_EXPORT std::pair, std::size_t> r Same as above, except that the index array is not allocated but put into @p indices instead. Expects that @p indices has the same size as @p data. +@see @ref removeDuplicatesInto() */ MAGNUM_MESHTOOLS_EXPORT std::size_t removeDuplicatesInPlaceInto(const Containers::StridedArrayView2D& data, const Containers::StridedArrayView1D& indices); +/** +@brief Remove duplicate data from given array +@param[in] data Data array +@return The resulting index array and count of unique items in the original + @p data array +@m_since_latest + +Compared to @ref removeDuplicatesInPlace(const Containers::StridedArrayView2D&) +this function doesn't modify the input data array in any way but instead +returns an index array pointing to original data locations. +*/ +MAGNUM_MESHTOOLS_EXPORT std::pair, std::size_t> removeDuplicates(const Containers::StridedArrayView2D& data); + +/** +@brief Remove duplicate data from given array +@param[in] data Data array +@param[out] indices Where to put the resulting index array +@return Count of unique items in the original @p data array +@m_since_latest + +Compared to @ref removeDuplicatesInPlaceInto(const Containers::StridedArrayView2D&, const Containers::StridedArrayView1D&) +this function doesn't modify the input data array in any way but instead +makes an index array pointing to original data locations. +*/ +MAGNUM_MESHTOOLS_EXPORT std::size_t removeDuplicatesInto(const Containers::StridedArrayView2D& data, const Containers::StridedArrayView1D& indices); + /** @brief Remove duplicates from indexed data in-place @param[in,out] indices Index array, which will get remapped to list just diff --git a/src/Magnum/MeshTools/Test/RemoveDuplicatesTest.cpp b/src/Magnum/MeshTools/Test/RemoveDuplicatesTest.cpp index 5d80a732b..64a6cba7c 100644 --- a/src/Magnum/MeshTools/Test/RemoveDuplicatesTest.cpp +++ b/src/Magnum/MeshTools/Test/RemoveDuplicatesTest.cpp @@ -36,9 +36,9 @@ namespace Magnum { namespace MeshTools { namespace Test { namespace { struct RemoveDuplicatesTest: TestSuite::Tester { explicit RemoveDuplicatesTest(); - void removeDuplicatesInPlace(); - void removeDuplicatesInPlaceNonContiguous(); - void removeDuplicatesInPlaceIntoWrongOutputSize(); + void removeDuplicates(); + void removeDuplicatesNonContiguous(); + void removeDuplicatesIntoWrongOutputSize(); template void removeDuplicatesIndexedInPlace(); void removeDuplicatesIndexedInPlaceSmallType(); void removeDuplicatesIndexedInPlaceEmptyIndices(); @@ -57,9 +57,9 @@ struct RemoveDuplicatesTest: TestSuite::Tester { }; RemoveDuplicatesTest::RemoveDuplicatesTest() { - addTests({&RemoveDuplicatesTest::removeDuplicatesInPlace, - &RemoveDuplicatesTest::removeDuplicatesInPlaceNonContiguous, - &RemoveDuplicatesTest::removeDuplicatesInPlaceIntoWrongOutputSize, + addTests({&RemoveDuplicatesTest::removeDuplicates, + &RemoveDuplicatesTest::removeDuplicatesNonContiguous, + &RemoveDuplicatesTest::removeDuplicatesIntoWrongOutputSize, &RemoveDuplicatesTest::removeDuplicatesIndexedInPlace, &RemoveDuplicatesTest::removeDuplicatesIndexedInPlace, &RemoveDuplicatesTest::removeDuplicatesIndexedInPlace, @@ -79,38 +79,52 @@ RemoveDuplicatesTest::RemoveDuplicatesTest() { &RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceEmptyIndicesVertices}); } -void RemoveDuplicatesTest::removeDuplicatesInPlace() { +void RemoveDuplicatesTest::removeDuplicates() { Int data[]{-15, 32, 24, -15, 15, 7541, 24, 32}; std::pair, std::size_t> result = - MeshTools::removeDuplicatesInPlace(Containers::arrayCast<2, char>(Containers::arrayView(data))); + MeshTools::removeDuplicates(Containers::arrayCast<2, char>(Containers::arrayView(data))); CORRADE_COMPARE_AS(Containers::arrayView(result.first), + Containers::arrayView({0, 1, 2, 0, 4, 5, 2, 1}), + TestSuite::Compare::Container); + + std::pair, std::size_t> resultInPlace = + MeshTools::removeDuplicatesInPlace(Containers::arrayCast<2, char>(Containers::arrayView(data))); + CORRADE_COMPARE_AS(Containers::arrayView(resultInPlace.first), Containers::arrayView({0, 1, 2, 0, 3, 4, 2, 1}), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(Containers::arrayView(data).prefix(result.second), + CORRADE_COMPARE_AS(Containers::arrayView(data).prefix(resultInPlace.second), Containers::arrayView({-15, 32, 24, 15, 7541}), TestSuite::Compare::Container); } -void RemoveDuplicatesTest::removeDuplicatesInPlaceNonContiguous() { +void RemoveDuplicatesTest::removeDuplicatesNonContiguous() { Int data[8]{}; std::ostringstream out; Error redirectError{&out}; + MeshTools::removeDuplicates(Containers::arrayCast<2, const char>(Containers::arrayView(data)).every({1, 2})); MeshTools::removeDuplicatesInPlace(Containers::arrayCast<2, char>(Containers::arrayView(data)).every({1, 2})); - CORRADE_COMPARE(out.str(), "MeshTools::removeDuplicatesInPlaceInto(): second data view dimension is not contiguous\n"); + CORRADE_COMPARE(out.str(), + "MeshTools::removeDuplicatesInto(): second data view dimension is not contiguous\n" + "MeshTools::removeDuplicatesInPlaceInto(): second data view dimension is not contiguous\n"); } -void RemoveDuplicatesTest::removeDuplicatesInPlaceIntoWrongOutputSize() { +void RemoveDuplicatesTest::removeDuplicatesIntoWrongOutputSize() { Int data[8]{}; UnsignedInt output[7]; std::ostringstream out; Error redirectError{&out}; + MeshTools::removeDuplicatesInto( + Containers::arrayCast<2, const char>(Containers::arrayView(data)), + output); MeshTools::removeDuplicatesInPlaceInto( Containers::arrayCast<2, char>(Containers::arrayView(data)), output); - CORRADE_COMPARE(out.str(), "MeshTools::removeDuplicatesInPlaceInto(): output index array has 7 elements but expected 8\n"); + CORRADE_COMPARE(out.str(), + "MeshTools::removeDuplicatesInto(): output index array has 7 elements but expected 8\n" + "MeshTools::removeDuplicatesInPlaceInto(): output index array has 7 elements but expected 8\n"); } template void RemoveDuplicatesTest::removeDuplicatesIndexedInPlace() {