就地合并像std :: vector中的元素

时间:2019-07-17 11:29:03

标签: c++ algorithm c++-standard-library

我有一个成对的数组,例如:

X = {{A, 1}, {B, 2}, {C, 1}, {A, 3}, {C, 4}}

我想产生一个数组:

Y = (x, n) such that n = sum i for (x, i) in X

因此在上面的示例中,我们将:

Y = {{A, 4}, {B, 2}, {C, 5}}

我当前拥有的代码是:

#include <vector>
#include <algorithm>
#include <iostream>

using namespace std;

int main() {

    char A = 'A';
    char B = 'B';
    char C = 'C'; 

    vector< pair<char, int> > X = {{A, 1}, {B, 2}, {C, 1}, {A, 3}, {C, 4}};

    // Sort by first element of the pair
    sort(begin(X), end(X), [](auto a, auto b) { return a.first < b.first; });

    // Could this be better? Is there an existing STL algorithm that will
    // do this in-place?
    vector< pair<char, int> > Y;
    for(auto p : X) {
        if(Y.empty() || Y.back().first != p.first) {
            Y.push_back(p);
        } else {
            Y.back().second += p.second;
        }
    }

    cout << "Y:";
    for (auto p : Y) {
       cout << '{' << p.first << ' ' << p.second << '}';
    }
    cout << '\n';

}

是否可以使此代码更简洁?(无需更改基础容器的类型)

我正在尝试通过替换标准库中的一种算法来消除raw loop,但我认为没有一个合适的方法。

我想要std::unique的某种变体,它不仅要用两个元素是否相等的谓词,还要用一个定义如何组合它们的函数。它可能看起来像:

coalesce(begin(X), end(X), [](auto a, auto b){ return a.first == b.first; }, [](auto a, auto b) { return {a.first, a.second+b.second} });

FWIW,这是coalesce的一种实现方式,似乎很有效:

template<class ForwardIt, class BinaryPredicate, class BinaryFunction>
ForwardIt coalesce(ForwardIt first, ForwardIt last, BinaryPredicate p, BinaryFunction f)
{
    if (first == last)
        return last;

    ForwardIt result = first;
    while (++first != last) {
        if(p(*result, *first)) {
            *result = f(*result, *first);
        } else {
            ++result;
            *result = *first;
        }
    }
    return ++result;
}

然后代码变成:

    vector< pair<char, int> > X = {{A, 1}, {B, 2}, {C, 1}, {A, 3}, {C, 4}};

    // Sort by first element of the pair
    sort(begin(X), end(X), [](auto a, auto b) { return a.first < b.first; });

    // Easier to understand the intent!
    auto e = coalesce(begin(X), end(X),
                      [](auto a, auto b) { return a.first == b.first; },
                      [](auto a, auto b) { return pair<char, int>{a.first, a.second+b.second}; });

    for_each(begin(X), e, [](auto p) {
        cout << '{' << p.first << ' ' << p.second << '}';
    });
    cout << '\n';

注意:我对map等非常熟悉,并且不想使用它。

3 个答案:

答案 0 :(得分:3)

我很想用Compare来定义它,而不是相等。您可以std::upper_bound来获取组,并在每个组中获取std::accumulate

template<class ForwardIt, class OutputIt, class Compare = std::less<>, class BinaryOperation = std::plus<>>
OutputIt coalesce(ForwardIt first, ForwardIt last, OutputIt d_first, Compare comp = {}, BinaryOperation op = {})
{
    while (first != last) {
        ForwardIt group = std::upper_bound(first, last, *first, comp);
        *d_first++ = std::accumulate(std::next(first), group, *first, op);
        first = group;
    }
    return d_first;
}

使用哪个

vector< pair<char, int> > X = {{'A', 1}, {'B', 2}, {'C', 1}, {'A', 3}, {'C', 4}};
less<> comp;
auto add = [](auto a, auto b) { return pair<char, int>{a.first, a.second+b.second}; };

sort(begin(X), end(X)/*, comp*/);

auto e = coalesce(begin(X), end(X), begin(X), comp, add);
X.erase(e, end(X));

for (auto [k, v] : X) {
    cout << '{' << k << ' ' << v << '}';
}

答案 1 :(得分:2)

(注意:OP在我回答后编辑了问题,以指定他们不想使用map或其变体,然后再次将其指定为需要就位)

哈希表将为您完成合并工作:

std::unordered_map<char, int> coalesced;
for(const auto key_val : X)
    coalesced[key_val.first] += key_val.second;

现在我们有一个哈希表,其内容为

A : 4
B : 2
C : 5

如果您想将其放入另一个std::vector,那就没问题了:

vector< pair<char, int> > Y(coalesced.begin(), coalesced.end());

或者您可以保持原样。

unordered_map没有使用w.r.t键进行排序(因此名称为“ unordered”)。如果您想对它们进行排序,则可以使用完全相同的方式使用std::map(但它是作为二进制搜索树而不是哈希表实现的)

Demo

答案 2 :(得分:2)

嗯,一种不使用其他容器且不存在原始循环(或std::for_each)的方法可能会将std::sortstd::partial_sum组合起来

std::partial_sum用于计算前缀和,或用于组合相邻元素的通用方法。进行初始排序后,我们可以使用std::partial_sum来组合具有相同键的元素:

std::vector< std::pair<char, int> > Y;
std::vector< std::pair<char, int> > Y(X.size());
std::partial_sum(X.begin(), X.end(),  Y.rbegin(), [](const auto& lhs, const auto& rhs)
{
    if (lhs.first != rhs.first)
        return rhs;
    return std::make_pair(lhs.first, lhs.second + rhs.second);
});

请注意,我们在Y中向后迭代。这是下一步的目的,我将在稍后详细说明。

这使我们成为其中的一部分。现在我们有了一个Y,看起来像这样:

Y:{C 5}{C 1}{B 2}{A 4}{A 1}

现在我们的任务是删除重复项,我们可以使用std::unique

Y.erase(std::unique(Y.begin(), Y.end(), 
   [](const auto& lhs, const auto& rhs){
      return lhs.first == rhs.first;}), Y.end());

我们需要在相反的范围内使用partial_sum,因为std::unique“从每个连续的等效元素组中消除了除第一个元素之外的所有元素”,而我们需要最后一个partial_sum首先出现。

由于排序,总算法为O(N log N)。内存使用量为O(N)。

Demo