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

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

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
}

19 ответов

Решение

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

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

В 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))

Я всегда предпочел бы чистый код против "типографски приятного" кода. Таким образом, я бы всегда использовал:

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

Вы можете считать это стандартным способом обратной петли.
Просто мои два цента...

В C#, используя Visual Studio 2005 или более позднюю версию, введите "forr" и нажмите [TAB] [TAB]. Это расширится до for цикл, который идет назад через коллекцию.

Так легко ошибиться (по крайней мере, для меня), что я подумал, что вставить этот фрагмент будет хорошей идеей.

Тем не менее, мне нравится Array.Reverse() / Enumerable.Reverse() и затем итерируйте вперед лучше - они более четко заявляют намерение.

В C# с использованием Linq:

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

Это определенно лучший способ для любого массива, длина которого является целочисленным типом со знаком. Для массивов, длина которых является целым типом без знака (например, std::vector в C++), вам нужно немного изменить конечное условие:

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

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

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


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

Пример C#, добавленный MusiGenesis:

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

Выглядит хорошо для меня. Если индексатор не подписан (uint и т. Д.), Возможно, вам придется принять это во внимание. Назовите меня ленивым, но в этом (без знака) случае я мог бы просто использовать встречную переменную:

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

(на самом деле, даже здесь вы должны быть осторожны с такими случаями, как arr.Length = uint.MaxValue... может быть!= где-то... конечно, это очень маловероятный случай!)

Лучший способ сделать это в 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 в строке.

Я предпочитаю цикл времени. Для меня это более понятно, чем уменьшение i в состоянии цикла for

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

я делаю это

      if (list.Count > 0)
    for (size_t i = list.Count - 1; i >= 0; i--)
    {
        //do your thing
    
        if (i == 0) //for preventing unsigned wrap
            break;
    }

но по какой-то причине Visual Studio 2019 злится и предупреждает меня о "нечетком цикле" или что-то в этом роде .. мне не доверяют

// this is how I always do it
for (i = n; --i >= 0;){
   ...
}

Если вы используете C++ и хотите использовать , а не ,

      for (size_t i = yourVector.size(); i--;) {
    // i is the index.
}

(Обратите внимание, что-1интерпретируется как большое положительное число, если это , таким образом, типичный цикл for, такой какfor (int i = yourVector.size()-1; i>=0; --i)не работает, еслиsize_tиспользуется вместоint.)

Я бы использовал код в исходном вопросе, но если бы вы действительно хотели использовать foreach и иметь целочисленный индекс в C#:

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

Для C++:

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

      std::vector<int> vec = {1,2,3,4};
for (auto it = vec.rbegin(); it != vec.rend(); ++it) {
    std::cout<<*it<<" ";
}

отпечатки 4 3 2 1 .

Вы также можете изменить значение во время цикла:

      std::vector<int> vec = {1,2,3,4};
for (auto it = vec.rbegin(); it != vec.rend(); ++it) {
    *it = *it + 10;
    std::cout<<*it<<" ";
}

ведущий к 14 13 12 11 печатается и {11, 12, 13, 14} находясь в std::vector после.

Если вы не планируете изменять значение во время цикла, вы должны убедиться, что вы получаете сообщение об ошибке, когда пытаетесь сделать это случайно, аналогично тому, как можно было бы написать for(const auto& element : vec). Это возможно так:

      std::vector<int> vec = {1,2,3,4};
for (auto it = vec.crbegin(); it != vec.crend(); ++it) { // used crbegin()/crend() here...
    *it = *it + 10; // ... so that this is a compile-time error
    std::cout<<*it<<" ";
}

Ошибка компилятора в этом случае для меня:

      /tmp/main.cpp:20:9: error: assignment of read-only location ‘it.std::reverse_iterator<__gnu_cxx::__normal_iterator<const int*, std::vector<int> > >::operator*()’
   20 |     *it = *it + 10;
      |     ~~~~^~~~~~~~~~

Также обратите внимание, что вы не должны использовать вместе разные типы итераторов:

      std::vector<int> vec = {1,2,3,4};
for (auto it = vec.rbegin(); it != vec.end(); ++it) { // mixed rbegin() and end()
    std::cout<<*it<<" ";
}

приводит к многословной ошибке:

      /tmp/main.cpp: In function ‘int main()’:
/tmp/main.cpp:19:33: error: no match for ‘operator!=’ (operand types are ‘std::reverse_iterator<__gnu_cxx::__normal_iterator<int*, std::vector<int> > >’ and ‘std::vector<int>::iterator’ {aka ‘__gnu_cxx::__normal_iterator<int*, std::vector<int> >’})
   19 | for (auto it = vec.rbegin(); it != vec.end(); ++it) {
      |                              ~~ ^~ ~~~~~~~~~
      |                              |            |
      |                              |            std::vector<int>::iterator {aka __gnu_cxx::__normal_iterator<int*, std::vector<int> >}
      |                              std::reverse_iterator<__gnu_cxx::__normal_iterator<int*, std::vector<int> > >

Если у вас есть массивы в стиле C в стеке, вы можете сделать следующее:

      int vec[] = {1,2,3,4};
for (auto it = std::crbegin(vec); it != std::crend(vec); ++it) {
    std::cout<<*it<<" ";
}

Если вам действительно нужен индекс, рассмотрите следующие варианты:

  • проверьте диапазон, затем работайте со знаковыми значениями, например:
      void loop_reverse(std::vector<int>& vec) {
    if (vec.size() > static_cast<size_t>(std::numeric_limits<int>::max())) {
        throw std::invalid_argument("Input too large");
    }
    const int sz = static_cast<int>(vec.size());
    for(int i=sz-1; i >= 0; --i) {
        // do something with i
    }
}
  • Работайте с беззнаковыми значениями, будьте осторожны и добавляйте комментарии, например:
      void loop_reverse2(std::vector<int>& vec) {
    for(size_t i=vec.size(); i-- > 0;) { // reverse indices from N-1 to 0
        // do something with i
    }
}
  • рассчитать фактический индекс отдельно, например:
      void loop_reverse3(std::vector<int>& vec) {
    for(size_t offset=0; offset < vec.size(); ++offset) {
        const size_t i = vec.size()-1-offset; // reverse indices from N-1 to 0
        // do something with i
    }
}

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

Я попытаюсь ответить на свой вопрос здесь, но мне это тоже не очень нравится:

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

Не то, чтобы это имело значение после 13+ лет, но только в образовательных целях и немного тривиального обучения;

Исходный код был;

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

Вам действительно не нужно снова проверять, что «i» больше или равно нулю, поскольку вам просто нужно только получить «ложный» результат, чтобы завершить цикл. Следовательно, вы можете просто сделать это, когда вы только проверяете само «i», если оно истинно или ложно, поскольку оно будет (неявно) ложным, когда достигнет нуля.;

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

Как я уже сказал, на самом деле это не имеет значения, просто интересно понять механику того, что происходит внутри цикла for().

ПРИМЕЧАНИЕ: этот пост оказался гораздо более подробным и поэтому не по теме, я прошу прощения.

При этом мои коллеги читают его и верят, что это "где-то". Эта тема не место. Буду признателен за ваши отзывы о том, куда это должно пойти (я новичок на сайте).


В любом случае, это версия 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', допустим любой тип,

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

Решение для.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;
    }
}
Другие вопросы по тегам