Reverse c что это
Перейти к содержимому

Reverse c что это

  • автор:

Reverse a String in C

The string reverse technique refers to the change in the order of characters of a given string so that the last character of the string gets changed with the first character and so on. In other words, swapping occurs. While working with strings, you may encounter various scenarios to perform several manipulations. We shall look at ways to achieve string reversal in C and explore some in-built methods to do the same.

Introduction

Let us consider a situation where you need to check whether a string is a palindrome or not. (A palindrome is a string that reads the same backward as forwards, for example, madam). The string reversal technique can be used in this case. If the reversed and original strings are the same, the string is a palindrome. There can be many such scenarios where string reversal is needed. In C language, there are several built-in methods to perform string reversal.

How to Reverse a String in C?

There are various ways to reverse a string in C. In this shall, we shall mainly be covering the following ways:

Reverse a string using the strrev() function

Reverse a string without using the library function

Reverse a string using the recursion function

Reverse a string using for loop

Reverse a string using a while loop

Reverse a string using pointers

Let us look at these ones by one.

Reverse a String Using the strrev() Function

strrev() is an in built function defined in the string.h header file.

Syntax: Syntax of the strrev() function or in other words, it is written as:

Parameters: It takes a single parameter.

str : String that is to be reversed

Return Value: It returns the modified version of the reversed string.

Let us look at an example.

Output:

In the above program, strrev( ) is called on the given string, and reversed string is obtained. Notice that strrev() is not standard function so it may not work in modern compilers. One can write user defined strrev() function.

Reverse a String Without Using the Library Function

In the above section, we used an in-built function to reverse the string in C. Let us define our function to do the same. In this case, we shall first calculate the length of the string first, and run a for loop to assign characters of another string equal to the characters of the original string in reverse order.

Let us see how:

Output:

In the above program, we calculated the length of the original string and declared an empty string. Further, all the character values of the original string were assigned to that of the new string in reverse order. The iteration was done till the calculated length.

Reverse a String Using For Loop

In this section, we shall use a for loop to reverse a string in C. A basic swapping technique will be used, where we swap the string's first character by value with the last and so on. This swapping is implemented inside the for loop.

Let us see how:

Output:

In the above program, we first calculated the length of the string and iterated over the length to swap the characters. Finally, the reversed string is printed.

Reverse a String Using While Loop

This way is similar to the one where we used the for loop. The only difference is that it is replaced while the rest of the code remains the same.

Let us look at the implementation:

Output:

Reverse a String Using the Recursion Function

To make the code more efficient, we shall use the recursion to replace the basic iteration. The swapping technique remains the same. We shall call the function containing logic recursively, and character by character swapping shall occur.

Let us look at the implementation:

Output:

In the above program, the reverse function is called recursively. The characters are swapped to get the reverse string.

Reverse a String Using Pointers

Let us implement the string reverse technique using two pointers, start and end. Start points to the beginning of the string, whereas the end to the last character of the string. Character by character swapping occurs, and the pointers are incremented or decremented.

Let's see the code:

Output:

In the above program, we declared two pointers, start and end . The swapping is achieved with the help of these pointers, start keeps incrementing, whereas end keeps decrementing during the iteration over the length of the string.

О разворачивании строк в .Net/C# и не только

Давайте поговорим о строках, точнее об их переворачивании средствами .Net/C#. Так уж сложилось, что в стандартной библиотеке соответсвующей функции не наблюдается. И как мне подсказывают, написание функции обращения строки довольно популярный вопрос на собеседованиях при приеме на работу. Давайте посмотрим, как можно эффективно перевернуть строку средствами данной платформы.

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

Кхрр-р-р… — сказала бензопила.
Тююю… — сказали мужики.
© Старый анекдот.

Ну что ж, начнем. Все реализации проверялись на быстродействие с одной строкой размером 256 мегабайт (128×1024×1024 символа) и 1024×1024 строками размером 256 байт (128 символов). Перед каждым замером форсировалась сборка мусора (что важно при таком размере тестовых данных), замер проводился 50 раз, 20 крайних отбрасывались, остальные значения усреднялись. Условными попугаями было выбрано количество тиков, выдаваемое объектом класса Stopwatch.

Тест проводился на двух компьютерах: Athlon64 x2 4200+, 2GB Dual-Channel DDR2 RAM и Pentium4 HT 3GHz, 3GB DDR RAM. Главным отличием между конфигурациями в данном тесте является быстродействие связки память-кэш — вторая система в этом отношении заметно медленнее.

С техническими формальностями покончено, теперь перейдем к собственно реализациям. Для большей наглядности, здесь не учтены unicode surrogate code points. Рассматривать подходы будем в порядке логичности и «красивости» реализации в контексте окружающей ее «экосистемы».

Сравнительные результаты замеров находятся в последней части этой заметки. Оптимальной в общем случае оказалась функция ReverseUnsafeCopy, если же ограничиваться только safe code — ReverseArrayManual. Если необходим safe code и огромные строки — прийдется мучаться с ReverseStringBuilder.

Часть первая: «нормальные» методы.
1. ReverseStringBuilder

Будем следовать рекомендациям и для построения «большой» строки возьмем специальный инструмент — класс StringBuilder. Идея проста до ужаса: создаем builder нужного размера и идем по строке в обратном порядке, добавляя символы в новую строку.

  1. static string ReverseStringBuilder( string str)
  2. <
  3. StringBuilder sb = new StringBuilder (str.Length);
  4. for ( int i = str.Length; i— != 0 ; )
  5. sb.Append(str[i]);
  6. return sb.ToString();
  7. >

Пробуем, запускаем, да… Как-то медленно работает это все, будем копать дальше.

2. ReverseArrayFramework

Ха! Так этот билдер же обставлен проверками для обеспечения потокобезопасности со всех сторон, не, нам такое не надо. Но строка — это ведь массив сиволов. Так давайте его и перевернем, а результат преобразуем обратно в строку:

  1. static string ReverseArrayFramework( string str)
  2. <
  3. char [] arr = str.ToCharArray();
  4. Array .Reverse(arr);
  5. return new String (arr);
  6. >

Совсем другое дело, получилось в 3.5 раза быстрее. Хм, а может можно еще лучше?

3. ReverseArrayManual

Так, думаем. Во-первых у нас данные копируются дважы: сначала из строки в массив, потом внутри массива. Во-вторых Array.Reverse — библиотечный метод, значит в нем есть проверки входных данных. Более того, для атомарных типов он явно реализован в виде native метода, а это дополнительное переключение контекста выполнения. Попробуем перевернуть строку в массив вручную:

  1. static string ReverseArrayManual( string originalString)
  2. <
  3. char [] reversedCharArray = new char [originalString.Length];
  4. for ( int i = originalString.Length — 1 ; i > — 1 ; i—)
  5. reversedCharArray[originalString.Length — i — 1 ] = originalString[i];
  6. return new string (reversedCharArray);
  7. >
4. ReverseManualHalf

Идем дальше. У нас же действия симметричны относительно середины строки, значит можно пустить два индекса навстречу и ученьшить количество итераций вдвое:

  1. static string ReverseManualHalf( string originalString)
  2. <
  3. char [] reversedCharArray = new char [originalString.Length];
  4. int i = 0 ;
  5. int j = originalString.Length — 1 ;
  6. while (i <= j)
  7. <
  8. reversedCharArray[i] = originalString[j];
  9. reversedCharArray[j] = originalString[i];
  10. i++; j—;
  11. >
  12. return new string (reversedCharArray);
  13. >

Хм… Что-то пошло не так, на системе с медленной памятью скорость упала в полтора раза. Учитывая специфику конфигураций и реализации, можем предположить, что виноваты скорость памяти и кэш процессора: раньше мы работали с двумя отдаленными областями памяти одновременно, теперь их стало четыре, соответсвенно подхват данных выполняется чаще.

LINQ и метод Reverse

Есть еще относительно красивый и короткий способ с LINQ, но он не выдерживает никакой критики в плане производительности — работает в 3-3.5 раза медленнее метода на базе StringBuilder. Виной тому прокачивание данных через IEnumerable и виртуальный вызов на каждую итерацию. Для желающих, ниже приведена реализация:

  1. static string ReverseStringLinq( string originalString)
  2. <
  3. return new string (originalString.Reverse().ToArray());
  4. >
Использование памяти

Проблема не столь критичная в большинстве случаев, но все «быстрые» из рассмотренных методов делают промежуточную копию строки в виде массива символов. На синтетических тестах это проявляется в том, что обернуть строку размером 512МБ смог только первый метод, остальные свалились по System.OutOfMemoryException. Также, не следует забывать, что лишние временные объекты повышают частоту срабатывания GC, а он хоть и оптимизирован до ужаса, но все-равно время кушает. В следующей части будем кроме скоростных оптимизаций также искать решение этой проблемы.

Часть вторая: когда хочется быстрее и эффективнее, или unsafe code.

Использование unsafe кода дает нам одно интересное преимущество: строки, которые раньше были immutable, теперь можно менять, но нужно быть предельно осторожным и изменять только копии строк — библиотека минимизирует количество копий одной строки, а вместе с интернированием строк это может привести к печальным последствиями для приложения.

Итак, создав новую строку нужного размера, мы можем смотреть на нее, как на массив и заполнить нужными данными. Ну и не стоит забывать об отстутствии проверок на валидность индексов, что тоже ускорит работу кода. Однако в силу специфики строк в .Net, мы не можем вот так просто создать строку нужной длины. Можно либо сделать строку из повторяющегося символа (например проблела) при помощи конструктора String(char, int), либо скопировать исходную строку используя String.Copy(String).

Вооружившись этими знаниями пишем следующие две реализации.

5. ReverseUnsafeFill

Делаем строку из пробелов и заполняем ее в обратном порядке:

  1. static unsafe string ReverseUnsafeFill( string str)
  2. <
  3. if (str.Length <= 1 ) return str;
  4. String copy = new String ( ‘ ‘ , str.Length);
  5. fixed ( char * buf_copy = copy)
  6. <
  7. fixed ( char * buf = str)
  8. <
  9. int i = 0 ;
  10. int j = str.Length — 1 ;
  11. while (i <= j)
  12. <
  13. buf_copy[i] = buf[j];
  14. buf_copy[j] = buf[i];
  15. i++; j—;
  16. >
  17. >
  18. >
  19. return copy;
  20. >
6. ReverseUnsafeCopy

Копируем и переворачиваем строку:

  1. static unsafe string ReverseUnsafeCopy( string str)
  2. <
  3. if (str.Length <= 1 ) return str;
  4. char tmp;
  5. String copy = String .Copy(str);
  6. fixed ( char * buf = copy)
  7. <
  8. char * p = buf;
  9. char * q = buf + str.Length — 1 ;
  10. while (p < q)
  11. <
  12. tmp = *p;
  13. *p = *q;
  14. *q = tmp;
  15. p++; q—;
  16. >
  17. >
  18. return copy;
  19. >

Как показали замеры, вторая версия работает заметно быстрее на медленной памяти и слегка медленнее на быстрой 🙂 Причин видимо несколько: оперирование двумя отдаленными областями памяти вместо четырех и различие в скорости копирования блока памяти и простого его заполнения в цикле. Желающие могут попробовать сделать версию ReverseUnsafeFill с полным проходом (это может уменьшить число захватов данных из памяти в кэш) и испытать ее на медленной памяти, однако у меня есть основания считать, что это будет все-равно медленнее ReverseUnsafeCopy (хотя могу и ошибаться).

7. ReverseUnsafeXorCopy

А что дальше? Ходят слухи, что обмен при помощи оператора XOR работает быстрее копирования через третью переменную (кстати в плюсах это еще и смотрится довольно красиво: «a ^= b ^= a ^= b;», в C#, увы, такая строка не cработает). Ну что, давайте проверим на деле.

  1. static unsafe string ReverseUnsafeXorCopy( string str)
  2. <
  3. if (str.Length <= 1 ) return str;
  4. String copy = String .Copy(str);
  5. fixed ( char * buf = copy)
  6. <
  7. char * p = buf;
  8. char * q = buf + str.Length — 1 ;
  9. while (p < q)
  10. <
  11. *p ^= *q;
  12. *q ^= *p;
  13. *p ^= *q;
  14. p++; q—;
  15. >
  16. >
  17. return copy;
  18. >

В итоге получается в 1.2-1.5 раза медленнее обмена копированием. Трюк, работавший для быстрого обмена значений на регистрах, для переменных себя не оправдал (что характено, во многих компиляторах С/С++ он тоже выиграша не дает).

В поисках объяснения этого факта полезем внутрь приложения и почитаем результирующий CIL код.

Часть третья: лезем в CIL и коды библиотек .Net.
Почему обмен через XOR оказался хуже

Для получения ответа на этот вопрос стоит посмотреть на CIL-код, сгенерированный для двух способов обмена. Чтоб эти инструкции казались понятнее, поясню их назначение: ldloc.N — загружает на стек локальную переменную под номером N, stloc.N — считывает верхушку стека в локальную переменную номер N, xor — вычисляет значение операции XOR для двух значений наверху стека и загружает результат на стек вместо них.

  1. int a, b;
  2. int tmp = a;
  3. a = b;
  4. b = tmp;
  1. int a, b;
  2. a ^= b;
  3. b ^= a;
  4. a ^= b;
Как работает Array.Reverse?

Ну и пока мы смотрим на внутренности сборок рефлектором, есть смысл взглянуть на реализацию библиотечных методов, использованных в первой части. Особняком здесь стоит Array.Reverse, который опирается на некую функцию Array.TrySZReverse, реализованную в виде native метода. Итак качаем Shared Source Common Language Infrastructure 2.0 — исходник .net 2.0 и смотрим, что ж это за зверь такой 🙂 После недолгих поисков, в файле «sscli20\clr\src\vm\comarrayhelpers.h» находится шаблонная функция Reverse (в данном случае KIND будет соответствовать UINT16), которая (сюрприз!) до ужаса похожа на реализацию ReverseUnsafeCopy.

  1. static void Reverse( KIND array[], UNIT32 index, UNIT32 count) <
  2. LEAF_CONTRACT;
  3. _ASSERTE(array != NULL);
  4. if (count == 0) <
  5. return ;
  6. >
  7. UNIT32 i = index;
  8. UNIT32 j = index + count — 1 ;
  9. while (i < j) <
  10. KIND temp = array[i];
  11. array[i] = array[j];
  12. array[j] = temp;
  13. i++;
  14. j—;
  15. >
  16. >
Часть четвертая: результаты бинарного марафона.

Анализ результатов замеров приведен в первых двух частях, здесь же изображены лишь сравнительные диаграммы отображающие отношение быстродействия рассмотренных выше функций. Точные числа в «ticks» слишком зависят от конфигурации и вряд-ли представляют особый интерес, все желающие могут самостоятельно замерить быстродействие используя предоставленные куски кода.

Сравнение лучших методов из первых двух частей

На быстрой памяти
Image and video hosting by TinyPic
На медленной памяти
Image and video hosting by TinyPic

Сравнение всех методов в Debug и Release конфигурациях на быстрой памяти

Большие строки
Image and video hosting by TinyPic
Короткие строки
Image and video hosting by TinyPic

Сравнение всех методов в Debug и Release конфигурациях на медленной памяти

Большие строки
Image and video hosting by TinyPic
Короткие строки
Image and video hosting by TinyPic

Reverse C: учимся работать с обратной последовательностью в языке программирования C

Reverse C — это функция в языке программирования Си, которая позволяет инвертировать содержимое массива элементов. То есть, она переворачивает элементы в обратном порядке. Зачастую данная возможность используется для реализации алгоритмов нахождения палиндромов, перестановок, сортировок и тому подобных задач.

Пример функции, которая инвертирует массив элементов int в C:

Данная функция принимает два аргумента: указатель на начало массива и размер массива. Она работает следующим образом: цикл for проходится по половине массива и меняет местами элементы, начиная с первого и последнего, затем со второго и предпоследнего и так далее, пока не перевернет весь массив.

Пример использования данной функции:

Этот код определяет массив <1, 2, 3, 4, 5>и размер массива, затем выводит его на экран с помощью цикла for. Далее используется функция reverse для обращения порядка элементов в массиве и затем тот же массив выводится на экран.

Результат выполнения данной программы:

Таким образом, функция reverse позволяет легко и удобно инвертировать содержимое массива элементов на языке программирования Си.

std::reverse() in C++

reverse() is a predefined function in header file algorithm. It is defined as a template in the above-mentioned header file. It reverses the order of the elements in the range [first, last) of any container. The time complexity is O(n).
Note: The range used is [first, last), which contains all the elements between first and last, including the element pointed by first but not the element pointed by last.

Syntax:

Examples:

Time complexity: O(n)
Auxiliary Space: O(1)

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *