Обратное преобразование строки в Python

В Python нет встроенной функции reverse для объекта str. Как лучше всего реализовать этот метод?

Если ответ будет очень кратким, пожалуйста, расскажите о его эффективности. Например, преобразуется ли объект str в другой объект и т.д.

Решение

Как насчет:

>>> 'hello world'[::-1]
'dlrow olleh'

Это синтаксис extended slice. Он работает по принципу [begin:end:step] - оставляя begin и end без внимания и указывая шаг -1, он переворачивает строку.

Комментарии (2)

@Paolo's[::-1] - самый быстрый; более медленный подход (возможно, более читабельный, но это спорно) - ''.join(reversed(s)).

Комментарии (4)

Какой лучший способ реализовать обратную функцию для строк?

Мой собственный опыт в этом вопросе академический. Однако, если вы профессионал, ищущий быстрый ответ, используйте фрагмент, который шагает по -1:

>>> 'a string'[::-1]
'gnirts a'

или более читаемо (но медленнее из-за поиска имени метода и того факта, что объединение формирует список при предоставлении итератора), str.join:

>>> ''.join(reversed('a string'))
'gnirts a'

или для удобочитаемости и повторного использования, поместите срез в функцию

def reversed_string(a_string):
    return a_string[::-1]

а потом:

>>> reversed_string('a_string')
'gnirts_a'

Более длинное объяснение

Если вы заинтересованы в академической экспозиции, пожалуйста, продолжайте читать.

В объекте Python str нет встроенной обратной функции.

Вот пара вещей о строках Python, которые вы должны знать:

  1. В Python струны неизменны . Изменение строки не изменяет строку. Это создает новый.

  2. Струны нарезаются. Нажатие строки дает вам новую строку из одной точки в строке, назад или вперед, в другую точку с заданными приращениями. Они берут нотацию среза или объект среза в нижнем индексе:

     string [subscript]

Нижний индекс создает срез, включая двоеточие в фигурных скобках:

    string[start:stop:step]

Чтобы создать срез вне скобок, вам нужно создать объект среза:

    slice_obj = slice(start, stop, step)
    string[slice_obj]

Читаемый подход:

Хотя ' .join (reversed ('foo'))читаем, для этого требуется вызвать строковый метод str.join для другой вызываемой функции, которая может быть относительно медленной. Давайте включим это в функцию - мы вернемся к этому:

def reverse_string_readable_answer(string):
    return ''.join(reversed(string))

Самый эффективный подход:

Гораздо быстрее использовать обратный срез:

'foo'[::-1]

Но как мы можем сделать это более читабельным и понятным для кого-то, кто менее знаком с ломтиками или намерением оригинального автора? Давайте создадим объект среза за пределами обозначения индекса, дадим ему описательное имя и передадим его в обозначение индекса.

start = stop = None
step = -1
reverse_slice = slice(start, stop, step)
'foo'[reverse_slice]

Реализовать как функцию

Чтобы реально реализовать это как функцию, я думаю, что это достаточно семантически ясно, чтобы просто использовать описательное имя:

def reversed_string(a_string):
    return a_string[::-1]

И использование просто:

reversed_string('foo')

Чего, вероятно, хочет ваш учитель:

Если у вас есть инструктор, они, вероятно, хотят, чтобы вы начали с пустой строки и создали новую строку из старой. Вы можете сделать это с помощью чистого синтаксиса и литералов, используя цикл while:

def reverse_a_string_slowly(a_string):
    new_string = ''
    index = len(a_string)
    while index:
        index -= 1                    # index = index - 1
        new_string += a_string[index] # new_string = new_string + character
    return new_string

Это теоретически плохо, потому что, помните, струны неизменны - поэтому каждый раз, когда кажется, что вы добавляете символ в свою new_string, он теоретически создает новую строку каждый раз! Тем не менее, CPython знает, как оптимизировать это в определенных случаях, одним из которых является этот тривиальный случай.

Лучшая практика

Теоретически лучше собрать ваши подстроки в списке и присоединиться к ним позже:

def reverse_a_string_more_slowly(a_string):
    new_strings = []
    index = len(a_string)
    while index:
        index -= 1                       
        new_strings.append(a_string[index])
    return ''.join(new_strings)

Однако, как мы увидим в приведенных ниже таймингах для CPython, это на самом деле занимает больше времени, поскольку CPython может оптимизировать конкатенацию строк.

Сроки

Вот тайминги:

>>> a_string = 'amanaplanacanalpanama' * 10
>>> min(timeit.repeat(lambda: reverse_string_readable_answer(a_string)))
10.38789987564087
>>> min(timeit.repeat(lambda: reversed_string(a_string)))
0.6622700691223145
>>> min(timeit.repeat(lambda: reverse_a_string_slowly(a_string)))
25.756799936294556
>>> min(timeit.repeat(lambda: reverse_a_string_more_slowly(a_string)))
38.73570013046265

CPython оптимизирует конкатенацию строк, тогда как другие реализации не могут:

... не полагайтесь на эффективную реализацию CPython встроенной конкатенации строк для операторов в форме a + = b или a + b . Эта оптимизация хрупка даже в CPython (она работает только для некоторых типов) и вообще не присутствует в реализациях, которые не используют рефонтинг. В чувствительных к производительности частях библиотеки вместо этого следует использовать форму '' .join (). Это обеспечит конкатенацию в линейное время для различных реализаций.

Комментарии (1)

Быстрый ответ (TL; DR)

  ### Пример
### example01 -------------------
mystring  =   'coup_ate_grouping'
backwards =   mystring[::-1]
print backwards

### ... or even ...
mystring  =   'coup_ate_grouping'[::-1]
print mystring

### result01 -------------------
'''
gnipuorg_eta_puoc
'''

Подробный ответ

Фон

Этот ответ предоставлен для решения следующей проблемы от @odigity:

Ух ты. Сначала я был в ужасе от решения, предложенного Паоло, но это отошел на второй план от ужаса, который я почувствовал, прочитав первый комментарий: «Это очень питонично. Хорошая работа!"Я так обеспокоен, что такой яркое сообщество думает, что использует такие загадочные методы для чего-то такого Базовая это хорошая идея. Почему это не просто s.reverse ()?

Проблема

  • Контекст
    • Python 2.x
    • Python 3.x
  • Сценарий:
    • Разработчик хочет преобразовать строку
    • Преобразование - это изменение порядка всех символов

Решение

  • example01 дает желаемый результат, используя [расширенную нотацию среза](https://docs.python.org/2/whatsnew/2.3.html?выделить = расширенные% 20slice # расширенные срезы).

Подводные камни

  • Разработчик может ожидать что-то вроде string.reverse ()
  • Решение нативного идиоматического (он же «pythonic») может быть недоступным для новых разработчиков
  • Разработчик может испытать желание реализовать свою собственную версию string.reverse (), чтобы избежать нотации срезов.
  • Выход обозначения среза может быть нелогичным в некоторых случаях:
    • см., например., example02
      • print 'coup_ate_grouping' [-4:] ## = > 'пинг'
      • по сравнению с
      • print 'coup_ate_grouping' [-4: -1] ## = > 'булавка'
      • по сравнению с
      • print 'coup_ate_grouping' [-1] ## = > 'Г'
    • различные результаты индексации на [-1] могут сбить некоторых разработчиков

Обоснование

У Python есть особые обстоятельства, о которых следует знать: строка является типом iterable.

Одним из обоснований исключения метода string.reverse () является стимулирование разработчиков python использовать силу этого особого обстоятельства.

В упрощенном выражении это просто означает, что каждый отдельный символ в строке может легко управляться как часть последовательного расположения элементов, как массивы в других языках программирования.

Чтобы понять, как это работает, обзор example02 может дать хороший обзор.

Example02

### example02 -------------------
## start (with positive integers)
print 'coup_ate_grouping'[0]  ## => 'c'
print 'coup_ate_grouping'[1]  ## => 'o' 
print 'coup_ate_grouping'[2]  ## => 'u' 

## start (with negative integers)
print 'coup_ate_grouping'[-1]  ## => 'g'
print 'coup_ate_grouping'[-2]  ## => 'n' 
print 'coup_ate_grouping'[-3]  ## => 'i' 

## start:end 
print 'coup_ate_grouping'[0:4]    ## => 'coup'    
print 'coup_ate_grouping'[4:8]    ## => '_ate'    
print 'coup_ate_grouping'[8:12]   ## => '_gro'    

## start:end 
print 'coup_ate_grouping'[-4:]    ## => 'ping' (counter-intuitive)
print 'coup_ate_grouping'[-4:-1]  ## => 'pin'
print 'coup_ate_grouping'[-4:-2]  ## => 'pi'
print 'coup_ate_grouping'[-4:-3]  ## => 'p'
print 'coup_ate_grouping'[-4:-4]  ## => ''
print 'coup_ate_grouping'[0:-1]   ## => 'coup_ate_groupin'
print 'coup_ate_grouping'[0:]     ## => 'coup_ate_grouping' (counter-intuitive)

## start:end:step (or start:end:stride)
print 'coup_ate_grouping'[-1::1]  ## => 'g'   
print 'coup_ate_grouping'[-1::-1] ## => 'gnipuorg_eta_puoc'

## combinations
print 'coup_ate_grouping'[-1::-1][-4:] ## => 'puoc'

Вывод

Когнитивная нагрузка, связанная с пониманием того, как в python работает запись срезов, действительно может быть слишком большой для некоторых пользователей и разработчиков, которые не хотят вкладывать много времени в изучение язык.

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

Для тех, кто думает иначе, существуют альтернативные подходы, такие как лямбда-функции, итераторы или простые одноразовые объявления функций.

При желании разработчик может реализовать свой собственный метод string.reverse (), однако полезно понять обоснование этого аспекта питона.

Смотрите также

Комментарии (0)

Менее озадачивающий способ взглянуть на это будет:

string = 'happy'
print(string)

«Счастливый»

string_reversed = string[-1::-1]
print(string_reversed)

«Иппа»

На английском языке [-1 :: - 1] читается как:

«Начиная с -1, пройдите весь путь, делая шаги -1»

Комментарии (1)

Существующие ответы верны только в том случае, если игнорируются модификаторы Unicode / кластеры графемы. Я разберусь с этим позже, но сначала взгляну на скорость некоторых алгоритмов реверсирования

[![введите описание изображения здесь][1]][1]

list_comprehension  : min:   0.6μs, mean:   0.6μs, max:    2.2μs
reverse_func        : min:   1.9μs, mean:   2.0μs, max:    7.9μs
reverse_reduce      : min:   5.7μs, mean:   5.9μs, max:   10.2μs
reverse_loop        : min:   3.0μs, mean:   3.1μs, max:    6.8μs

[![введите описание изображения здесь][2]][2]

list_comprehension  : min:   4.2μs, mean:   4.5μs, max:   31.7μs
reverse_func        : min:  75.4μs, mean:  76.6μs, max:  109.5μs
reverse_reduce      : min: 749.2μs, mean: 882.4μs, max: 2310.4μs
reverse_loop        : min: 469.7μs, mean: 577.2μs, max: 1227.6μs

Вы можете видеть, что время для понимания списка (reversed = string [:: -1]) во всех случаях является самым низким (даже после исправления моей опечатки).

Реверсация струн

Если вы действительно хотите изменить строку в здравом смысле, это НАМНОГО сложнее. Например, возьмите следующую строку (коричневый палец, указывающий влево, [желтый палец, указывающий вверх](https:// emojipedia.org/white-up-pointing-backhand-index/)). Это две графемы, но 3 кодовых точки Unicode. Дополнительным является модификатор кожи.

example = "👈🏾👆"

Но если вы измените его любым из указанных методов, вы получите коричневый палец, указывающий вверх, желтый палец, указывающий влево. Причина этого заключается в том, что «коричневый» модификатор цвета все еще находится посередине и применяется ко всему, что есть до него. Итак, у нас есть

  • U: палец вверх
  • М: коричневый модификатор
  • L: палец, указывающий влево

а также

original: LMU
reversed: UML (above solutions)
reversed: ULM (correct reversal)

Unicode Grapheme Clusters немного сложнее, чем просто кодовые точки модификатора. К счастью, есть библиотека для обработки graphemes:

>>> import grapheme
>>> g = grapheme.graphemes("👈🏾👆")
>>> list(g)
['👈🏾', '👆']

и, следовательно, правильный ответ будет

def reverse_graphemes(string):
    g = list(grapheme.graphemes(string))
    return ''.join(g[::-1])

который также является безусловно самым медленным:

list_comprehension  : min:    0.5μs, mean:    0.5μs, max:    2.1μs
reverse_func        : min:   68.9μs, mean:   70.3μs, max:  111.4μs
reverse_reduce      : min:  742.7μs, mean:  810.1μs, max: 1821.9μs
reverse_loop        : min:  513.7μs, mean:  552.6μs, max: 1125.8μs
reverse_graphemes   : min: 3882.4μs, mean: 4130.9μs, max: 6416.2μs

Код


#!/usr/bin/env python

import numpy as np
import random
import timeit
from functools import reduce
random.seed(0)

def main():
    longstring = ''.join(random.choices("ABCDEFGHIJKLM", k=2000))
    functions = [(list_comprehension, 'list_comprehension', longstring),
                 (reverse_func, 'reverse_func', longstring),
                 (reverse_reduce, 'reverse_reduce', longstring),
                 (reverse_loop, 'reverse_loop', longstring)
                 ]
    duration_list = {}
    for func, name, params in functions:
        durations = timeit.repeat(lambda: func(params), repeat=100, number=3)
        duration_list[name] = list(np.array(durations) * 1000)
        print('{func:
Комментарии (0)

1. используя нотацию среза

def rev_string(s): 
    return s[::-1]

2. с помощью функции revid ()

def rev_string(s): 
    return ''.join(reversed(s))

3. с использованием рекурсии

def rev_string(s): 
    if len(s) == 1:
        return s

    return s[-1] + rev_string(s[:-1])
Комментарии (1)

Это также интересный способ:

def reverse_words_1(s):
    rev = ''
    for i in range(len(s)):
        j = ~i  # equivalent to j = -(i + 1)
        rev += s[j]
    return rev

или аналогичный:

def reverse_words_2(s):
    rev = ''
    for i in reversed(range(len(s)):
        rev += s[i]
    return rev

Еще один более «экзотический» способ с использованием byterarray, который поддерживает .reverse ()

b = bytearray('Reverse this!', 'UTF-8')
b.reverse()
b.decode('UTF-8')

будет производить:

'!siht esreveR'
Комментарии (0)

Обращение строки в python без использования reversed() или [::-1]

def reverse(test):
    n = len(test)
    x=""
    for i in range(n-1,-1,-1):
        x += test[i]
    return x
Комментарии (1)
def reverse(input):
    return reduce(lambda x,y : y+x, input)
Комментарии (1)
original = "string"

rev_index = original[::-1]
rev_func = list(reversed(list(original))) #nsfw

print(original)
print(rev_index)
print(''.join(rev_func))
Комментарии (1)

Рекурсивный метод:

def reverse(s): return s[0] if len(s)==1 else s[len(s)-1] + reverse(s[0:len(s)-1])

пример:

print(reverse("Hello!"))    #!olleH
Комментарии (0)

Здесь нет фантазии:

def reverse(text):
    r_text = ''
    index = len(text) - 1

    while index >= 0:
        r_text += text[index] #string canbe concatenated
        index -= 1

    return r_text

print reverse("hello, world!")
Комментарии (0)

Все вышеперечисленные решения идеальны, но если мы попытаемся изменить строку с помощью цикла для python, это станет немного сложно, поэтому мы можем изменить строку, используя цикл

string ="hello,world"
for i in range(-1,-len(string)-1,-1):
    print (string[i],end=(" ")) 

Я надеюсь, что этот будет полезен для кого-то.

Комментарии (0)

Вот мой путь:

def reverse_string(string):
    character_list = []
    for char in string:
        character_list.append(char)
    reversed_string = ""
    for char in reversed(character_list):
        reversed_string += char
    return reversed_string
Комментарии (0)
def reverse_string(string):
    length = len(string)
    temp = ''
    for i in range(length):
        temp += string[length - i - 1]
    return temp

print(reverse_string('foo')) #prints "oof"

Это работает путем циклирования строки и присвоения ее значений в обратном порядке другой строке.

Комментарии (0)

Есть много способов изменить строку, но я также создал еще один просто для удовольствия. Я думаю, что этот подход не так уж и плох.

def reverse(_str):
    list_char = list(_str) # Create a hypothetical list. because string is immutable

    for i in range(len(list_char)/2): # just t(n/2) to reverse a big string
        list_char[i], list_char[-i - 1] = list_char[-i - 1], list_char[i]

    return ''.join(list_char)

print(reverse("Ehsan"))
Комментарии (0)

Этот класс использует магические функции питона для изменения строки:

class Reverse(object):
    """ Builds a reverse method using magic methods """

    def __init__(self, data):
        self.data = data
        self.index = len(data)

    def __iter__(self):
        return self

    def __next__(self):
        if self.index == 0:
            raise StopIteration

        self.index = self.index - 1
        return self.data[self.index]

REV_INSTANCE = Reverse('hello world')

iter(REV_INSTANCE)

rev_str = ''
for char in REV_INSTANCE:
    rev_str += char

print(rev_str)  

Output

dlrow olleh

Ссылка

Комментарии (0)

Вот один без [:: -1]илиreversed (для учебных целей):

def reverse(text):
    new_string = []
    n = len(text)
    while (n > 0):
        new_string.append(text[n-1])
        n -= 1
    return ''.join(new_string)
print reverse("abcd")

Вы можете использовать + =, чтобы объединить строки, ноjoin () быстрее.

Комментарии (0)

Это простая и значимая обратная функция, простая для понимания и кодирующая

def reverse_sentence(text):
    words = text.split(" ")
    reverse =""
    for word in reversed(words):
        reverse += word+ " "
    return reverse
Комментарии (1)