Lasonic
Lasonic

Reputation: 901

Non destructively append object to an array with Ruby

So I need to create an instance method for Array that takes two arguments, the size of an array and an optional object that will be appended to an array.

If the the size argument is less than or equal to the Array.length or the size argument is equal to 0, then just return the array. If the optional argument is left blank, then it inputs nil.

Example output:

array = [1,2,3]

array.class_meth(0) => [1,2,3]
array.class_meth(2) => [1,2,3]
array.class_meth(5) => [1,2,3,nil,nil]
array.class_meth(5, "string") => [1,2,3,"string","string"]

Here is my code that I've been working on:

class Array
  def class_meth(a ,b=nil)
    self_copy = self
    diff = a - self_copy.length
    if diff <= 0
      self_copy
    elsif diff > 0
      a.times {self_copy.push b}
    end
    self_copy
  end

  def class_meth!(a ,b=nil)
    # self_copy = self
    diff = a - self.length
    if diff <= 0
      self
    elsif diff > 0
      a.times {self.push b}
    end
    self
  end
end

I've been able to create the destructive method, class_meth!, but can't seem to figure out a way to make it non-destructive.

Upvotes: 1

Views: 2148

Answers (3)

user2864740
user2864740

Reputation: 61865

self_copy = self does not make a new object - assignment in Ruby never "copies" or creates a new object implicitly.

Thus the non-destructive case works on the same object (the instance the method was invoked upon) as in the destructive case, with a different variable bound to the same object - that is self.equal? self_copy is true.

The simplest solution is to merely use #clone, keeping in mind it is a shallow clone operation:

def class_meth(a ,b=nil)
  self_copy = self.clone   # NOW we have a new object ..
  # .. so we can modify the duplicate object (self_copy)
  # down here without affecting the original (self) object.
end

If #clone cannot be used other solutions involve create a new array or obtain an array #slice (returns a new array) or even append (returning a new array) with #+; however, unlike #clone, these generally lock-into returning an Array and not any sub-type as may be derived.

After the above change is made it should also be apparent that it can written as so:

def class_meth(a ,b=nil)
  clone.class_meth!(a, b)  # create a NEW object; modify it; return it
                           # (assumes class_meth! returns the object)
end

A more appropriate implementation of #class_meth!, or #class_meth using one of the other forms to avoid modification of the current instance, is left as an exercise.


FWIW: Those are instance methods, which is appropriate, and not "class meth[ods]"; don't be confused by the ill-naming.

Upvotes: 0

Cary Swoveland
Cary Swoveland

Reputation: 110675

As you problem has been diagnosed, I will just offer a suggestion for how you might do it. I assume you want to pass two and optionally three, not one and optionally two, parameters to the method.

Code

class Array
  def self.class_meth(n, arr, str=nil)
    arr + (str ? ([str] : [nil]) * [n-arr.size,0].max)
  end
end

Examples

Array.class_meth(0, [1,2,3])
  #=> [1,2,3]
Array.class_meth(2, [1,2,3])
  #=> [1,2,3]
Array.class_meth(5, [1,2,3])
  #=> [1,2,3,nil,nil]
Array.class_meth(5, [1,2,3], "string")
  #=> [1,2,3,"string","string"]
Array.class_meth(5, ["dog","cat","pig"])
  #=> [1,2,3,"string","string"]
Array.class_meth(5, ["dog","cat","pig"], "string")
  #=> [1,2,3,"string","string"]
Array.class_meth(5, ["dog","cat","pig"])
  #=> ["dog", "cat", "pig", nil, nil]
Array.class_meth(5, ["dog","cat","pig"], "string")
  #=> ["dog", "cat", "pig", "string", "string"]

Before withdrawing his answer, @PatriceGahide suggested using Array#fill. That would be an improvement here; i.e., replace the operative line with:

arr.fill(str ? str : nil, arr.size, [n-arr.size,0].max)

Upvotes: 0

Patrice Gahide
Patrice Gahide

Reputation: 3744

Here's (IMHO) a cleaner solution:

class Array
  def class_meth(a, b = nil)
    clone.fill(b, size, a - size)
  end

  def class_meth!(a, b = nil)
    fill(b, size, a - size)
  end
end

I think it should meet all your needs. To avoid code duplication, you can make either method call the other one (but not both simulaneously, of course):

def class_meth(a, b = nil)
  clone.class_meth!(a, b)
end

or:

def class_meth!(a, b = nil)
  replace(class_meth(a, b))
end

Upvotes: 1

Related Questions