Какой лучший способ сделать обратный цикл в C / C # / C ++?


102

Мне нужно вернуться назад по массиву, поэтому у меня есть такой код:

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
}

1
Я не уверен, что понимаю, почему какая-либо из альтернатив лучше, если качество включает ясность или ремонтопригодность.
dkretz

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

4
Большая часть этого вопроса представляет собой своего рода обзор ответов ниже. Предлагаю значительно сократить его.
einpoklum 06

Удалите C и C ++ из заголовка и хэштегов.
jaskmar

Ответы:


150

Хотя это, по общему признанию, немного неясно, но я бы сказал, что наиболее приятный с точки зрения типографии способ сделать это

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

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

4
Я думаю, что i -> 0; это специально. Вот что он имеет в виду под «типографически приятным»
Йоханнес Шауб - лит

16
Я сам нашел это "типографически запутанным". Для меня знак «-» не выглядит правильным, если он не находится рядом с переменной, на которую он влияет.
MusiGenesis

26
Это слишком непонятно и запутанно. Я бы никогда не написал что-то подобное в производственном коде ...
Михай Тодор

9
Ааа, оператор перехода (->) делает свое дело!
nawfal

118

В C ++ у вас есть выбор между итерацией с использованием итераторов или индексов. В зависимости от того, есть ли у вас простой массив или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]; ... */
}

Как избежать ошибок с применением sizeof к указателям

На самом деле описанный выше способ определения размера массива - отстой. Если 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))

Кроме того, если в вашем контейнере нет обратного итератора, вы можете использовать реализацию reverse_iterator в boost: boost.org/doc/libs/1_36_0/libs/iterator/doc/…
MP24,

ваш array_size, похоже, работает для статически распределенных массивов, но не удался для меня, например, когда a был 'new int [7]'.
Нейт Парсонс,

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

также попробуйте array_size (+ statically_allocated_array), что также не удастся, потому что оператор + превращает массив в указатель на его первый элемент. использование простого sizeof снова даст вам размер указателя
Йоханнес Шауб - 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) необходимо проверять на размер массива каждый раз, когда он используется как в index.
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 (). Зависит только от свойств переполнения unsigned 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 ... может быть! = где-то ... конечно, это очень маловероятный случай!)


Однако с "--pos" сложно. В прошлом у меня были коллеги, которые любили случайным образом «исправлять» в коде вещи, которые им не казались подходящими.
MusiGenesis

1
Затем используйте .arr.Length-1 и pos--
Marc Gravell

4

В C мне нравится это делать:


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 is this?" реакция тех, кто должен был его поддерживать, но на самом деле набирать его легче, чем обычный цикл for, и без знаков минус или символов> =.
MusiGenesis

1
Жаль, что версии C # нужно дополнительное "> 0".
MusiGenesis

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

@litb: я полагаю, что "myArray.Length" недействителен C (?), но часть цикла while работает и отлично выглядит.
MusiGenesis

3

Лучший способ сделать это в C ++ - это, вероятно, использовать адаптеры итератора (или, лучше, диапазона), которые будут лениво преобразовывать последовательность по мере ее прохождения.

В принципе,

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

Отображает диапазон "range" (здесь он пуст, но я уверен, что вы можете добавлять элементы самостоятельно) в обратном порядке. Конечно, простая итерация диапазона не очень полезна, но передача этого нового диапазона в алгоритмы и прочее - это довольно круто.

Этот механизм также можно использовать для гораздо более мощных целей:

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

Будет лениво вычислять диапазон «range», где функция «f» применяется ко всем элементам, элементы, для которых «p» не соответствует действительности, удаляются, и, наконец, результирующий диапазон меняется на противоположный.

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


Вы можете сказать нам, откуда у вас "foreach"? Это #define для BOOST_FOREACH? Выглядит мило во всем.
Йоханнес Шауб - лит


1

Я предпочитаю цикл while. Для меня это более понятно, чем уменьшение 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 тип Array наследуется от интерфейса IEnumerable (а не от универсального IEnumerable, только от IEnumerable).

Это все, что вам нужно повторить от начала до конца, однако вы хотите двигаться в обратном направлении. Поскольку IEnumerable работает с массивом типа 'object', допустим любой тип,

КРИТИЧЕСКОЕ ИЗМЕРЕНИЕ: Мы предполагаем, что если вы можете обработать любую последовательность в обратном порядке, что «лучше», то это можно сделать только с целыми числами.

Решение a для .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] открытый класс Testing123 {

    /// <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.