Some questions on ruby - case1(hash values changed unexpected)

#################################

  1. cat case1.rb
    #################################
    #!/usr/bin/env ruby

tools_info_array = {
‘tool_a’ => [‘tool_a_home’, ‘2009.12-17’, ‘bin’, {‘LD_LIBRARY_PATH’
=> ‘lib’}]
}

toolName = ‘tool_a’

tools_info = tools_info_array[toolName]

if tools_info.size > 3
print “\nbefore delete:\n”
puts “tools_info_array:\n”, tools_info_array
tools_info.slice!(0…2)
print “\nafter delete:\n”
puts “tools_info_array:\n”, tools_info_array
end

tools_info.each do |xsub|

end

#################################
2. run case1.rb get results below
#################################
before delete:
tools_info_array:
{“tool_a”=>[“tool_a_home”, “2009.12-17”, “bin”,
{“LD_LIBRARY_PATH”=>“lib”}]}

after delete:
tools_info_array:
{“tool_a”=>[{“LD_LIBRARY_PATH”=>“lib”}]}

#################################
3. my question
#################################
Why modify ‘tools_info’ cause ‘tools_info_array’ changed?
Define a temp variable to save tools_info as below can avoid issue, but
is there other method can avoid using temp variable, that get
‘tools_info’ modified but without influence on ‘tools_info_array’?

tools_info_tmp = tools_info.slice(3…-1)
tools_info_tmp.each do |xsub|

end

It’s this: “tools_info.slice!”

The clue is in the “!”. It’s a “bang” method, which means it modifies
the receiver.

Joel P. wrote in post #1125477:

It’s this: “tools_info.slice!”

The clue is in the “!”. It’s a “bang” method, which means it modifies
the receiver.

I know this, so I use “!”, what I want is to modify ‘tools_info’ but
don’t
change ‘tools_info_array’.

You can duplicate objects with the #dup method. This creates a copy
without introducing a temporary variable. The normal assignment
operation just copies a reference, so in your code tools_info refers to
the same object instance and not a copy. (Also, I think #clone is an
alias of #dup.)

tools_info = tools_info_array[toolName].dup

You could always use tap if you don’t want a local variable hanging
around.

tools_info.slice(3…-1).tap { |tools_info_tmp| other_stuff }

Another way to handle it is to use #slice instead of #slice! as follows:

tools_info = tools_info.slice(0…2)

This allows the slice method to do the duplication as well as the
slicing.

One way to define bang and non-bang method pairs is to make the safe
method duplicate the arguments of the unsafe (bang!) method:

def some_method!(some_object)
# do something dangerous to some_object here
end

def some_method(some_object)
some_method!(some_object.dup)
end

As you can see, some_method does the exact same thing as some_method!,
but on a duplicate of the original object. This is an implicit way of
the explicit #dup I described before:

tools_info = tools_info_array[toolName].dup

I suppose it depends on the desired level of code, memory, and
processing complexity desired.

Jamal Wills

Sorry, I’m less familiar with that particular method. In that case,
this may work better:

tools_info = tools_info.dup.slice!(0…2)

Or this:

tools_info = tools_info.slice(3…-1)

Jamal Wills

WILLS, JAMAL A wrote in post #1125494:

Another way to handle it is to use #slice instead of #slice! as follows:

tools_info = tools_info.slice(0…2)

This doesn’t do the same thing as #slice!, the return value is what is
removed, not the remainder.

Hi WILLS,

Thanks for your help check this issue, now I understand, in ruby, the
default behavior of the array variable is a reference(or name it as a
pointer) to the array(even slice of array), so any change on the contect
of slice will cause the original array value changed, the safe way is
dupicate the slice, or just move the pointer to the start of the slice
data, am I right?

Thanks a lot,
Previn