Как реверсировать строку с
Как реверсировать строку с
К сожалению, класс String в C# не содержит метода Reverse(), поэтому вот три способа как можно реверсировать строку:
1. Ручное реверсирование
Самый очевидный способ это реверсировать строку с помощью ручного прохода по ее символам и формирования новой строки:
Учтите, что если вы воспользуетесь этим подходом в серьезной программе, вы должны использовать StringBuilder вместо String, так как в вышеприведенном коде новая временная строка создается каждый раз когда вы добавляете отдельный символ в выходную строку.
2. Array.Reverse()
Вторым способ с помощью которого мы можем реверсировать строку является статический метод Array.Reverse():
Сначала мы конвертируем строку в массив символов, затем реверсируем массив, и наконец формируем новую строку из масиива.
3. LINQ
LINQ позволяет нам сократить реверсирование строки в одну линию:
Сначала строка снова конвертируется в массив символов. Массив (Array) реализует интерфейс IEnumerable, так что мы можем вызвать LINQ метод Reverse() на нем. С помощью вызова метода ToArray() результирующий IEnumerable снова конвертируется в массив символов, которы используется в конструкторе строки.
Для максимального удобства мы можем создать метод расширения (extension method) для класса String чтобы выполнять реверсирование:
С помощью этого класса в нашем коде мы теперь можем с легкостью реверсировать любую строку:
Однако учтите, что все методы для реверсирования строки приведенные выше работают только с 16 битными символами Unicode, которые помещаются в одну символьную переменную. Если вам необходимо реверсировать строки которые содержат расширенные символы Unicode, например chinese kanji, смотрите здесь.
Под катом приведен сравнительный анализ быстродействия разных методов переворачивания строки.
Кхрр-р-р… — сказала бензопила.
Тююю… — сказали мужики.
© Старый анекдот.
Ну что ж, начнем. Все реализации проверялись на быстродействие с одной строкой размером 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 нужного размера и идем по строке в обратном порядке, добавляя символы в новую строку.
Пробуем, запускаем, да… Как-то медленно работает это все, будем копать дальше.
2. ReverseArrayFramework
Ха! Так этот билдер же обставлен проверками для обеспечения потокобезопасности со всех сторон, не, нам такое не надо. Но строка — это ведь массив сиволов. Так давайте его и перевернем, а результат преобразуем обратно в строку:
Совсем другое дело, получилось в 3.5 раза быстрее. Хм, а может можно еще лучше?
3. ReverseArrayManual
Так, думаем. Во-первых у нас данные копируются дважы: сначала из строки в массив, потом внутри массива. Во-вторых Array.Reverse — библиотечный метод, значит в нем есть проверки входных данных. Более того, для атомарных типов он явно реализован в виде native метода, а это дополнительное переключение контекста выполнения. Попробуем перевернуть строку в массив вручную:
4. ReverseManualHalf
Идем дальше. У нас же действия симметричны относительно середины строки, значит можно пустить два индекса навстречу и ученьшить количество итераций вдвое:
Хм… Что-то пошло не так, на системе с медленной памятью скорость упала в полтора раза. Учитывая специфику конфигураций и реализации, можем предположить, что виноваты скорость памяти и кэш процессора: раньше мы работали с двумя отдаленными областями памяти одновременно, теперь их стало четыре, соответсвенно подхват данных выполняется чаще.
LINQ и метод Reverse
Есть еще относительно красивый и короткий способ с LINQ, но он не выдерживает никакой критики в плане производительности — работает в 3-3.5 раза медленнее метода на базе StringBuilder. Виной тому прокачивание данных через IEnumerable и виртуальный вызов на каждую итерацию. Для желающих, ниже приведена реализация:
Использование памяти
Проблема не столь критичная в большинстве случаев, но все «быстрые» из рассмотренных методов делают промежуточную копию строки в виде массива символов. На синтетических тестах это проявляется в том, что обернуть строку размером 512МБ смог только первый метод, остальные свалились по System.OutOfMemoryException. Также, не следует забывать, что лишние временные объекты повышают частоту срабатывания GC, а он хоть и оптимизирован до ужаса, но все-равно время кушает. В следующей части будем кроме скоростных оптимизаций также искать решение этой проблемы.
Часть вторая: когда хочется быстрее и эффективнее, или unsafe code.
Использование unsafe кода дает нам одно интересное преимущество: строки, которые раньше были immutable, теперь можно менять, но нужно быть предельно осторожным и изменять только копии строк — библиотека минимизирует количество копий одной строки, а вместе с интернированием строк это может привести к печальным последствиями для приложения.
Вооружившись этими знаниями пишем следующие две реализации.
5. ReverseUnsafeFill
Делаем строку из пробелов и заполняем ее в обратном порядке:
6. ReverseUnsafeCopy
Копируем и переворачиваем строку:
Как показали замеры, вторая версия работает заметно быстрее на медленной памяти и слегка медленнее на быстрой 🙂 Причин видимо несколько: оперирование двумя отдаленными областями памяти вместо четырех и различие в скорости копирования блока памяти и простого его заполнения в цикле. Желающие могут попробовать сделать версию ReverseUnsafeFill с полным проходом (это может уменьшить число захватов данных из памяти в кэш) и испытать ее на медленной памяти, однако у меня есть основания считать, что это будет все-равно медленнее ReverseUnsafeCopy (хотя могу и ошибаться).
7. ReverseUnsafeXorCopy
А что дальше? Ходят слухи, что обмен при помощи оператора XOR работает быстрее копирования через третью переменную (кстати в плюсах это еще и смотрится довольно красиво: «a ^= b ^= a ^= b;», в C#, увы, такая строка не cработает). Ну что, давайте проверим на деле.
В итоге получается в 1.2-1.5 раза медленнее обмена копированием. Трюк, работавший для быстрого обмена значений на регистрах, для переменных себя не оправдал (что характено, во многих компиляторах С/С++ он тоже выиграша не дает).
В поисках объяснения этого факта полезем внутрь приложения и почитаем результирующий CIL код.
Почему обмен через XOR оказался хуже
Для получения ответа на этот вопрос стоит посмотреть на CIL-код, сгенерированный для двух способов обмена. Чтоб эти инструкции казались понятнее, поясню их назначение: ldloc.N — загружает на стек локальную переменную под номером N, stloc.N — считывает верхушку стека в локальную переменную номер N, xor — вычисляет значение операции XOR для двух значений наверху стека и загружает результат на стек вместо них.
Перевернуть строку в Python
Обзор трех основных способов перевернуть строку Python. Также известная как «срез», обратная итерация и классический алгоритм переворота на месте. Также вы увидите показатели производительности выполняемого кода.
Какой лучший способ перевернуть строки Python? Разумеется, переворот строк не используется так часто в повседневном программировании, однако это нередкий вопрос во время интервью:
Одна из вариаций этого вопроса — это написать функцию, которая проверяет, является ли заданная строка палиндромом, т.е., читается ли она одинаково в правильном и в обратном порядке:
Очевидно, нам нужно выяснить, как перевернуть строку для реализации функции is_palindrome в Python… как это сделать?
В данном руководстве мы изучим три основных способа перевернуть строку в Python:
Переворот строки Python при помощи среза
Строки следуют протоколу последовательности Python. И все последовательности поддерживают любопытную функцию под названием срез. Вы можете смотреть на срез как на расширение синтаксиса индексирования квадратных скобок.
Есть вопросы по Python?
На нашем форуме вы можете задать любой вопрос и получить ответ от всего нашего сообщества!
Telegram Чат & Канал
Вступите в наш дружный чат по Python и начните общение с единомышленниками! Станьте частью большого сообщества!
Паблик VK
Одно из самых больших сообществ по Python в социальной сети ВК. Видео уроки и книги для вас!
Это включает в себя отдельный случай, где срез последовательности с “[::-1]” создает перевернутую копию. Так как строки Python являются последовательностями, это быстрый и простой способ получить отраженную копию строки:
Конечно, вы можете вставить срез в функцию, чтобы сделать более очевидным то, что делает код:
Как вам такое решение?
Это быстро и удобно. Но, на мой взгляд, главный недостаток переворота строки при помощи среза заключается в том, что он использует продвинутую возможность Python, которую многие разработчики могут назвать «тайной и древней».
Я не виню их — срез списка может быть трудным для понимания, особенно если вы впервые сталкиваетесь с этим непонятным лаконичным синтаксисом.
Когда я читаю код Python, в котором используется срез, мне, как правило, приходиться задерживаться и фокусироваться, чтобы мысленно разобрать утверждение, чтобы убедиться в том, что я правильно понимаю происходящее.
Самой большой проблемой для меня является то, что синтаксис среза “[::-1]” недостаточно явно информирует о том, что он создает отраженную копию оригинальной строки.
По этой причине я думаю, что использование функцию среза в Python для переворота строки — достойное решение, но это может быть сложно для чтения неподготовленному человеку.
Перевернуть сроку при помощи reversed() и str.join()
Переворот строки с обратной итерацией при помощи встроенной функции reversed() — еще один способ сделать это. Вы получаете обратный итератор, который можно использовать цикличного перемещения элементов строки в обратном порядке:
Использование reversed() не модифицирует оригинальную строку (что не сработало бы в любом случае, так как строки неизменны в Python). Происходит следующее: вы получаете «вид» существующей строки, который вы можете использовать для обзора всех элементов в обратном порядке.
Это сильная техника, которая использует преимущество протокола итерации Python.
Итак, все что вы видели — это способы итерации над символами строки в обратном порядке. Но как использовать эту технику для создания отраженной копии строки Python при помощи функции reversed()?
Сделаем это вот так:
Конечно, вы можете еще раз извлечь этот код в отдельную функцию для создания надлежащей функции «перевернутой строки» в Python. Вот так:
Мне действительно нравится этот подход обратного итератора для переворота строк в Python.
Он четко сообщает о том, что происходит, и даже новичок в языке на интуитивном уровне может понять, что я создаю перевернутую копию оригинальной строки.
И хотя понимание работы итераторов на глубоком уровне — полезно и похвально, это не абсолютная необходимость для использования этой техники.
«Классический» алгоритм переворота строк Python
Это классический алгоритм переворачивания строк из учебников, портированный для Python. Так как строки Python являются неизменными, вам для начала нужно конвертировать вводимую строку в меняемый список символов, таким образом вы сможете выполнить смену символов на месте:
Как вы видите, это решение не то чтобы родное для Python, и не то, чтобы идиоматическое. Здесь не используются возможности Python и вообще, это явный порт алгоритма из языка программирования C.
И если этого не достаточно — это самое медленное решение, как вы увидите в следующем разделе, я буду проводить бенчмаркинг по всем трем реализациям.
Сравнение производительности
После реализации всех трех подходов к переворачиванию строк, показанных в этом руководстве, мне стало любопытна их относительная производительность.
Так что я провел небольшой бенчмаркинг:
Хорошо, это интересно… вот результаты в форме таблицы:
Как вы видите, есть огромная разница в производительности между этими тремя реализациями.
Срез — самый быстрый подход, reversed() медленнее среза в 8 раз, и «классический» алгоритм медленнее в 71 раз в этой проверке!
Теперь, смену символов на месте определенно можно оптимизировать (сообщите в комментариях внизу о вашем решении по улучшению, если хотите) — однако это сравнение производительности дает нам явное представление о том, какая операция отражения является самой быстрой в Python.
Итог: Переворачивания строк в Python
Переворачивание строк — это стандартная операция в программировании (и во время интервью). В этом руководстве вы узнали о трех разных подходах к переворачиванию строк в Python.
Давайте проведем краткий обзор каждого из способов, перед тем как я дам рекомендации о каждом варианте:
Вариант 1: срез списка [::-1]
Вы можете использовать синтаксис среза Python для создания перевернутой копии строки. Это хорошо работает, однако синтаксис может быть непонятным для пользователей Python.
Вариант 2: reversed() and str.join()
Встроенная функция reversed() позволяет вам создать отраженный итератор строки Python (или любой другой последовательный объект). Это гибкое и простое решение, которое использует определенные продвинутые функции Python, но при этом остается читаемым благодаря четкому названию reversed()
Вариант 3: «Крутите сами»
Использование стандартного алгоритма замены символов с предварительным портированием в Python работает, но сопровождается вялой производительностью и читаемостью, в сравнении с другими способами.
Если вы думаете о том, какой из способов подходит для переворачивания строки лучше всего, мой ответ: «В зависимости от ситуации». Лично я предпочитаю подход с использованием функции reversed(), так как она объясняет саму себя и по понятным причинам быстрая.
Однако, также присутствует аргумент, где наш подход среза является в 8 раз быстрее, что может быть предпочтительно, если есть необходимость в производительности.
В зависимости от вашего случая, это может быть грамотным решением. Кроме этого, это весьма уместная ситуация для цитаты Дональда Кнута:
Программисты тратят огромное количество времени думая, или переживая о скорости некритичных частей программ, и эти попытки увеличения эффективности на самом деле оказывают сильное негативное воздействие при отладке и обслуживании.
Мы должны забыть о существовании несущественной эффективности, скажем, в 97% случаев: преждевременная оптимизация — корень зла.
Однако мы должны прилагать все усилия в этих критических 3%.»
— Дональд Кнут
По этой причине, я бы не стал беспокоиться о производительности переворачивания строк в ваших программах, только если это не интегральная часть того, что делает ваш софт. Если вы отражаете миллионы строк в узком цикле, любой ценой оптимизируйте скорость.
Но для обычного приложения Python это не даст существенной разницы. Так что я выбираю наиболее читаемым (и следовательно, поддерживаемым) подходом.
В моем случае это вариант 2: reversed() + join().
Если вы хотите углубиться в вопрос, вы можете найти море информации в документации и интернете. Кстати, комментарии в разделе ниже приветствуются! Поделитесь с нами вашими любимыми техниками отражения строк.
Являюсь администратором нескольких порталов по обучению языков программирования Python, Golang и Kotlin. В составе небольшой команды единомышленников, мы занимаемся популяризацией языков программирования на русскоязычную аудиторию. Большая часть статей была адаптирована нами на русский язык и распространяется бесплатно.
E-mail: vasile.buldumac@ati.utm.md
Образование
Universitatea Tehnică a Moldovei (utm.md)
Реверсирование строки в C
Я разработал программу обратной строки. Мне интересно, есть ли лучший способ сделать это, и если у моего кода есть какие-либо потенциальные проблемы. Я ищу, чтобы практиковать некоторые расширенные функции C.
23 ответов
если вы хотите практиковать расширенные функции C, как насчет указателей? Мы можем бросить макросы и XOR-swap для удовольствия тоже!
мы можем делать простую арифметику с указателями. Когда мы увеличиваем (или уменьшаем) указатель, мы просто перемещаем его, чтобы ссылаться на следующий (или предыдущий) расположение памяти для этого типа значения. Приращение указателей разные типы могут перемещать указатель на разное число байты, потому что разные значения имеют разные размеры байтов в C.
так как я использовал только XOR_SWAP макрос однажды, вероятно, было излишне определять его, но теперь стало ясно, что я делаю. После препроцессора C расширяет макро, цикл while выглядит так:
тогда это расширится до
имеющего тройной в операции приращения / уменьшения и на самом деле не делать своп это делать.
таким образом, значения меняются местами. У этого есть одна ошибка-когда a и b являются одной и той же переменной:
с мы!—39—>, это никогда не происходит в вышеуказанном коде, поэтому мы в порядке.
есть куча C для изучения. Весело с ним!
обновление: mmw поднимает хороший момент, который заключается в том, что вы должны быть немного осторожны, как вы вызываете это, поскольку он работает на месте.
заставит ваш код гореть и умирать во время выполнения. Это потому что string_literal просто указывает на строку, которая хранится в часть вашего исполняемого файла-это обычно память, которую вы не можете редактировать ОС. В более счастливом мире ваш компилятор знал бы это и кашлял бы ошибкой, когда вы пытались скомпилировать, говоря вам, что string_literal должен быть типа char const * так как вы не можете изменить содержимое. Однако это не тот мир, в котором живет мой компилятор.
есть некоторые хаки, которые вы можете попытаться убедиться, что некоторая память находится в стеке или в куче (и поэтому редактируется), но они не обязательно портативный, и он может быть довольно уродливым. Тем не менее, я более чем счастлив возложить ответственность за это на функцию invoker. Я сказал им, что эта функция выполняет манипуляции с памятью на месте, это их обязанность дать мне аргумент, который позволяет это.
просто перестановка и проверка безопасности. Я также удалил ваш неиспользуемый тип возврата. Я думаю, что это безопасно и чисто, как он получает:
отредактировано так, чтобы конец не указывал на возможно плохое расположение памяти, когда strlen равен 0.
ф-ция реверса строки

Пишу программу реверса строки (меняет местами первый символ и последний, второй и предпоследний и.
незнаю, нубик я еще, сделал как смог)
Добавлено через 4 минуты
просто я вот через дебагер смотрел и когда я в 1й строке ф-ции созданному указателью присваиваю указатель на переданную в ф-цию строку то вижу что оба указателя видят строку полностью, а не один символ, как я предполагал, но в то же время узнать длинну строки я не могу, и судя по увиденному через дебаг передавая указатель ф-ция, каким-то образом, принимает и данные о кол-ве символов в строке и что это за символы. Интересно, можно ли как-то выковырять эти данные не передавая их в ф-цию явно.
нет. дебагер просто идет по куску памяти до нулевого символа и показывает содержимое в виде текста
Добавлено через 1 минуту
а вообще я бы пару моментов убрал но это не принципиально
Добавлено через 34 секунды
igor001, Ну как прошол собеседование? Я кодато тоже проходил токо по С там задания простенькие были, там мне после него предложили питон учить со стипендией в 200 долларов, я тогда отказался мб зря, наверно заметили, что я шото шарю от и предложили, если б по С++, то я б согласился не раздумывая, а так питон с нуля начинать, хоть и не бесплатно ну его нафиг. Это одно было собеседование простенькое, а так сразу задания присылали тоже простые, но там оконные приложения нужно было делать, а я не шарю как, хотя задания простые очень были.
Добавлено через 2 минуты
Наоборот мне так проще. Да можно string просто принимать и возвращать string.
Добавлено через 1 минуту
А что за переполнение буфера? вродеж длинны кургом совпадают c_str возвращает строку с нулем, а динна str равна новой строке.
Добавлено через 59 секунд
Ну да тут нужно плюс 1 добавить char* obcj=new char[strlen(str)]; чтобы 8 символов было





