C / C # / C ++ ile geriye doğru döngü yapmanın en iyi yolu nedir?


102

Bir dizide geriye doğru gitmem gerekiyor, bu yüzden şöyle bir kodum var:

for (int i = myArray.Length - 1; i >= 0; i--)
{
    // Do something
    myArray[i] = 42;
}

Bunu yapmanın daha iyi bir yolu var mı?

Güncelleme: C # 'ın bunun için bazı yerleşik mekanizmaları olduğunu umuyordum:

foreachbackwards (int i in myArray)
{
    // so easy
}

Güncelleme 2: Orada olan iyi yollar. Rune ödülü şu şekilde alır:

for (int i = myArray.Length; i-- > 0; )
{    
    //do something
}
//or
for (int i = myArray.Length; i --> 0; )
{
    // do something
}

normal C'de daha da iyi görünen (Twotymz sayesinde):

for (int i = lengthOfArray; i--; )
{    
    //do something
}

1
İyilik netlik veya sürdürülebilirlik içeriyorsa, alternatiflerden herhangi birinin neden daha iyi olduğunu anlayamıyorum.
dkretz

Doğru. Bunu oldukça sık yapmak zorunda olduğum için bunu yapmanın daha makul bir yolunu bulmayı umuyordum.
MusiGenesis

4
Bu sorunun çoğu, aşağıdaki yanıtların bir tür incelemesidir. Önemli ölçüde kısaltılmasını önerin.
einpoklum

Lütfen C ve C ++ 'ı başlıktan ve hashtaglerden kaldırın.
jaskmar

Yanıtlar:


150

Kuşkusuz biraz belirsiz olsa da, bunu yapmanın tipografik olarak en hoş yolunun olduğunu söyleyebilirim.

for (int i = myArray.Length; i --> 0; )
{
    //do something
}

33
Cevabınızı ilk okuduğumda, derlenmeyecekmiş gibi görünüyordu, bu yüzden deli bir insan olduğunu varsaydım. Ama tam olarak aradığım şey buydu: geriye doğru döngü yazmanın daha iyi bir yolu.
MusiGenesis

4
Sanırım i -> 0; kasıtlıdır. "Tipografik olarak hoş" derken kastettiği buydu
Johannes Schaub - litb

16
Ben de bunu "tipografik olarak kafa karıştırıcı" buldum. Benim için "-", etkilediği değişkene bitişik olmadığı sürece doğru görünmüyor.
MusiGenesis

26
Bu çok belirsiz ve şaşkın. Prodüksiyon koduna asla böyle bir şey
yazmam

9
Aah operatöre gider (->) hile yapar!
nawfal

118

C ++ 'da temelde yineleyiciler veya indeksler kullanarak yineleme arasında seçim yapabilirsiniz. Düz bir diziniz veya birstd::vector farklı teknikler kullanırsınız.

Std :: vector kullanma

Yineleyicileri kullanma

C ++ bunu kullanarak yapmanıza olanak sağlar std::reverse_iterator:

for(std::vector<T>::reverse_iterator it = v.rbegin(); it != v.rend(); ++it) {
    /* std::cout << *it; ... */
}

Endeksleri kullanma

Tarafından döndürülen imzasız ayrılmaz tipi std::vector<T>::sizeolduğunu değil her zaman std::size_t. Daha fazla veya daha az olabilir. Döngünün çalışması için bu çok önemlidir.

for(std::vector<int>::size_type i = someVector.size() - 1; 
    i != (std::vector<int>::size_type) -1; i--) {
    /* std::cout << someVector[i]; ... */
}

İşaretsiz integral türleri değerleri, modulo bit sayıları ile tanımlandığı için işe yarar. Böylece, ayarlıyorsanız-N , sonunda(2 ^ BIT_SIZE) -N

Dizileri Kullanma

Yineleyicileri kullanma

std::reverse_iteratorYinelemeyi yapmak için kullanıyoruz .

for(std::reverse_iterator<element_type*> it(a + sizeof a / sizeof *a), itb(a); 
    it != itb; 
    ++it) {
    /* std::cout << *it; .... */
}

Endeksleri kullanma

Her zaman tanımı gereği geri döndüğü std::size_tiçin yukarıdakinin aksine burada güvenle kullanabiliriz .sizeofstd::size_t

for(std::size_t i = (sizeof a / sizeof *a) - 1; i != (std::size_t) -1; i--) {
   /* std::cout << a[i]; ... */
}

Sizeof işaretçilerine uygulanmış tuzaklardan kaçınma

Aslında bir dizinin boyutunu belirlemenin yukarıdaki yolu berbat. Eğer a aslında bir dizi yerine bir işaretçi ise (ki bu oldukça sık meydana gelir ve yeni başlayanlar onu karıştırır), sessizce başarısız olur. Daha iyi bir yol, bir işaretçi verilirse derleme zamanında başarısız olacak aşağıdakileri kullanmaktır:

template<typename T, std::size_t N> char (& array_size(T(&)[N]) )[N];

Önce aktarılan dizinin boyutunu alarak ve ardından aynı boyuttaki char türünden bir diziye bir başvuru döndürmeyi bildirerek çalışır. charşunlara sahip olacak şekilde tanımlanır sizeof: 1. Yani döndürülen dizi birsizeof : N * 1 değerine sahip olacaktır; bu, yalnızca derleme zamanı değerlendirmesi ve sıfır çalışma zamanı ek yükü ile aradığımız şeydir.

Yapmak yerine

(sizeof a / sizeof *a)

Kodunuzu şimdi değiştirecek şekilde değiştirin

(sizeof array_size(a))

Ek olarak, kapsayıcınızda ters yineleyici yoksa, boost'un reverse_iterator uygulamasını kullanabilirsiniz: boost.org/doc/libs/1_36_0/libs/iterator/doc/…
MP24

array_size değeriniz statik olarak ayrılmış diziler için çalışıyor gibi görünüyor, ancak örneğin 'new int [7]' olduğunda benim için başarısız oldu.
Nate Parsons

2
evet bunun amacı budur :) new int [7] bir işaretçi döndürür. bu nedenle sizeof (new int [7]) 7 * sizeof (int) değil, sizeof (int *) döndürür. array_size, sessizce çalışmak yerine bu durum için bir derleme hatasına neden olur.
Johannes Schaub - litb

Ayrıca array_size (+ statically_allocated_array) 'yi deneyin, bu da başarısız olur, çünkü + operatörü diziyi ilk elemanına bir gösterici yapar. düz sizeof kullanmak size yine bir işaretçi boyutunu verir
Johannes Schaub - litb

İlk olarak - neden sadece endve beginters sırada kullanmıyoruz ?
Tomáš Zato - Monica'yı eski durumuna getir

54

C # , Visual Studio 2005 veya sonraki bir sürümünü kullanarak, 'forr' yazın ve [TAB] [TAB] tuşuna basın . Bu genişleyecekfor bir koleksiyonda geriye doğru giden döngüye .

Yanlış yapmak o kadar kolay ki (en azından benim için), bu pasajı yerleştirmenin iyi bir fikir olacağını düşündüm.

Bununla birlikte, ben daha çok seviyorum Array.Reverse()/ Enumerable.Reverse()sonra ileriye doğru yineliyorum - daha net bir şekilde niyeti ifade ediyorlar.


41

Her zaman ' tipografik olarak hoş ' koda karşı açık kodu tercih ederim . Bu nedenle, her zaman kullanırım:

for (int i = myArray.Length - 1; i >= 0; i--)  
{  
    // Do something ...  
}    

Geriye doğru döngü yapmanın standart yolu olarak düşünebilirsiniz.
Sadece iki sentim...


Çalıştı. Bunu henüz C # ile yapmadım, ama teşekkürler.
PCPGMR

Peki dizi gerçekten büyükse (bu durumda dizin işaretsiz tipte olmak zorundadır)? size_t aslında işaretsiz, değil mi?
lalala

İ uint olarak ilan edebilirsiniz.Marc Gravell'in gönderisine bakın.
Jack Griffin

Tipografik olarak hoş olanın aksine, sarma nedeniyle imzasız bir tür için çalışmaz. İyi değil.
Ocelot

17

In C # kullanarak Linq :

foreach(var item in myArray.Reverse())
{
    // do something
}

Bu kesinlikle en basit olanıdır, ancak CLR'nin mevcut sürümünde bir listeyi ters çevirmenin her zaman O (1) işleminden ziyade bir O (n) işlemi olduğuna dikkat edin, bu bir IList <T> ise olabilir; see connect.microsoft.com/VisualStudio/feedback/…
Greg Beech

1
Görünüşe göre .Reverse (), dizinin yerel bir kopyasını oluşturuyor ve ardından geriye doğru yineliyor. Sırf içinde yineleme yapabilmek için bir diziyi kopyalamak biraz verimsiz görünüyor. Sanırım içinde "Linq" olan herhangi bir gönderi olumlu oylamaya değer. :)
MusiGenesis

Olası bir değiş tokuş vardır, ancak daha sonra "cevaba oy verildi" (i -> 0) 'nın daha yavaş kodla sonuçlanabileceği sorunuyla karşılaşırsınız çünkü (i) her defasında dizinin boyutuna göre kontrol edilmelidir. dizinde olduğu gibi kullanılır.
Keltex

1
Linq'in harika olduğu konusunda hemfikir olsam da, bu yaklaşımdaki sorun, bir yineleyicinin, dizinin öğelerini seçmenize izin vermemesidir - bir Dizinin bir bölümünde gezinmek istediğiniz durumu düşünün, ancak tamamını değil. şey ...
jesses.co.tt

11

Uzunluğu işaretli integral türü olan herhangi bir dizi için kesinlikle en iyi yoldur. Uzunlukları işaretsiz bir integral türü olan diziler için (örneğin std::vector, C ++ 'da), o zaman bitiş koşulunu biraz değiştirmeniz gerekir:

for(size_t i = myArray.size() - 1; i != (size_t)-1; i--)
    // blah

Az önce söylediyseniz i >= 0, bu her zaman işaretsiz bir tamsayı için doğrudur, bu nedenle döngü sonsuz bir döngü olacaktır.


C ++ 'da "i--", 0 olsaydım otomatik olarak en yüksek değere sarılır mıydı? Üzgünüm, C ++ bozukum.
MusiGenesis

İnanılmaz. 12 yıl önce yazdığım bir şeydeki sabit sürücü doldurma hatasını anlamama yardım ettiniz. İyi ki C ++ ile çalışmıyorum.
MusiGenesis

sonlandırma koşulu şöyle olmalıdır: i <myArray.size (). Yalnızca unsigned int'in taşma özelliklerine bağlıdır; tamsayıların ve yayınların uygulama ayrıntıları değil. Sonuç olarak, alternatif tamsayı gösterimlerine sahip dillerde okunması ve çalışması daha kolaydır.
ejgottl

Bence benim için daha iyi bir çözüm kimseyi incitemeyeceğim C # dünyasında kalmak.
MusiGenesis

4

Bana iyi görünüyor. Dizin oluşturucu imzalanmamışsa (uint vb.), Bunu hesaba katmanız gerekebilir. Bana tembel deyin, ama bu (işaretsiz) durumda, bir karşı değişken kullanabilirim:

uint pos = arr.Length;
for(uint i = 0; i < arr.Length ; i++)
{
    arr[--pos] = 42;
}

(aslında, burada bile arr.Length = uint.MaxValue gibi durumlara dikkat etmeniz gerekir ... belki a! = bir yerlerde ... elbette, bu pek olası olmayan bir durumdur!)


Yine de "--pos" meselesi aldatıcıdır. Geçmişte, kendilerine pek de doğru gelmeyen şeyleri kodda rastgele "düzeltmeyi" seven iş arkadaşlarım oldu.
MusiGenesis

1
Ardından .arr.Length-1 ve pos--
Marc Gravell

4

CI'da bunu yapmayı severim:


int i = myArray.Length;
while (i--) {
  myArray[i] = 42;
}

MusiGenesis tarafından eklenen C # örneği:

{int i = myArray.Length; while (i-- > 0)
{
    myArray[i] = 42;
}}

Bunu severim. Metodunun işe yaradığını anlamam bir iki saniyemi aldı. Umarım eklenen C # sürümüne aldırış etmezsiniz (ekstra kaşlı ayraçlar, dizinin kapsamı bir for döngüsüyle aynı olacak şekilde yapılır).
MusiGenesis

Bunu üretim kodunda kullanacağımdan emin değilim, çünkü evrensel bir "WTF bu mu?" sürdürmek zorunda olanların tepkisi, ancak yazmak normal bir for döngüsünden daha kolaydır ve eksi işaretleri veya> = sembolleri yoktur.
MusiGenesis

1
C # sürümünün fazladan "> 0" gerektirmesi çok kötü.
MusiGenesis

Hangi dil olduğundan emin değilim. ancak ilk kod parçacığınız kesinlikle C DEĞİLDİR :) sadece size bariz olanı söylemek için. belki C # yazmak istediniz ve html onu ayrıştıramadı veya başka bir şey?
Johannes Schaub - litb

@litb: Sanırım "myArray.Length" geçerli C (?) değil, ancak while döngüsü kısmı çalışıyor ve harika görünüyor.
MusiGenesis

3

Bunu C ++ 'da yapmanın en iyi yolu, muhtemelen sırayı geçerken tembel bir şekilde dönüştürecek yineleyici (veya daha iyisi, aralık) bağdaştırıcıları kullanmaktır.

Temel olarak,

vector<value_type> range;
foreach(value_type v, range | reversed)
    cout << v;

"Aralık" aralığını (burada boştur, ancak öğeleri kendi başınıza ekleyebileceğinizden oldukça eminim) ters sırada görüntüler. Elbette basitçe aralığı yinelemek pek bir işe yaramaz, ancak bu yeni aralığı algoritmalara ve diğer şeylere geçirmek oldukça güzel.

Bu mekanizma çok daha güçlü kullanımlar için de kullanılabilir:

range | transformed(f) | filtered(p) | reversed

"F" fonksiyonunun tüm öğelere uygulandığı, "p" nin doğru olmadığı öğeler kaldırıldığı ve son olarak ortaya çıkan aralık tersine çevrildiği "aralık" aralığını tembel bir şekilde hesaplayacaktır.

Boru sözdizimi, eki göz önüne alındığında en okunabilir IMO'dur. İncelenmeyi bekleyen Boost.Range kitaplık güncellemesi bunu uygular, ancak bunu kendiniz de yapmak oldukça kolaydır. Bir lambda DSEL ile f fonksiyonunu ve p tahminini satır içi olarak oluşturmak daha da güzel.


bize nereden "foreach" aldığınızı söyleyebilir misiniz? BOOST_FOREACH için bir # tanım mı? Her yönden sevimli görünüyor.
Johannes Schaub -


1

Bir süre döngüsünü tercih ederim. Bu benim iiçin bir for döngüsü durumunda azaltmaktan daha açık

int i = arrayLength;
while(i)
{
    i--;
    //do something with array[i]
}

0

Orijinal soruda kodu kullanırdım, ancak gerçekten foreach'i kullanmak ve C #'da bir tamsayı indeksine sahip olmak istiyorsanız:

foreach (int i in Enumerable.Range(0, myArray.Length).Reverse())
{
    myArray[i] = 42; 
}

-1

Burada kendi sorumu cevaplamayı deneyeceğim, ama bundan da pek hoşlanmıyorum:

for (int i = 0; i < myArray.Length; i++)
{
    int iBackwards = myArray.Length - 1 - i; // ugh
    myArray[iBackwards] = 666;
}

Her seferinde .Length - 1 - i yapmak yerine, belki ikinci bir değişken düşünün? [Güncellenen] gönderime bakın.
Marc Gravell

Kendi soruma olumsuz oy verdim. Sert. Orijinalinden daha sakar değil.
MusiGenesis

-4

NOT: Bu gönderi çok daha ayrıntılı hale geldi ve bu nedenle konu dışı, özür dilerim.

Bununla birlikte, arkadaşlarım bunu okudu ve 'bir yerde' değerli olduğuna inandılar. Bu konu yer değil. Bunun nereye gitmesi gerektiğiyle ilgili görüşlerinizi takdir ediyorum (bu sitede yeniyim).


Her neyse, bu .NET 3.5'teki C # sürümüdür ve tanımlanmış semantiği kullanan herhangi bir koleksiyon türü üzerinde çalışması şaşırtıcıdır. Bu, çoğu yaygın geliştirme senaryosunda performans veya CPU döngüsü minimizasyonu değil, varsayılan bir ölçüdür (yeniden kullanım!), Ancak gerçek dünyada asla böyle görünmüyor (erken optimizasyon).

*** Uzantı yöntemi, herhangi bir koleksiyon türü üzerinde çalışan ve türün tek bir değerini bekleyen bir eylem temsilcisi gerçekleştiren, tümü her öğe üzerinde tersten uygulandı **

3.5 Gereksinimleri:

public static void PerformOverReversed<T>(this IEnumerable<T> sequenceToReverse, Action<T> doForEachReversed)
      {
          foreach (var contextItem in sequenceToReverse.Reverse())
              doForEachReversed(contextItem);
      }

Daha eski .NET sürümleri veya Linq'in iç özelliklerini daha iyi anlamak ister misiniz? Okuyun .. Ya da değil ..

VARSAYIM: .NET türü sistemde, Dizi türü IEnumerable arabiriminden miras alır (genel IEnumerable yalnızca IEnumerable değil).

Baştan sona yinelemeniz gereken tek şey bu, ancak ters yönde ilerlemek istiyorsunuz. IEnumerable, 'nesne' türündeki Array üzerinde çalıştığından, herhangi bir tür geçerlidir,

KRİTİK ÖLÇÜ: Herhangi bir diziyi ters sırayla işleyebildiğinizi varsayıyoruz ki bu 'daha iyi', o zaman bunu sadece tamsayılar üzerinde yapabileceksiniz.

.NET CLR 2.0-3.0 için çözüm a:

Açıklama: İçerdiği her bir örneğin aynı türde olması yetkisine sahip herhangi bir IEnumerable uygulama örneğini kabul edeceğiz. Yani bir dizi alırsak, dizinin tamamı X tipi örnekleri içerir.! = X türünde başka örnekler varsa bir istisna atılır:

Tekil hizmet:

public class ReverserService {private ReverserService () {}

    /// <summary>
    /// Most importantly uses yield command for efficiency
    /// </summary>
    /// <param name="enumerableInstance"></param>
    /// <returns></returns>
    public static IEnumerable ToReveresed(IEnumerable enumerableInstance)
    {
        if (enumerableInstance == null)
        {
            throw new ArgumentNullException("enumerableInstance");
        }

        // First we need to move forwarad and create a temp
        // copy of a type that allows us to move backwards
        // We can use ArrayList for this as the concrete
        // type

        IList reversedEnumerable = new ArrayList();
        IEnumerator tempEnumerator = enumerableInstance.GetEnumerator();

        while (tempEnumerator.MoveNext())
        {
            reversedEnumerable.Add(tempEnumerator.Current);
        }

        // Now we do the standard reverse over this using yield to return
        // the result
        // NOTE: This is an immutable result by design. That is 
        // a design goal for this simple question as well as most other set related 
        // requirements, which is why Linq results are immutable for example
        // In fact this is foundational code to understand Linq

        for (var i = reversedEnumerable.Count - 1; i >= 0; i--)
        {
            yield return reversedEnumerable[i];
        }
    }
}



public static class ExtensionMethods
{

      public static IEnumerable ToReveresed(this IEnumerable enumerableInstance)
      {
          return ReverserService.ToReveresed(enumerableInstance);
      }
 }

[TestFixture] genel sınıf Testing123 {

    /// <summary>
    /// .NET 1.1 CLR
    /// </summary>
    [Test]
    public void Tester_fornet_1_dot_1()
    {
        const int initialSize = 1000;

        // Create the baseline data
        int[] myArray = new int[initialSize];

        for (var i = 0; i < initialSize; i++)
        {
            myArray[i] = i + 1;
        }

        IEnumerable _revered = ReverserService.ToReveresed(myArray);

        Assert.IsTrue(TestAndGetResult(_revered).Equals(1000));
    }

    [Test]
    public void tester_why_this_is_good()
    {

        ArrayList names = new ArrayList();
        names.Add("Jim");
        names.Add("Bob");
        names.Add("Eric");
        names.Add("Sam");

        IEnumerable _revered = ReverserService.ToReveresed(names);

        Assert.IsTrue(TestAndGetResult(_revered).Equals("Sam"));


    }

    [Test]
    public void tester_extension_method()
  {

        // Extension Methods No Linq (Linq does this for you as I will show)
        var enumerableOfInt = Enumerable.Range(1, 1000);

        // Use Extension Method - which simply wraps older clr code
        IEnumerable _revered = enumerableOfInt.ToReveresed();

        Assert.IsTrue(TestAndGetResult(_revered).Equals(1000));


    }


    [Test]
    public void tester_linq_3_dot_5_clr()
    {

        // Extension Methods No Linq (Linq does this for you as I will show)
        IEnumerable enumerableOfInt = Enumerable.Range(1, 1000);

        // Reverse is Linq (which is are extension methods off IEnumerable<T>
        // Note you must case IEnumerable (non generic) using OfType or Cast
        IEnumerable _revered = enumerableOfInt.Cast<int>().Reverse();

        Assert.IsTrue(TestAndGetResult(_revered).Equals(1000));


    }



    [Test]
    public void tester_final_and_recommended_colution()
    {

        var enumerableOfInt = Enumerable.Range(1, 1000);
        enumerableOfInt.PerformOverReversed(i => Debug.WriteLine(i));

    }



    private static object TestAndGetResult(IEnumerable enumerableIn)
    {
      //  IEnumerable x = ReverserService.ToReveresed(names);

        Assert.IsTrue(enumerableIn != null);
        IEnumerator _test = enumerableIn.GetEnumerator();

        // Move to first
        Assert.IsTrue(_test.MoveNext());
        return _test.Current;
    }
}

2
Dostum veya dudette: "for (int i = myArray.Length - 1; i> = 0; i--)" yazmanın daha az karmaşık bir yolunu arıyordum.
MusiGenesis

1
bu yanıtta hiç bir değer yok
Matt Melton
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.