Elegancki sposób na tablicę jako argument metody

Witam Szanowne Grono tego Forum :slight_smile:
To mój pierwszy post - na imię mam Jarek i zaczynam przygodę z Ruby i Rails.

A teraz pytanie…

Jaka jest inna metoda by użyć tablicy w jakiejś metodzie, która potem zmienia wartości z tej tablicy, tak by wartości oryginalnej tablicy pozostały nienaruszone?

Dotarłem sposobu z Marshal.load:

[code]schema = [“aaa”,“bbb”,“ccc”]
some_array = Marshal.load( Marshal.dump(schema) )

metoda_ktora_zmienia_wartosci_z_tablicy(some_array)

some_array.pop

end[/code]
To działa ale kłuje w oczy (przynajmniej początkującego takiego jak ja:) )

Żadne schema.dup
i schema.clone
nie pomaga - przy ich użyciu nadal zmienia się oryginalna tablica schema

Jest coś jeszcze??

Byłbym wdzięczny za link który pomógłby mi zrozumieć tego typu zawiłości referencyjne obiektów.

http://rubyonrails.pl/forum/t4263-Modyfikacja-parametru-metody.

[quote=pjar]Żadne schema.dup
i schema.clone
nie pomaga - przy ich użyciu nadal zmienia się oryginalna tablica schema[/quote]
Pokaż kod, który nie działa, bo nie wiem o jakim scenariuszu piszesz. Zabawa w Marshala jest tutaj nie na miejscu. Może chodzi Ci o to, że elementy są te same? Ale tablice, zarówno przy dup jak i clone są kopiami:

[code=ruby]def change(array)
array_copy = array.dup
array_copy.pop
end

a = [1,2,3]
change(a)
p a #=> [1,2,3][/code]
Jeśli chodzi Ci o sytuację, w której wartości maja być kopiowane, to najprościej zrobić to tak:

[code=ruby]def change(array)
array_copy = array.map(&:dup)
array_copy.map!{|e| e.upcase!}
p array_copy #=> [“ALA”, “MA”, “KOTA”]
end

a = %w{ala ma kota} # to samo co [“ala”,“ma”,“kota”]
change(a)
p a #=> [“ala”,“ma”,“kota”][/code]

Jak to clone nie działa?

[code]a = [“1”, “2”, “3”]

def foo(array)
copy = array.clone
copy.pop
puts “Copy: #{copy.inspect}”
end

puts “Original Before: #{a.inspect}”
foo(a)
puts “Original After #{a.inspect}”[/code]
Takie coś zwraca:

Original Before: ["1", "2", "3"] Copy: ["1", "2"] Original After ["1", "2", "3"]
O coś takiego chodzi?

Z popem działa, z elementami prymitywnymi (Integery) działa. Ale podejrzewam że pjar pokazuje tu kod przykładowy, a ma problem głębiej, na poziomie kopiowania zaledwie referencji przez Array#clone i brak mechanizmu głębokiej kopii.

W sensie, podejrzewam problem tego typu:

irb(main):001:0> a = ["aaa","bbb","ccc"] => ["aaa", "bbb", "ccc"] irb(main):002:0> b = a.clone => ["aaa", "bbb", "ccc"] irb(main):003:0> b.pop => "ccc" irb(main):004:0> b => ["aaa", "bbb"] irb(main):005:0> b[1].sub!("b","z") => "zbb" irb(main):006:0> a => ["aaa", "zbb", "ccc"] irb(main):007:0> b => ["aaa", "zbb"]
Jarek, dobry trop?

EDIT: also this http://www.ruby-forum.com/topic/121005 , najwyraźniej używanie Marshala jest popularnym ersatzem głębokiej kopii z prawdziwego zdarzenia.

Marshall się przydaje jak potrzebujesz deep clone, ale przy czymś takim nie powinno mieć znaczenia.

Chyba, że w tej tablicy są obiekty, które bezpośrednio modyfikujesz.

UPDATE: o, widzę, że kilka osób już wytłumaczyło, tak to jest jak się zostawi otwartą stronę i nie odświeża :wink:

Chłopcy mają refleks :slight_smile:

Szczerze mówiąc nie spodziewałem się takiego odzewu w piątkowy wieczór :slight_smile:

Dość biadolenia. Oto kod (uwaga - proszę o wyrozumiałość - wiem że jest brzydki i być może nie spełnia postawionych wymagań:) )

[code]# tu się wszystko popsuje
def get_shortest_path(start_point, end_point, array)

mini_array = []

wyłapujemy potrzebne nam odcinki

array.each do |tiny_array|
if (tiny_array.include?(end_point))
mini_array << tiny_array
end
end

i szukamy tego jedynego…

shortest_node = shortest_element_from(mini_array)

konstruujemy najkrótszą ścieżkę element po elemencie

@traversed << shortest_node.dup

jak nie ma więcej elementów to znaczy że skończyliśmy pracę

if shortest_node.include?(start_point)
return
else

 # miejsce gdzie robi się smutno..... niszczymy tablicę schema - why????
 # nawet object_id są różne
 
 # tu chcę wydobyć symbol końca odcinka,
 # który ma posłużyć za oznaczenie następnego,
 # a nie zawsze będą to kolejne litery alfabetu
 shortest_node.delete(end_point)
 shortest_node.pop
 next_last_point = shortest_node.first
 
 # nie chcemy ponownie sprawdzać już odhaczonych elementów
 array -= mini_array
 
 # i od nowa..
 get_shortest_path(start_point, next_last_point, array)

end
end

def shortest_element_from(array)
infinity = 1.0/0
current_lowest_element = [“X”,“Y”, infinity]
array.each do |element|
if element.last < current_lowest_element.last
current_lowest_element = element
end
end
current_lowest_element
end

#------------------Koniec metod--------------------------------

docelowa najkrótsza ścieżka

@traversed = []

tablica tablic z oznaczeniem końców odcinka i jego długości

to ona ma się nie zmieniać

schema = [
[ ‘A’, ‘B’, 50],
[ ‘A’, ‘D’, 150],
[ ‘B’, ‘C’, 250],
[ ‘B’, ‘E’, 250],
[ ‘C’, ‘E’, 350],
[ ‘C’, ‘D’, 50],
[ ‘C’, ‘F’, 100],
[ ‘D’, ‘F’, 400],
[ ‘E’, ‘G’, 200],
[ ‘F’, ‘G’, 100]
]

#some_array = Marshal.load( Marshal.dump(schema) )
some_array = schema.clone

id obiektów nie mówi wszystkiego??

puts " schema id: " + schema.object_id.to_s
puts "some_array id: " + some_array.object_id.to_s

get_shortest_path(“A”,“G”, some_array)

redundant = schema - @traversed
puts "redundant: "
redundant.each { |element| print element; puts }

puts "schema: "
schema.each { |element| print element; puts }[/code]
Sytuacja podobna do przedstawionych przez wszystkich przedmówców i… dlatego nie wiem co jest u mnie nie tak skoro nie działa.

Prawdopodbnie winny jest jakiś błąd konstrukcyjny całego kodu, może @traversed coś psuje?

Z góry dzięki za cierpliwość…

Pozdrawiam i zamieniam fotel na wyro… :confused:

Problem bierze się stąd, że zakładasz, że dup czy clone stworza tzw. głęboką kopię, tzn. nie tylko sam obiekt będzie sklonowany, ale również wszystkie jego “składniki”, składniki tych składników, itd. i faktycznie w tym wypadku można to osiągnąć dzięki marshalowaniu.
Co nie zmienia faktu, że musisz zrozumieć jak “działają” obiekty w Rubim. Jeśli masz tablicę obiektów, to ona trzyma referencje do tych obiektów (“składników”). Tak jest w zasadzie zawsze z wyjątkiem liczb całkowitych i symboli. Jeśli więc robisz kopię tablicy, to faktycznie dostajesz nową tablicę, ale ona zawiera referencje do tych samych obiektów. Twój kod nie niszczy tablicy “array”, ale jej składniki.
W przedstawionym scenariuszu można osiągnąć pożądany cel tak jak pisałem wyżej, tzn.

def get_shortest_path(start_point, end_point, array) array = array.map(&:dup) # to samo co array.map{|e| e.dup} - czyli kopiujemy składniki tablicy, a nie tylko jej "szkielet" end

Dzięki za wszystkie odpowiedzi. Teraz już czaję. Po prostu wydawało mi się że samo clone’owanie powinno wystaczyć. Widzę że zmusiłem apohllo do powtórzenia się - sorry i duży szacun dla Ciebie za zachowanie spokoju.