【问题标题】:Find First Two Non-Adjacent Elements in a set Using an STL Algorithm使用 STL 算法查找集合中的前两个不相邻元素
【发布时间】:2015-01-09 13:12:52
【问题描述】:

所以我真的很挣扎,即使现在我对我的解决方案也不满意。

我有一个至少包含 0 的set,并且可能包含其他正数ints。我需要在set中找到第一个正数not

所以编写一个标准的while-loop 来完成这个很容易。

i = foo.begin();
while (i != prev(foo.end()) && *i + 1 == *next(i)){
    ++i;
}
cout << "First number that cannot be formed " << *i + 1 << endl;

但是当我尝试编写循环的 STL 算法版本时,我得到了一些失败的东西,比如这个循环:

auto i = foo.begin();
while (++i != prev(foo.end()) && *prev(i) + 1 == *i);
cout << "First number that cannot be formed " << *prev(i) + 1 << endl;

在以下情况下,这两个循环都正确产生 3

set<int> foo{0, 1, 2, 4};

但在这种情况下,第二个循环错误地产生了 3 而不是 4

set<int> foo{0, 1, 2, 3};

如何使用 STL 算法编写此代码并完成第一个循环的行为?

编辑:

看到一些答案后,我想增加难度。我真正想要的是不需要临时变量并且可以放在cout 语句中的东西。

【问题讨论】:

    标签: c++ loops stl set stl-algorithm


    【解决方案1】:

    你的循环的问题是你过早地停止了一个元素。这有效:

    while (++i != foo.end() && *prev(i) + 1 == *i);
    

    与第一个循环的区别在于条件*prev(i) + 1 == *i)而不是*i + 1 == *next(i);你检查的范围必须相应地改变。

    你也可以使用std::adjacent_find:

    auto i = std::adjacent_find(begin(s), end(s), [](int i, int j) { return i + 1 != j; });
    
    if(i == s.end()) {
      std::cout << *s.rbegin() + 1 << std::endl;
    } else {
      std::cout << *i + 1 << std::endl;
    }
    

    对编辑的回应:一种使它完全可内联的方法是

      std::cout << std::accumulate(begin(s), end(s), 0,
                                   [](int last, int x) {
                                     return last + 1 == x ? x : last;
                                   }) + 1 << '\n';
    

    ...但这效率较低,因为它在找到间隙时不会短路。另一种短路的方法是

    std::cout << *std::mismatch(begin     (s),
                                prev(end  (s)),
                                next(begin(s)),
                                [](int lhs, int rhs) { 
                                  return lhs + 1 == rhs;
                                }).first + 1 << '\n';
    

    【讨论】:

    • 最后的 if 语句有点难看,我真的很想在 cout 语句中完成这个,没有其他临时变量。
    • 查看编辑。我认为mismatch 行是我能想到的最好的。
    • 是的,这也是我设计的解决方案。如果没有其他人有更清洁的东西,我会接受。
    • 我相信mismatch 是满足要求的最佳解决方案:stackoverflow.com/a/27848729/2642059
    【解决方案2】:

    你试过adjacent_find吗?

    #include <algorithm>
    #include <iostream>
    #include <set>
    
    int main()
    {
        std::set<int> foo{0, 1, 2, 4};
        auto it = std::adjacent_find(begin(foo), end(foo),
             [](auto e1, auto e2){ return (e2 - e1) > 1; });
    
        // precondition: foo is not empty
        if (it == end(foo)) --it;
        std::cout << *it+1;
    }
    

    编辑:好的,如果你认为 Boost 足够标准,你可以这样做,这太棒了:

    #include <algorithm>
    #include <boost/iterator/counting_iterator.hpp>
    #include <set>
    #include <iostream>
    
    int main()
    {
        std::set<int> foo{0, 1, 2, 4};
        auto it =
            std::mismatch(
               begin(foo), end(foo),
               boost::counting_iterator<int>(*begin(foo))
            );
        std::cout << *it.second;
    }
    

    Live example.

    编辑 2:我在阅读另一个问题时想到的另一个问题:

    int i = 0;
    std::find_if(begin(foo), end(foo),
        [&i](int e){ return e != i++; });
    std::cout << i;
    

    这只是以前使用mismatch 的另一种方式。

    【讨论】:

    • 最后的 if 语句有点难看,我真的很想在 cout 语句中完成这个,没有其他临时变量
    • 嗯,现在好点了吗? :) 除了滥用其他算法并使用带状态的比较器之外,我没有看到摆脱 if 语句的好方法。
    • 你还需要应付it == end吗?
    • @Useless 如果你的意思是第二个 sn-p,那么没有。 it.secondboost::counting_iterator 并且始终可以取消引用。
    • 知道了 - 很好地使用了不匹配。
    【解决方案3】:

    你遇到了一个边缘案例。一旦 i == location 在集合末尾,您的 while 循环就会失败。在这种情况下,它以 i == 3 结束。您需要让 i 越过您的数组的边界才能使其工作。

    您可以通过将第 2 行更改为:

    while (++i **<**= prev(foo.end()) && *prev(i) + 1 == *i);
    

    通过

    还有其他一些需要考虑的事情: 1) 不保证对集合进行排序。 2) set foo(0, 1, 1); 的情况下会发生什么?即重复其中失败的那个是正确的,但它也是集合末尾的那个?

    您需要稍微复杂一点的算法来捕捉所有这些情况。

    【讨论】:

    • std::set 已排序,不能包含两次相同的元素。 std::multiset 允许相同值的多个实例,但也已排序。
    • 我认为 std:set 只有在使用 std::iterator 遍历它时才能保证被排序?
    • @RubixRechvin set 总是排序的,并且只有一个任意数量的实例:cplusplus.com/reference/set/set
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-02
    • 2015-10-20
    • 1970-01-01
    • 2012-04-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多