Bir foreach boyunca geriye doğru yineleme mümkün mü?


129

Bir forifade kullanabileceğimi ve aynı etkiyi elde edebileceğimi biliyorum , ancak foreachC # 'da bir döngü boyunca geriye doğru döngü yapabilir miyim ?


6
Her biri için yapmadan önce listedeki öğeyi (list.Reverse ()) tersine çevirebilirsiniz.
Akaanthan Ccoder


Numaralandırmadaki tüm öğeleri somutlaştırmanız gerekir, böylece sıralarını tersine çevirebilirsiniz. Mümkün olmayabilir. Sırayı düşünün: IEnumerable<int> Infinity() { int i = 1; while (true) yield return i++; }Bunu nasıl tersine çevirirsiniz?
Suncat2000

Yanıtlar:


85

Bir liste ile çalışırken (doğrudan indeksleme), bunu bir fordöngü kullanmak kadar verimli bir şekilde yapamazsınız .

Düzenle: ne zaman genel olarak anlamı muktedir bir kullanımı fordöngü, muhtemelen bu görev için doğru bir yöntem. Artı, foreachsırayla uygulandığı kadarıyla , yapının kendisi, paralel programlamada özellikle önemli olan öğe indekslerinden ve yineleme sırasından bağımsız döngüleri ifade etmek için oluşturulmuştur . Öyle Bence yineleme kullanmamalısınız sipariş güvenerek bu foreachdöngü için.


3
Son ifadenizi biraz fazla genel buluyorum. Elbette, örneğin, bir tür IEnumerable'ın sırayla yinelenmesi gereken durumlar vardır? Bu durumda foreach'i kullanmaz mısın? Ne kullanırdın?
avl_sweden

1
GÜNCELLEME: Linq kullanarak ve hem listeleri hem de diğer numaralandırılabilir öğeleri tartışarak daha modern bir cevap için [Bryan'ın benzer bir soruya cevabına [( stackoverflow.com/a/3320924/199364 ) bakın.
ToolmakerSteve

GÜNCELLEME: Jon Skeet'in " yield return" kullanarak artık mümkün olan daha zarif bir cevabı var .
ToolmakerSteve

2
@Avl_sweden ile aynı ilk tepkiyi verdim - "sıraya dayanan yineleme foreach kullanmamalıdır" sesleri çok geniş. Evet, foreach, düzenden bağımsız paralel görevleri ifade etmek için iyidir. AMA aynı zamanda modern yineleyici tabanlı programlamanın önemli bir parçasıdır . Belki de mesele şu ki, iki farklı anahtar kelime olsaydı , birinin yinelemelerin sırayla bağımsız olduğunu iddia edip etmediğini açıklığa kavuşturmak daha açık / daha güvenli olurdu ? [Eiffel gibi sözleşmeleri yayabilen bir dil verildiğinde, bu tür iddialar verilen kod için kanıtlanabilir şekilde doğru veya yanlış olabilir.]
ToolmakerSteve

2
Foreach'in "eleman indekslerinden ve yineleme sırasından bağımsız döngüleri ifade etmek için inşa edildiği" fikri yanlıştır. C # dil belirtimi, foreach işlem öğelerinin sırayla olmasını gerektirir. Yineleyicilerde MoveNext kullanarak veya sıfırdan başlayan ve dizilerin her yinelemesinde bir artan indisleri işleyerek.
Donald Rich

145

NET 3.5 üzerindeyseniz, bunu yapabilirsiniz:

IEnumerable<int> enumerableThing = ...;
foreach (var x in enumerableThing.Reverse())

Temelde numaralandırıcıdan her şeyi bir yığına koyarak ileriye gitmesi ve ardından her şeyi ters sırayla geri çıkarması gerektiği için çok verimli değildir.

Doğrudan indekslenebilir bir koleksiyonunuz varsa (örn. IList), forbunun yerine kesinlikle bir döngü kullanmalısınız.

.NET 2.0 üzerindeyseniz ve bir for döngüsü kullanamıyorsanız (yani, yalnızca bir IEnumerable'a sahipseniz), o zaman kendi Ters işlevinizi yazmanız gerekecektir. Bu çalışmalı:

static IEnumerable<T> Reverse<T>(IEnumerable<T> input)
{
    return new Stack<T>(input);
}

Bu, belki de o kadar açık olmayan bazı davranışlara dayanır. Bir IEnumerable'ı yığın yapıcısına ilettiğinizde, onu yineler ve öğeleri yığına iter. Daha sonra yığın üzerinde yinelediğinizde, her şeyi ters sırayla geri çıkarır.

Bu ve .NET 3.5 Reverse()genişletme yöntemi, öğeleri döndürmeyi asla bırakmayan bir IEnumerable ile beslerseniz açıkça patlayacaktır.


Ayrıca sadece .net v2'den bahsetmeyi unuttum
JL.

4
İlginç .NET 2.0 çözümü.
RichardOD

11
Bir şey mi eksik yoksa .Net 3.5 çözümünüz gerçekten çalışmıyor mu? Reverse (), listeyi yerinde ters çevirir ve geri döndürmez. Böyle bir çözüm umduğum için çok kötü.
user12861

2
Bazı yöneticiler bu yanıtı lütfen YANLIŞ olarak işaretleyin. Reverse () bir void [1] 'dir ve bu nedenle yukarıdaki örnek bir derleme hatasına yol açar. [1] msdn.microsoft.com/en-us/library/b0axc2h2(v=vs.110).aspx
MickyD

6
@ user12861, Micky Duncan: cevap yanlış değil, bir şeyi kaçırıyorsun. System.Collections.Generic.List <T> üzerinde, yerinde tersi yapan bir Reverse yöntemi vardır. .Net 3.5'te IEnumerable <T> üzerinde Reverse adında bir genişletme yöntemi vardır. Bunu daha açık hale getirmek için örneği var'dan IEnumerable <int> olarak değiştirdim.
Matt Howells

55

280Z28'in dediği gibi, bir için IList<T>sadece dizini kullanabilirsiniz. Bunu bir uzantı yönteminde gizleyebilirsiniz:

public static IEnumerable<T> FastReverse<T>(this IList<T> items)
{
    for (int i = items.Count-1; i >= 0; i--)
    {
        yield return items[i];
    }
}

Bu, Enumerable.Reverse()tüm verileri önce arabelleğe alandan daha hızlı olacaktır . (Bu Reverseşekilde uygulanan herhangi bir optimizasyon olduğuna inanmıyorum Count().) Bu arabelleğe alma işleminin, yinelemeye ilk başladığınızda verilerin tamamen okunduğu anlamına FastReversegelirken, siz yineleme sırasında listede yapılan değişiklikleri "göreceğiniz" anlamına gelir . (Yinelemeler arasında birden çok öğeyi kaldırırsanız da kırılır.)

Genel diziler için, ters yönde yinelemenin bir yolu yoktur - sıra sonsuz olabilir, örneğin:

public static IEnumerable<T> GetStringsOfIncreasingSize()
{
    string ret = "";
    while (true)
    {
        yield return ret;
        ret = ret + "x";
    }
}

Bunu tersine yinelemeye çalışsaydınız ne olmasını beklerdiniz?


Merak ediyorum, neden "> -1" yerine "> = 0" kullanılsın?
Chris S

15
> neden "> -1" yerine "> = 0" kullanılsın? Çünkü> = 0, niyeti kodu okuyan insanlara daha iyi iletir. Performansı artıracaksa, derleyicinin bunu eşdeğer> -1'e optimize edebilmesi gerekir.
Mark Maslar

1
FastReverse (bu IList <T> öğeleri) FastReverse <T> olmalıdır (bu IList <T> öğeleri. :)
Rob

Bölme çubukları 0 <= i daha da iyidir. Yıllar boyunca pek çok çocuğa matematik öğrettikten ve insanlara kodlamada yardımcı olduktan sonra, her şeyin bir doğrunun doğal sırasına göre HER ZAMAN a> b'den b <a'ya yer değiştirmesinin insanların yaptığı hataları azalttığını gördüm. sayıların sayısı (veya bir koordinat sisteminin X ekseni) - tek dezavantajı, XML'e bir denklem yapıştırmanız gerektiğinde, örneğin bir .config
Eske Rahn

13

foreachYineleme için kullanmadan önce listeyi şu reverseyöntemle ters çevirin :

    myList.Reverse();
    foreach( List listItem in myList)
    {
       Console.WriteLine(listItem);
    }


Neyin myListyardımcı olacağına dair bir uyarı . IEnumerable.Reverse burada çalışmaz.
nawfal

2
@ MA-Maddin hayır ikisi farklı. Sanırım cevaplayıcı, List <T> .Reverse olan yerinde olduğuna güveniyor.
nawfal

Aslında bunu neden söylediğimi bilmiyorum. Daha fazla ayrıntı eklemeliydim ... Her neyse, bu kafa karıştırıcı bir kod çünkü bir myListtür System.Collections.Generic.List<System.Windows.Documents.List>(veya başka herhangi bir özel Listtip) gibi görünüyor, aksi takdirde bu kod çalışmıyor: P
Martin Schneider

5

Bazen indeksleme lüksüne sahip değilsiniz ya da belki bir Linq sorgusunun sonuçlarını tersine çevirmek istiyorsunuz ya da belki kaynak koleksiyonunu değiştirmek istemiyorsunuz, bunlardan herhangi biri doğruysa, Linq size yardımcı olabilir.

Linq OrderByDescending için bir sıralama anahtarı sağlamak üzere Linq Select ile anonim türleri kullanan bir Linq genişletme yöntemi;

    public static IEnumerable<T> Invert<T>(this IEnumerable<T> source)
    {
        var transform = source.Select(
            (o, i) => new
            {
                Index = i,
                Object = o
            });

        return transform.OrderByDescending(o => o.Index)
                        .Select(o => o.Object);
    }

Kullanımı:

    var eable = new[]{ "a", "b", "c" };

    foreach(var o in eable.Invert())
    {
        Console.WriteLine(o);
    }

    // "c", "b", "a"

"Tersine Çevir" olarak adlandırılmıştır çünkü "Tersine Çevir" ile eşanlamlıdır ve Ters Liste uygulamasıyla belirsizliği gidermeyi etkinleştirir.

Bir koleksiyonun belirli aralıklarını tersine çevirmek de mümkündür, çünkü Int32.MinValue ve Int32.MaxValue herhangi bir koleksiyon indeksinin aralığının dışında olduğundan, sipariş süreci için bunları kullanabiliriz; bir öğe dizini verilen aralığın altındaysa, buna Int32.MaxValue atanır, böylece OrderByDescending kullanılırken sırası değişmez, benzer şekilde, verilen aralıktan daha büyük bir dizindeki öğelere Int32.MinValue atanır, böylece bunlar sipariş sürecinin sonunda görünür. Verilen aralıktaki tüm öğelere normal indeksleri atanır ve buna göre tersine çevrilir.

    public static IEnumerable<T> Invert<T>(this IEnumerable<T> source, int index, int count)
    {
        var transform = source.Select(
            (o, i) => new
            {
                Index = i < index ? Int32.MaxValue : i >= index + count ? Int32.MinValue : i,
                Object = o
            });

        return transform.OrderByDescending(o => o.Index)
                        .Select(o => o.Object);
    }

Kullanımı:

    var eable = new[]{ "a", "b", "c", "d" };

    foreach(var o in eable.Invert(1, 2))
    {
        Console.WriteLine(o);
    }

    // "a", "c", "b", "d"

Bir koleksiyonu tersine çevirmek için geçici bir Liste kullanmak yerine bu Linq uygulamalarının performans vuruşlarından emin değilim.


Yazarken, Linq'in kendi Reverse uygulamasından haberdar değildim, yine de bunu çalışmak eğlenceliydi. https://msdn.microsoft.com/en-us/library/vstudio/bb358497(v=vs.100).aspx


4

Liste <T> kullanıyorsanız, şu kodu da kullanabilirsiniz:

List<string> list = new List<string>();
list.Add("1");
list.Add("2");
list.Add("3");
list.Reverse();

Bu, listeyi kendi içinde tersine yazan bir yöntemdir.

Şimdi foreach:

foreach(string s in list)
{
    Console.WriteLine(s);
}

Çıktı:

3
2
1

3

Öyle Eğer toplama kodunu değiştirmek eğer mümkünse uygular IEnumerable veya IEnumerable (IList kendi uygulaması örneğin) o.

Bu işi sizin için yapan bir Yineleyici oluşturun , örneğin IEnumerable arabirimi aracılığıyla aşağıdaki uygulama gibi (bu örnekte ' öğeler'in bir Liste alanı olduğunu varsayarsak):

public IEnumerator<TObject> GetEnumerator()
{
    for (var i = items.Count - 1; i >= 0; i--)
    { 
        yield return items[i];
    }
}

IEnumerator IEnumerable.GetEnumerator()
{
    return GetEnumerator();
}

Bu nedenle Listeniz, listeniz boyunca ters sırada yinelenecektir.

Sadece bir ipucu: Listenizin bu özel davranışını dokümantasyon içinde açıkça belirtmelisiniz (Stack veya Queue gibi kendi kendini açıklayan bir sınıf adı seçerek daha da iyi).


2

Hayır. ForEach, her öğe için koleksiyon boyunca yineler ve sipariş, IEnumerable veya GetEnumerator () kullanıp kullanmadığına bağlıdır.


1
Eh orada olan toplama tipine göre düzenin garantileri,.
Jon Skeet

1

Jon Skeet'in güzel cevabını biraz detaylandırarak , bu çok yönlü olabilir:

public static IEnumerable<T> Directional<T>(this IList<T> items, bool Forwards) {
    if (Forwards) foreach (T item in items) yield return item;
    else for (int i = items.Count-1; 0<=i; i--) yield return items[i];
}

Ve sonra olarak kullan

foreach (var item in myList.Directional(forwardsCondition)) {
    .
    .
}

-1

İşe yarayan bu kodu kullandım

                if (element.HasAttributes) {

                    foreach(var attr in element.Attributes().Reverse())
                    {

                        if (depth > 1)
                        {
                            elements_upper_hierarchy_text = "";
                            foreach (var ancest  in element.Ancestors().Reverse())
                            {
                                elements_upper_hierarchy_text += ancest.Name + "_";
                            }// foreach(var ancest  in element.Ancestors())

                        }//if (depth > 1)
                        xml_taglist_report += " " + depth  + " " + elements_upper_hierarchy_text+ element.Name + "_" + attr.Name +"(" + attr.Name +")" + "   =   " + attr.Value + "\r\n";
                    }// foreach(var attr in element.Attributes().Reverse())

                }// if (element.HasAttributes) {

2
Bu kötü çünkü .Reverse()aslında listeyi değiştiriyor.
Colin Basnett

-8

Bu oldukça iyi çalışıyor

List<string> list = new List<string>();

list.Add("Hello");
list.Add("Who");
list.Add("Are");
list.Add("You");

foreach (String s in list)
{
    Console.WriteLine(list[list.Count - list.IndexOf(s) - 1]);
}

9
Bu bana inanılmaz derecede verimsiz geliyor.
Jean Azzopardi
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.