Мне нужно вернуться назад по массиву, поэтому у меня есть такой код:
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
}
Я не уверен, что понимаю, почему какая-либо из альтернатив лучше, если совершенство включает ясность или ремонтопригодность.
Правда. Я надеялся найти более разумный способ сделать это, поскольку мне приходится делать это довольно часто.
Удалите C и C++ из заголовка и хэштегов.





Это определенно лучший способ для любого массива, длина которого представляет собой целочисленный тип со знаком. Для массивов, длина которых представляет собой целочисленный тип без знака (например, std::vector в C++), вам необходимо немного изменить конечное условие:
for(size_t i = myArray.size() - 1; i != (size_t)-1; i--)
// blah
Если вы только что сказали i >= 0, это всегда верно для целого числа без знака, поэтому цикл будет бесконечным.
В C++ "i--" автоматически переходит к максимальному значению, если я был равен 0? Извините, у меня проблемы с C++.
Удивительный. Вы, ребята, только что помогли мне понять причину ошибки заполнения жесткого диска в том, что я написал 12 лет назад. Хорошо, что я не работаю на C++.
условие завершения должно быть: i <myArray.size (). Зависит только от свойств переполнения unsigned int; а не детали реализации целых чисел и приведений. Следовательно, легче читать и работать на языках с альтернативными целочисленными представлениями.
Я думаю, что для меня лучшее решение - остаться в мире C#, где я не могу никому навредить.
Мне нравится. Если индексатор был беззнаковым (uint и т. д.), Возможно, вам придется это принять во внимание. Назовите меня ленивым, но в этом (беззнаковом) случае я мог бы просто использовать переменную-счетчик:
uint pos = arr.Length;
for(uint i = 0; i < arr.Length ; i++)
{
arr[--pos] = 42;
}
(на самом деле, даже здесь вам нужно быть осторожным с такими случаями, как arr.Length = uint.MaxValue ... может быть! = где-то ... конечно, это очень маловероятный случай!)
Однако с "--pos" сложно. В прошлом у меня были коллеги, которые любили случайным образом «исправлять» в коде вещи, которые им не казались подходящими.
Затем используйте .arr.Length-1 и pos--
Я попытаюсь ответить на свой вопрос здесь, но мне это тоже не очень нравится:
for (int i = 0; i < myArray.Length; i++)
{
int iBackwards = myArray.Length - 1 - i; // ugh
myArray[iBackwards] = 666;
}
Вместо того, чтобы каждый раз делать .Length - 1 - i, возможно, рассмотреть вторую переменную? См. Мой [обновленный] пост.
Проголосовал против моего собственного вопроса. Жесткий. Он не хуже оригинала.
В C# с использованием Linq:
foreach(var item in myArray.Reverse())
{
// do something
}
Это, безусловно, самый простой вариант, но обратите внимание, что в текущей версии CLR реверсирование списка всегда является операцией O (n), а не операцией O (1), как это могло бы быть, если это IList <T>; см. connect.microsoft.com/VisualStudio/feedback/…
Похоже, .Reverse () создает локальную копию массива, а затем выполняет итерацию в обратном направлении. Кажется неэффективным копировать массив только для того, чтобы вы могли его перебирать. Я полагаю, что любой пост с "Linq" достоин голосов. :)
Возможен компромисс, но тогда вы сталкиваетесь с проблемой, что «проголосовало за ответ» (i -> 0) может привести к более медленному коду, потому что (i) необходимо проверять на размер массива каждый раз, когда он используется как в index.
Хотя я согласен с тем, что Linq великолепен, проблема с этим подходом заключается в том, что итератор не позволяет вам выборочно выбирать элементы массива - рассмотрите ситуацию, когда вы хотите перемещаться по части массива, но не по всему массиву. предмет...
В C++ у вас есть выбор между итерацией с использованием итераторов или индексов.
В зависимости от того, используете ли вы простой массив или 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))
Кроме того, если в вашем контейнере нет обратного итератора, вы можете использовать реализацию reverse_iterator в boost: boost.org/doc/libs/1_36_0/libs/iterator/doc/…
ваш array_size, похоже, работает для статически распределенных массивов, но не работал для меня, например, когда a был 'new int [7]'.
да, это и есть цель :) new int [7] возвращает указатель. поэтому sizeof (new int [7]) возвращает не 7 * sizeof (int), а возвращает sizeof (int *). array_size в этом случае вызывает ошибку компиляции, вместо того, чтобы работать в автоматическом режиме.
также попробуйте array_size (+ statically_allocated_array), что также не удастся, потому что оператор + превращает массив в указатель на его первый элемент. использование простого sizeof снова даст вам размер указателя
Для начала - почему бы просто не использовать end и begin в обратном порядке?
for (auto i = someVector.size(); i--;) попроще.
Хотя это, по общему признанию, немного неясно, но я бы сказал, что наиболее приятный с точки зрения типографии способ сделать это -
for (int i = myArray.Length; i --> 0; )
{
//do something
}
Когда я впервые прочитал ваш ответ, казалось, что он даже не компилируется, поэтому я решил, что вы сумасшедший. Но это именно то, что я искал: лучший способ написать цикл for в обратном направлении.
Я думаю, что i -> 0; это специально. Вот что он имеет в виду под "типографически приятным".
Я сам нашел это "типографически запутанным". Для меня знак «-» выглядит неправильно, если он не находится рядом с переменной, на которую он влияет.
Я изменил его версию обратно. Таким образом, это выглядит лучше эстетически, но также похоже, что это даже не валидный код.
Это довольно круто - не уверен, будет ли это хорошей идеей использовать, но все же довольно круто
Я предпочитаю это: for (int i = myArray.Length; i-->0 ;) - думаю, это принесет одно дополнительное очко WTF.
@Hamish, к сожалению, это вызовет segfault, когда вы попытаетесь получить доступ к массиву [i], и он попадет в ячейку памяти сразу после конца массива. Если бы не segfault, в худшем случае вы бы никогда не попали в первый элемент массива, так как 0 > 0 == false.
@corsiKa: он никогда не получит доступ к myArray [i].
Это мило, но мило ≠ в хорошем стиле. Все, что замедляет чтение, - плохая идея. Кроме того, это больше, чем просто for (int i = myArray.Length; i--;) { … }.
Это слишком непонятно и запутанно. Я бы никогда не написал что-то подобное в производственном коде ...
Это так вводит в заблуждение. Думал, это другой оператор. Это не. Но круто.
Ааа, переходит к оператору (->) делает свое дело!
for (int i = myArray.Length; i-- > 0; ) Вот, починил.
Это не непонятно, это единственное правильное действие (после изменения int на size_t).
В то время как i приближается к 0, то есть: i --> 0; функционально такой же, как (i = i - 1) + 1 > 0. Какая красивая @Rune.
Разве myArray.Length не должен быть «myArray.Length -1», поскольку индексы массива начинаются с нуля?
@ PastExpiry.com нет, потому что первый шаг сравнения (и, следовательно, уменьшения) выполняется до первого выполнения тела цикла.
ПРИМЕЧАНИЕ: этот пост оказался более подробным и, следовательно, не по теме, прошу прощения.
При этом мои сверстники читают его и считают, что это «где-то» ценно. Эта ветка не к месту. Буду признателен за ваш отзыв о том, куда это следует направить (я новичок на сайте).
В любом случае это версия 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;
}
}
Чувак или чувак: я просто искал менее неуклюжий способ написать «for (int i = myArray.Length - 1; i> = 0; i--)».
в этом ответе нет никакой ценности
Я бы использовал код в исходном вопросе, но если вы действительно хотите использовать foreach и иметь целочисленный индекс в C#:
foreach (int i in Enumerable.Range(0, myArray.Length).Reverse())
{
myArray[i] = 42;
}
В C мне нравится делать это:
int i = myArray.Length;
while (i--) {
myArray[i] = 42;
}
Пример C#, добавленный MusiGenesis:
{int i = myArray.Length; while (i-- > 0)
{
myArray[i] = 42;
}}
Мне это нравится. Мне потребовалась секунда или две, чтобы понять, что ваш метод работает. Надеюсь, вы не возражаете против добавленной версии C# (дополнительные фигурные скобки предназначены для того, чтобы индекс имел ту же область видимости, что и в цикле for).
Я не уверен, что смогу использовать это в производственном коде, потому что это вызовет универсальный "WTF is this?" реакция со стороны тех, кто должен был его поддерживать, но на самом деле это легче набрать, чем обычный цикл for, и без знаков минус или символов> =.
Жаль, что версии C# нужно дополнительное "> 0".
Я не знаю, на каком это языке. но ваш первый фрагмент кода, безусловно, НЕ С :), просто чтобы сказать вам очевидное. может быть, вы хотели написать C#, а html не смог его разобрать или что-то в этом роде?
@litb: я думаю, что "myArray.Length" недействителен C (?), но часть цикла while работает и отлично выглядит.
Ой. Ваша правильная инициализация i - это не C. Я просто пытался использовать исходный фрагмент OP, чтобы проиллюстрировать точку.
Выглядит здорово, но, согласен, на первый взгляд сбивает с толку.
В C#, используя Visual Studio 2005 или новее, введите "forr" и нажмите [TAB] [TAB]. Это расширится до цикла for, который проходит через коллекцию в обратном направлении.
Так легко ошибиться (по крайней мере, для меня), что я подумал, что вставить этот фрагмент будет хорошей идеей.
Тем не менее, мне нравятся Array.Reverse() / Enumerable.Reverse(), а затем лучше повторять вперед - они более четко формулируют намерение.
Лучший способ сделать это в 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? Выглядит мило во всех отношениях.
// this is how I always do it
for (i = n; --i >= 0;){
...
}
Я бы предпочел всегда чистый код вместо кода типографически приятный. Таким образом, я бы всегда использовал:
for (int i = myArray.Length - 1; i >= 0; i--)
{
// Do something ...
}
Вы можете рассматривать это как стандартный способ обратного цикла. Всего два цента ...
Работал. Еще не делал этого на C#, но спасибо.
Так как же быть, если массив действительно большой (поэтому индекс должен быть беззнакового типа)? size_t на самом деле беззнаковый, не так ли?
Вы можете объявить i как uint (см. Сообщение Марка Гравелла).
Не работает для беззнакового шрифта из-за переноса текста, в отличие от типографически приятного шрифта. Нехорошо.
@lalala Вы можете использовать long long. Он имеет как минимум 64 бита, поэтому в вашем массиве может быть до 2 ^ 63 записей. Этого должно хватить на время ...
Я предпочитаю цикл while. Для меня это более понятно, чем уменьшение i в состоянии цикла for
int i = arrayLength;
while(i)
{
i--;
//do something with array[i]
}
Для 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
}
}
По большей части этот вопрос представляет собой своего рода обзор ответов ниже. Предлагаем значительно сократить его.