std :: harita ekleme veya std :: harita bulma?


93

Mevcut girişleri korumak istediğiniz bir harita varsayarsak. Zamanın% 20'si, eklediğiniz giriş yeni verilerdir. Std :: map :: find sonra std :: map :: insert yapmanın o dönen yineleyiciyi kullanarak bir avantajı var mı? Yoksa eklemeyi denemek ve ardından yineleyicinin kaydın eklenip eklenmediğini gösterip göstermediğine bağlı olarak hareket etmek daha mı hızlı?


4
Düzeltildim ve std :: map :: find yerine std :: map :: lower_bound kullanmayı amaçlamıştım.
Superpolock

Yanıtlar:


148

Cevap, siz de yapmıyorsunuz. Bunun yerine , Scott Meyers tarafından Etkili STL Madde 24'te önerilen bir şey yapmak istersiniz :

typedef map<int, int> MapType;    // Your map type may vary, just change the typedef

MapType mymap;
// Add elements to map here
int k = 4;   // assume we're searching for keys equal to 4
int v = 0;   // assume we want the value 0 associated with the key of 4

MapType::iterator lb = mymap.lower_bound(k);

if(lb != mymap.end() && !(mymap.key_comp()(k, lb->first)))
{
    // key already exists
    // update lb->second if you care to
}
else
{
    // the key does not exist in the map
    // add it to the map
    mymap.insert(lb, MapType::value_type(k, v));    // Use lb as a hint to insert,
                                                    // so it can avoid another lookup
}

2
Bu gerçekten bulmanın işleyiş şeklidir, işin püf noktası, bul ve yerleştir ile gereken aramayı birleştirmesidir. Tabii ki, sadece insert kullanarak ve sonra ikinci dönüş değerine bakarak yapar.
puetzk

1
İki soru: 1) Lower_bound kullanmak, harita için bul kullanmaktan nasıl farklıdır? 2) Bir 'harita' için, && 'nin sağ el operasyonu' lb! = Mymap.end () 'olduğunda her zaman doğru olmaz mı?
Richard Corden

12
@Richard: find () anahtar yoksa end () döndürür, lower_bound öğenin olması gereken konumu döndürür (bu da ekleme ipucu olarak kullanılabilir). @puetzek: "Sadece ekle" mevcut anahtarlar için referans değerin üzerine yazmaz mı? OP'nin bunu isteyip istemediğinden emin değil.
peterchen

2
unordered_map için benzer bir şey olup olmadığını bilen var mı?
Giovanni Funchal

3
@peterchen map :: insert, varsa mevcut değerin üzerine yazmaz , bakınız cplusplus.com/reference/map/map/insert .
Chris Drew

11

Bu sorunun cevabı, haritada sakladığınız değer türünü yaratmanın ne kadar pahalı olduğuna da bağlıdır:

typedef std::map <int, int> MapOfInts;
typedef std::pair <MapOfInts::iterator, bool> IResult;

void foo (MapOfInts & m, int k, int v) {
  IResult ir = m.insert (std::make_pair (k, v));
  if (ir.second) {
    // insertion took place (ie. new entry)
  }
  else if ( replaceEntry ( ir.first->first ) ) {
    ir.first->second = v;
  }
}

İnt gibi bir değer türü için, yukarıdakiler bir bul ve ardından gelen bir eklemeden daha verimli olacaktır (derleyici optimizasyonları olmadığında). Yukarıda belirtildiği gibi, bunun nedeni harita üzerinden aramanın yalnızca bir kez yapılmasıdır.

Ancak, eklenecek çağrı, yeni "değeri" oluşturmuş olmanızı gerektirir:

class LargeDataType { /* ... */ };
typedef std::map <int, LargeDataType> MapOfLargeDataType;
typedef std::pair <MapOfLargeDataType::iterator, bool> IResult;

void foo (MapOfLargeDataType & m, int k) {

  // This call is more expensive than a find through the map:
  LargeDataType const & v = VeryExpensiveCall ( /* ... */ );

  IResult ir = m.insert (std::make_pair (k, v));
  if (ir.second) {
    // insertion took place (ie. new entry)
  }
  else if ( replaceEntry ( ir.first->first ) ) {
    ir.first->second = v;
  }
}

'Ekleme' olarak adlandırmak için, değer türümüzü oluşturmaya yönelik pahalı çağrının bedelini ödüyoruz - ve soruda söylediklerinizden, bu yeni değeri% 20 oranında kullanmayacaksınız. Yukarıdaki durumda, harita değer türünü değiştirmek bir seçenek değilse, öğeyi inşa etmemiz gerekip gerekmediğini kontrol etmek için önce 'bul'u gerçekleştirmek daha etkilidir.

Alternatif olarak, haritanın değer türü, favori akıllı işaretçi türünüzü kullanarak tutamaçları verilere depolamak için değiştirilebilir. Ekleme çağrısı bir boş gösterici kullanır (inşa etmesi çok ucuzdur) ve yalnızca gerekirse yeni veri türü oluşturulur.


8

2 arasında hız açısından neredeyse hiç fark olmayacak, bul bir yineleyici döndürecektir, ekleme aynı şeyi yapacak ve girişin zaten var olup olmadığını belirlemek için yine de haritayı arayacaktır.

Yani .. kişisel tercihlere bağlı. Her zaman gerekirse eklemeyi ve sonra güncellemeyi denerim, ancak bazı insanlar döndürülen çifti işlemekten hoşlanmaz.


5

Bence bir bulup yerleştirirseniz, anahtarı bulamadığınızda ve eki daha sonra gerçekleştirdiğinizde ekstra maliyet olacaktır. Bu, kitaplara alfabetik sırayla bakıp kitabı bulamamak, sonra nereye yerleştireceğinizi görmek için kitaplara tekrar bakmak gibi bir şey. Anahtarları nasıl kullanacağınıza ve sürekli değişip değişmediklerine bağlıdır. Şimdi, onu bulamazsanız, günlük yapabilir, istisna yapabilir, istediğiniz her şeyi yapabilirsiniz.


1

Verimlilik konusunda endişeleriniz varsa, hash_map <> öğesini kontrol etmek isteyebilirsiniz .

Tipik olarak harita <> bir ikili ağaç olarak uygulanır. İhtiyaçlarınıza bağlı olarak bir hash_map daha verimli olabilir.


Çok isterdim. Ancak C ++ standart kitaplığında hash_map yoktur ve PHB'ler bunun dışında koda izin vermez.
Superpolock

1
std :: tr1 :: unordered_map , sonraki standarda eklenmesi önerilen ve STL'nin en güncel uygulamalarında mevcut olması gereken karma eşlemdir.
beldaz

1

Bir yorum bırakmak için yeterli puanım yok gibi görünüyor, ancak işaretli cevap bana uzun soluklu görünüyor - ekin yine de yineleyiciyi döndürdüğünü düşündüğünüzde, neden yalnızca geri dönen yineleyiciyi kullanabildiğiniz zaman alt sınırda arama yapalım. Garip.


1
Çünkü (kesinlikle C ++ 11 öncesi) insert kullanmak, hala bir std::map::value_typenesne oluşturmanız gerektiği anlamına gelir , kabul edilen cevap bundan bile kaçınır.
KillianDS

-1

Verimlilikle ilgili herhangi bir cevap, STL'nizin tam olarak uygulanmasına bağlı olacaktır. Kesin olarak bilmenin tek yolu, her iki şekilde de kıyaslamaktır. Farkın önemli olmayacağını tahmin ediyorum, bu yüzden tercih ettiğiniz stile göre karar verin.


1
Bu tam olarak doğru değil. STL, işlemlerinin çoğu için açık büyük O gereksinimleri sağlaması açısından diğer kitaplıkların çoğundan farklıdır. 2 * O (log n) ve 1 * O (log n) arasında, fonksiyonların bu O (log n) davranışını elde etmek için hangi uygulamayı kullandığına bakılmaksızın garantili bir fark vardır. Bu farkın platformunuzda önemli olup olmadığı farklı bir sorudur. Ama fark her zaman orada olacak.
srm

@srm büyük-O gereksinimlerini tanımlarken hala bir işlemin mutlak olarak ne kadar süreceğini söylemiyor. Bahsettiğiniz garantili fark mevcut değil.
Mark Ransom

-2

harita [anahtar] - stl sıralansın. Bu, niyetinizi en etkili şekilde iletmektir.

Evet, yeterince adil.

Bir bulup sonra 2 x O (log N) uyguladığınız bir ek yaparsanız, bir ıskayla karşılaşırsanız, bulma yalnızca ekin gitmesi gereken yere değil, eklemeniz gerekip gerekmediğini size bildirir (lower_bound size yardımcı olabilir) . Sadece düz bir ek ve ardından sonucu incelemek benim gideceğim yoldur.


Hayır, giriş varsa, mevcut girişe bir referans döndürür.
Kris Kumler

2
Bu cevap için -1. Kris K'nin dediği gibi, map [key] = value kullanmak mevcut girişin üzerine yazacak, onu soruda gerektiği gibi "korumayacak" değil. [Anahtar] eşlemini kullanarak varoluşu test edemezsiniz, çünkü anahtar yoksa varsayılan olarak oluşturulmuş bir nesne döndürecek ve bunu anahtar için giriş olarak oluşturacaktır
netjeff

Buradaki amaç, haritanın önceden doldurulmuş olup olmadığını test etmek ve sadece orada değilse ekleme / üzerine yazmaktır. [Anahtar] haritasını kullanmak, değerin zaten her zaman orada olduğunu varsayar.
srm
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.