використовуйте std :: fill для заповнення вектора зі збільшенням чисел


85

Я хотів би заповнити vector<int>використання std::fill, але замість одного значення вектор повинен містити цифри у зростаючому порядку після.

Я спробував досягти цього, повторивши третій параметр функції на одиницю, але це дало б мені лише вектори, заповнені 1 або 2 (залежно від положення ++оператора).

Приклад:

vector<int> ivec;
int i = 0;
std::fill(ivec.begin(), ivec.end(), i++); // elements are set to 1
std::fill(ivec.begin(), ivec.end(), ++i); // elements are set to 2

25
Використовуйте std::iotaзамість std::fill(якщо припустимо, ваш компілятор є достатньо новим для його підтримки).
Джеррі Коффін

1
На жаль, це, здається, є частиною нового стандарту (яким я не повинен користуватися). Я бачив, що бібліотека BOOST має таку функцію, але вона не приймає векторів ( boost.org/doc/libs/1_50_0/libs/range/doc/html/range/reference/… ), але певного користувацького типу. Чи немає іншого варіанту?
BlackMamba

2
user1612880, якщо ви не можете використовувати C ++ 11 / Boost, просто скористайтеся кодом Лірана. Це не є вимогою, щоб кожна операція мала бути в одному рядку, і не існує загальносвітової нестачі символів, доступних для файлів вихідного коду C :-)
paxdiablo

Це було б не через брак, а заради продуктивності. Однак, якщо немає можливості зробити це можливим без жорстоких хаків, я скористаюся рішенням, наданим Liran.
BlackMamba

@ user1612880 Чи пробували ви це разом std::vector. Версія boost - це шаблон функції, а "ім'я типу" першого аргументу визначає концепцію. Важко сказати, тому що я можу знайти лише дуже формалістичну специфікацію, а не простий опис, але я думаю, що це std::vectorвідповідає концепції.
James Kanze,

Відповіді:


128

Переважно використовувати std::iotaтак:

std::vector<int> v(100) ; // vector with 100 ints.
std::iota (std::begin(v), std::end(v), 0); // Fill with 0, 1, ..., 99.

Тим не менш, якщо у вас немає c++11підтримки (все ще є справжньою проблемою, коли я працюю), використовуйте std::generateнаступне:

struct IncGenerator {
    int current_;
    IncGenerator (int start) : current_(start) {}
    int operator() () { return current_++; }
};

// ...

std::vector<int> v(100) ; // vector with 100 ints.
IncGenerator g (0);
std::generate( v.begin(), v.end(), g); // Fill with the result of calling g() repeatedly.

6
що взагалі означає iota? (Схоже, хтось неправильно itoa
набрав

9
Це нічого не "стоїть", це грецький еквівалент літери i. Саме ця назва використовується для подібної функції в APL та мови масивів, що породило багато ідей у ​​STL Степанова.
BoBTFish

1
А-ха, дякую! Добре, то це слово, а не ініціалізм; Можливо, мені повезло б згадати це зараз. Довідник CPP говорив про "Збільшення стартового значення" (зверніть увагу на невелику схожість), тому в мене в голові були ініціали. (І грецький зв’язок не відразу очевидний для Googling.) Дякую також за історичну довідку.
Люк Ашервуд,

47

Ви повинні використовувати std::iotaалгоритм (визначений у <numeric>):

  std::vector<int> ivec(100);
  std::iota(ivec.begin(), ivec.end(), 0); // ivec will become: [0..99]

Тому що std::fillпросто присвоює задане фіксоване значення елементам у заданому діапазоні [n1,n2). І std::iotaзаповнює заданий діапазон [n1, n2)послідовно зростаючими значеннями, починаючи з початкового значення, а потім використовуючи. ++valueВи також можете використовувати std::generateяк альтернативу.

Не забувайте, що std::iotaце алгоритм C ++ 11 STL. Але багато сучасних компіляторів підтримують це, наприклад, GCC, Clang та VS2012: http://msdn.microsoft.com/en-us/library/vstudio/jj651033.aspx

PS Ця функція названа на честь цілочисельної функції з мови програмування APL і означає грецьку букву йота. Я припускаю, що спочатку в APL цю дивну назву було обрано, оскільки вона нагадує “integer”(хоча в математиці йота широко використовується для позначення уявної частини комплексного числа).


1
Можливо, ви захочете додати std :: iota від C ++ 11
hetepeperfan

@AlexanderKaraberov Але більшість місць не використовують останню версію компілятора і не можуть використовувати C ++ 11.
James Kanze,

1
iotaбув у STL більше 15 років тому, тому деякі компілятори завжди підтримували це, задовго до C ++ 11
Джонатан Уейклі

2
Цей вектор матиме розмір 0. Вам потрібно буде додати розмір до контейнера: std :: vector <int> ivec (100); std :: iota (ivec.begin (), ivec.end (), 0);
AKludges 03.03.20

14

Моїм першим вибором (навіть у C ++ 11) буде boost::counting_iterator:

std::vector<int> ivec( boost::counting_iterator<int>( 0 ),
                       boost::counting_iterator<int>( n ) );

або якщо вектор вже побудований:

std::copy( boost::counting_iterator<int>( 0 ),
           boost::counting_iterator<int>( ivec.size() ),
           ivec.begin() );

Якщо ви не можете використовувати Boost: або std::generate(як пропонується в інших відповідях), або реалізуйте counting_iteratorсамостійно, якщо вам це потрібно в різних місцях. (З Boost, ви можете використовувати transform_iteratorв counting_iteratorстворювати всі види цікавих послідовностей. Без Boost, ви можете зробити багато це вручну, або у вигляді об'єктного типу генератора для std::generate, або як то , що ви можете підключити до а рукописний ітератор підрахунку.)


У першій частині коду, який конструктор з std::vectorвикликається? Має бути конструктором діапазону , але boost::counting_iteratorнеявно конвертується в InputIterator ?
CinCout

8

Я бачив відповіді за допомогою std :: generate, але ви також можете "вдосконалити" це, використовуючи статичні змінні всередині лямбда-сигналу, замість того, щоб оголошувати лічильник поза функцією або створювати клас генератора:

std::vector<int> vec;
std::generate(vec.begin(), vec.end(), [] {
    static int i = 0;
    return i++;
});

Я вважаю це трохи більш лаконічним


5
staticмає неправильну семантику тут, IMO. Я б використав узагальнене захоплення, [i = 0]() mutableщоб було зрозуміло, що змінна масштабується до конкретного екземпляра лямбда, а не до її генерованого типу класу. Важко скласти ситуацію, коли на практиці могла б бути різниця, і це, мабуть, вказувало б на сумнівний дизайн, але в будь-якому випадку я думаю, що семантика є вищою за допомогою змінної-члена. Крім того, це робить більш лаконічним код; тепер тіло лямбди може бути єдиним твердженням.
underscore_d

Так, це виглядає краще; Я ніколи раніше не бачив змінної, ініціалізованої в лямбда-
захопінні

6

Якщо ви не хочете використовувати функції C ++ 11, ви можете використовувати std::generate:

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

struct Generator {
    Generator() : m_value( 0 ) { }
    int operator()() { return m_value++; }
    int m_value;
};

int main()
{
    std::vector<int> ivec( 10 );

    std::generate( ivec.begin(), ivec.end(), Generator() );

    std::vector<int>::const_iterator it, end = ivec.end();
    for ( it = ivec.begin(); it != end; ++it ) {
        std::cout << *it << std::endl;
    }
}

Ця програма друкує від 0 до 9.


3
@bitmask Звичайно, якщо у вас є лямбди, які у вас є std::iota, так?
BoBTFish

1
@bitmask Але для цього потрібен C ++ 11 :)
juanchopanza

2
Досить неінтуїтивним та непростим є той факт, що itі endвизначаються за межами циклу for. Будь-яка причина для цього?
Крістіан Рау,

1
@FrerichRaabe Друга причина звучить розумно (наскільки така дурна помилка VS може бути розумною взагалі, хоча це можна змінити в налаштуваннях проекту), але я не розумію першої причини, в чому проблема std::vector<int>::const_iterator it = vec.begin(), end = ivec.end()(ні повторне введення тексту, ні повторний дзвінок)? А рядок довший, ну, це C ++, і ви все одно не обминете якийсь випадковий розрив рядка (і часи старих добрих дисплеїв із 80 символів минули). Але це питання смаку, я думаю, і ти все одно давно отримав моє прихильність.
Крістіан Рау,

2
@ChristianRau: Чесно кажучи: на практиці я використовую будь-який стиль, який використовує код у файлі, який я редагую, тобто я ціную узгодженість вище, ніж переваги чи недоліки, про які ми згадали до цього часу.
Frerich Raabe

6

Ми можемо використовувати згенерувати функцію яка існує у файлі заголовка алгоритму.

Фрагмент коду:

#include<bits/stdc++.h>
using namespace std;


int main()
{
    ios::sync_with_stdio(false);

    vector<int>v(10);

    int n=0;

    generate(v.begin(), v.end(), [&n] { return n++;});

    for(auto item : v)
    {
      cout<<item<<" ";
    }
    cout<<endl;

    return 0;
}

Це дуже елегантна відповідь і, мабуть, найкоротша у загальному випадку.
Сова

1
ІМО має перевагу зробити nчленом лямбди загальним захопленням [n = 0]() mutable, щоб це не забруднювало навколишнє поле.
underscore_d

Я використовую цей. Може бути не корисним для досвідчених ситуацій, але досить хорошим для початківців на C ++. хороше рішення, якщо можна використовувати лямбду.
Abinash Dash

5

std :: iota обмежується послідовністю n, n + 1, n + 2, ...

Але що, якщо ви хочете заповнити масив загальною послідовністю f (0), f (1), f (2) тощо? Часто ми можемо уникнути генератора відстеження стану. Наприклад,

int a[7];
auto f = [](int x) { return x*x; };
transform(a, a+7, a, [a, f](int &x) {return f(&x - a);});

створить послідовність квадратів

0 1 4 9 16 25 36

Однак цей трюк не спрацює з іншими контейнерами.

Якщо ви застрягли в C ++ 98, ви можете робити такі жахливі речі, як:

int f(int &x) { int y = (int) (long) &x / sizeof(int); return y*y; }

і потім

int a[7];
transform((int *) 0, ((int *) 0) + 7, a, f);

Але я б не рекомендував. :)


1
OP не може використовувати C ++ 11 (без ягнят)
yizzlez

2

Якщо говорити про підсилення:

auto ivec = boost::copy_range<std::vector<int>>(boost::irange(5, 10));

2

це також працює

j=0;
for(std::vector<int>::iterator it = myvector.begin() ; it != myvector.end(); ++it){
    *it = j++;
}

2
Звичайно, ручне тикання з ітераторами працює, але якби ОР хотіло це зробити, вони б не запитали про алгоритм stdlib, чи не так?
underscore_d

2

Що стосується продуктивності, вам слід ініціалізувати вектор за допомогою reserve()комбінованих push_back()функцій, як у прикладі нижче:

const int numberOfElements = 10;

std::vector<int> data;
data.reserve(numberOfElements);

for(int i = 0; i < numberOfElements; i++)
    data.push_back(i);

Все std::fill, std::generateі т.д. працює на діапазоні існуючого векторних вмісту, і, отже, вектор повинен бути заповнений деякими даними раніше. Навіть роблячи наступне: std::vector<int> data(10);створює вектор із усіма елементами, встановленими за значенням за замовчуванням (тобто 0 у випадку int).

Наведений вище код дозволяє уникнути ініціалізації векторного вмісту, перш ніж заповнити його даними, які ви дійсно хочете. Ефективність цього рішення добре видно на великих наборах даних.


2

Є й інший варіант - без використання йоти. Можна використовувати вираз For_each + лямбда:

vector<int> ivec(10); // the vector of size 10
int i = 0;            // incrementor
for_each(ivec.begin(), ivec.end(), [&](int& item) { ++i; item += i;});

Дві важливі речі, чому це працює:

  1. Лямбда фіксує зовнішню область [&], що означає, що я буду доступний всередині виразу
  2. елемент, переданий як посилання, отже, він може змінюватися всередині вектора

1

Якщо ви дійсно хочете використовувати std::fillі обмежуєтесь C ++ 98, ви можете використовувати щось на зразок наступного,

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

struct increasing {
    increasing(int start) : x(start) {}
    operator int () const { return x++; }
    mutable int x;
};

int main(int argc, char* argv[])
{
    using namespace std;

    vector<int> v(10);
    fill(v.begin(), v.end(), increasing(0));
    copy(v.begin(), v.end(), ostream_iterator<int>(cout, " "));
    cout << endl;
    return 0;
}

1

Я знаю, що це старе запитання, але наразі я граюся з бібліотекою, щоб вирішити саме цю проблему. Для цього потрібен c ++ 14.

#include "htl.hpp"

htl::Token _;

std::vector<int> vec = _[0, _, 100];
// or
for (auto const e: _[0, _, 100]) { ... }

// supports also custom steps
// _[0, _%3, 100] == 0, 4, 7, 10, ...

1
Так Це дуже важкий для читання код, і він також був би недійсним у глобальному масштабі, як написано, оскільки ідентифікатори, що починаються з, _є резервними для реалізації там.
underscore_d

0

Я створив просту шаблонну функцію Sequence()для генерації послідовностей чисел. Функціонал відповідає seq()функції в R ( посилання ). Приємна річ цієї функції полягає в тому, що вона працює для генерації різноманітних числових послідовностей і типів.

#include <iostream>
#include <vector>

template <typename T>
std::vector<T> Sequence(T min, T max, T by) {
  size_t n_elements = ((max - min) / by) + 1;
  std::vector<T> vec(n_elements);
  min -= by;
  for (size_t i = 0; i < vec.size(); ++i) {
    min += by;
    vec[i] = min;
  }
  return vec;
}

Приклад використання:

int main()
{
    auto vec = Sequence(0., 10., 0.5);
    for(auto &v : vec) {
        std::cout << v << std::endl;
    }
}

Єдине застереження полягає в тому, що всі цифри повинні мати один і той же висновок. Іншими словами, для парних чи плаваючих знаків включайте десяткові цифри для всіх входів, як показано.

Оновлено: 14 червня 2018 р


0

brainsandwich та underscore_d дали дуже хороші ідеї. Оскільки заповнення - це зміна вмісту, for_each (), найпростіший серед алгоритмів STL, також повинен заповнити рахунок:

std::vector<int> v(10);
std::for_each(v.begin(), v.end(), [i=0] (int& x) mutable {x = i++;});    

Узагальнений захоплення [i=o] надає лямбда-виразу інваріант і ініціалізує його до відомого стану (в даному випадку 0). ключове словоmutable дозволяє оновити цей стан кожного разу, коли викликається лямбда.

Потрібна лише невелика модифікація, щоб отримати послідовність квадратів:

std::vector<int> v(10);
std::for_each(v.begin(), v.end(), [i=0] (int& x) mutable {x = i*i; i++;});

Згенерувати R-подібні послідовності не складніше, але зауважте, що в R числовий режим насправді подвійний, тому насправді немає потреби в параметризації типу. Просто використовуйте подвійний.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.