2015-01-25 7 views
7

Mam skrypt Pythona, który z różnych powodów ma zmienną, która jest dość dużym ciągiem, powiedzmy 10mb. Ten ciąg zawiera wiele linii.Najszybszy sposób na usunięcie pierwszej i ostatniej linii z łańcucha w języku Python

Jaki jest najszybszy sposób na usunięcie pierwszej i ostatniej linii tego ciągu? Ze względu na rozmiar sznurka im szybsza operacja, tym lepiej; nacisk kładzie się na szybkość. Program zwraca nieco mniejszy ciąg, bez pierwszej i ostatniej linii.

to najprostszy sposób, aby to zrobić, ale jest bardzo powolny, ponieważ funkcja split() kopiuje obiekt w pamięci, a join() kopiuje go ponownie.

Przykład ciąg:

*** START OF DATA *** 
data 
data 
data 
*** END OF DATA *** 

Extra kredyt: Czy ten program nie zadławić, jeśli nie ma żadnych danych pomiędzy; jest to opcjonalne, ponieważ dla mojego przypadku nie powinien istnieć ciąg bez danych pomiędzy.

+0

masz kontrolę nad tym, jak ciąg wchodzi do twojego programu, np .: czy robisz 'my_string = file_obj.read()', aby pobrać ciąg znaków? Czy potrzebujesz wszystkich linii obecnych w pamięci w tym samym czasie lub tylko jednej linii naraz? –

Odpowiedz

9

Pierwszy rozłam w '\n' raz, a następnie sprawdzić, czy ciąg w ostatnim indeksu zawiera '\n', jeśli tak str.rsplit na '\n' raz i wybrać pozycję w indeksie 0th inaczej zwróci pusty ciąg:

def solve(s): 
    s = s.split('\n', 1)[-1] 
    if s.find('\n') == -1: 
     return '' 
    return s.rsplit('\n', 1)[0] 
... 
>>> s = '''*** START OF DATA *** 
data 
data 
data 
*** END OF DATA ***''' 
>>> solve(s) 
'data\ndata\ndata' 
>>> s = '''*** START OF DATA *** 
*** END OF DATA ***''' 
>>> solve(s) 
'' 
>>> s = '\n'.join(['a'*100]*10**5) 
>>> %timeit solve(s) 
100 loops, best of 3: 4.49 ms per loop 

Lub nie dziel w ogóle, znajdź indeks '\n' z obu końców i ułóż ciąg:

>>> def solve_fast(s): 
    ind1 = s.find('\n') 
    ind2 = s.rfind('\n') 
    return s[ind1+1:ind2] 
... 
>>> s = '''*** START OF DATA *** 
data 
data 
data 
*** END OF DATA ***''' 
>>> solve_fast(s) 
'data\ndata\ndata' 
>>> s = '''*** START OF DATA *** 
*** END OF DATA ***''' 
>>> solve_fast(s) 
'' 
>>> s = '\n'.join(['a'*100]*10**5) 
>>> %timeit solve_fast(s) 
100 loops, best of 3: 2.65 ms per loop 
0

W zależności od sposobu, w jaki twój przypadek użycia pochłonie ciąg, najszybszym sposobem jego usunięcia może być jego nie usunięcie.

Jeśli planujesz uzyskać dostęp do linii w łańcuchu sekwencyjnie, możesz zbudować generator, który pomija pierwszą i ostatnią linię, jednocześnie generując każdą linię, zamiast budować nowy zestaw kopii wszystkich linii.

Doraźnym sposobem uniknięcia pierwszej i ostatniej linii jest powtarzanie ciągu bez generowania niepotrzebnych kopii przez śledzenie trzech kolejnych linii i zwracanie tylko drugiej, w ten sposób iteracja zakończy się przed osiągnięciem ostatnia linia bez konieczności znajomości położenia ostatniej linii.

Poniższa funkcja powinna dać pożądany wynik:

def split_generator(s): 
    # Keep track of start/end positions for three lines 
    start_prev = end_prev = 0 
    start = end = 0 
    start_next = end_next = 0 

    nr_lines = 0 

    for idx, c in enumerate(s): 
    if c == '\n': 
     nr_lines += 1 

     start_prev = start 
     end_prev = end 
     start = start_next 
     end = end_next 
     start_next = end_next 
     end_next = idx 

     if nr_lines >= 3: 
     yield s[(start + 1) : end] 

    # Handle the case when input string does not finish on "\n" 
    if s[-1] != '\n' and nr_lines >= 2: 
    yield s[(start_next+1):end_next] 

mogę przetestować go z:

print("1st example") 
for filtered_strs in split_generator('first\nsecond\nthird'): 
    print(filtered_strs) 

print("2nd example") 
for filtered_strs in split_generator('first\nsecond\nthird\n'): 
    print(filtered_strs) 

print("3rd example") 
for filtered_strs in split_generator('first\nsecond\nthird\nfourth'): 
    print(filtered_strs) 

print("4th example") 
for filtered_strs in split_generator('first\nsecond\nthird\nfourth\n'): 
    print(filtered_strs) 

print("5th example") 
for filtered_strs in split_generator('first\nsecond\nthird\nfourth\nfifth'): 
    print(filtered_strs) 

Will generuje dane wyjściowe:

1st example 
second 
2nd example 
second 
3rd example 
second 
third 
4th example 
second 
third 
5th example 
second 
third 
fourth 

pamiętać, że największa Zaletą tego podejścia jest to, że stworzy tylko jedną nową linię w czasie i praktycznie nie będzie czas na wygenerowanie pierwszego wiersza danych wyjściowych (zamiast oczekiwania na znalezienie wszystkich linii, zanim przejdziesz dalej), ale znowu, może być przydatny lub nie, w zależności od twojego przypadku użycia.

6

Rozważmy łańcuch s, który jest mniej więcej tak:

s = "line1\nline2\nline3\nline4\nline5" 

następujący kod ...

s[s.find('\n')+1:s.rfind('\n')] 

... produkuje wyjście:

'line2\nline3\nline4' 

, a tym samym jest najkrótszy kod, aby usunąć pierwszą i ostatnią linię łańcucha. Nie sądzę, że metody .find i .rfind robią cokolwiek poza szukaniem danego ciągu znaków. Wypróbuj prędkość!

0

Inna metoda polega na podzieleniu danych na nowej linii, a następnie dołączyć do wszystkiego, ale w pierwszym i ostatnim wierszu:

>>> s = '*** START OF DATA *** \n\ 
... data\n\ 
... data\n\ 
... data\n\ 
... *** END OF DATA ***' 
>>> '\n'.join(s.split('\n')[1:-1]) 
'data\ndata\ndata' 

Działa to dobrze bez danych:

>>> s = '*** START OF DATA *** \n\ 
... *** END OF DATA ***' 
>>> '\n'.join(s.split('\n')[1:-1]) 
'' 
+0

Jak odnotowano w PO, będzie to bardzo powolne w przypadku dużych danych. –

Powiązane problemy