[英]How to remove duplicates in a vector based on an equality predicate?
我有一個大致看起來像這樣的struct
:
struct vec3
{
int x;
int y;
int z;
constexpr bool operator==(const vec3& v) const
{
return (x == v.x) && (y == v.y) && (z == v.z);
}
constexpr vec3 operator-() const
{
return {-x, -y, -z};
}
};
然后我生成一個vec3
的std::vector
,每個坐標都有隨機值。 使用它的 function 要求該向量中沒有一對值{v1, v2}
填充v1 == -v2
。 我顯然需要在代碼的其他地方定義operator==
,否則這個問題就微不足道了。
我首先嘗試了std::set
和std::sort
+ std::unique
,但找不到任何方法來為該應用程序提供名為 requirements Compare的struct
歸檔(這兩種方法都需要)。
我該如何進行?
筆記:
這與Removing duplicates from a non-sortable vector which pointers is sorted 以及C++ how to remove duplicates from vector of Class type? 有所不同。 其中元素可以根據某些標准進行排序(我認為)。
我相信最簡單的方法是使用std::unordered_set
並利用它的第二個和第三個template
參數。
此步驟的目標是提供一個“預過濾”步驟,根據上下文中的含義消除明顯的重復項(例如, v1
和-v1
應該具有相同的哈希值)。
這應該基於每個班級。 沒有辦法想出一個通用的高性能哈希 function,盡管非高性能關鍵應用程序可能會使用下面的第一個哈希器(但我不會再推薦它了)。
一種。 壞散列器
這是我最初提出的,在考慮@axnsan和@ François Andrieux的評論之前。
我能想到的最簡單的哈希器看起來像這樣:
struct bad_hasher
{
std::size_t operator()(const value_type&) const
{
return 0;
}
};
它使所有 hash 發生碰撞並強制std::unordered_set
使用KeyEqual
來確定對象是否相等。 所以確實,這行得通,但這並不是一個好的選擇。 @axnsan和@ François Andrieux在下面的評論中指出了以下缺點:
換句話說,這使得std::unordered_set
變得與std::vector
+ std::remove_if
相同。
b. 更好的哈希器
@axnsan建議針對此特定應用程序使用以下哈希器:
struct better_hasher
{
std::size_t operator()(const vec3& v) const
{
return static_cast<std::size_t>(std::abs(v.x) + std::abs(v.y) + std::abs(v.z));
}
};
它滿足以下要求:
better_hasher(v) == better_hasher(-v)
。v1 != v2
=> better_hasher(v1) != better_hasher(v2)
在大多數情況下(例如(1, 0, 1)
將與(1, 1, 0)
發生沖突)這可能接近我們希望在此配置中達到的最佳狀態。
然后,我們需要刪除由於 hash 次碰撞導致的那些“誤報”。
此處的目標是刪除哈希器未檢測到的重復項(此處通常是向量,例如(1, 0, 1) / (1, 1, 0)
或溢出)。
聲明一個大致如下所示的謂詞struct
:
struct key_equal
{
bool operator()(const value_type& a, const value_type& b) const
{
return (a == b) || <...>;
}
};
<...>
是在當前上下文中使兩個值相同的任何東西(所以這里a == b) || -a == b
a == b) || -a == b
例如)。
請注意,這需要實現operator==
。
聲明一個std::unordered_set
來刪除重復項:
const std::unordered_set<value_type, hasher, key_equal> s(container.begin(), container.end());
container.assign(s.begin(), s.end());
基本相同,但這會檢查一個元素是否可以插入到std::unordered_set
中,如果不能,則將其刪除。 改編自@yury在What's the most efficient way to erase duplicates and sort a vector? 中的回答? .
std::unordered_set<value_type, hasher, comparator> s;
const auto predicate = [&s](const value_type& value){return !s.insert(value).second;};
container.erase(std::remove_if(container.begin(), container.end(), predicate), container.end());
template<typename key_equal_t, typename hasher_t, bool order_conservative, typename container_t>
void remove_duplicates(container_t& container)
{
using value_type = typename container_t::value_type;
if constexpr(order_conservative)
{
std::unordered_set<value_type, hasher_t, key_equal_t> s;
const auto predicate = [&s](const value_type& value){return !s.insert(value).second;};
container.erase(std::remove_if(container.begin(), container.end(), predicate), container.end());
}
else
{
const std::unordered_set<value_type, hasher, key_equal_t> s(container.begin(), container.end());
container.assign(s.begin(), s.end());
}
}
期望提供key_equal_t
和hasher_t
(以及一個已知編譯時間的bool
,指示您是否關心元素是否保持相同的順序)。 我沒有對這個 function 中的兩個分支中的任何一個進行基准測試,所以我不知道一個是否比另一個好得多,盡管這個答案似乎表明手動插入可能更快。
/// "Predicate" indicating if two values are considered duplicates or not
struct key_equal
{
bool operator()(const vec3& a, const vec3& b) const
{
// Remove identical vectors and their opposite
return (a == b) || (-a == b);
}
};
/// Hashes a vec3 by adding absolute values of its components.
struct hasher
{
std::size_t operator()(const vec3& v) const
{
return static_cast<std::size_t>(std::abs(v.x) + std::abs(v.y) + std::abs(v.z));
}
};
remove_duplicates<key_equal, hasher, order_conservative>(vec);
vec3 v1{1, 1, 0}; // Keep
vec3 v2{0, 1, 0}; // Keep
vec3 v3{1, -1, 0}; // Keep
vec3 v4{-1, -1, 0}; // Remove
vec3 v5{1, 1, 0}; // Remove
std::vector vec{v1, v2, v3, v4, v5};
// ...<print vec values>
remove_duplicates<key_equal, hasher, false>(vec);
// ... <print vec values>
Output(之前/之后):
(1, 1, 0) (0, 1, 0) (1, -1, 0) (-1, -1, 0) (1, 1, 0)
(1, -1, 0) (0, 1, 0) (1, 1, 0)
// ... <print vec values>
remove_duplicates<key_equal, hasher, true>(vec);
// ... <print vec values>
Output(之前/之后):
(1, 1, 0) (0, 1, 0) (1, -1, 0) (-1, -1, 0) (1, 1, 0)
(1, 1, 0) (0, 1, 0) (1, -1, 0)
使用它的 function 要求該向量中沒有一對值 {v1, v2} 填充 v1 == -v2
但找不到任何方法來為該應用程序命名 requirements Compare 的結構文件(這兩種方法都需要)。
在我看來,您正在嘗試解決 X,但這是XY-problem中的 Y。
實現滿足-v == v
等式的有序比較器相當簡單。 簡單比較絕對值:
struct vec3
{
int x;
int y;
int z;
// normal comparison that treats -x != x
friend auto operator<=>(const vec3&, const vec3&) = default;
};
// declare in same namespace as vec3 for ADL
vec3 abs(const vec3& v) {
return {std::abs(v.x), std::abs(v.y), std::abs(v.z)};
}
struct abs_less {
template< class T, class U>
constexpr auto operator()( T&& lhs, U&& rhs ) const
-> decltype(std::forward<T>(lhs) < std::forward<U>(rhs))
{
using std::abs; // for integers
return abs(lhs) < abs(rhs); // this implementation assumes normal comparison operator
// you can implement logic directly here if that's not possible
}
};
此比較器適用於std::set
和std::sort
+ std::unique
。 集合示例:
std::set<vec3, abs_less> example;
當然,您可以直接重載operator<
並使用std::less
,但通常我建議不要使用具有異常行為的非默認運算符重載。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.