it-swarm.com.ru

Перевернуть строку в Python

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

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

1190
oneself

Как насчет:

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

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

2430
Paolo Bergantino

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

236
Alex Martelli

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

Мой собственный опыт в этом вопросе академический. Однако, если вы профессионал, ищущий быстрый ответ, используйте фрагмент, который шаг за шагом -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'

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

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

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

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

188
Aaron Hall

Быстрый ответ (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:

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

Проблема

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

Решение

Ловушки

  • Разработчик может ожидать что-то вроде string.reverse()
  • Собственное идиоматическое (иначе называемое " Pythonic ") решение может быть нечитаемым для новых разработчиков
  • Разработчик может испытать желание реализовать свою собственную версию string.reverse(), чтобы избежать обозначения слайсов.
  • Вывод записи среза может быть нелогичным в некоторых случаях:
    • см., например, example02
      • print 'coup_ate_grouping'[-4:] ## => 'ping'
      • по сравнению с
      • print 'coup_ate_grouping'[-4:-1] ## => 'pin'
      • по сравнению с
      • print 'coup_ate_grouping'[-1] ## => 'g'
    • различные результаты индексации на [-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 (), однако полезно понять обоснование этого аспекта python.

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

34
dreftymac

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

string = 'happy'
print(string)

'счастливый'

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

'Yppah'

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

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

10
pX0r

используя обозначение среза

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

используя функцию reversed ()

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

используя рекурсию

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

    return s[-1] + rev_string(s[:-1])
6
harry

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

def reverse(test):
    n = len(test)
    x=""
    for i in range(n-1,-1,-1):
        x += test[i]
    return x
5
akshaynagpal
def reverse(input):
    return reduce(lambda x,y : y+x, input)
3
Javier

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

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 = byterarray('Reverse this!', 'UTF-8')
b.reverse()
b.decode('UTF-8')`

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

'!siht esreveR'
3
mdn

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

 enter image description here 

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

 enter image description here 

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]) во всех случаях является самым низким (даже после исправления моей опечатки).

Обращение строки

Если вы действительно хотите перевернуть строку в обычном смысле этого слова, это НАМНОГО сложнее. Например, возьмите следующую строку ( коричневый палец, указывающий влево , желтый палец, указывающий вверх ). Это две графемы, но три юникодных кода. Дополнительным является модификатор скина .

example = "????????????"

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

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

а также

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

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

>>> 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:<20}: '
              'min: {min:5.1f}μs, mean: {mean:5.1f}μs, max: {max:6.1f}μs'
              .format(func=name,
                      min=min(durations) * 10**6,
                      mean=np.mean(durations) * 10**6,
                      max=max(durations) * 10**6,
                      ))
        create_boxplot('Reversing a string of length {}'.format(len(longstring)),
                       duration_list)


def list_comprehension(string):
    return string[::-1]


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


def reverse_reduce(string):
    return reduce(lambda x, y: y + x, string)


def reverse_loop(string):
    reversed_str = ""
    for i in string:
        reversed_str = i + reversed_str
    return reversed_str


def create_boxplot(title, duration_list, showfliers=False):
    import seaborn as sns
    import matplotlib.pyplot as plt
    import operator
    plt.figure(num=None, figsize=(8, 4), dpi=300,
               facecolor='w', edgecolor='k')
    sns.set(style="whitegrid")
    sorted_keys, sorted_vals = Zip(*sorted(duration_list.items(),
                                           key=operator.itemgetter(1)))
    flierprops = dict(markerfacecolor='0.75', markersize=1,
                      linestyle='none')
    ax = sns.boxplot(data=sorted_vals, width=.3, orient='h',
                     flierprops=flierprops,
                     showfliers=showfliers)
    ax.set(xlabel="Time in ms", ylabel="")
    plt.yticks(plt.yticks()[0], sorted_keys)
    ax.set_title(title)
    plt.tight_layout()
    plt.savefig("output-string.png")


if __== '__main__':
    main()
2
Martin Thoma

Здесь нет ничего необычного:

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!")
1
buzhidao

Это мой путь:

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
1
Alex

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

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

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

1
Nitin Khanna
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"

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

1
original = "string"

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

print(original)
print(rev_index)
print(''.join(rev_func))
1
JEX

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

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
matt

Вот один без [::-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
Claudiu Creanga

Не знаю об эффективности, но короткое решение, найденное на лекциях MIT -

s = "abcd"

s[-1:-(len(s)+1):-1]
Out[5]: 'dcba'

Вы можете прочитать больше о ломтиках , чтобы лучше понять этот код.

0
Krishnadas PC

Переверните строку без магии питона.

>>> def reversest(st):
    a=len(st)-1
    for i in st:
        print(st[a],end="")
        a=a-1
0
Hardik Patel