Можливо повторити назад за допомогою передбачення?


129

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


6
Ви можете змінити елемент (list.Reverse ()) у списку, перш ніж робити для кожного.
Акантан


Вам доведеться створити всі елементи в перерахунку, щоб ви могли змінити їх порядок. Це може бути неможливим. Розглянемо послідовність: IEnumerable<int> Infinity() { int i = 1; while (true) yield return i++; }Як би ти це змінив?
Suncat2000

Відповіді:


84

Працюючи зі списком (пряма індексація), ви не можете зробити це так ефективно, як використовувати forцикл.

Змінити: Що взагалі означає, що коли ви в змозі використовувати forцикл, ймовірно , правильний метод для вирішення цього завдання. Крім того, стільки, скільки foreachреалізовано в порядку, сама конструкція побудована для вираження циклів, які не залежать від індексів елементів і порядку ітерації, що особливо важливо при паралельному програмуванні . На мою думку , ітерацію, що покладається на порядок, не слід використовувати foreachдля циклічного циклу.


3
Я вважаю ваше останнє твердження занадто загальним. Безумовно, є випадки, коли, наприклад, якийсь IE Numerable має бути перероблений для того, щоб впорядкувати? Ви б не використовували foreach у такому випадку? Що б ви використали?
avl_sweden

1
ОНОВЛЕННЯ: Дивіться відповідь [Брайана на аналогічне запитання [( stackoverflow.com/a/3320924/199364 ), для більш сучасної відповіді, використовуючи Linq, обговорюючи обидва списки та інші переліки.
ToolmakerSteve

ОНОВЛЕННЯ: Джон Скіт має ще більш елегантну відповідь, що зараз можливо, використовуючи " yield return".
ToolmakerSteve

2
У мене була така ж початкова реакція, що і @avl_sweden - "ітерація, що покладається на порядок, не повинен використовувати foreach", звучить зашироко. Так, foreach хороший для вираження паралельних завдань, незалежних від порядку. Але це також важлива частина сучасного програмування на основі ітераторів . Можливо, справа в тому, що було б чіткіше / безпечніше, якби було два чіткі ключові слова , щоб уточнити, чи можна стверджувати, що ітерації не залежать від порядку? [З огляду на таку мову, як Ейфелева, яка може поширювати контракти, такі твердження можуть бути правдивими або хибними для даного коду.]
ToolmakerSteve

2
Ідея, що foreach "побудована для вираження циклів, які не залежать від індексів елементів та порядку ітерації", є неправильною. Специфікація мови c # вимагає, щоб елементи передбачуваного процесу в порядку. Або використовуючи MoveNext на ітераторах, або обробляючи індекси, що починаються з нуля і збільшуються по одному на кожній ітерації масивів.
Дональд Річ

145

Якщо ви перебуваєте на .NET 3.5, ви можете зробити це:

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

Це не дуже ефективно, оскільки в основному потрібно пройти перелік перелічувача вперед, поклавши все на стек, а потім вискакує все назад у зворотному порядку.

Якщо у вас є колекція, що безпосередньо індексується (наприклад, IList), ви обов'язково forзамість цього використовуєте цикл.

Якщо ви перебуваєте на .NET 2.0 і не можете використовувати цикл (тобто у вас просто 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 () повертає список на місце і не повертає його. Шкода, як я сподівався на таке рішення.
користувач12861

2
Деякі адміністратори відзначають цю відповідь WRONG, будь ласка. Reverse () - пустота [1], і тому вище приклад призводить до помилки компіляції. [1] msdn.microsoft.com/en-us/library/b0axc2h2(v=vs.110).aspx
MickyD

6
@ user12861, Міккі Дункан: відповідь неправильна, у вас щось пропущено. Існує метод Reverse на System.Collections.Generic.List <T>, який робить реверс на місці. У .Net 3.5 є метод розширення на IEnumerable <T> з назвою Reverse. Я змінив приклад з var на IEnumerable <int>, щоб зробити це більш явним.
Метт Хоуеллз

55

Як каже 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";
    }
}

Що ви очікуєте, що це станеться, якщо ви спробуєте повторити це в зворотному напрямку?


Просто цікавість, навіщо використовувати "> = 0" замість "> -1"?
Кріс С

15
> навіщо використовувати "> = 0" замість "> -1"? Оскільки> = 0 краще повідомляє про наміри людям, які читають код. Компілятор повинен бути в змозі оптимізувати його до еквівалента> -1, якщо це зробить підвищення продуктивності.
Марк Масляр

1
FastReverse (це елементи IList <T>) має бути FastReverse <T> (це елементи IList <T>. :):
Роб

Розщеплення паличок 0 <= i ще краще. Викладаючи протягом багатьох років математику з математики та допомагаючи людям з кодуванням, я виявив, що це значно зменшує помилки, які люди роблять, якщо ВИНАГИ замінюють a> b на b <a, оскільки речі приходять у природний порядок рядка чисел (Або вісь X системи координат) - єдиний недолік, якщо вам потрібно вставити рівняння в XML, скажімо .config
Eske Rahn

13

Перш ніж використовувати foreachдля ітерації, поверніть список reverseметодом:

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

2
те саме, що відповідь Метта Хоуеллса від '09
Мартін Шнайдер

Слово застереження щодо того, що myListє, буде корисним. IEnumerable.Reverse не буде працювати тут.
nawfal

2
@ MA-Maddin ні два не відрізняються. Я думаю, що Answerer покладається на Список <T>. Зворотний, який існує.
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"

Він названий "Інвертувати", тому що він є синонімом "Зворотний" і дозволяє розбиратися з реалізацією списку Зворотній.

Можна також змінити певні діапазони колекції, оскільки 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 порівняно з тимчасовим списком, щоб обернути колекцію для реверсування.


На момент написання я не знав про власну зворотну реалізацію Linq, все-таки мені було цікаво розробити це. https://msdn.microsoft.com/en-us/library/vstudio/bb358497(v=vs.100).aspx


4

Якщо ви використовуєте Список <T>, ви також можете використовувати цей код:

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

Це метод, який записує список зворотний сам по собі.

Тепер передбачення:

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

Вихід:

3
2
1

3

Це можливо, якщо ви можете змінити код колекції, який реалізує IEnumerable або IEnumerable (наприклад, ваша власна реалізація IList).

Створіть ітератор, який виконує цю роботу для вас, наприклад, як наступна реалізація через інтерфейс IEnumerable (якщо "пункти" є полем списку в цьому зразку):

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

Ні. 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]);
}

9
Це звучить для мене неймовірно неефективно.
Жан Ацопарді
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.