Можно ли выполнить итерацию назад через foreach?

129

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

JL.
источник
6
Вы можете перевернуть элемент (list.Reverse ()) в списке, прежде чем делать это для каждого.
Akaanthan Ccoder
Вам нужно будет создать экземпляры всех элементов в перечислении, чтобы вы могли изменить их порядок. Это может быть невозможно. Рассмотрим последовательность: IEnumerable<int> Infinity() { int i = 1; while (true) yield return i++; }как бы вы это изменили?
Suncat2000

Ответы:

85

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

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

Сэм Харвелл
источник
3
Я считаю ваше последнее заявление слишком общим. Конечно, бывают случаи, когда, например, какой-то IEnumerable нужно перебирать по порядку? Не стали бы вы в этом случае использовать foreach? Что бы вы использовали?
avl_sweden
1
ОБНОВЛЕНИЕ: см. [Ответ Брайана на аналогичный вопрос [( stackoverflow.com/a/3320924/199364 ) для более современного ответа с использованием Linq и обсуждения обоих списков и других перечисляемых элементов.
ToolmakerSteve
ОБНОВЛЕНИЕ: у Джона Скита есть еще более элегантный ответ, который теперь возможен с помощью " yield return".
ToolmakerSteve
2
У меня была та же первая реакция, что и у @avl_sweden - «итерация, основанная на порядке, не должна использовать foreach» звучит слишком широко. Да, foreach хорош для выражения параллельных задач, не зависящих от порядка. НО это также важная часть современного программирования на основе итераторов . Возможно, дело в том, что было бы яснее / безопаснее, если бы было два разных ключевых слова , чтобы прояснить, утверждает ли одно, что итерации не зависят от порядка? [Учитывая язык как Eiffel , которые могут распространяться контракты, такие утверждения могут быть доказуемо истинным или ложным, для данного кода.]
ToolmakerSteve
2
Идея о том, что foreach «построен для выражения циклов, не зависящих от индексов элементов и порядка итерации», неверна. Спецификация языка C # требует, чтобы элементы процесса foreach располагались по порядку. Либо с помощью MoveNext на итераторах, либо путем обработки индексов, начинающихся с нуля и увеличивающихся на единицу на каждой итерации массивов.
Дональд Рич
145

Если вы используете .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 конструктору стека, он будет перебирать его и помещать элементы в стек. Когда вы затем перебираете стек, он возвращает вещи обратно в обратном порядке.

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

Мэтт Хауэллс
источник
Также забыл упомянуть .net v2, только пожалуйста
JL.
4
Интересное решение .NET 2.0.
RichardOD
11
Мне что-то не хватает или ваше решение .Net 3.5 на самом деле не работает? Reverse () переворачивает список на место и не возвращает его. Жаль, что я надеялся на подобное решение.
user12861
2
Некоторые администраторы помечают этот ответ как НЕПРАВИЛЬНЫЙ, пожалуйста. Reverse () - это void [1], поэтому приведенный выше пример приводит к ошибке компиляции. [1] msdn.microsoft.com/en-us/library/b0axc2h2(v=vs.110).aspx
MickyD 03
6
@ user12861, Микки Дункан: ответ правильный, вы чего-то упускаете. В System.Collections.Generic.List <T> есть метод Reverse, который выполняет обратный ход на месте. В .Net 3.5 есть метод расширения для IEnumerable <T> с именем Reverse. Я изменил пример с var на IEnumerable <int>, чтобы сделать это более явным.
Мэтт Хауэллс
55

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

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

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

Джон Скит
источник
Просто любопытство, зачем использовать «> = 0» вместо «> -1»?
Chris S
15
> зачем использовать "> = 0" вместо "> -1"? Потому что> = 0 лучше передает намерение людям, читающим код. Компилятор должен иметь возможность оптимизировать это до эквивалента> -1, если это улучшит производительность.
Марк Маслар
1
FastReverse (это элементы IList <T>) должен быть FastReverse <T> (это элементы IList <T>. :)
Роб
Разделение палочек 0 <= i еще лучше. После обучения многих детей математике на протяжении многих лет и помощи людям с программированием, я обнаружил, что это значительно снижает количество ошибок, которые люди допускают, если ВСЕГДА меняют местами a> b на b <a, поскольку тогда все идет в естественном порядке строк. чисел (или ось X системы координат) - единственный недостаток заключается в том, что вам нужно вставить уравнение в XML, скажем .config
Эске Ран
13

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

    myList.Reverse();
    foreach( List listItem in myList)
    {
       Console.WriteLine(listItem);
    }
Prem
источник
2
то же, что и ответ Мэтта Хауэлса от '09
Мартин Шнайдер
Небольшое предупреждение о том, что myListэто может быть полезно. IEnumerable.Reverse здесь не работает.
nawfal
2
@ MA-Maddin нет, они разные. Я предполагаю, что ответчик полагается на List <T> .Reverse, который существует.
nawfal
На самом деле, я не знаю, почему я это сказал. Я должен был добавить больше деталей ... В любом случае, это сбивает с толку код, поскольку он, myListпохоже, относится к типу System.Collections.Generic.List<System.Windows.Documents.List>(или любому другому настраиваемому Listтипу), иначе этот код не работает: P
Мартин Шнайдер
5

Иногда у вас нет роскоши индексирования, или, возможно, вы хотите отменить результаты запроса 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 по сравнению с использованием временного списка для обертывания коллекции для реверсирования.


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

Vorspire
источник
4

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

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
th3s0urc3
источник
3

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

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

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

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

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

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

Beachwalker
источник
2

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

Иосип Медведь
источник
1
Ну есть гарантии того, в зависимости от типа коллекции.
Джон Скит,
1

Немного проработав хороший ответ Джона Скита , он может быть универсальным:

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)) {
    .
    .
}
Эске Ран
источник
-1

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

                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
Это плохо, потому что .Reverse()фактически изменяет список.
Колин Баснетт
-8

Это работает очень хорошо

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]);
}
Mc_Topaz
источник
9
Мне это кажется невероятно неэффективным.
Жан Аззопарди