użyj std :: fill, aby zapełnić wektor rosnącymi liczbami


85

Chciałbym wypełnić vector<int>użycie std::fill, ale zamiast jednej wartości wektor powinien zawierać liczby w kolejności rosnącej po.

Próbowałem to osiągnąć, iterując trzeci parametr funkcji o jeden, ale to dałoby mi tylko wektory wypełnione 1 lub 2 (w zależności od pozycji ++operatora).

Przykład:

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
Użyj std::iotazamiast std::fill(zakładając, że Twój kompilator jest wystarczająco nowy, aby go obsługiwać).
Jerry Coffin,

1
Niestety, wydaje się, że jest to część nowego standardu (którego nie mogę używać). Widziałem, że biblioteka BOOST ma taką funkcję, ale nie akceptuje wektorów ( boost.org/doc/libs/1_50_0/libs/range/doc/html/range/reference/… ), ale jakiś niestandardowy typ. Czy nie ma innej opcji?
BlackMamba,

2
user1612880, jeśli nie możesz użyć C ++ 11 / Boost, po prostu użyj kodu Lirana. Nie jest wymagane, aby każda operacja znajdowała się w jednej linii, ani nie ma światowego niedoboru znaków dostępnych w plikach kodu źródłowego C :-)
paxdiablo

Nie brakowałoby go, gdyby nie wydajność. Jeśli jednak nie da się tego zrobić bez okrutnych hacków, skorzystam z rozwiązania dostarczonego przez Lirana.
BlackMamba

@ user1612880 Czy próbowałeś tego z std::vector. Wersja przyspieszenia jest szablonem funkcji, a „nazwa typu” pierwszego argumentu określa koncepcję. Trudno powiedzieć, ponieważ mogę znaleźć tylko bardzo formalistyczną specyfikację, a nie prosty opis, ale myślę, że std::vectorjest to zgodne z koncepcją.
James Kanze,

Odpowiedzi:


128

Najlepiej używać w std::iotaten sposób:

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

To powiedziawszy, jeśli nie masz żadnego c++11wsparcia (nadal jest to prawdziwy problem w miejscu pracy), użyj w std::generateten sposób:

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
co to właściwie iotaoznacza? (Wygląda na to, że ktoś itoa
błędnie wpisał

9
To nic nie oznacza, jest greckim odpowiednikiem litery i. Jest to nazwa używana dla podobnej funkcji w APL i język tablicowy, który zapoczątkował wiele pomysłów w STL Stiepanowa.
BoBTFish

1
Ah-ha dzięki! OK, więc jest to raczej słowo niż inicjalizm; Mogę mieć więcej szczęścia, pamiętając o tym teraz. CPP Reference mówił o „Przyrostach wartości początkowej” (zwróć uwagę na niewielkie podobieństwo), więc mam inicjały w mojej głowie. (A greckie powiązanie nie jest od razu oczywiste w Google). Również dziękuję za odniesienie historyczne.
Luke Usherwood

47

Powinieneś użyć std::iotaalgorytmu (zdefiniowanego w <numeric>):

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

Ponieważ std::fillpo prostu przypisuje daną stałą wartość do elementów w podanym zakresie [n1,n2). I std::iotawypełnia podany zakres [n1, n2)kolejno rosnącymi wartościami, zaczynając od wartości początkowej, a następnie używając ++value.Możesz również użyć std::generatejako alternatywy.

Nie zapominaj, że std::iotajest to algorytm C ++ 11 STL. Ale obsługuje go wiele nowoczesnych kompilatorów, np. GCC, Clang i VS2012: http://msdn.microsoft.com/en-us/library/vstudio/jj651033.aspx

PS Ta funkcja została nazwana na cześć funkcji całkowitej z języka programowania APL i oznacza grecką literę iota. Przypuszczam, że pierwotnie w APL ta dziwna nazwa została wybrana, ponieważ przypomina “integer”(chociaż w matematyce jota jest powszechnie używana do oznaczania urojonej części liczby zespolonej).


1
Możesz dodać std :: iota pochodzi z C ++ 11
hetepeperfan

@AlexanderKaraberov Ale większość miejsc nie używa najnowszej wersji kompilatora i nie może używać C ++ 11.
James Kanze,

1
iotabył w STL ponad 15 lat temu, więc niektórzy kompilatorzy zawsze go wspierali, na długo przed C ++ 11
Jonathan Wakely

2
Ten wektor będzie miał rozmiar 0. Będziesz musiał dodać rozmiar do kontenera: std :: vector <int> ivec (100); std :: iota (ivec.begin (), ivec.end (), 0);
AKludges

14

Moim pierwszym wyborem (nawet w C ++ 11) byłoby boost::counting_iterator:

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

lub jeśli wektor został już skonstruowany:

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

Jeśli nie możesz użyć Boost: albo std::generate(jak sugerowano w innych odpowiedziach), albo wdrożyć counting_iteratorsamemu, jeśli potrzebujesz go w różnych miejscach. (Z Boost możesz użyć a transform_iteratorz a counting_iteratordo tworzenia różnego rodzaju interesujących sekwencji. Bez Boost możesz to zrobić ręcznie, albo w postaci typu obiektu generatora std::generate, albo jako coś, co możesz podłączyć do odręczny iterator liczący).


W pierwszym kawałek kodu, który konstruktora z std::vectorjest nazywany? Musi być konstruktorem zakresu , ale jest boost::counting_iteratorniejawnie konwertowany na InputIterator ?
CinCout

8

Widziałem odpowiedzi za pomocą std :: gene, ale można to również „poprawić”, używając zmiennych statycznych wewnątrz lambdy, zamiast deklarowania licznika poza funkcją lub tworzenia klasy generatora:

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

Uważam, że jest to trochę bardziej zwięzłe


5
staticma tutaj niewłaściwą semantykę, IMO. Użyłbym uogólnionego przechwytywania [i = 0]() mutable, aby było jasne, że zmienna jest ograniczona do konkretnego wystąpienia lambda, a nie do jej wygenerowanego typu klasy. Trudno jest wymyślić sytuację, w której byłaby różnica w praktyce, a to prawdopodobnie wskazywałoby na wątpliwy projekt, ale w każdym razie myślę, że semantyka jest lepsza przy użyciu zmiennej składowej. Poza tym tworzy bardziej zwięzły kod; teraz ciało lambdy może być pojedynczą instrukcją.
underscore_d

Tak, to wygląda lepiej; Nigdy wcześniej nie widziałem zmiennej zainicjalizowanej w przechwytywaniu lambdy :)
brainsandwich

6

Jeśli wolisz nie używać funkcji C ++ 11, możesz użyć 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;
    }
}

Ten program drukuje od 0 do 9.


3
@bitmask Na pewno, jeśli masz lambdy, które i std::iotatak masz , nie?
BoBTFish

1
@bitmask Ale to wymagałoby C ++ 11 :)
juanchopanza

2
To, co jest dość nieintuicyjne i nieopracowane, to fakt, że iti endsą zdefiniowane poza pętlą for. Jakiś powód?
Christian Rau,

1
@FrerichRaabe Drugi powód brzmi rozsądnie (o ile taki głupi błąd VS może być w ogóle rozsądny, ale można go zmienić w ustawieniach projektu), ale nie rozumiem pierwszego powodu, w czym jest nie tak std::vector<int>::const_iterator it = vec.begin(), end = ivec.end()(ani wielokrotne pisanie, ani powtarzające się dzwonienie)? A linia jest dłuższa, cóż, to C ++ i i tak nie da się obejść od czasu do czasu przerwania linii (a czasy dobrych starych 80-znakowych wyświetlaczy się skończyły). Ale myślę, że to kwestia gustu, a i tak dostałeś moją opinię dawno temu.
Christian Rau,

2
@ChristianRau: Szczerze mówiąc: w praktyce używam dowolnego stylu, którego używa kod w edytowanym pliku, tj. Bardziej cenię spójność niż zalety i wady, o których wspominaliśmy do tej pory.
Frerich Raabe,

6

Możemy użyć generatora funkcji która istnieje w pliku nagłówkowym algorytmu.

Fragment kodu:

#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;
}

To bardzo elegancka odpowiedź i prawdopodobnie najbardziej zwięzła w ogólnym przypadku.
Sowa

1
IMO Lepiej jest zrobić nelement lambda przez uogólnione przechwytywanie [n = 0]() mutable, więc nie zanieczyszcza otaczającego zakresu.
underscore_d

Używam tego. Może nie być przydatne w zaawansowanych sytuacjach, ale wystarczająco dobre dla początkujących C ++. dobre rozwiązanie, jeśli można użyć lambdy.
Abinash Dash

5

std :: iota jest ograniczone do sekwencji n, n + 1, n + 2, ...

Ale co, jeśli chcesz wypełnić tablicę generyczną sekwencją f (0), f (1), f (2) itd.? Często możemy uniknąć generatora śledzenia stanu. Na przykład,

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

stworzy sekwencję kwadratów

0 1 4 9 16 25 36

Jednak ta sztuczka nie zadziała z innymi kontenerami.

Jeśli utkniesz z C ++ 98, możesz robić okropne rzeczy, takie jak:

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

i wtedy

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

Ale nie polecałbym tego. :)


1
OP nie może używać języka C ++ 11 (bez lambda)
yizzlez

2

Mówiąc o wzmocnieniu:

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

2

to też działa

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

2
Oczywiście ręczne szperanie w iteratorach działa, ale gdyby OP chciał to zrobić, nie zapytałby o algorytm standardowej biblioteki, prawda?
underscore_d

2

Pod względem wydajności należy zainicjalizować wektor za pomocą reserve()kombinacji push_back()funkcji jak w poniższym przykładzie:

const int numberOfElements = 10;

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

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

Wszyscy std::fill, std::generateitp działają w zakresie istniejącej zawartości wektorowych, a zatem wektor musi być wypełniona niektóre dane wcześniej. Nawet wykonując następujące czynności: std::vector<int> data(10);tworzy wektor ze wszystkimi elementami ustawionymi na wartości domyślne (tj. 0 w przypadku int).

Powyższy kod pozwala uniknąć inicjalizacji zawartości wektorowej przed wypełnieniem jej danymi, które naprawdę chcesz. Wydajność tego rozwiązania jest dobrze widoczna na dużych zbiorach danych.


2

Jest jeszcze jedna opcja - bez użycia joty. Można użyć wyrażenia lambda 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;});

Dwie ważne rzeczy, dlaczego to działa:

  1. Lambda przechwytuje zakres zewnętrzny [&], co oznacza, że ​​będę dostępny w wyrażeniu
  2. element przekazany jako odniesienie, dlatego jest zmienny wewnątrz wektora

1

Jeśli naprawdę chcesz używać std::filli jesteś ograniczony do C ++ 98, możesz użyć czegoś podobnego do następującego:

#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

Wiem, że to stare pytanie, ale obecnie bawię się biblioteką, aby rozwiązać dokładnie ten problem. Wymaga 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
Yikes. Jest to bardzo trudny do odczytania kod i byłby również nieprawidłowy w zakresie globalnym, jak napisano, ponieważ identyfikatory zaczynające się od _są zastrzeżone dla implementacji tam.
underscore_d

0

Stworzyłem prostą funkcję szablonową Sequence()do generowania ciągów liczb. Funkcjonalność jest zgodna z seq()funkcją w R ( link ). Zaletą tej funkcji jest to, że działa ona w celu generowania różnych sekwencji liczb i typów.

#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;
}

Przykładowe użycie:

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

Jedynym zastrzeżeniem jest to, że wszystkie liczby powinny być tego samego wywnioskowanego typu. Innymi słowy, w przypadku liczb podwójnych lub zmiennoprzecinkowych uwzględnij ułamki dziesiętne dla wszystkich danych wejściowych, jak pokazano.

Zaktualizowano: 14 czerwca 2018 r


0

brainsandwich i underscore_d dały bardzo dobre pomysły. Ponieważ wypełnienie jest zmianą treści, for_each (), najprostszy z algorytmów STL, powinien również wypełnić rachunek:

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

Uogólnione przechwytywanie [i=o]przekazuje wyrażenie lambda niezmiennikiem i inicjuje je do znanego stanu (w tym przypadku 0). słowo kluczowe mutableumożliwia aktualizację tego stanu przy każdym wywołaniu lambda.

Wystarczy niewielka modyfikacja, aby uzyskać sekwencję kwadratów:

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

Generowanie sekwencji podobnej do R nie jest trudniejsze, ale zauważ, że w R tryb numeryczny jest w rzeczywistości podwójny, więc naprawdę nie ma potrzeby parametryzowania typu. Po prostu użyj podwójnego.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.