fizzbuzz
fizzbuzz

Reputation: 157

How to use a STL algorithm to replace the for loop?

How can I replace the for loop with a STL algorithm? Here I want to assign the value of vals[i] in vector vals to nodeList[i]->val in nodeList. And what if I use vals[i] * 2 to replace it?

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

using namespace std;

struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};

int main()
{
    TreeNode node1(1);
    TreeNode node2(2);
    TreeNode node3(3);
    node1.left = &node2;
    node1.right = &node3;

    vector<TreeNode*> nodeList = {&node1, &node2, &node3};
    vector<int> vals = {1, 3, 2};
    for (int i = 0; i < vals.size(); i++) {
        nodeList[i]->val = vals[i];
        // nodeList[i]->val = vals[i] * 2;
    }
}

Upvotes: 2

Views: 1266

Answers (3)

Caleth
Caleth

Reputation: 62686

Your first case is a copy, the second is a transform. The wrinkle is that you need to transform the output iterator.

auto as_vals = std::views::transform(nodeList, std::mem_fn(&Node::val));
std::copy(vals.begin(), vals.end(), as_vals.begin());
std::transform(vals.begin(), vals.end(), as_vals.begin(), [](int i){ return i * 2; });

Or with boost

auto as_val = boost::make_transform_iterator(nodeList.begin(), std::mem_fn(&Node::val));
std::copy(vals.begin(), vals.end(), as_val);
std::transform(vals.begin(), vals.end(), as_val, [](int i){ return i * 2; });

If you don't have access to C++20 or boost, you can write something like

template <typename Iterator, typename T>
class member_iterator
{
    Iterator wrapped;
    using base_t = typename std::iterator_traits<Iterator>::value_type;
    T (base_t::*mem);
public

    using value_type = T;
    using pointer = T*;
    using reference = T&;
    using difference_type = typename std::iterator_traits<Iterator>::difference_type;
    using iterator_category = typename std::iterator_traits<Iterator>::iterator_category;

    member_iterator(Iterator wrapped, T (base_t::*mem)) : wrapped(wrapped), mem(mem) {} 

    T & operator*() const { return (*wrapped).*mem; }
    member_iterator& operator++() { ++wrapped; return *this; }
    // etc.
};

Upvotes: 2

rustyx
rustyx

Reputation: 85341

The standard "algorithm" for for-loops is std::for_each. It is essentially a wrapper around a for-loop:

std::for_each(begin(nodeList), end(nodeList), [&vals, i = 0](auto& it) mutable {
    it->val = vals[i++];
});

Due to the fact that you need to iterate over two collections at once, you still need an index variable (i in this example), or use std::distance to compute it in each iteration.

Upvotes: 2

Audrius Meškauskas
Audrius Meškauskas

Reputation: 21718

What are you doing here you are iterating over the two containers at once.

While it is possible to have a very neat iteration over a single container in modern C++, there is nothing that I would recommend from the depth of my heart for iterating over the two - not ready yet. This question lists various possible solutions but I am not really sure.

It is possible to try a ranged loop while manually maintaining the second index or iterator:

  auto i = vals.begin();
  for (auto & node: nodeList) {
    node->val = *i++;
  }

This is less code and would be faster while iterating over something like std::deque where (unlike for std::vector) direct indexing is expensive. But may be also reasonable to leave the code as is and just wait for the next C++ standard.

Upvotes: 2

Related Questions