Можно ли выполнить итерацию в обратном направлении через foreach? - PullRequest
109 голосов
/ 31 июля 2009

Я знаю, что мог бы использовать оператор for и добиться того же эффекта, но могу ли я вернуться назад через foreach цикл в C #?

Ответы [ 11 ]

131 голосов
/ 31 июля 2009

Если вы работаете в .NET 3.5, вы можете сделать это:

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

Это не очень эффективно, так как в основном приходится проходить через перечислитель вперед, помещая все в стек, а затем выталкивая все обратно.в обратном порядке.

Если у вас есть непосредственно индексируемая коллекция (например, IList), вам определенно следует использовать цикл for.

Если вы используете .NET 2.0 и не можете использоватьцикл for (т.е. у вас просто есть IEnumerable), тогда вам просто нужно написать свою собственную функцию Reverse.Это должно работать:

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

Это зависит от некоторого поведения, которое, возможно, не столь очевидно.Когда вы передаете IEnumerable конструктору стека, он будет перебирать его и помещать элементы в стек.Когда вы затем выполняете итерацию по стеку, он выводит вещи обратно в обратном порядке.

Это и метод расширения .NET 3.5 Reverse(), очевидно, взорвутся, если вы передадите ему IEnumerable, который никогда не прекращает возвращать элементы.

73 голосов
/ 31 июля 2009

При работе со списком (прямая индексация) вы не можете сделать это так же эффективно, как при использовании цикла for.

Редактировать: что обычно означает, что когда вы способны использовать цикл for, это, вероятно, правильный метод для этой задачи. Кроме того, поскольку foreach реализовано по порядку, сама конструкция построена для выражения циклов, которые не зависят от индексов элементов и порядка итераций, что особенно важно в параллельном программировании . мое мнение , что итерация, опирающаяся на порядок, не должна использовать foreach для циклического выполнения.

48 голосов
/ 31 июля 2009

Как говорит 280Z28, для IList<T> вы можете просто использовать индекс. Вы можете скрыть это в методе расширения:

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

Это будет быстрее, чем Enumerable.Reverse(), который сначала буферизует все данные. (Я не верю, что Reverse применяет какие-либо оптимизации так, как это делает Count().) Обратите внимание, что эта буферизация означает, что данные полностью читаются при первом запуске итерации, тогда как FastReverse "увидит" любые изменения внесен в список во время итерации. (Он также сломается, если вы удалите несколько элементов между итерациями.)

Для общих последовательностей нет способа повторения в обратном порядке - последовательность может быть бесконечной, например:

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

Что бы вы ожидали, если бы попытались повторить это в обратном порядке?

8 голосов
/ 19 февраля 2015

Перед использованием 'foreach' для итерации, переверните список методом 'reverse':

    myList.Reverse();
    foreach( List listItem in myList)
    {
       Console.WriteLine(listItem);
    }
4 голосов
/ 01 июля 2015

Иногда вам не хватает роскоши индексирования, или, возможно, вы хотите отменить результаты запроса Linq, или, возможно, вы не хотите изменять исходную коллекцию, если какой-либо из них верен, Linq может помочь вы.

Метод расширения Linq, использующий анонимные типы с Linq Select для предоставления ключа сортировки для Linq OrderByDescending;

    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);
    }

Использование:

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

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

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

Он называется «Invert», потому что он синонимичен с «Reverse» и разрешает устранение неоднозначности с реализацией List Reverse.

Можно также изменить некоторые диапазоны коллекции, так как Int32.MinValue и Int32.MaxValue находятся вне диапазона любого вида индекса коллекции, мы можем использовать их для процесса заказа; если индекс элемента ниже заданного диапазона, ему присваивается Int32.MaxValue, чтобы его порядок не изменялся при использовании OrderByDescending, аналогично, элементам с индексом, превышающим заданный диапазон, будет назначаться Int32.MinValue, чтобы они появиться в конце процесса заказа. Всем элементам в данном диапазоне назначается их нормальный индекс, и они соответственно меняются местами.

    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);
    }

Использование:

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

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

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

Я не уверен в падениях производительности этих реализаций Linq по сравнению с использованием временного List для переноса коллекции для реверсирования.


На момент написания статьи я не знал о собственной реализации Reverse в Linq, но все же было весело работать с этим. https://msdn.microsoft.com/en-us/library/vstudio/bb358497(v=vs.100).aspx

4 голосов
/ 30 июня 2014

Если вы используете список , вы также можете использовать этот код:

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

Это метод, который записывает обратный список сам по себе.

Теперь foreach:

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

Вывод:

3
2
1
2 голосов
/ 03 октября 2012

Возможно , если вы можете изменить код коллекции , который реализует IEnumerable или IEnumerable (например, ваша собственная реализация IList).

Создайте Итератор , выполняющий эту работу за вас, например, как в следующей реализации через интерфейс IEnumerable (при условии, что 'items' является полем List в этом примере):

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

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

Из-за этого ваш список будет перебирать ваш список в обратном порядке.

Просто подсказка: вам следует четко указать это специальное поведение вашего списка в документации (еще лучше, выбрав самообъясняющееся имя класса, например, Stack или Queue).

2 голосов
/ 31 июля 2009

Нет. ForEach просто перебирает коллекцию для каждого элемента, и порядок зависит от того, использует ли он IEnumerable или GetEnumerator ().

0 голосов
/ 27 июня 2018

Обдумав приятный ответ Джон Скит , это может быть универсально:

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];
}

А затем использовать как

foreach (var item in myList.Directional(forwardsCondition)) {
    .
    .
}
0 голосов
/ 29 июля 2016

Я использовал этот код, который работал

                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) {
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...