Come recuperare tutte le chiavi (o valori) da una std :: map e metterle in un vettore?


246

Questo è uno dei modi possibili in cui esco:

struct RetrieveKey
{
    template <typename T>
    typename T::first_type operator()(T keyValuePair) const
    {
        return keyValuePair.first;
    }
};

map<int, int> m;
vector<int> keys;

// Retrieve all keys
transform(m.begin(), m.end(), back_inserter(keys), RetrieveKey());

// Dump all keys
copy(keys.begin(), keys.end(), ostream_iterator<int>(cout, "\n"));

Ovviamente, possiamo anche recuperare tutti i valori dalla mappa definendo un altro funzione RetrieveValues .

C'è un altro modo per farlo facilmente? (Mi chiedo sempre perché std :: map non include una funzione membro per consentirci di farlo.)


10
la tua soluzione è la migliore ...
linello

4
L'unica cosa che vorrei aggiungere è questo keys.reserve(m.size());.
Galik,

Risposte:


176

Mentre la tua soluzione dovrebbe funzionare, può essere difficile da leggere a seconda del livello di abilità dei tuoi colleghi programmatori. Inoltre, sposta la funzionalità lontano dal sito di chiamata. Il che può rendere la manutenzione un po 'più difficile.

Non sono sicuro che il tuo obiettivo sia quello di mettere le chiavi in ​​un vettore o stamparle per tagliarle, quindi sto facendo entrambe le cose. Puoi provare qualcosa del genere:

map<int, int> m;
vector<int> v;
for(map<int,int>::iterator it = m.begin(); it != m.end(); ++it) {
  v.push_back(it->first);
  cout << it->first << "\n";
}

O ancora più semplice, se stai usando Boost:

map<int,int> m;
pair<int,int> me; // what a map<int, int> is made of
vector<int> v;
BOOST_FOREACH(me, m) {
  v.push_back(me.first);
  cout << me.first << "\n";
}

Personalmente, mi piace la versione di BOOST_FOREACH perché c'è meno digitazione ed è molto esplicito su ciò che sta facendo.


1
Vai a capire che finirei qui dopo la mia ricerca su Google. La tua è la risposta che preferisco :)
mpen

4
@Jere - Hai davvero lavorato con BOOST_FOREACH? Il codice che proponi qui è totalmente sbagliato
Manuel

2
@Jamie - questo è un altro modo, ma i documenti boost mostrano che specifica la variabile e il suo tipo prima di BOOST_FOREACH se il tipo contiene una virgola. Mostrano anche che la sta digitando. Quindi, sono confuso, cosa c'è di sbagliato nel mio codice?
Jere.Jones,

17
Curioso, non avrebbe senso preselezionare il vettore per impedire l'allocazione del ridimensionamento?
Alan,

2
Non dimenticare di fare v.reserve(m.size())per evitare il ridimensionamento del vettore durante il trasferimento.
Brian White,

157
//c++0x too
std::map<int,int> mapints;
std::vector<int> vints;
vints.reserve(mapints.size());
for(auto const& imap: mapints)
    vints.push_back(imap.first);

4
Bello. Dimentica it = ...begin(); it != ...end. La cosa migliore sarebbe ovviamente std :: map con un metodo keys () che restituisce quel vettore ...
masterxilo,

2
@BenHymers: mi sembra che questa risposta sia stata data a answered Mar 13 '12 at 22:33, che è diversi mesi dopo che C ++ 11 è diventato C ++.
Sebastian Mach,

37
@BenHymers, ma è utile a chiunque legga la domanda ora, che è di cosa tratta SO - non solo aiutando il richiedente, ma tutti gli altri.
Luchian Grigore,

9
per (auto & imap) è più preciso perché nessuna operazione di copia.
HelloWorld,

2
@StudentT, meglio ancora for(auto const & imap : mapints).
cp.engr,

61

C'è un adattatore per la gamma boost a questo scopo:

vector<int> keys;
// Retrieve all keys
boost::copy(m | boost::adaptors::map_keys, std::back_inserter(keys));

Esiste un adattatore di intervallo map_values ​​simile per l'estrazione dei valori.


1
Sfortunatamente, sembra che boost::adaptorsnon sia disponibile fino a Boost 1.43. L'attuale versione stabile di Debian (Squeeze) offre solo Boost 1.42
Mickaël Le Baillif,

2
È un peccato. Boost 1.42 è stato rilasciato nel febbraio 2010, oltre 2,5 anni prima di Squeeze.
Alastair,

A questo punto, Squeeze Updates o il repository backport non dovrebbero offrire Boost 1.44?
Luis Machuca,

in quale intestazione boost è definita?
James Wierzba,

1
Vedi il documento collegato, è definito inboost/range/adaptor/map.hpp
Alastair,

47

C ++ 0x ci ha fornito un'ulteriore, eccellente soluzione:

std::vector<int> keys;

std::transform(
    m_Inputs.begin(),
    m_Inputs.end(),
    std::back_inserter(keys),
    [](const std::map<int,int>::value_type &pair){return pair.first;});

22
Dal mio punto di vista non c'è niente di eccellente. std :: vector <int> chiavi; keys.reserve (m_Inputs.size ()); for (auto keyValue: m_Inputs) {keys.push_back (keyValue.first); } È molto meglio della trasformazione criptica. Anche in termini di prestazioni. Questo è migliore.
Jagannath,

5
Puoi riservare la dimensione dei tasti anche qui se desideri prestazioni comparabili. usa la trasformazione se vuoi evitare un ciclo for.
Dan Dan,

4
voglio solo aggiungere - può usare [] (const auto & pair)
ivan.ukr il

@ ivan.ukr che compilatore stai usando? Questa sintassi non è consentita qui: 'const auto &': un parametro non può avere un tipo che contiene 'auto'
Gobe

4
@ Il parametro automatico ivan.ukr in lambda è c ++ 14
roalz,

16

@La risposta di DanDan, usando C ++ 11 è:

using namespace std;
vector<int> keys;

transform(begin(map_in), end(map_in), back_inserter(keys), 
            [](decltype(map_in)::value_type const& pair) {
    return pair.first;
}); 

e con C ++ 14 (come notato da @ ivan.ukr) possiamo sostituire decltype(map_in)::value_typecon auto.


5
Potresti aggiungere keys.reserve(map_in.size());per efficienza.
Galik,

Trovo che il metodo di trasformazione in realtà richieda più codice di for-loop.
user1633272

const può essere messo dietro il tipo! L'ho quasi dimenticato.
Zhang,


10

La tua soluzione va bene ma puoi usare un iteratore per farlo:

std::map<int, int> m;
m.insert(std::pair<int, int>(3, 4));
m.insert(std::pair<int, int>(5, 6));
for(std::map<int, int>::const_iterator it = m.begin(); it != m.end(); it++)
{
    int key = it->first;
    int value = it->second;
    //Do something
}

10

Basato sulla soluzione @ rusty-parks, ma in c ++ 17:

std :: map <int, int> articoli;
std :: vector <int> itemKeys;

per (const auto & [chiave, ignorato]: elementi)
{
    itemKeys.push_back (chiave);
}

Non credo che possa std::ignoreessere usato in questo modo in associazioni strutturate. Ricevo un errore di compilazione. Dovrebbe essere sufficiente usare solo una variabile regolare, ad es. ignoredChe semplicemente non viene utilizzata.
jb

1
@jb Grazie. In effetti, std::ignoreè inteso per l'uso con std::tiema non con attacchi strutturali. Ho aggiornato il mio codice.
Madiyar,

9

Penso che il BOOST_FOREACH presentato sopra sia bello e pulito, tuttavia c'è un'altra opzione che utilizza anche BOOST.

#include <boost/lambda/lambda.hpp>
#include <boost/lambda/bind.hpp>

std::map<int, int> m;
std::vector<int> keys;

using namespace boost::lambda;

transform(      m.begin(), 
                m.end(), 
                back_inserter(keys), 
                bind( &std::map<int,int>::value_type::first, _1 ) 
          );

copy( keys.begin(), keys.end(), std::ostream_iterator<int>(std::cout, "\n") );

Personalmente, non penso che questo approccio sia pulito come l'approccio BOOST_FOREACH in questo caso, ma boost :: lambda può essere davvero pulito in altri casi.


7

Inoltre, se si dispone di Boost, utilizzare transform_iterator per evitare di creare una copia temporanea delle chiavi.


7

Bit di un c ++ 11:

std::map<uint32_t, uint32_t> items;
std::vector<uint32_t> itemKeys;
for (auto & kvp : items)
{
    itemKeys.emplace_back(kvp.first);
    std::cout << kvp.first << std::endl;
}


5

Ecco un bel modello di funzione che usa C ++ 11 magic, che funziona sia con std :: map, std :: unordered_map:

template<template <typename...> class MAP, class KEY, class VALUE>
std::vector<KEY>
keys(const MAP<KEY, VALUE>& map)
{
    std::vector<KEY> result;
    result.reserve(map.size());
    for(const auto& it : map){
        result.emplace_back(it.first);
    }
    return result;
}

Dai un'occhiata qui: http://ideone.com/lYBzpL


4

La migliore soluzione STL non-sgi, non-boost è quella di estendere map :: iterator in questo modo:

template<class map_type>
class key_iterator : public map_type::iterator
{
public:
    typedef typename map_type::iterator map_iterator;
    typedef typename map_iterator::value_type::first_type key_type;

    key_iterator(const map_iterator& other) : map_type::iterator(other) {} ;

    key_type& operator *()
    {
        return map_type::iterator::operator*().first;
    }
};

// helpers to create iterators easier:
template<class map_type>
key_iterator<map_type> key_begin(map_type& m)
{
    return key_iterator<map_type>(m.begin());
}
template<class map_type>
key_iterator<map_type> key_end(map_type& m)
{
    return key_iterator<map_type>(m.end());
}

e poi usali così:

        map<string,int> test;
        test["one"] = 1;
        test["two"] = 2;

        vector<string> keys;

//      // method one
//      key_iterator<map<string,int> > kb(test.begin());
//      key_iterator<map<string,int> > ke(test.end());
//      keys.insert(keys.begin(), kb, ke);

//      // method two
//      keys.insert(keys.begin(),
//           key_iterator<map<string,int> >(test.begin()),
//           key_iterator<map<string,int> >(test.end()));

        // method three (with helpers)
        keys.insert(keys.begin(), key_begin(test), key_end(test));

        string one = keys[0];

1
Lascio al lettore la possibilità di creare anche const_iterator e invertire gli iteratori se / quando necessario.
Marius

-1

Con esempio di mappa atomica

#include <iostream>
#include <map>
#include <vector> 
#include <atomic>

using namespace std;

typedef std::atomic<std::uint32_t> atomic_uint32_t;
typedef std::map<int, atomic_uint32_t> atomic_map_t;

int main()
{
    atomic_map_t m;

    m[4] = 456;
    m[2] = 45678;

    vector<int> v;
    for(map<int,atomic_uint32_t>::iterator it = m.begin(); it != m.end(); ++it) {
      v.push_back(it->second);
      cout << it->first << " "<<it->second<<"\n";
    }

    return 0;
}

-2

Leggermente simile a uno degli esempi qui, semplificato dal std::mappunto di vista dell'uso.

template<class KEY, class VALUE>
std::vector<KEY> getKeys(const std::map<KEY, VALUE>& map)
{
    std::vector<KEY> keys(map.size());
    for (const auto& it : map)
        keys.push_back(it.first);
    return keys;
}

Usa così:

auto keys = getKeys(yourMap);

2
Ehi, so che questa risposta è vecchia ma è anche sbagliata. Inizializzare con dimensione map.size()significa raddoppiare il ritorno della dimensione del vettore. Correggi per salvare qualcun altro il mal di testa :(
thc

-3

(Mi chiedo sempre perché std :: map non include una funzione membro per consentirci di farlo.)

Perché non può farlo meglio di quanto tu possa fare. Se l'implementazione di un metodo non sarà superiore all'implementazione di una funzione libera, in generale non dovresti scrivere un metodo; dovresti scrivere una funzione gratuita.

Inoltre, non è immediatamente chiaro perché sia ​​utile comunque.


8
Esistono ragioni diverse dall'efficienza per una libreria per fornire un metodo, come la funzionalità "batterie incluse" e un'API coerente e incapsulata. Anche se certamente nessuno di questi termini descrive l'STL particolarmente bene :) Ri. non è chiaro perché sia ​​utile - davvero? Penso che sia abbastanza ovvio il motivo per cui elencare le chiavi disponibili è una cosa utile da fare con una mappa / dict: dipende da cosa la stai usando.
Andybuckley,

4
Con questo ragionamento, non dovremmo avere empty()perché può essere implementato come size() == 0.
gd1

1
Cosa ha detto @ gd1. Sebbene non ci dovrebbe essere molta ridondanza funzionale in una classe, insistere su assolutamente zero non è una buona idea IMO - almeno fino a quando C ++ ci permetterà di "benedire" le funzioni libere nei metodi.
einpoklum,

1
Nelle versioni precedenti di C ++ c'erano contenitori per i quali empty () e size () potevano ragionevolmente avere diverse garanzie di prestazione, e penso che le specifiche fossero sufficientemente larghe da permetterlo (in particolare, elenchi collegati che offrivano splice a tempo costante) . Come tale, il loro disaccoppiamento aveva un senso. Tuttavia, non credo che questa discrepanza sia più consentita.
DrPizza,

Sono d'accordo. Il C ++ considera std::map<T,U>un contenitore di coppie. In Python, dictagisce come le sue chiavi quando viene ripetuto, ma consente di dire d.items()di ottenere il comportamento C ++. Python fornisce anche d.values(). std::map<T,U>certamente potrebbe fornire un metodo keys()e values()che restituisce un oggetto che ha begin()e end()che fornisce iteratori su chiavi e valori.
Ben
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.