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

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

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

Есть ли лучший способ сделать это?

Обновление: я надеялся, что, возможно, у С# был встроенный механизм для этого:

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

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

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

Ответ 2

В С++ у вас есть основной выбор между итерацией с использованием итераторов или индексов. В зависимости от того, есть ли у вас простой массив или std::vector, вы используете разные методы.

Использование std::vector

Использование итераторов

С++ позволяет это сделать с помощью 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 of: 1. Таким образом, возвращаемый массив будет иметь sizeof из: N * 1, который мы ищем, только с оценкой времени компиляции и служебными данными с нулевым временем выполнения.

Вместо того, чтобы делать

(sizeof a / sizeof *a)

Измените свой код так, чтобы он теперь выполнял

(sizeof array_size(a))

Ответ 3

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

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

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

Ответ 4

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

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

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

Ответ 5

В С# с помощью Linq:

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

Ответ 6

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

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

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

Ответ 7

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

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

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

Ответ 8

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


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

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

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

Ответ 9

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

В принципе,

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

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

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

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

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

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

Ответ 10

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

Ответ 11

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

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

Ответ 12

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

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

Ответ 13

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

Ответ 14

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

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

Ответ 15

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

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


В любом случае это версия С# в .NET 3.5, которая поразительна тем, что она работает с любым типом коллекции, используя определенную семантику. Это стандартная мера (повторное использование!), А не минимальная производительность или минимизация цикла процессора в большинстве распространенных сценариев, хотя, похоже, это никогда не происходит в реальном мире (преждевременная оптимизация).

*** Метод расширения, работающий над любым типом коллекции и принимающий делегат действия, ожидающий единственное значение типа, все выполняемое над каждым элементом в обратном порядке **

Requres 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 работает в массиве типа "объект", любой тип действителен,

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

Решение 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;
    }
}