Какой лучший способ сделать обратный цикл в 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 злится и предупреждает меня о "нечетком цикле" или что-то в этом роде .. мне не доверяют
Если вы используете 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;
}
}