Який найкращий спосіб зробити зворотний цикл у C / C # / C ++?


101

Мені потрібно рухатись назад через масив, тому у мене є такий код:

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

Чи є кращий спосіб зробити це?

Оновлення: Я сподівався, що, можливо, у C # є якийсь вбудований механізм для цього, наприклад:

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

Оновлення 2: Там є більш ефективними способами. Руна приймає приз із:

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

що виглядає ще краще в звичайному C (завдяки Twotymz):

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

Я не впевнений, що бачу, чому будь-яка з альтернатив є кращою, якщо добро включає чіткість чи ремонтопридатність.
dkretz

Правда. Я сподівався знайти більш розумний спосіб зробити це, оскільки мені доводиться це робити досить часто.
MusiGenesis

3
Більшість цього питання є свого роду оглядом відповідей нижче. Запропонуйте її значно скоротити.
einpoklum

Видаліть C і C ++ із заголовка та хештегів.
jaskmar

Відповіді:


149

Хоча, правда, трохи незрозуміло, я б сказав, що найприємніший спосіб зробити це

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

32
Коли я вперше прочитав вашу відповідь, то виглядало, що вона навіть не складеться, тому я припустив, що ви божевільна людина. Але це саме те, що я шукав: кращий спосіб написання зворотного циклу для циклу.
MusiGenesis

3
Я думаю, що i -> 0; цілеспрямовано. Ось що він має на увазі під "типографічно приємним"
Йоганнесом Шаубом -

16
Я вважав це «типографічно заплутаним». Для мене "-" не виглядає правильним, якщо він не прилягає до змінної, на яку він впливає.
MusiGenesis

26
Це занадто незрозуміло і затуманено. Я б ніколи не писав подібного у виробничому коді ...
Михай Тодор

9
Аа, іде оператору (->) робить трюк!
nawfal

118

У C ++ ви маєте вибір між ітерацією за допомогою ітераторів чи індексів. Залежно від того, чи є у вас звичайний масив, або a std::vector, ви використовуєте різні методи.

Використання std :: vector

Використання ітераторів

C ++ дозволяє це робити за допомогою std::reverse_iterator:

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

Використання індексів

Без знака інтеграла типу повертається std::vector<T>::sizeце НЕ завжди std::size_t. Він може бути більшим або меншим. Це має вирішальне значення для роботи циклу.

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

Це працює, оскільки значення безпідписаних інтегральних типів визначаються за допомогою модуля їх кількість розрядів. Таким чином, якщо ви налаштовуєтесь -N, ви закінчите о(2 ^ BIT_SIZE) -N

Використання масивів

Використання ітераторів

Ми використовуємо std::reverse_iteratorдля повторення.

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

Використання індексів

Тут ми можемо сміливо використовувати std::size_t, на відміну від вище, оскільки sizeofзавжди повертається std::size_tза визначенням.

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

Уникнення підводних каменів із розміром, що застосовується до покажчиків

Насправді вищезазначений спосіб визначення розміру масиву смокче. Якщо a насправді вказівник замість масиву (що трапляється досить часто, і початківці плутатимуть його), він мовчки вийде з ладу. Кращим способом є використання наступного, яке не вдасться під час компіляції, якщо дано вказівник:

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

Він працює, отримуючи спочатку розмір пропущеного масиву, а потім оголошуючи повернення посилання на масив char типу того ж розміру. charвизначаються мати sizeofз: 1. Таким чином , повертається масив буде мати sizeofз: N * 1, який є те , що ми шукаємо, тільки з оцінкою часу компіляції і без накладних витрат під час виконання.

Замість того, щоб робити

(sizeof a / sizeof *a)

Змініть свій код так, щоб він став тепер

(sizeof array_size(a))

Крім того, якщо контейнер не має зворотний ітератор, ви можете використовувати реалізацію BOOST в reverse_iterator: boost.org/doc/libs/1_36_0/libs/iterator/doc / ...
МР24

здається, що ваш array_size працює для статично розподілених масивів, але для мене не вдалося, наприклад, коли було "new int [7]".
Нейт Парсонс

2
так, це є наміром :) new int [7] повертає вказівник. тому sizeof (new int [7]) повертає не 7 * sizeof (int), але повертає sizeof (int *). array_size викликає помилку компіляції для цього випадку, а не мовчки працювати.
Йоханнес Шауб - ліб

також спробуйте array_size (+ statically_allocated_array), що також не вдається, тому що оператор + перетворює масив вказівник на його перший елемент. використання простого розміру дасть вам ще раз розмір вказівника
Йоганнес Шауб - litb

Перше - чому не просто використовувати endі beginв зворотному порядку?
Томаш Зато - Відновіть Моніку

54

У C # за допомогою Visual Studio 2005 або новішої версії введіть 'forr' та натисніть [TAB] [TAB] . Це розшириться до forциклу, який йде назад через колекцію.

Настільки легко помилитися (принаймні для мене), що я подумав, що розмістити цей фрагмент було б хорошою ідеєю.

Це сказало, що мені подобається Array.Reverse()/ Enumerable.Reverse()а потім краще повторити вперед - вони чіткіше заявляють про наміри.


41

Я завжди вважаю за краще чіткий код проти типографічно приємного коду. Таким чином, я завжди використовував:

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

Ви можете розглянути це як стандартний спосіб циклічного зворотного руху.
Всього два мої центи ...


Працювали. Ще не робили цього в C #, але дякую.
PCPGMR

Так як, якщо масив дійсно великий (тому індекс повинен бути непідписаним типом)? size_t насправді не підписаний, чи не так?
lalala

Ви можете оголосити i як uint.Перегляньте пост Марка Гравелла.
Джек Гріффін

Не працює для неподписаного типу через обгортання, на відміну від типографічно приємного. Не добре.
Оцелот

17

У C # за допомогою Linq :

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

Це, безумовно, найпростіший, але зауважте, що в поточній версії CLR перетворення списку завжди є операцією O (n), а не операцією O (1), якою вона могла бути, якщо це IList <T>; дивіться connect.microsoft.com/VisualStudio/feedback/…
Грег Бук

1
Схоже .Reverse () робить локальну копію масиву, а потім повторює через нього назад. Скопіювати масив здається неефективним лише для того, щоб ви могли повторити його. Я думаю, що будь-яка публікація із "Linq" в ній гідна голосів. :)
MusiGenesis

Можливий компроміс, але тоді ви стикаєтеся з тим, що "проголосований відповідь" (i -> 0) може призвести до уповільнення коду, тому що (i) потрібно перевіряти розмір масиву кожен раз, коли він використовується як в індексі.
Keltex

1
Хоча я погоджуюся, що Linq чудова, проблема цього підходу полягає в тому, що ітератор не дозволяє вам вибірково вибирати елементи масиву - врахуйте ситуацію, коли ви хотіли переходити через частину масиву, але не весь річ ...
jesses.co.tt

11

Це, безумовно, найкращий спосіб для будь-якого масиву, довжина якого є підписаним інтегральним типом. Для масивів, довжини яких є непідписаним цілісним типом (наприклад, std::vectorв C ++), тоді вам потрібно трохи змінити умову кінця:

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

Якщо ви тільки що сказали i >= 0, це завжди справедливо для безпідписаного цілого числа, тому цикл буде нескінченним циклом.


У C ++ "i--" автоматично завершиться до найвищого значення, якби я був 0? Вибачте, я страждаю на C ++.
MusiGenesis

Дивовижний. Ви, хлопці, просто допомогли мені зрозуміти причину помилки заповнення жорсткого диска в чомусь, про що я писав 12 років тому. Добре, що я не працюю на C ++.
MusiGenesis

умовою припинення повинно бути: i <myArray.size (). Тільки залежить від властивостей переповнення неподписаного int; не деталі реалізації цілих чисел і записів. Отже, простіше читати та працює мовами з поперемінними цілими поданнями.
ejgottl

Я думаю, що кращим рішенням для мене є перебування у світі C #, де я нікому не можу заподіяти шкоду.
MusiGenesis

4

Мені добре виглядає. Якщо індексатор не був підписаний (uint тощо), можливо, вам доведеться це врахувати. Називайте мене ледачим, але в такому (непідписаному) випадку я можу просто використати зустрічну змінну:

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

(насправді навіть тут вам слід бути обережними щодо таких випадків, як arr.Length = uint.MaxValue ... можливо, a! = десь ... звичайно, це дуже малоймовірний випадок!)


Однак "--pos" річ хитра. У мене в минулому були співробітники, які захоплювались випадковим чином "виправленням" речей у коді, що їм не дуже подобалося.
MusiGenesis

1
Потім використовуйте .arr.Length-1 і pos--
Marc Gravell

4

У CI люблять це робити:


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

Приклад C #, доданий MusiGenesis:

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

Мені подобається це. Мені знадобилося секунда-дві, щоб зрозуміти, що працює твій метод. Сподіваємось, ви не заперечуєте над доданою версією C # (додаткові фігурні дужки такі, що індекс розміщується так само, як у циклі for).
MusiGenesis

Я не впевнений, що використовував би це у виробничому коді, оскільки це викликало б універсальне "WTF це?" реакція від того, хто мав її підтримувати, але насправді простіше набрати, ніж нормальний цикл, і без мінусових знаків або> = символів.
MusiGenesis

1
Шкода, що версія C # потребує додаткових "> 0".
MusiGenesis

Я не впевнений, що це за мова. але ваш перший фрагмент коду, звичайно, НЕ C :) просто щоб сказати вам очевидне. можливо ви хотіли написати C #, а html не вдалося його розібрати чи щось?
Йоханнес Шауб - ліб

@litb: Я думаю, що "myArray.Length" не дійсний C (?), але частина циклу while працює і виглядає чудово.
MusiGenesis

3

Найкращий спосіб зробити це в C ++ - це, ймовірно, використовувати ітераторні адаптери (або, краще, діапазон) адаптерів, які будуть ліниво трансформувати послідовність під час проходження.

В основному,

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

Відображає діапазон "діапазон" (тут він порожній, але я впевнений, що ви можете додавати елементи самостійно) у зворотному порядку. Звичайно просто ітерація діапазону не є великою користю, але передавати цей новий діапазон алгоритмам та іншим ресурсам - це дуже здорово.

Цей механізм можна також використовувати для набагато більш потужних напрямків:

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

Буде лінь обчислювати діапазон "діапазон", де функція "f" застосовується до всіх елементів, елементи, для яких "p" не відповідає дійсності, видаляються, і, нарешті, отриманий діапазон змінюється назад.

Синтаксис труби - це найбільш читаний IMO, враховуючи його інфікцію. Оновлення бібліотеки Boost.Range, що очікує на огляд, реалізує це, але зробити це також досить просто. Це ще крутіше з лямбда-DSEL для створення функції f та предиката p в рядку.


Ви можете нам сказати, звідки у вас "передбачення"? BOOST_FOREACH це #define? Виглядає мило весь шлях.
Йоханнес Шауб - ліб


1

Я віддаю перевагу циклу часу. Мені це більш зрозуміло, ніж декрементація iв умовах циклу for

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

0

Я б використовував код у вихідному запитанні, але якщо ви дійсно хотіли використовувати foreach та мати цілий індекс у C #:

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

-1

Я спробую відповісти на власне запитання тут, але мені це теж не подобається:

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

Замість того, щоб .Length - 1 - i кожного разу, можливо, розгляньте другу змінну? Дивіться мою [оновлену] публікацію.
Марк Гравелл

Проголосував на моєму власному питанні. Різкий. Це не будь-який незграбний, ніж оригінал.
MusiGenesis

-4

ПРИМІТКА: Ця публікація виявилася набагато детальнішою і тому поза темою, прошу вибачення.

Як сказано, мої однолітки читали це і вважають, що це «десь». Цій нитці не місце. Буду вдячний за ваші відгуки про те, куди це має йти (я новачок на сайті).


У будь-якому випадку це версія C # в .NET 3.5, яка дивовижна тим, що вона працює на будь-якому типі колекції з використанням визначеної семантики. Це міра за замовчуванням (повторне використання!), Неефективність або мінімізація циклу процесора в найбільш поширених сценаріях розробників, хоча це ніколи не відбувається у реальному світі (передчасна оптимізація).

*** Метод розширення, що працює над будь-яким типом колекції та приймає делегата дії, очікуючи єдиного значення типу, всі виконані над кожним елементом у зворотному порядку **

Потрібно 3.5:

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

Старіші версії .NET чи ви хочете краще зрозуміти внутрішність Linq? Читай далі .. Або ні ..

ПЕРЕДАЧА: У системі типу .NET тип масиву успадковується від інтерфейсу IEnumerable (не загального IEnumerable тільки IEnumerable).

Це все, що вам потрібно повторити від початку до кінця, проте ви хочете рухатись у зворотному напрямку. Оскільки IEnumerable працює над масивом типу 'object', будь-який тип є дійсним,

КРИТИЧНИЙ ВИМІР: Ми припускаємо, що якщо ви можете обробити будь-яку послідовність у зворотному порядку, який є "кращим", то зможете це зробити лише на цілі числа.

Рішення для .NET CLR 2.0-3.0:

Опис: Ми приймемо будь-який екземпляр IEnumerable, який має мандат, який містить кожен екземпляр одного типу. Отже, якщо ми отримуємо масив, весь масив містить екземпляри типу X. Якщо будь-які інші екземпляри типу! = X, викидається виняток:

Одномісна послуга:

публічний клас 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] тестування публічного класу123 {

    /// <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
Чувак чи дудет: Я просто шукав менш чіткий спосіб написання "for (int i = myArray.Length - 1; i> = 0; i--)".
MusiGenesis

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