【问题标题】:Repeated elements in a std::vectorstd::vector 中的重复元素
【发布时间】:2012-03-13 11:53:25
【问题描述】:

我有一个std::vector,我想检查其中的所有元素。如果某个元素出现不止一次,我会发出错误信号。

我就是这样做的:

std::vector<std::string> test;
test.push_back("YES");
test.push_back("YES");

for(int i = 0; i < test.size(); i++)
{
    if(test[i] > 1)
    {
        DCS_LOG_DEBUG("ERROR WITH COUNT")
    }
}

虽然我知道如何使用std::vector::count() 方法进行计数,但这不起作用。但我想计算每个元素的数量,而不是计算所有内容......有什么想法吗?

【问题讨论】:

    标签: c++ vector count


    【解决方案1】:

    最简单的方法是std::sort这个向量,然后使用std::adjacent_find


    但是,如果你不想对向量进行排序,你可以在 C++11 中做这样的事情:

    #include <unordered_map>
    #include <functional> // For std::hash<std::string>.
    #include <string>
    #include <iostream>
    
    int main() {
    
        // Test data.
        std::vector<std::string> v;
        v.push_back("a");
        v.push_back("b");
        v.push_back("c");
        v.push_back("a");
        v.push_back("c");
        v.push_back("d");
        v.push_back("a");
    
        // Hash function for the hashtable.
        auto h = [](const std::string* s) {
            return std::hash<std::string>()(*s);
        };
    
        // Equality comparer for the hashtable.
        auto eq = [](const std::string* s1, const std::string* s2) {
            return s1->compare(*s2) == 0;
        };
    
        // The hashtable:
        //      Key: Pointer to element of 'v'.
        //      Value: Occurrence count.
        std::unordered_map<const std::string*, size_t, decltype(h), decltype(eq)> m(v.size(), h, eq);
    
        // Count occurances.
        for (auto v_i = v.cbegin(); v_i != v.cend(); ++v_i)
            ++m[&(*v_i)];
    
        // Print strings that occur more than once:
        for (auto m_i = m.begin(); m_i != m.end(); ++m_i)
            if (m_i->second > 1)
                std::cout << *m_i->first << ": " << m_i->second << std::endl;
    
        return 0;
    
    }
    

    打印出来:

    a: 3
    c: 2
    

    我实际上并没有对其进行基准测试,但这有可能表现得相当出色,原因如下:

    • 假设实际向量元素不会产生病态的不平衡哈希,这实际上是一个 O(n) 算法,而不是 O(n*log(n)) 进行排序。
    • 我们使用 指针 的哈希表指向字符串,而不是字符串本身,因此不会发生不必要的复制。
    • 我们可以“预分配”哈希表存储桶(我们在构造 m 时传递 v.size()),因此最小化哈希表调整大小。

    【讨论】:

      【解决方案2】:

      特定元素

      计数是标准方法:

      #include <algorithm>
      ...
      
          if (count (test.begin(), test.end(), "YES") > 1)
              std::cerr << "positive\n";
      

      如果你需要更高的性能,你可以用经典的方式来做:

      bool exists = false;
      for (auto const& v : test) {
          if (v == "YES") {
              if (exists) {
                  std::cerr << "positive\n";
                  break;
              }
              else exists = true;
          }
      }
      

      任何元素多次

      对于大型向量,请尝试std::set

      std::set<std::string> exists;
      for (auto const &v : test) {
          if (!exists.insert(v).second)
              std::cerr << "positive\n";
      }
      

      在这种方法中,如果您还希望能够识别您是否已经提到它的非唯一性,您可能需要使用std::multiset

      const std::multiset<std::string> counts (test.begin(), test.end());
      for (auto const &v: test)
          if (counts.count (v) == 2) std::cerr << "meh\n";
      

      如果容器很小,并且您只想查看是否有任何元素不止一次:

      auto multitimes = [&test] (std::string const &str) {
          return count(test.begin(),test.end(),str)>1;
      };
      if (any_of (test.begin(), test.begin(), multitimes))
          std::cerr << "something was there more than once\n";
      

      【讨论】:

      • 如果还没有,请不要忘记将其放入集合中,或者使用exists.insert(v).second 而不是exists.find(v) != exists.end()
      • 先find然后insert肯定效率不高,因为它遍历set两次,为什么不检查insert的返回值呢?
      • @ChristianRau:感谢您的提示。这也让我建议使用 multiset 来检测是否已打印消息。
      【解决方案3】:

      您可以使用std::map 并定义从键(字符串)到计数(int)的映射:

      #include <map>
      #include <string>
      /* ... */
      std::map<std::string, int> count_map;
      
      /* ... */
      
      count_map[key]++;
      

      【讨论】:

      • 你可以摆脱 if 块;如果密钥丢失,count_map[key]++ 会做正确的事情。
      • mapmore than once 来说太过分了
      • @phresnel,如果我可以用 perl 做一些事情,map 很好。
      • @perreal:这样我的电池放电更快,系统性能下降。
      • 为什么不使用多重集? std::multiset&lt;string&gt; s(test.begin(), test.end()); 那么获取字符串的出现次数就是简单的count调用:s.count("key");
      【解决方案4】:

      【讨论】:

      • 谁赞成一个没有解决实际问题的答案?
      • @Christian Rau 当我回答它时,我没有在描述中看到带有 std:count 的部分.. 嗯,那一刻在那里吗?
      • 不知道,也许不知道,但实际的问题一直都是一样的,而且永远无法通过单独应用std::count 来解决。
      • @Christian Rau 如果你在每个项目的 if 检查中使用 Std:count,它确实
      【解决方案5】:

      做你想做的最简单的方法是对数组进行排序,然后查看哪些元素被多次满足。如果您不想修改数组本身,则必须创建一个副本。这是一个 O(n * lg n) 解决方案,如果您不关心订单,则没有额外空间,如果您关心订单,则有 O(n) 额外空间。

      sort(test.begin(), test.end());
      
      // If you only care if there is a repeated element, do this:
      int size = test.size();
      unique(test.begin(), test.end());
      if (test.size() != size) {
        cout << "An element is repeated.";
      }
      
      // If you do care which elements are repeated, do this:
      for (unsigned index = 1; index < test.size(); ++index) {
        if (test[index] == test[index - 1] && (index == 1 || test[index - 2] != test[index])) {
           cout << test[index] << " is repeated.";
        }
      }
      

      我提供了两种解决方案:第一种是您只关心字符串是否重复,第二种是您确切关心哪些字符串重复。

      【讨论】:

      • 或者,他可以在第二种情况下使用std::adjacent_find
      • 如果你从 index = 1 开始你的索引并且你检查 test[index-2] 这不是一个越界的数组吗?我认为您需要在 index = 2 .... 处开始 for 循环?
      • 没有问题,因为我的 if 条件:index == 1 || test[index - 2 ] != test[index] if index is 1 第二个语句永远不会被验证
      • 如果我有多个字符串,比如 5,这个词可以吗? @izomorphius
      • @BrankoDimitrijevic 在第一种情况下也是如此,它可以防止复制(或移动)东西。
      【解决方案6】:

      如果您不介意额外的空间,请尝试将元素推入map。每当您发现您的元素已经在地图中时,您都可以直接发出错误信号。

      map<string, int> occurrences;
      
      for (vector<string>::const_iterator cit = test.begin(); cit != test.end(); ++cit)
          if ((++occurrences[*cit]) == 2)
              cout << "ERROR"; // You can even signal which element is repeated here easily, using *cit.
      

      请注意,根据Tony Delroy 的巧妙修改,此代码对每个重复项仅正确发出一次消息(即使该项重复多次)。虽然这种方式正确计算了整个集合中每个字符串的出现次数(这可能是必需的),但如果同一元素有 231 个副本(或者更多)。如果是这种情况并且您确实想要每个字符串的计数,则可以使用long long int

      如果您对每个字符串的计数不感兴趣,更有效的方法是使用set,正如smerlin 建议的那样(因为它只维护字符串,而不是一对字符串和int正如map 所做的那样),从而减少空间需求......并在您找到集合中的项目时发出错误消息:

      set<string> occurrences;
      
      for (vector<string>::const_iterator cit = test.begin(); cit != test.end(); ++cit)
          if (false == occurrences.insert(*cit).second)
              cout << "ERROR"; // You can even signal which element is repeated here easily, using *cit.
      

      如果您想在问题发生之前消除它,请将元素插入到set 中。它会自动删除重复项。但请注意 set 中的元素已排序,因此您不会保留插入顺序。如果您不介意,set 会更好,因为搜索它并按排序顺序读取元素效率更高。

      【讨论】:

      • +1 用于显示完整、干净、简洁的代码; ... == 1 将避免同一值出现多个错误消息 - 问题不清楚需要哪种行为。
      • 改用set,需要更少的内存。他不需要实际的计数,他只需要知道计数是否大于 1。编辑:嗯,是的set 是有道理的,但所需的错误处理可能必须转到元素插入集合的位置,所以如果那不可能,你的解决方案很好。
      • 感谢@TonyDelroy。我认为您的意思是 == 2。无论如何,他说他会发出一般错误的信号。但你的评论是完全正确的,而且更笼统。我已经更新了我的答案。
      【解决方案7】:

      一个解决方案可能是使用两个 for 循环......我认为这很简单......

      例如:

      std::vector<std::string> test;
      test.push_back("YES");
      test.push_back("YES");
      
      for(int i = 0; i < test.size(); i++)
      {
          for(int j = 0; j < test.size(); j++)
          {
               if(i != j)
               {
                    if(test[i] == test[j])
                    {
                         DCS_LOG_DEBUG("ERROR WITH COUNT")
                    }
               }
          }
      }
      

      【讨论】:

      • 此代码将始终产生错误,因为 test[0] == test[0] 始终为真。我试图在不发表评论的情况下编辑您的帖子,但由于未知原因,编辑被拒绝。 j 应该从 i + 1 开始。
      • 很好,但关键是从 i + 1 开始 j 更好,因为你总是会做两次比较。例如,(test[1] == test[3]) 将在 (i == 1 && j == 3) 时完成,并在 (i == 3 && j == 1) 时重复。此外 - 即使在修复之后 - 您的代码也会多次针对重复两次以上的项目发出错误消息,例如 { "A", "B", "B", "B" }。
      猜你喜欢
      • 2022-10-24
      • 2011-09-04
      • 1970-01-01
      • 2018-01-27
      • 2016-03-30
      • 1970-01-01
      • 2011-12-27
      • 2018-01-11
      • 2016-09-21
      相关资源
      最近更新 更多