簡體   English   中英

如何基於相等謂詞刪除向量中的重復項?

[英]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};
    }
};

然后我生成一個vec3std::vector ,每個坐標都有隨機值。 使用它的 function 要求該向量中沒有一對值{v1, v2}填充v1 == -v2 我顯然需要在代碼的其他地方定義operator== ,否則這個問題就微不足道了。

我首先嘗試了std::setstd::sort + std::unique ,但找不到任何方法來為該應用程序提供名為 requirements Comparestruct歸檔(這兩種方法都需要)。

我該如何進行?

筆記:

這與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參數。

方法

  1. 定義一個 hash function

此步驟的目標是提供一個“預過濾”步驟,根據上下文中的含義消除明顯的重復項(例如, 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在下面的評論中指出了以下缺點:

  • “它將其性能特征更改為 O(n^2)(它必須在每次查找時遍歷所有元素)”(- @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. 定義鍵相等謂詞

此處的目標是刪除哈希器未檢測到的重復項(此處通常是向量,例如(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==

  1. 刪除重復項

聲明一個std::unordered_set來刪除重復項:

const std::unordered_set<value_type, hasher, key_equal> s(container.begin(), container.end());
container.assign(s.begin(), s.end());
  1. (alt) 刪除重復項(並在容器中保存原始順序)

基本相同,但這會檢查一個元素是否可以插入到std::unordered_set中,如果不能,則將其刪除。 改編自@yuryWhat'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());

通用(容器無關)模板化 function:

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_thasher_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};

測試 1:非順序保守:

// ...<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) 

測試 2:順序保守:

// ... <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::setstd::sort + std::unique 集合示例:

std::set<vec3, abs_less> example;

當然,您可以直接重載operator<並使用std::less ,但通常我建議不要使用具有異常行為的非默認運算符重載。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM