Menzile dayalı 'for' döngüsü birçok basit algoritmayı kullanımdan kaldırıyor mu?


81

Algoritma çözümü:

std::generate(numbers.begin(), numbers.end(), rand);

Menzile dayalı for-loop çözümü:

for (int& x : numbers) x = rand();

Neden std::generateC ++ 11'de aralık tabanlı for-döngüler yerine daha ayrıntılı kullanmak isteyeyim ?


14
Düzenlenebilirlik? Unutmayın, yinelemeli algoritmalar zaten oluşturulamaz ... :(
R. Martinho Fernandes

2
... hangi değildir begin()ve end()?
the_mandrill

6
@jrok Şimdiye kadar birçok kişinin rangearaç kutusunda bir işlevi olmasını bekliyorum . (yani for(auto& x : range(first, last)))
R. Martinho Fernandes

14
boost::generate(numbers, rand); // ♪
Xeo

5
@JamesBrock Bunu sık sık C ++ sohbet odasında tartıştık (transkriptlerde bir yerde olmalı: P). Temel sorun, algoritmaların genellikle bir yineleyici döndürmesi ve iki yineleyici almasıdır.
R. Martinho Fernandes

Yanıtlar:


79

İlk versiyon

std::generate(numbers.begin(), numbers.end(), rand);

bize bir dizi değer oluşturmak istediğinizi söyler.

İkinci versiyonda okuyucunun bunu kendi başına çözmesi gerekecektir.

Yazarken tasarruf etmek genellikle yetersizdir, çünkü çoğunlukla okuma süresinde kaybolur. Çoğu kod yazıldığından çok daha fazla okunur.


13
Yazarken mi tasarruf ediyorsunuz? Oh, anladım. Neden ah, neden "derleme zamanı mantıklı kontroller" ve "klavyedeki tuşlara basmak" için aynı terimlere sahibiz? :)
fredoverflow

25
" Yazarken tasarruf etmek genellikle yetersizdir " Saçma; her şey hangi kitaplığı kullandığınızla ilgili. std :: generate uzundur çünkü numberssebepsiz yere iki kez belirtmeniz gerekir . Dolayısıyla: boost::range::generate(numbers, rand);. İyi oluşturulmuş bir kitaplıkta hem daha kısa hem de daha okunaklı koda sahip olmamanız için hiçbir neden yok.
Nicol Bolas

9
Hepsi okuyucunun gözünde. Döngü sürümü için çoğu programlama arka planı anlaşılabilir: koleksiyonun her bir öğesine rand değeri koyun. Std :: generate en son C ++ 'ı bilmeyi veya üretmenin gerçekte "üretilen değerleri döndür" değil "öğeleri değiştir" anlamına geldiğini tahmin etmeyi gerektirir.
hyde

2
Bir kabın sadece bir kısmını değiştirmek istiyorsanız, o zaman yapabilirsiniz std::generate(number.begin(), numbers.begin()+3, rand), değil mi? Bu yüzden numberbazen iki kez belirtmek faydalı olabilir sanırım .
Marson Mao

7
@MarsonMao: Eğer sadece iki argümanınız std::generate()varsa, bunun yerine , aralığın bir kısmının esnek spesifikasyonuna izin verirken tekrarını kaldıran std::generate(slice(number.begin(), 3), rand)varsayımsal bir aralık dilimleme sözdizimi ile yapabilir veya hatta daha iyi yapabilirsiniz . Üç tartışmadan başlayarak tersini yapmak daha sıkıcıdır. std::generate(number[0:3], rand)numberstd::generate()
Lie Ryan

42

For döngüsünün aralık temelli olup olmaması hiç fark etmez, yalnızca parantez içindeki kodu basitleştirir. Algoritmalar, amacı gösterdikleri için daha nettir .


30

Şahsen benim ilk okumam:

std::generate(numbers.begin(), numbers.end(), rand);

"Bir aralıktaki her şeye atıyoruz. Aralık. numbersAtanan değerler rastgele" dir.

İlk okumam:

for (int& x : numbers) x = rand();

"Bir aralıktaki her şeye bir şeyler yapıyoruz. Aralık. numbersYaptığımız şey rastgele bir değer atamaktır."

Bunlar oldukça benzer, ancak aynı değil. İlk okumayı kışkırtmak isteyebilmemin makul bir nedeni, bence bu kodla ilgili en önemli gerçeğin aralığa ataması olduğunu düşünüyorum. İşte "neden isteyeyim ki ..." Kullanıyorum generateçünkü C ++ ' std::generateda "aralık ataması" anlamına geliyor. Btw'nin yaptığı gibi std::copy, ikisi arasındaki fark atadığınız şeydir.

Yine de kafa karıştırıcı faktörler var. Aralık tabanlı döngülerin numbers, yineleyici tabanlı algoritmalardan daha doğal olarak, aralığın olduğunu ifade etmenin daha doğrudan bir yolu vardır . Bu yüzden insanlar menzile dayalı algoritma kitaplıkları üzerinde çalışıyor: sürümden boost::range::generate(numbers, rand);daha iyi görünüyor std::generate.

Buna karşı, int&menzile dayalı for döngüsünüzde bir kırışıklık var. Ya aralığın değer türü değilse int, o zaman burada dönüştürülebilir olmasına bağlı sinir bozucu derecede ince bir şey yapıyoruz int&, oysa generatekod yalnızca randöğeye atanabilir olmanın getirisine bağlı . Değer türü olsa bile int, hala olup olmadığını düşünmeyi bırakabilirim. Bu nedenle auto, ben neyin atandığını görene kadar türleri düşünmeyi erteliyor - auto &x"ne tür olursa olsun, aralık öğesine bir referans alın" diyorum. (Onlar olduğunuz işlevi şablonları için) idi Geri C ++ 03, algoritmalar kesin türlerini gizlemek için bir yol, şimdi onlar konum bir yol.

En basit algoritmaların eşdeğer döngülere göre yalnızca marjinal bir faydası olduğunu düşünüyorum. Aralık tabanlı döngü döngüleri, döngüleri iyileştirir (bundan biraz daha fazlası olmasına rağmen, temel olarak standart şablonun çoğunu kaldırarak). Dolayısıyla, kenar boşlukları daha sıkı çekilir ve belki bazı özel durumlarda fikrinizi değiştirirsiniz. Ama orada hala bir stil farkı var.


Hiç bir kullanıcı tanımlı türü gördünüz mü operator int&()? :)
fredoverflow

Yerine @FredOverflow int&ile SomeClass&ve şimdi değil işaretlenmiş dönüşüm operatörleri ve tek parametreli yapıcıları hakkında endişe zorunda explicit.
TemplateRex

@FredOverflow: öyle düşünmeyin. Bu yüzden eğer olursa, bunu beklemeyeceğim ve şimdi ne kadar paranoyak olursam olayım, o zaman düşünmezsem beni ısırır ;-) Bir proxy nesnesi olabilir aşırı yükleyerek çalışın operator int&()ve operator int const &() constsonra tekrar operator int() constve aşırı yükleyerek çalışabilir operator=(int).
Steve Jessop

1
@rhalbersma: Sabit olmayan bir ref, geçici bir referansa bağlanmadığından, kurucular hakkında endişelenmenize gerek olmadığını düşünüyorum. Yalnızca başvuru türlerine dönüştürme operatörleri.
Steve Jessop

23

Kanımca, Etkili STL Öğesi 43: "Algoritma çağrılarını elle yazılmış döngülere tercih et." hala iyi bir tavsiye.

begin()/ end()Cehennemden kurtulmak için genellikle sarmalayıcı işlevleri yazıyorum . Bunu yaparsanız, örneğiniz şöyle görünecektir:

my_util::generate(numbers, rand);

Hem amacı iletme hem de okunabilirlik açısından aralığa dayalı döngüyü geçtiğine inanıyorum .


Bunu söyledikten sonra, C ++ 98'de bazı STL algoritma çağrılarının anlatılamaz kodlar verdiğini ve "El ile yazılmış döngüler için algoritma çağrılarını tercih et" ifadesini izlemenin iyi bir fikir olmadığını kabul etmeliyim. Şans eseri, lambdalar bunu değiştirdi.

Herb Sutter'dan şu örneği düşünün : Lambdas, Lambdas Everywhere .

Görev: olduğu v ilk öğesini bulun > xve < y.

Lambdas olmadan:

auto i = find_if( v.begin(), v.end(),
bind( logical_and<bool>(),
bind(greater<int>(), _1, x),
bind(less<int>(), _1, y) ) );

Lambda ile

auto i=find_if( v.begin(), v.end(), [=](int i) { return i > x && i < y; } );

1
Soruya biraz ortogonal. Sadece ilk cümle soruyu ele alıyor.
David Rodríguez - dribeas

@ DavidRod Rodríguez-dribeas Evet. İkinci yarı, 43. Maddenin neden hala iyi bir tavsiye olduğunu düşündüğümü açıklıyor .
Ali

Boost.Lambda ile, C ++ lambda işlevlerinden bile daha iyidir: auto i = find_if (v.begin (), v.end (), _1> x && _1 <y);
sdkljhdf hda

1
Sarmalayıcılar için +1. Aynısını yapmak. 1. günden itibaren standartta
olmalıydı

22

In my ayrıntı düşürebilir olsa görüşüne, manuel döngü, readabitly yoksun:

for (int& x : numbers) x = rand();

Bu döngüyü, sayılarla tanımlanan aralığı 1 başlatmak için kullanmazdım , çünkü ona baktığımda, bir sayı aralığı üzerinde yineliyor gibi görünüyor , ama gerçekte değil (özünde), yani yerine aralıktan okuyor , aralığa yazıyor .

Amaç, kullandığınızda çok daha nettir std::generate.

1. Bu bağlamda başlatma , kabın elemanlarına anlamlı bir değer vermek anlamına gelir .


5
Yine de, döngüler için menzile dayalı olmaya alışkın olmadığınız için değil mi? Bana göre, bu ifadenin aralıktaki her bir öğeyi atadığı oldukça açık. Açıkça görülüyor ki, üretme aşina olduğunla aynı şeyi yapıyor std::generate, ki bu bir C ++ programcısı olarak kabul edilebilir (eğer aşina değillerse, bakacaklar, aynı sonuç).
Steve Jessop

4
@SteveJessop: Bu cevap diğer ikisinden farklı değil. Okuyucudan biraz daha fazla çaba gerektirir ve biraz daha hataya meyillidir (ya tek bir &karakteri unutursanız ?) Algoritmaların avantajı, niyet göstermeleridir, döngülerle bunu anlamanız gerekir. Döngünün uygulanmasında bir hata varsa, bunun bir hata mı yoksa kasıtlı mı olduğu açık değildir.
David Rodríguez - dribeas

1
@ DavidRod Rodríguez-dribeas: Bu cevap diğer ikisinden önemli ölçüde farklı, IMO. Yazarın bir kod parçasını diğerinden daha açık / anlaşılır bulmasının nedenini derinlemesine incelemeye çalışır . Diğerleri bunu analiz etmeden ifade ediyor. İşte bu yüzden bunu yanıtlayacak kadar ilginç buluyorum :-)
Steve Jessop 13

1
@SteveJessop: Gerçekte sayı ürettiğiniz sonucuna varmak için döngünün gövdesine bakmanız gerekir , ancak std::generatesadece bir bakışla, bu işlev tarafından bir şey üretildiği söylenebilir ; Ne bir şeylerin işlevine üçüncü argüman tarafından cevaplanır. Bunun çok daha iyi olduğunu düşünüyorum.
Nawaz

1
@SteveJessop: Yani bu, azınlığa ait olduğun anlamına geliyor. Çoğunluğa daha açık olan bir kod yazardım: P. Sonuncusu: Hiçbir yerde başkalarının da benim yaptığım gibi döngüyü okuyacağını söylemedim. I (daha doğrusu sözü geliyordu bu bana yanıltıcıdır döngü okumak için bir yoludur ve döngü gövdesi olduğundan, farklı programcılar orada ne olup bittiğini anlamaya farklı şekilde okuyacağı); bu tür bir döngünün kullanımına farklı nedenlerle itiraz edebilirler ve bunların hepsi kendi algılarına göre doğru olabilir.
Nawaz

9

Yineleyicileri girdi olarak alan algoritmaların, aralık tabanlı döngülerle yapamayacağınız (basitçe) bazı şeyler vardır. Örneğin std::generate:

Kapsayıcıyı en fazla limit(hariç tutulan, limitgeçerli bir yineleyici açık numbers) bir dağıtımdaki değişkenlerle ve geri kalanını başka bir dağıtımdaki değişkenlerle doldurun .

std::generate(numbers.begin(), limit, rand1);
std::generate(limit, numbers.end(), rand2);

Yineleyici tabanlı algoritmalar, üzerinde çalıştığınız aralık üzerinde size daha iyi bir kontrol sağlar.


8
Okunabilirliği nedeni iken BÜYÜK algoritmaları tercih biri, bu gösterileri tek cevaptır döngü için-tabanlı aralığı ne sadece bir alt kümesidir algoritmalar dolayısıyla can değildir ve kullanımdan kaldırmak şey ...
K-ballo

6

Belirli bir durum için std::generate, okunabilirlik / niyet sorunu ile ilgili önceki cevaplara katılıyorum. std :: generate daha açık bir versiyon gibi görünüyor. Ama bunun bir bakıma zevk meselesi olduğunu kabul ediyorum.

Bununla birlikte, std :: algoritmasını çöpe atmamak için başka bir nedenim var - bazı veri türleri için özelleşmiş belirli algoritmalar var.

En basit örnek olacaktır std::fill. Genel sürüm, sağlanan aralık üzerinde bir döngü olarak uygulanır ve bu sürüm, şablonun başlatılması sırasında kullanılacaktır. Ama her zaman değil. Örneğin, ona bir aralık sağlarsanız std::vector<int>- çoğu zaman aslında memsetkaputun altında arayacak ve çok daha hızlı ve daha iyi bir kod üretecektir .

Bu yüzden burada bir verimlilik kartı oynamaya çalışıyorum.

El yazısı döngünüz std :: algoritma sürümü kadar hızlı olabilir, ancak daha hızlı olamaz. Dahası, std :: algoritması belirli kaplar ve türler için özelleştirilebilir ve temiz STL arabirimi altında yapılır.


3

Cevabım belki olur ve hayır. C ++ 11'den bahsediyorsak, o zaman belki (daha çok hayır gibi). Örneğin std::for_each, lambdalarda bile kullanmak gerçekten can sıkıcıdır:

std::for_each(c.begin(), c.end(), [&](ExactTypeOfContainedValue& x)
{
    // do stuff with x
});

Ancak aralık tabanlı kullanmak çok daha iyidir:

for (auto& x : c)
{
    // do stuff with x
}

Öte yandan, C ++ 1y'den bahsediyorsak, o zaman hayır, algoritmalar için menzile dayalı olarak kullanılmaz hale gelmeyeceğini iddia ediyorum. C ++ standart komitesinde, C ++ 'ya aralıklar eklemek için bir öneri üzerinde çalışan bir çalışma grubu vardır ve ayrıca polimorfik lambdalar üzerinde de çalışmalar yapılmaktadır. Aralıklar yineleyici çifti kullanma ihtiyacını ortadan kaldırır ve polimorfik lambda, lambda'nın tam bağımsız değişken türünü belirtmenize izin vermez. Bu, bunun std::for_eachşu şekilde kullanılabileceği anlamına gelir (bunu zor bir gerçek olarak almayın, bugün rüyalar tam olarak nasıl görünür)

std::for_each(c.range(), [](x)
{
    // do stuff with x
});

Öyleyse ikinci durumda, algoritmanın avantajı []lambda ile yazarak sıfır yakalama belirlemenizdir. Yani, sadece bir döngü gövdesi yazmakla karşılaştırıldığında, sözcüksel olarak göründüğü değişken arama bağlamından bir kod yığınını izole ettiniz. İzolasyon genellikle okuyucuya yardımcı olur, okurken düşünmesi daha azdır.
Steve Jessop

1
Konu ele geçirme değil. Mesele şu ki, polimorfik lambda ile x tipinin ne olduğunu açıkça belirtmeniz gerekmeyecek.
sdkljhdf hda

1
Bu durumda, bana öyle geliyor ki, bu varsayımsal C ++ 1y, for_eachbir lambda ile kullanıldığında bile hala anlamsız. foreach + yakalama lambda şu anda aralık tabanlı bir for döngüsü yazmanın ayrıntılı bir yoludur ve biraz daha az ayrıntılı bir yol haline gelir, ancak yine de döngüden daha fazladır. for_eachElbette savunmanız gerektiğini düşündüğümden değil , ama cevabınızı görmeden önce bile soruyu soran kişi algoritmaları yenmek isterse, for_eacholası tüm hedefler arasında en yumuşak olanı seçebilirdi diye düşünüyordum ;-)
Steve Jessop

Savunmayacak for_each, ancak menzil tabanlı için küçük bir avantajı var - dönüştürmek için paralel_ ile ön ekleyerek daha kolay paralel hale getirebilirsiniz parallel_for_each(PPL kullanıyorsanız ve bunu yapmanın iş parçacığı güvenli olduğunu varsayarak) . :-D
sdkljhdf hda

@lego "Küçük" avantajınız, eğer s 'uygulamasının arayüzlerinin arkasında gizli olduğu ve keyfi olarak karmaşık (veya keyfi olarak optimize edilmiş) olabileceği gerçeğine kadar genelleştiriyorsanız , gerçekten de "büyük" bir avantajdır std::algorithm.
Christian Rau

1

Dikkat edilmesi gereken bir şey, bir algoritmanın nasıl yapıldığını değil, ne yapıldığını ifade etmesidir.

Menzile dayalı döngü, işlerin yapılma şeklini içerir: ilk ile başlayın, uygulayın ve sonuna kadar bir sonraki öğeye gidin. Basit bir algoritma bile işleri farklı şekilde yapabilir (en azından belirli kapsayıcılar için bazı aşırı yüklemeler, korkunç vektörü düşünmeden bile) ve en azından yapılma şekli yazar işi değildir.

Bence bu farkın büyük bir kısmı, olabildiğince özetleyin ve bu, yapabildiğiniz zaman, algoritmaları kullanın.


1

Menzile dayalı for-döngüsü tam da budur. Tabii ki standart değişene kadar.

Algoritma bir işlevdir. Parametrelerine bazı gereksinimler getiren bir işlev. Gereksinimler, mevcut tüm yürütme iş parçacıklarından yararlanan ve sizi otomatik olarak hızlandıracak örnek uygulamaya izin vermek için bir standartta ifade edilmiştir.

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.