Pracuję nad problemem z CTCI.
Trzeci problem z rozdziału 1 polega na tym, że bierzesz ciąg, taki jak
'Mr John Smith '
i prosi o zastąpienie spacji pośrednich %20
:
'Mr%20John%20Smith'
Autor oferuje takie rozwiązanie w Pythonie, nazywając je O (n):
def urlify(string, length):
'''function replaces single spaces with %20 and removes trailing spaces'''
counter = 0
output = ''
for char in string:
counter += 1
if counter > length:
return output
elif char == ' ':
output = output + '%20'
elif char != ' ':
output = output + char
return output
Moje pytanie:
Rozumiem, że jest to O (n) pod względem przeglądania rzeczywistego ciągu od lewej do prawej. Ale czy łańcuchy w Pythonie nie są niezmienne? Jeśli mam ciąg i dodam do niego kolejny ciąg z +
operatorem, czy nie przydziela on niezbędnej przestrzeni, nie kopiuje oryginału, a następnie kopiuje dołączany ciąg?
Jeśli mam kolekcję n
ciągów o długości 1, to przyjmuje:
1 + 2 + 3 + 4 + 5 + ... + n = n(n+1)/2
lub O (n ^ 2) czas , tak? A może mylę się co do tego, jak Python obsługuje dołączanie?
Ewentualnie, gdybyś zechciał nauczyć mnie łowić ryby: jak bym się tego nauczył? Nie udało mi się znaleźć oficjalnego źródła w Google. Znalazłem https://wiki.python.org/moin/TimeComplexity, ale to nie ma nic na łańcuchach.
źródło
urllib.urlencode
rtrim
ireplace
byłoby bardziej preferowane i na boiskuO(n)
. Kopiowanie po łańcuchach wydaje się najmniej wydajnym sposobem.Odpowiedzi:
W CPythonie, standardowej implementacji Pythona, istnieje szczegół implementacji, który sprawia, że zwykle jest to O (n), zaimplementowane w kodzie, w którym pętla oceny kodu bajtowego wywołuje
+
lub+=
z dwoma operandami łańcuchowymi . Jeśli Python wykryje, że lewy argument nie ma innych odniesień, wywołujerealloc
próbę uniknięcia kopii, zmieniając rozmiar ciągu w miejscu. Nie jest to coś, na czym powinieneś zawsze polegać, ponieważ jest to szczegół implementacji, a ponieważ jeśli wrealloc
końcu będziesz musiał często przesuwać ciąg, wydajność i tak spada do O (n ^ 2).Bez dziwnych szczegółów implementacji algorytm ma wartość O (n ^ 2) ze względu na kwadratową wielkość kopiowania. Taki kod miałby sens tylko w języku ze zmiennymi łańcuchami, jak C ++, a nawet w C ++, którego chciałbyś użyć
+=
.źródło
_PyString_Resize(&v, new_len)
alokację pamięci dla połączonego ciągu, a następniememcpy(PyString_AS_STRING(v) + v_len, PyString_AS_STRING(w), w_len);
wykonuje kopię. Jeśli zmiana rozmiaru w miejscu nie powiedzie się, tak się dziejePyString_Concat(&v, w);
(zakładam, że oznacza to, że ciągła pamięć na końcu oryginalnego adresu ciągu nie jest wolna). Jak to pokazuje przyspieszenie?realloc
i ma nadzieję na najlepsze.memcpy(PyString_AS_STRING(v) + v_len, PyString_AS_STRING(w), w_len);
działa? Według cplusplus.com/reference/cstring/memcpy ma definicjęvoid * memcpy ( void * destination, const void * source, size_t num );
i opis:"Copies the values of num bytes from the location pointed to by source directly to the memory block pointed to by destination."
num w tym przypadku jest rozmiarem dołączanego ciągu, a źródło to adres drugiego łańcucha, jak zakładam? Ale dlaczego w takim razie miejsce docelowe (pierwszy ciąg) + len (pierwszy ciąg)? Podwójna pamięć?PyString_AS_STRING(v)
to adres danych pierwszego ciągu i dodaniev_len
daje adres zaraz po ciągu dane się kończą.Autor polega na optymalizacji, która jest tutaj, ale nie jest wyraźnie niezawodna.
strA = strB + strC
jest zazwyczajO(n)
tworzeniem funkcjiO(n^2)
. Jednak dość łatwo jest upewnić się, że cały proces jest taki samO(n)
, użyj tablicy:output = [] # ... loop thing output.append('%20') # ... output.append(char) # ... return ''.join(output)
Krótko mówiąc,
append
operacja jest amortyzowanaO(1)
(chociaż można ją wzmocnićO(1)
, wstępnie przydzielając tablicę do odpowiedniego rozmiaru), tworząc pętlęO(n)
.A potem
join
jest równieżO(n)
, ale to jest w porządku, ponieważ jest poza pętlą.źródło
Znalazłem ten fragment tekstu w Python Speed> Użyj najlepszych algorytmów i najszybszych narzędzi :
źródło
Dla przyszłych odwiedzających: ponieważ jest to pytanie CTCI, wszelkie odniesienia do uczenia się urllib pakietu nie jest tutaj wymagane, szczególnie zgodnie z OP i książką, to pytanie dotyczy tablic i ciągów.
Oto bardziej kompletne rozwiązanie, zainspirowane pseudo @ njzk2:
text = 'Mr John Smith'#13 special_str = '%20' def URLify(text, text_len, special_str): url = [] for i in range(text_len): # O(n) if text[i] == ' ': # n-s url.append(special_str) # append() is O(1) else: url.append(text[i]) # O(1) print(url) return ''.join(url) #O(n) print(URLify(text, 13, '%20'))
źródło