Znaleźć pozycję elementu w zakresie C ++ 11 na podstawie pętli for?


Załóżmy, że mam następujący kod:
vector<int> list;
for(auto& elem:list) {
int i = elem;
}

Czy mogę znaleźć pozycję
elem
w wektorze bez utrzymywania oddzielnego iteratora?
Zaproszony:
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Tak, wystarczy mały masaż;)
Sztuczka polega na użyciu kompozycji: zamiast iterować bezpośrednio po kontenerze, „spakuj” go z indeksem wzdłuż ścieżki.
Specjalistyczny kod błyskawicy:
template <typename T>
struct iterator_extractor { typedef typename T::iterator type; };template <typename T>
struct iterator_extractor<T const> { typedef typename T::const_iterator type; };
template <typename T>
class Indexer {
public:
class iterator {
typedef typename iterator_extractor<T>::type inner_iterator; typedef typename std::iterator_traits<inner_iterator>::reference inner_reference;
public:
typedef std::pair<size_t, inner_reference> reference; iterator(inner_iterator it): _pos(0), _it(it) {} reference operator*() const { return reference(_pos, *_it); } iterator& operator++() { ++_pos; ++_it; return *this; }
iterator operator++(int) { iterator tmp(*this); ++*this; return tmp; } bool operator==(iterator const& it) const { return _it == it._it; }
bool operator!=(iterator const& it) const { return !(*this == it); } private:
size_t _pos;
inner_iterator _it;
}; Indexer(T& t): _container(t) {} iterator begin() const { return iterator(_container.begin()); }
iterator end() const { return iterator(_container.end()); }private:
T& _container;
};// class Indexertemplate <typename T>
Indexer<T> index(T& t) { return Indexer<T>(t); }

I użyj go:
#include <iostream>
#include <iterator>
#include <limits>
#include <vector>// Zipper code hereint main() {
std::vector<int> v{1, 2, 3, 4, 5, 6, 7, 8, 9}; for (auto p: index(v)) {
std::cout << p.first << ": " << p.second << "\n";
}
}

Możesz to zobaczyć w

ideone
http://ideone.com/rAcaZ
chociaż nie obsługuje pętli for-range, więc jest mniej ładny.

EDIT

:
Właśnie sobie przypomniałem, że powinienem częściej sprawdzać Boost.Range. Niestety nie ma zakresu
zip
, ale znalazłem perl:
boost::adaptors::indexed
http://www.boost.org/doc/libs/ ... ml... Jednak pobranie indeksu wymaga dostępu do iteratora. Wstyd: x
W przeciwnym razie z
counting_range
http://www.boost.org/doc/libs/ ... .html
i ogólnie
zip
Jestem pewien, że można by zrobić coś interesującego ...
W idealnym świecie wyobrażam sobie:
int main() {
std::vector<int> v{1, 2, 3, 4, 5, 6, 7, 8, 9}; for (auto tuple: zip(iota(0), v)) {
std::cout << tuple.at<0>() << ": " << tuple.at<1>() << "\n";
}
}

Z
zip
automatycznym tworzeniem widoku jako zakresu krotek linków i
iota (0)
tworzącym po prostu „fałszywy” zakres zaczynający się od
0
i po prostu odlicza do nieskończoności (lub cóż, maksimum tego typu ...).
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

jrok ma rację: zakresy oparte na pętlach nie są przeznaczone do tego celu.
Jednak w twoim przypadku można to obliczyć za pomocą arytmetyki wskaźników, ponieważ
wektor
przechowuje swoje elementy sekwencyjnie (*)
vector<int> list;
for(auto& elem:list) {
int i = elem;
int pos = &elem-&list[0];// pos contains the position in the vector// also a &-operator overload proof alternative (thanks to ildjarn) :
// int pos = addressof(elem)-addressof(list[0]); }

Jest to jednak zdecydowanie zła praktyka, ponieważ zaciemnia & amp; kod, czyni go bardziej delikatnym (łatwo się psuje, jeśli ktoś zmieni typ kontenera, przeładuje operator
& amp;
lub zastępuje 'auto & amp ; 'z' auto '.)
UWAGA: Przyleganie jest gwarantowane dla wektora w standardzie C ++ 03 oraz dla tablicy i ciągu znaków w standardzie C ++ 11.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Nie, nie możesz (przynajmniej nie bez wysiłku). Jeśli chcesz określić położenie elementu, nie powinieneś używać funkcji dla. Pamiętaj, że to tylko przydatne narzędzie w najczęstszym przypadku: uruchom kod dla każdego elementu. W rzadszych przypadkach, w których potrzebujesz pozycji elementu, powinieneś użyć mniej wygodnej zwykłej pętli
for
.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Na podstawie odpowiedzi z @Matthieu istnieje bardzo eleganckie rozwiązanie wykorzystujące wspomniane

boost::adaptors::indexed
https://www.boost.org/doc/libs ... .html
:
std::vector<std::string> strings{10, "Hello"};
int main(){
strings[5] = "World";
for(auto const& el: strings| boost::adaptors::indexed(0))
std::cout << el.index() << ": " << el.value() << std::endl;
}

Możesz tego spróbować
https://ideone.com/JDjsTB
Działa w podobny sposób, jak wspomniane „rozwiązanie idealnego świata”, ma ładną składnię i jest zwięzłe. Zauważ, że typ
el
w tym przypadku jest czymś w rodzaju
boost :: foobar & < const std :: string & amp;, int & >
, więc obsługuje linkowanie tam i kopiowanie nie zostało wykonane. Jest nawet niesamowicie skuteczny:

https://godbolt.org/g/e4LMnJ
https://godbolt.org/g/e4LMnJ
(kod jest równoważny przechowywaniu własnej zmiennej licznika, która jest tak dobra, jak to tylko możliwe)
Aby uzyskać kompletność, rozważ alternatywy:
size_t i = 0;
for(auto const& el: strings) {
std::cout << i << ": " << el << std::endl;
++i;
}

Lub używając ciągłej właściwości wektorowej:
for(auto const& el: strings) {
size_t i = &el - &strings.front();
std::cout << i << ": " << el << std::endl;
}

Pierwsza generuje ten sam kod co wersja adaptera boost (optymalna), a druga jest o 1 instrukcję dłuższa:

https://godbolt.org/g/nEG8f9
https://godbolt.org/g/nEG8f9
Uwaga: jeśli chcesz tylko wiedzieć, czy masz ostatni element, którego możesz użyć:
for(auto const& el: strings) {
bool isLast = &el == &strings.back();
std::cout << isLast << ": " << el << std::endl;
}

Działa to dla każdego standardowego kontenera, ale należy użyć
auto & amp;
/
auto const & amp;
(tak samo jak powyżej), ale nadal jest zalecane. W zależności od danych wejściowych może to być również dość szybkie (zwłaszcza gdy kompilator zna rozmiar twojego wektora)
Zamień
& amp; foo
na
std :: addressof (foo)
, aby był bezpieczny dla kodu ogólnego.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Jeśli masz kompilator z obsługą C ++ 14, możesz to zrobić w stylu funkcjonalnym:
#include <iostream>
#include <string>
#include <vector>
#include <functional>template<typename T>
void for_enum(T& container, std::function<void(int, typename T::value_type&)> op)
{
int idx = 0;
for(auto& value : container)
op(idx++, value);
}int main()
{
std::vector<std::string> sv {"hi", "there"};
for_enum(sv, [](auto i, auto v) {
std::cout << i << " " << v << std::endl;
});
}

Działa z clang 3.4 i gcc 4.9 (nie 4.8); w obu przypadkach musisz ustawić
-std = c ++ 1y
. Powodem, dla którego potrzebujesz C ++ 14, są parametry
auto
w funkcji lambda.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Jeśli nalegasz na użycie zakresu opartego na indeksie i znajomości indeksu, utrzymanie indeksu, jak pokazano poniżej, jest dość trywialne.
Nie sądzę, że istnieje czystsze/prostsze rozwiązanie dla zakresu opartego na pętli. Ale tak naprawdę, dlaczego nie użyć standardu dla (;;)? To prawdopodobnie uczyniłoby twoje intencje i kod jaśniejszym.
vector<int> list;
int idx = 0;
for(auto& elem:list) {
int i = elem;
//TODO whatever made you want the idx
++idx;
}
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Jest na to zaskakująco prosty sposób.
vector<int> list;
for(auto& elem:list) {
int i = (&elem-&*(list.begin()));
}

gdzie
i
będzie wymaganym indeksem.
Wykorzystuje to fakt, że

Wektory C ++ są zawsze sąsiadujące
https://coderoad.ru/7609169/
.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Czytałem z twoich komentarzy, że jednym z powodów, dla których chcesz znać indeks, jest wiedzieć, czy element jest pierwszy/ostatni w sekwencji. Jeśli tak, możesz to zrobić
for(auto& elem:list) {
// loop code ...
if(&elem == &*std::begin(list)){ ... special code for first element ... }
if(&elem == &*std::prev(std::end(list))){ ... special code for last element ... }
// if(&elem == &*std::rbegin(list)){... (C++14 only) special code for last element ...}
// loop code ...
}


EDIT:

na przykład, to drukuje kontener, w którym brakuje separatora w ostatnim elemencie. Działa dla większości kontenerów, jakie mogę sobie wyobrazić (w tym tablic), (demo online

http://coliru.stacked-crooked. ... 87f91
http://coliru.stacked-crooked. ... 87f91
):
#include <iostream>
#include <vector>
#include <list>
#include <set>
using namespace std;template<class Container>
void print(Container const& c){
for(auto& x:c){
std::cout << x;
if(&x != &*std::prev(std::end(c))) std::cout << ", ";// special code for last element
}
std::cout << std::endl;
}int main() {
std::vector<double> v{1.,2.,3.};
print(v);// prints 1,2,3
std::list<double> l{1.,2.,3.};
print(l);// prints 1,2,3
std::initializer_list<double> i{1.,2.,3.};
print(i);// prints 1,2,3
std::set<double> s{1.,2.,3.};
print(s);// print 1,2,3
double a[3] = {1.,2.,3.};// works for C-arrays as well
print(a);// print 1,2,3
}
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Tobias Widlund napisał ładną, licencjonowaną przez MIT, nagłówek w stylu wyliczenia tylko w języku Python (chociaż C ++ 17):
GitHub
https://github.com/therocode/e ... e.hpp
Post na blogu
https://blog.therocode.net/201 ... index
Bardzo przyjemny w użyciu:
std::vector<int> my_vector {1,3,3,7};for(auto [i, my_element] : en::enumerate(my_vector))
{
// do stuff
}
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Oto rozwiązanie oparte na makrach, które prawdopodobnie przewyższa większość innych pod względem prostoty, czasu kompilacji i jakości generowania kodu:
#include <iostream>#define fori(i, ...) if(size_t i = -1) for(__VA_ARGS__) if(i++, true)int main() {
fori(i, auto const & x : {"hello", "world", "!"}) {
std::cout << i << " " << x << std::endl;
}
}

Wynik:
$ g++ -o enumerate enumerate.cpp -std=c++11 && ./enumerate 
0 hello
1 world
2 !
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Jeśli chcesz uniknąć konieczności pisania funkcji pomocniczej, mając
zmienna indeksu lokalna dla pętli, możesz użyć lambdy ze zmienną mutowalną.:
<pre class="lang-cpp prettyprint-override">
int main() {
std::vector<char> values = {'a', 'b', 'c'};
std::for_each(begin(values), end(values), [i = size_t{}] (auto x) mutable {
std::cout << i << ' ' << x << '\n';
++i;
});
}

Aby odpowiedzieć na pytania, Zaloguj się lub Zarejestruj się