String each_char a modyfikacja

Ostatnio piszę mały programik który operuje dość dużo na Stringach
Natkąłem się podczas poszukiwania jakiejś pierdoły na ten wpis:
http://thesingularity.pl/blog/2011/string-ruby-1-9-ma-liniowy-czas-dostepu/

Jednak each_char nie bangla w takiej kombinacji.

[code=ruby]t = “asd”
t.each_char {|e| e = e.next}
puts t # => “asd”

A powinno być “bte”[/code]

Da się jakoś zmodyfikować stringa w locie w iteracji each_char?

koniecznie musi być przez each_char? coś takiego możesz uzyskać chociażby tak:

"abc".split('').map(&:next).join

jeżeli koniecznie przez each_char to:

[code=ruby]old_str = “abc”
new_str = “”

old_str.each_char {|v| new_str << v.next} # tutaj ostrożnie bo to zwróci old_str

new_str #=> “bcd”[/code]

Chodzi mi ogólnie o modyfikacje chara w iteracji .each_char.

Co ciekawe taka operacja działa, ale na tablicy stringów

tab = ['1', '2', '3', '4', '5'] tab.each {|e| e.next! } # => ['2', '3', '4', '5', '6']

mhm :slight_smile: bo używasz next! które zmienia wartość “w miejscu” a nie tylko zwraca zmienioną wartość :slight_smile:
pomyśl tylko czy na pewno nie lepiej jednak pisać odrobinę bardziej funkcyjnie?

[quote=zlw]mhm :slight_smile: bo używasz next! które zmienia wartość “w miejscu” a nie tylko zwraca zmienioną wartość :slight_smile:
pomyśl tylko czy na pewno nie lepiej jednak pisać odrobinę bardziej funkcyjnie?[/quote]
Bo e to jest kopia, a nie tab[i].
Nie ogarniam pisania funkcyjnego, jak chcesz to napisz przykład.

No chociażby pierwszy przykład (ten z split, map, join) jest napisany funkcyjnie, nigdzie nie zmieniasz “w miejscu” wartości, operujesz tylko na tym co zwróciła Ci poprzednia metoda.

Nie wiem jakie tam masz wykonywać operacje, ale dla tej konkretnej wydaje mi się, że najlepiej będzie to zrobić tak jak napisałem na początku:

"abc".split('').map(&:next).join #=> "bcd"

No ale to w moim przypadku nie zadziała. Jeden string będzie przechodził pare tysięcy iteracji dodatkowo algorytm jest dość skomplikowany.

No okej, ale ten sposób też nie jest jakiś koszmarnie wolny:

[code=ruby]require ‘benchmark’

Benchmark.bm(10) do |bm|
[1000, 10_000, 100_000, 1_000_000].each do |i|
bm.report("#{i} iterations: ") { (‘abc’*i).split(’’).map(&:next).join }
end
end

user system total real

1000 iterations: 0.010000 0.000000 0.010000 (0.002597)

10000 iterations: 0.020000 0.000000 0.020000 (0.025910)

100000 iterations: 0.230000 0.010000 0.240000 (0.237470)

1000000 iterations: 2.230000 0.100000 2.330000 (2.331432)[/code]

Jak duże będą te stringi i co mniej więcej będziesz z nimi robił? To będzie się działo w czasie rzeczywistym czy tylko raz?

Jeśli chcesz żeby ci to działało z each_char

t = "asd" t.each_char {|e| t[t.index(e)] = e.next} puts t # => "bte"

[quote=zlw]No okej, ale ten sposób też nie jest jakiś koszmarnie wolny:

[code=ruby]require ‘benchmark’

Benchmark.bm(10) do |bm|
[1000, 10_000, 100_000, 1_000_000].each do |i|
bm.report("#{i} iterations: ") { (‘abc’*i).split(’’).map(&:next).join }
end
end

user system total real

1000 iterations: 0.010000 0.000000 0.010000 (0.002597)

10000 iterations: 0.020000 0.000000 0.020000 (0.025910)

100000 iterations: 0.230000 0.010000 0.240000 (0.237470)

1000000 iterations: 2.230000 0.100000 2.330000 (2.331432)[/code]

Jak duże będą te stringi i co mniej więcej będziesz z nimi robił? To będzie się działo w czasie rzeczywistym czy tylko raz?[/quote]
Tylko raz.

@goozzik
W ten sposób string także ma liniowy czas dostępu bo odwołujesz się poprzez tab[i].

Ogólnie zależy mi po prostu na speedzie.

PS Przydały by się guziki bo BBcode b oraz code=ruby

Jeżeli odnosisz się do tego postu co do liniowego dostępu to zostało to poprawione. Zresztą gdzieś na forum jest dyskusja dotycząca tego problemu. Ktoś z forum zgłosił błąd i zostało to naprawione.

Jeżeli masz tą operację zrobić raz to w sumie pal sześć czas - dawno już byś to napisał i 10 razy odpalił zamiast rozmyślać jak będzie najszybciej :wink:

p.s.

[quote=goozzik]Jeśli chcesz żeby ci to działało z each_char

t = "asd" t.each_char {|e| t[t.index(e)] = e.next} puts t # => "bte"
[/quote]
moim zdaniem to strasznie nieczytelny kod i zupełnie nie idiomatyczny. no i zmieniasz w miejscu wartość, a nie widać tego na pierwszy rzut oka - nie ma metod z ! które zazwyczaj właśnie to oznaczają.

Ooo. Trzeba czytać komentarze. :slight_smile:

Jeszcze jedna rzecz mnie zastanawia.
Czemu tab[i].next! nie działa?
Da się wywołać destrukcyjną metodę(w sensie czy da się ZMIENIĆ wartość) na stringu w iteracji?

Ehh, ale czemu tak się upierasz przy tej zmianie w miejscu? Przyjdzie Ci kiedyś napisać współbieżną czy wielowątkową aplikację i będą z tego powodu same problemy.

Ruby ma pewne korzenie w językach funkcyjnych (stąd normalnie metody zwracają kopie a nie zmieniają wartość) i idiomatyczny ruby zdecydowanie bardziej przypomina funkcyjny niż imperatywny kod. To nie jest trudne a i kod zazwyczaj jest czytelniejszy i bardziej zwięzły :slight_smile:

Mam dość długą nazwę zmiennej. Dodatkowo jest to tablica której indeks zależy od innej tablicy. Dodatkowo jem tam if. Czyli:

tab_cos_tam[tab_cos_tam[index]] = tab_cos_tam[tab_cos_tam[index]].next! if tab_cos_tam[tab_cos_tam[index]].nil?

Znacznie krótsze było by to w zapisie gdyby był wykrzyknik.

Dzięki za informacje. Ale ja nie piszę współbieżną appkę, także takich problemów nie będzie.

Z tym można polemizować. Jednakże jest to teoretyzowanie, dlatego sobie odpuszczę.

W Rubim metoda each_char wrzuca po prostu do bloku kopię znaku, a nie jego referencję - w przeciwieństwie do metody each dla tablicy, dlatego zasadniczo nie da się tego zrobić elegancko z wykorzystaniem modyfikacji “w miejscu”. Podejście zlw jest jak najbardziej eleganckie - ale oczywiście możesz się upierać przy swojej koncepcji.

Ok to chciałem wiedzieć.

[archeolog]
A tak w ogóle to można zrobić:

"abc".gsub(/[a-z]?/) {|m| m.next} #=> "bcd"

dopasuj sobie tylko regexpa. ten pasuje tylko dla małych liter więc:

"aBc".gsub(/[a-z]?/) {|m| m.next} #=> "bCd"

[/archeolog]

:smiley:

btw. na blogu umieściłem benchmark rozwiązań z tego tematu. wyszło całkiem ciekawie :slight_smile: