Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby: how can I copy this array?

Tags:

ruby

(Follow-up to my earlier question, Ruby: how can I copy a variable without pointing to the same object?)

I'm writing a simple Ruby program to make some substitutions in an .svg file. The first step is to pull information from the file and put it in an array. In order to keep from reading the file from disk every time this function is called, I'm trying to use the memoize design pattern - use a cached result on every call after the first one.

To do this, I'm using a global variable, defined just before the function. But even though I .dup that variable to a local one before returning the local variable, the function that calls this one is still modifying the global variable.

Here is my actual code:

#memoize to keep from having to read original file on each pass
$svg_filedata_cache = [] #the global variable
def svg_filedata(filename)
    if $svg_filedata_cache.empty?
        File.open(filename, "r"){|f| $svg_filedata_cache = f.readlines}
    end
    svg_filedata_cache = $svg_filedata_cache.dup #try to copy it
    return svg_filedata_cache #SHOULD point to a different object (but doesn't)
end

Two questions (answer either or both):

  1. Why do other functions, which take in and modify the value returned here, also affect the global variable, even though I used .dup to copy it?
  2. I'm new to Ruby and I'm sure this isn't the most Rubyesque way to do this (and I don't like global variables, anyway). Can you suggest a better strategy?
like image 753
Nathan Long Avatar asked Sep 23 '09 12:09

Nathan Long


3 Answers

Modifying the duped array will not affect the original. However modifications to the strings inside the array will be visible globally because the global array and the duped array still contain references to the same strings (dup does not perform a deep copy).

So either perform a deep copy (svg_filedata_cache = $svg_filedata_cache.map {|line| line.dup}) or simply avoid mutating operations on the strings.

like image 157
sepp2k Avatar answered Oct 12 '22 02:10

sepp2k


Enhancing the code a bit:

$svg_filedata_cache = [] #the global variable
def svg_filedata(filename)
    # Use ||= for memoiziation 
    $svg_filedata_cache ||= File.open(filename, "r"){|f| $svg_filedata_cache = f.readlines} 
    $svg_filedata_cache.dup #shallow copying
end

Update: a simple trick to do deep copying in general is:

def deep_copy(obj)
  Marshal.load(Marshal.dump(obj))
end
like image 32
khelll Avatar answered Oct 12 '22 02:10

khelll


The global is probably not being modified, but the elements that it and your .dup reference are changing. To make it more canonical ruby, get rid of the global, use a class, and read the file in the initialize function. (The constructor.) Make the array be an instance variable with @v.

like image 40
DigitalRoss Avatar answered Oct 12 '22 03:10

DigitalRoss