简体   繁体   中英

Fill a C++ array with numbers according to a pattern

I have a pattern in which I want to fill an array:

even indices value = -1

odd indices value = 1

I currently achieve it like this:

#include <array>
#include <algorithm>

using namespace std;
int generator(){
    static int i = 1;
    i *= -1;
    return i;
}

std::array<int ,64> arr;
std::generate(arr.begin(), arr.end(), generator);

Edit: Current implementation has a caveat - returned value of generator doesn't depend on iteration's object index.

Is there a way to pass current index to the generator function so it's output will depend on this index?

Your generator function has a static local variable, which preserves its state across every call. This means that if you use generator in a different call it remembers its old state, which might not be what you want.

You can fix this by writing a function that gives you a "generator" every time you call it. Note that the returned value (ie the generator itself) is just a mutable lambda, so that the value of i is preserved across multiple calls to that lambda.

auto gen() 
{
    return [i = -1]() mutable 
    { 
       i *= -1; 
       return i; 
    };
}

// usage ...

std::generate(arr.begin(), arr.end(), gen());

Here's a demo .

As @cigen has pointed out, your code works as written.

But to try to answer your question, "Is there a way to pass current index to the generator function?" - the answer is "no, not with std::generate ". Here's a sample implementation of that call (taken from CppReference :

template<class ForwardIt, class Generator>
void generate(ForwardIt first, ForwardIt last, Generator g)
{
    while (first != last) {
        *first++ = g();
    }
}

Points to note:

  • This implementation of generate does not use indexes, so it can't pass them to the generator.
  • The generator function is nullary (it takes no parameters), so generate can't pass the index, even if it had it.

All of the solutions assume your generator will be called sequentially, what about parallel and out-of-sequence execution?

std::array<int, 64> arr;
std::iota(arr.begin(), arr.end(), 0);
std::transform(arr.begin(), arr.end(), arr.begin(), [](auto const i){return i % 2: -1 : 1;});

You want to use the wrong function. If the element index is an argument to your generator, then std::transform() fits your requirements better.

EDIT: You need c++20 for an authoritative solution:

  std::array<int, 64> arr;
  auto const g(std::views::iota(decltype(arr)::size_type{}, arr.size()));
  std::transform(g.begin(), g.end(), arr.begin(), [](auto const i){return i % 2 ? -1 : 1;});

or:

  std::array<int, 64> arr;
  std::ranges::copy(std::views::iota(decltype(arr)::size_type{}, arr.size()) | std::views::transform([](auto const i){return i % 2 ? -1 : 1;}), arr.begin());

You can run this in parallel and/or out-of-sequence and there may be other solutions as well.

If you want to attach state to a function it is better to use a function object rather than function local static variables (for the very reason you discovered: reusability).

struct generator2 {
    bool even = true;
    int operator()(){
        even = !even;
        return even ? -1 : 1;
    }
};

int main() {
    std::array<int ,64> arr;
    std::generate(arr.begin(), arr.end(), generator2{});
    for (auto e : arr) std::cout << e;
}

What is passed to std::generate here is an instance of type generator2 whose operator() will be called.

Is there a way to pass current index to the generator function?

No. The generator for std::generate must be callable without parameter:

The signature of the function should be equivalent to the following:

 Ret fun();

The type Ret must be such that an object of type ForwardIt can be dereferenced and assigned a value of type Ret.

Just because it's hard to do with std::generate doesn't mean it can't be done. You can write your own version of generate :

namespace mystl {
  template<class ForwardIt, class Generator>
  void generate(ForwardIt first, ForwardIt last, Generator g)
  {
    int idx = 0;
    while (first != last) {
        *first++ = g(idx++);
    }
  }
}

Don't be limited by the standard algorithms; there's nothing magical about most of the ones in the standard library. Don't be afraid to write your own!

You cannot pass current index to the generator function with std::generate . But you can use a lambda to do that:

std::array<int ,64> arr;
auto generator = [i = 1, n = 0] () mutable {
    // n is the current index here
    i = -i;
    n++;
    return i;
};
std::generate(arr.begin(), arr.end(), generator);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM