Zmiana jednego znaku w ciągu w Pythonie


385

Jak najłatwiej jest w Pythonie zamienić znak w ciągu?

Na przykład:

text = "abcdefg";
text[1] = "Z";
           ^

Odpowiedzi:


534

Nie modyfikuj ciągów.

Pracuj z nimi jako listami; zamieniaj je w łańcuchy tylko w razie potrzeby.

>>> s = list("Hello zorld")
>>> s
['H', 'e', 'l', 'l', 'o', ' ', 'z', 'o', 'r', 'l', 'd']
>>> s[6] = 'W'
>>> s
['H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd']
>>> "".join(s)
'Hello World'

Ciągi w języku Python są niezmienne (tzn. Nie można ich modyfikować). Jest wiele powodów. Używaj list, dopóki nie będziesz mieć wyboru, tylko zamień je w ciągi znaków.


4
Ci, którzy szukają prędkości / wydajności, przeczytaj to
AneesAhmed777,

4
„Nie modyfikuj ciągów”. dlaczego
hacksoi

2
„Twórz-> modyfikuj-> serializuj-> przypisuj-> bezpłatnie” bardziej skuteczny niż s [6] = „W”? Hmm ... Dlaczego inne języki na to pozwalają, pomimo tak wielu powodów? Ciekawe, jak można obronić dziwny projekt (przypuszczam, że dla miłości). Dlaczego nie zaproponować dodania funkcji MID (strVar, index, newChar) do rdzenia Pythona, która bezpośrednio uzyskuje dostęp do pozycji pamięci char, zamiast niepotrzebnego tasowania bajtów całym łańcuchem?
oscar

@hacksoi, @oscar, powód jest dość prosty: nie trzeba ponownie liczyć przy przekazywaniu wskaźników do implementacji kopiowania-modyfikowania lub wprost kopiować cały ciąg w przypadku, gdy ktoś chce zmodyfikować ten ciąg - prowadzi to do zwiększenia prędkości posługiwać się. Nie ma potrzeby takich rzeczy jak MIDze względu na plasterki:s[:index] + c + s[index+1:]
MultiSkill

1
@oscar Przez głupie języki rozumiem, że nie mają do czynienia z Unicode, chyba że wyraźnie im to powiesz. Oczywiście możesz pisać aplikacje obsługujące Unicode w C. Ale musisz się tym cały czas przejmować i jawnie przetestować, aby uniknąć problemów. Wszystko jest zorientowane na maszynę. Pracowałem z PHP przed nauką Pythona, a ten język to totalny bałagan. Jeśli chodzi o twoją notatkę dotyczącą szybkich procesorów, jestem z Tobą całkowicie. Ale częścią tego problemu jest powszechna dezaprobata przedwczesnej optymalizacji, która prowadzi do powolnych interpretatorów i bibliotek poprzez wyciekanie wielu cykli procesora po drodze.
Bachsau

202

Najszybsza metoda?

Istnieją trzy sposoby. Osobom poszukującym prędkości polecam „Method 2”

Metoda 1

Biorąc pod uwagę tę odpowiedź

text = 'abcdefg'
new = list(text)
new[6] = 'W'
''.join(new)

Co jest dość wolne w porównaniu z „metodą 2”

timeit.timeit("text = 'abcdefg'; s = list(text); s[6] = 'W'; ''.join(s)", number=1000000)
1.0411581993103027

Metoda 2 (SZYBKA METODA)

Biorąc pod uwagę tę odpowiedź

text = 'abcdefg'
text = text[:1] + 'Z' + text[2:]

Co jest znacznie szybsze:

timeit.timeit("text = 'abcdefg'; text = text[:1] + 'Z' + text[2:]", number=1000000)
0.34651994705200195

Metoda 3:

Tablica bajtów:

timeit.timeit("text = 'abcdefg'; s = bytearray(text); s[1] = 'Z'; str(s)", number=1000000)
1.0387420654296875

1
Byłoby również interesujące zobaczyć, jak to wygląda w porównaniu z metodą bytearray.
gaboryczny

1
Dobry pomysł. Metoda bytearray jest również wolniejsza: timeit.timeit("text = 'abcdefg'; s = bytearray(text); s[1] = 'Z'; str(s)", number=1000000)dwa razy wolniejsza niż najszybsza.
Mehdi Nellen,

2
Doceń testy, które zmuszają mnie do przemyślenia, jak powinienem manipulować ciągami Pythona.
Spectral

1
Miły. Edytuj odpowiedź, aby uwzględnić również metodę 3 (bytearray).
AneesAhmed777,

1
Należy zauważyć, że większość czasu tutaj spędza się na konwersji ... (string -> tablica bajtów). Jeśli musisz wprowadzić wiele zmian do łańcucha, metoda tablicy bajtów będzie szybsza.
Ian Sudbery


37

Ciągi w języku Python są niezmienne, można je zmienić, wykonując kopię.
Najłatwiejszym sposobem na zrobienie tego, co chcesz, jest prawdopodobnie:

text = "Z" + text[1:]

Do text[1:]zwraca łańcuch w textod pozycji 1 do końca, liczyć pozycje od 0 więc „1” to drugi znak.

edycja: Możesz użyć tej samej techniki odcinania łańcucha dla dowolnej części łańcucha

text = text[:1] + "Z" + text[2:]

Lub jeśli litera pojawia się tylko raz, możesz skorzystać z techniki wyszukiwania i zamiany zaproponowanej poniżej


Mam na myśli drugą postać, IE. znak na miejscu numer 1 (w odniesieniu do pierwszego znaku, numer 0)
kostia

tekst [0] + „Z” + tekst [2:]
wbg

13

Począwszy od Pythona 2.6 i Pythona 3, możesz używać bajtów, które są mutowalne (można je zmieniać w zależności od elementu w przeciwieństwie do ciągów znaków):

s = "abcdefg"
b_s = bytearray(s)
b_s[1] = "Z"
s = str(b_s)
print s
aZcdefg

edycja: Zmieniono str na s

edit2: Jak wspomniano w komentarzach Two-Bit Alchemist, ten kod nie działa z Unicode.


Ta odpowiedź jest niepoprawna. Po pierwsze, powinien on być bytearray(s)nie bytearray(str). Po drugie, to będzie produkować: TypeError: string argument without an encoding. Jeśli określisz kodowanie, otrzymasz TypeError: an integer is required. Tak jest w przypadku kodu Unicode Python 3 lub Python 2. Jeśli zrobisz to w Pythonie 2 (z poprawioną drugą linią), nie będzie działać dla znaków spoza ASCII, ponieważ mogą one nie być tylko jednym bajtem. Spróbuj z, s = 'Héllo'a dostaniesz 'He\xa9llo'.
Two-Bit Alchemist

Próbowałem tego ponownie w Pythonie 2.7.9. Nie udało mi się zregenerować wspomnianego błędu (TypeError: string argument bez kodowania).
Mahmoud,

Ten błąd ma zastosowanie tylko wtedy, gdy używasz Unicode. Spróbować s = u'abcdefg'.
Two-Bit Alchemist

4
NIE RÓB TEGO. Ta metoda ignoruje całą koncepcję kodowania ciągów, co oznacza, że ​​działa tylko na znaki ASCII. W dzisiejszych czasach nie możesz założyć ASCII, nawet jeśli mówisz po angielsku w kraju anglojęzycznym. Największą niezgodnością wsteczną Pythona i, moim zdaniem, najważniejszą jest poprawianie całego bajtu = ciąg fałszywej równoważności. Nie przynoś go z powrotem.
Adam

5

Jak powiedzieli inni ludzie, generalnie ciągi Pythona powinny być niezmienne.

Jeśli jednak używasz CPython, implementacji w python.org, możliwe jest użycie ctypów do modyfikacji struktury łańcucha w pamięci.

Oto przykład, w którym używam techniki do usuwania ciągu.

Oznacz dane jako wrażliwe w pythonie

Wspominam o tym ze względu na kompletność i powinno to być twoje ostateczne rozwiązanie, ponieważ jest hackerskie.


6
Ostatnia deska ratunku? Jeśli kiedykolwiek to zrobić nagle napiętnowany jako zło!
Chris Morgan

@ChrisMorgan, jeśli ciąg zawiera hasło, wyczyszczenie go za pomocą s = '' nie wystarczy, ponieważ hasło jest nadal zapisane gdzieś w pamięci. Oczyszczanie go za pomocą typów jest jedynym sposobem.
Cabu,

1
@Cabu Nigdy w żadnym wypadku nie zaakceptuję kodu, który to zrobił. Jeśli Twoje dane są wrażliwe i zależy Ci na takim bezpieczeństwie, strnie jest to odpowiedni typ dla Ciebie. Po prostu tego nie używaj. bytearrayZamiast tego użyj czegoś takiego . (Jeszcze lepiej, zawiń go w coś, co pozwala traktować go mniej więcej jako nieprzejrzyste dane, aby naprawdę nie można byłostr z niego odzyskać , aby uchronić Cię przed wypadkami. Może to być biblioteka. Nie ma pojęcia.)
Chris Morgan

4

Ten kod nie jest mój. Nie mogłem sobie przypomnieć formularza witryny, w którym go wziąłem. Co ciekawe, możesz użyć tego, aby zastąpić jedną lub więcej postaci jednym lub więcej postaciami. Chociaż ta odpowiedź jest bardzo późna, nowicjusze tacy jak ja (w każdej chwili) mogą uznać ją za przydatną.

Zmień funkcję tekstu.

mytext = 'Hello Zorld'
mytext = mytext.replace('Z', 'W')
print mytext,

11
To nie odpowiada na pytanie. Wcale nie było to pożądane.
Chris Morgan

2
Ten kod jest zły, jeśli chcesz zastąpić tylko pierwszy l. mytext = mytext.replace('l', 'W')->HeWWo Zorld
Ooker

Jeśli chcesz chirurgicznie zastąpić tylko 1 znak (którym jestem), idealnie pasuje do rachunku. Dzięki!
ProfVersaggi,

@ProfVersaggi To absolutnie nieprawda. Zobacz komentarz Ookera powyżej.
Two-Bit Alchemist

3
@Ooker Jeśli chcesz zastąpić tylko pierwszy znak, którego możesz użyć mytext = mytext.replace('l', 'W',1). Link do dokumentu
Alex

2

Właściwie za pomocą łańcuchów możesz zrobić coś takiego:

oldStr = 'Hello World!'    
newStr = ''

for i in oldStr:  
    if 'a' < i < 'z':    
        newStr += chr(ord(i)-32)     
    else:      
        newStr += i
print(newStr)

'HELLO WORLD!'

Zasadniczo „dodam” ciągi „+” razem do nowego ciągu :).


4
Będzie to bardzo powolne, ponieważ każda konkatenacja musi wytworzyć nowy obiekt łańcuchowy, ponieważ są one niezmienne, o to właśnie chodzi w tym pytaniu.
Two-Bit Alchemist

0

jeśli twój świat jest w 100% ascii/utf-8(wiele przypadków użycia mieści się w tym polu):

b = bytearray(s, 'utf-8')
# process - e.g., lowercasing: 
#    b[0] = b[i+1] - 32
s = str(b, 'utf-8')

python 3.7.3


0

Chciałbym dodać inny sposób zmiany znaku w ciągu.

>>> text = '~~~~~~~~~~~'
>>> text = text[:1] + (text[1:].replace(text[0], '+', 1))
'~+~~~~~~~~~'

Jak to jest szybsze w porównaniu do zamiany łańcucha na listę i zamiany i-tej wartości, a następnie ponownego przyłączenia ?.

Podejście do listy

>>> timeit.timeit("text = '~~~~~~~~~~~'; s = list(text); s[1] = '+'; ''.join(s)", number=1000000)
0.8268570480013295

Moje rozwiązanie

>>> timeit.timeit("text = '~~~~~~~~~~~'; text=text[:1] + (text[1:].replace(text[0], '+', 1))", number=1000000)
0.588400217000526
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.