Каков наилучший способ сделать обратный цикл в C / C# / C++?
мне нужно двигаться назад через массив, поэтому у меня есть такой код:
for (int i = myArray.Length - 1; i >= 0; i--)
{
// Do something
myArray[i] = 42;
}
есть ли лучший способ сделать это?
Update: я надеялся, что, возможно, у C# был какой-то встроенный механизм для этого, например:
foreachbackwards (int i in myArray)
{
// so easy
}
Обновление 2: Есть are лучшие способы. Руна берет приз с:
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
}
15 ответов:
хотя, по общему признанию, немного неясно, я бы сказал, что наиболее типографически приятный способ сделать это
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
of: N * 1, что мы и ищем, только с оценкой времени компиляции и нулевыми накладными расходами во время выполнения.вместо
(sizeof a / sizeof *a)
измените свой код так, чтобы он теперь делал
(sizeof array_size(a))
В C#, используя Visual Studio 2005 или более позднюю версию,введите 'forr' и нажмите [TAB] [TAB]. Это будет расширяться до
for
цикл, который идет назад по коллекции.это так легко ошибиться (по крайней мере для меня), что я думал, что положить этот фрагмент будет хорошей идеей.
что сказал, мне нравится
Array.Reverse()
/Enumerable.Reverse()
а затем повторить вперед лучше - они более четко заявить о намерениях.
Я бы всегда предпочитаю четкий код против 'типографским способом приятно'. Таким образом, я всегда буду использовать :
for (int i = myArray.Length - 1; i >= 0; i--) { // Do something ... }
вы можете рассматривать его как стандартный способ петли назад.
Только мои два цента...
Это определенно лучший способ для любого массива, длина которого является целочисленным типом со знаком. Для массивов, длина которых является беззнаковым целочисленным типом (например,
std::vector
в C++), то вам нужно немного изменить граничное условие:for(size_t i = myArray.size() - 1; i != (size_t)-1; i--) // blah
Если ты только что сказал
i >= 0
, это всегда верно для целого числа без знака, поэтому цикл будет бесконечным циклом.
выглядит хорошо для меня. Если индексатор был без знака (uint и т. д.), Вам, возможно, придется принять это во внимание. Назовите меня ленивым, но в этом (без знака) случае я мог бы просто использовать переменную счетчика:
uint pos = arr.Length; for(uint i = 0; i < arr.Length ; i++) { arr[--pos] = 42; }
(на самом деле, даже здесь вам нужно быть осторожным в таких случаях, как arr.Длина = uint имеет.Максвелл... может быть != где-то... конечно, это очень маловероятно!)
В C мне нравится это делать:
int i = myArray.Length; while (i--) { myArray[i] = 42; }
C# пример добавлен MusiGenesis:
{int i = myArray.Length; while (i-- > 0) { myArray[i] = 42; }}
лучший способ сделать это в C++, вероятно, использовать итератор (или лучше, диапазон) адаптеры, которые будут лениво преобразовывать последовательность по мере ее прохождения.
по сути,
vector<value_type> range; foreach(value_type v, range | reversed) cout << v;
отображает диапазон "диапазон" (здесь он пуст, но я уверен, что вы можете добавить элементы самостоятельно) в обратном порядке. Конечно, просто повторять диапазон не очень полезно, но передача этого нового диапазона алгоритмам и прочее довольно круто.
этот механизм также может используйте для гораздо более мощных применений:
range | transformed(f) | filtered(p) | reversed
будет лениво вычислять диапазон "диапазон", где функция" f "применяется ко всем элементам, элементы, для которых" p " не является истинным, удаляются, и, наконец, результирующий диапазон отменяется.
синтаксис трубы является наиболее читаемым IMO, учитывая его инфикс. повышение.Обновление библиотеки диапазона в ожидании обзора реализует это, но довольно просто сделать это самостоятельно. Это еще более круто с лямбда DSEL для генерации функции f и предикат p в строке.
Я предпочитаю цикл while. Это более ясно для меня, чем уменьшение
i
в состоянии цикла forint i = arrayLength; while(i) { i--; //do something with array[i] }
Я бы использовал код в исходном вопросе, но если вы действительно хотите использовать foreach и иметь целочисленный индекс в C#:
foreach (int i in Enumerable.Range(0, myArray.Length).Reverse()) { myArray[i] = 42; }
Я не уверен, что понимаю, почему любая из альтернатив лучше, если доброта включает ясность или ремонтопригодность.
Я попробую ответить на свой собственный вопрос здесь, но мне это тоже не нравится:
for (int i = 0; i < myArray.Length; i++) { int iBackwards = myArray.Length - 1 - i; // ugh myArray[iBackwards] = 666; }
Примечание: этот пост оказался гораздо более подробным и, следовательно, не по теме, я прошу прощения.
это, как говорится, мои сверстники читают его и считают, что это ценно "где-то". Этой теме не место. Я был бы признателен за ваши отзывы о том, где это должно идти (я новичок на сайте).
В любом случае это версия C# в .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 тип массива наследуется от интерфейса IEnumerable (не общий IEnumerable только IEnumerable).
Это все, что вам нужно повторить от начала до конца, однако вы хотите двигаться в противоположном направлении. Поскольку IEnumerable работает с массивом типа 'object', допустим любой тип,
критическая мера: мы предполагаем, что если вы можете обрабатывать любую последовательность в обратном порядке, что "лучше" , то только будучи в состоянии сделать это на целые.
решение a для .NET CLR 2.0-3.0:
описание: мы примем любой экземпляр реализации IEnumerable с мандатом, что каждый экземпляр, который он содержит, имеет тот же тип. Поэтому, если мы получаем массив, весь массив содержит экземпляры типа X. Если какие-либо другие экземпляры имеют тип !=X выбрасывается исключение:
одноэлементный услуги:
public class 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; } }