0

I am trying to replace the hand written for loop in using_for_loop() by algorithm methods. no_for_loop() is the version that would replace using_for_loop(). Any clue on how to implement the lambda? In general, for a vector of vectors how to iterate the inner for loop in the lambda or a function object. It would require access to the outer loop index. If I were to use std::distance() to compute the index inside the lambda, I would need access to the the iterator. But I have only the value/object inside passed to the lambda. Is there is a way to pass the iterator to the lambda?
The motivation is to follow the suggestion made by the book Effective STL: 50 Specific Ways to Improve Your Use of the Standard Template Library by Scott Meyers. One of them is Item 43: Prefer algorithm calls to hand-written loops

Note: In the real program "vec" is the inputs. I made it local and added assign_vec(10'000'000,vec); call to make it stand alone.
The goal is to remove the repeated 3-elements-array entirely(not need to keep one of them)

#include <algorithm> 
void assign_vec(const int64_t &count, std::vector<std::array<int64_t, 3>> &v)
{
  for (int i = 0; i < count;++i)
  {
    std::array<int64_t, 3 > a{i,i+1,i+2};
    v.emplace_back(a);
  }
}

int using_for_loop() {
  std::vector<std::array<int64_t, 3>> vec;
  assign_vec(10'000'000,vec);//vec is arbitrary. Not assume this hard coded case.
  std::sort(vec.begin(), vec.end(), [](const auto& a, const auto& b)
    {
      if (a[0] != b[0])
        return a[0] < b[0];

      return std::minmax(a[1], a[2]) < std::minmax(b[1], b[2]);
    });

  size_t k{};
  for (auto i = (size_t)0, n = vec.size(); i < n; )
  {
    auto j = i + (size_t)1;
    for (; j < n; ++j)
    {
      if (vec[i][0] != vec[j][0] || std::minmax(vec[i][1], vec[i][2]) != std::minmax(vec[j][1], vec[j][2]))
        break;
    }

    if (i + (size_t)1 == j)
      vec[k++] = vec[i];

    i = j;
  }
  vec.resize(k);
  return 0;
}

int no_for_loop() {
  std::vector<std::array<int64_t, 3>> vec;
  assign_vec(10'000'000, vec);
  size_t k{};
  //??std::for_each(std::begin(vec), std::end(vec), []() {});
  vec.resize(k);
  return 0;
}

qqqqq
  • 837
  • 12
  • 31
  • 5
    using the standard algorithms is great where they fit but if they don't fit don't torture your code to make them work – Alan Birtles Aug 31 '23 at 14:59
  • Perhaps you need a decent reference for [`std::for_each`](https://en.cppreference.com/w/cpp/algorithm/for_each)? Or learn about [the range `for` loop](https://en.cppreference.com/w/cpp/language/range-for)`And if you need nested loops, then just nest the loops. And remember, sometimes it's just simpler to use something else entirely. – Some programmer dude Aug 31 '23 at 15:00
  • 2
    Please describe what `using_for_loop` does rather than just showing the code. In fact, having a couple of input/output examples would help a lot. – cigien Aug 31 '23 at 15:02
  • 1
    Aside: the body of `using_for_loop` is equivalent to `return 0;`, it doesn't do anything – Caleth Aug 31 '23 at 15:06
  • 1
    Please start with this: _"From a `std::vector>` I need to ..."_ and then describe exactly what the purpose of the algorithm is. – Ted Lyngmo Aug 31 '23 at 15:08
  • I have a deleted answer ready to be undeleted once you confirm that the purpose of the algorithm is to do absolutely nothing. – Ted Lyngmo Aug 31 '23 at 15:28
  • 1
    Too bad we don't have C++23 yet : https://en.cppreference.com/w/cpp/ranges/enumerate_view – Pepijn Kramer Aug 31 '23 at 15:33
  • @Caleth In the real program "vec" is the inputs. I made it local and added assign_vec(10'000'000,vec); call to make it stand alone. – qqqqq Aug 31 '23 at 15:40
  • @qqqqq It still does nothing. `i + (size_t)1 == j` is always true, so `k++` is always equal to `i` – Caleth Aug 31 '23 at 15:45
  • @Caleth the vec is input and output, passed by reference in the real code. – qqqqq Aug 31 '23 at 15:47
  • 1
    My best guess is you have something that fits [`std::unique`](https://en.cppreference.com/w/cpp/algorithm/unique), with a custom predicate – Caleth Aug 31 '23 at 15:53
  • @qqqqq You still haven't described the requirements on this algorithm. What is the algorithm supposed to do exactly? – Ted Lyngmo Aug 31 '23 at 15:59
  • Lambda captures FTW. That’s where you put the outer index. – Andrej Podzimek Aug 31 '23 at 16:01
  • @Caleth i + (size_t)1 == j may be untrue because j is incremented in the inner for loop. – qqqqq Aug 31 '23 at 16:17
  • This answer may help you: https://stackoverflow.com/questions/24881799/get-index-in-c11-foreach-loop – Jean-Baptiste Yunès Sep 01 '23 at 07:02
  • @qqqqq `assign_vec` was *really bad* for this example, because every element was unique – Caleth Sep 01 '23 at 08:17

2 Answers2

1

There isn't an algorithm in std that does exactly what is needed here, so it's maybe worth writing one in an iterator style. This will still use for, but we can extract the element testing.

template <typename ForwardIt, typename Equiv>
ForwardIt remove_duplicated(ForwardIt first, ForwardIt last, Equiv equiv) {
    ForwardIt result = first;
    for (; first != last;) {
        next = std::find_if_not(first, last, equiv);
        if (std::next(first) == next) {
            *result++ = std::move(*first);
        }
        first = next;
    }
    return result;
}

void no_for_loop(std::vector<std::array<int64_t, 3>> & vec) {
  auto end = remove_duplicated(vec.begin(), vec.end(), equiv);
  vec.erase(end, vec.end());
}

I originally thought the algorithm was removing duplicate entries, but it also removes the first of the duplicates. Below is my original answer, which points out an existing algorithm that does just that.

I'm going to assume that this is meant to be applied to an arbitrary std::vector<std::array<int64_t, 3>>, and not one filled by assign_vec.

std::for_each is the wrong algorithm. You are removing adjacent elements that are equivalent, that is std::unique.

bool equiv(std::array<int64_t, 3> & lhs, std::array<int64_t, 3> & rhs) {
    return (lhs[0] == rhs[0]) && (std::minmax(lhs[1], lhs[2]) == std::minmax(lhs[1], rhs[2])); 
}

void no_for_loop(std::vector<std::array<int64_t, 3>> & vec) {
  auto end = std::unique(vec.begin(), vec.end(), equiv);
  vec.erase(end, vec.end());
}
Caleth
  • 52,200
  • 2
  • 44
  • 75
  • Your assumption about arbitrary vector is correct. – qqqqq Aug 31 '23 at 16:14
  • Not using std::for_each was helpful. The goal is to remove the repeated element entirely. As in "ABBC" to "AC" and not "ABC". Is remove or remove_if needed? – qqqqq Aug 31 '23 at 16:42
  • @qqqqq you could do that, but I'd suggest you use a for loop with iterators – Caleth Sep 01 '23 at 08:07
0

You could make a poor man's enumeration helper like this for simplicity I only made this work for std::vector not all containers. :

#include <iostream>
#include <vector>
#include <string>

template<typename type_t>
struct enumerator_t
{
    enumerator_t(std::size_t i, type_t& v) :
        index{ i },
        value{ v }
    {
    }

    const std::size_t index{};
    type_t& value;
};

template<typename type_t>
struct const_enumerator_t
{
    const_enumerator_t(std::size_t i, const type_t& v) :
        index{ i },
        value{ v }
    {
    }

    const std::size_t index{};
    const type_t& value;
};

template<typename type_t>
auto enumerate(std::vector<type_t>& values)
{
    std::size_t index{ 0ul };
    std::vector<enumerator_t<type_t>> enumerator;
    enumerator.reserve(values.size());

    for (auto& value : values)
    {
        enumerator.emplace_back(index++, value);
    }

    return enumerator;
}

template<typename type_t>
auto const_enumerate(const std::vector<type_t>& values)
{
    std::size_t index{ 0ul };
    std::vector<const_enumerator_t<type_t>> enumerator;
    enumerator.reserve(values.size());

    for (auto& value : values)
    {
        enumerator.emplace_back(index++, value);
    }

    return enumerator;
}


int main()
{
    std::vector<std::string> values{ "one", "two", "three" };
    for (auto& [index, value] : enumerate(values))
    {
        std::cout << index << ": " << value << "\n";
    }

    for (const auto& [index, value] : const_enumerate(values))
    {
        std::cout << index << ": " << value << "\n";
    }

    return 0;
}
Pepijn Kramer
  • 9,356
  • 2
  • 8
  • 19