1

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.

Lasonic
  • 841
  • 2
  • 9
  • 28

3 Answers3

1

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
Patrice Gahide
  • 3,644
  • 1
  • 27
  • 37
0

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.

Community
  • 1
  • 1
user2864740
  • 60,010
  • 15
  • 145
  • 220
  • Would I need to add self in front of the method name? – Lasonic Oct 11 '14 at 20:56
  • @Lasonic Yes, **but** in this case it's just a silly-named method as the example shows use of a normal *instance method*, which is appropriate - if it was a class method it would have no access to instance data! In any case it is *not* related to the fundamental issue of modifying the *same object* (ie. `self`) in both code snippets. – user2864740 Oct 11 '14 at 20:58
0

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)
Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
  • `array = ["I","just broke","it"]` (look at the first paragraph, example code, and required arguments - not the terrible method name). This is also terribly confusing - and if *I* am saying that, a beginner (posting the given code) likely shouldn't be encouraged such. – user2864740 Oct 11 '14 at 21:13
  • @user2864740, the question does not show `array` to be an argument of the class method, so it is irrelevant. imo, the only way to interpreted the question is to assume `[1,2,3]` is hard-wired. How could `class_meth` know about `array`? – Cary Swoveland Oct 11 '14 at 21:18
  • @user2864740, after reflecting on your comment, I concluded it would be more useful to pass `array` to the method as well, so have modified my answer. – Cary Swoveland Oct 11 '14 at 21:36
  • I still don't think it meets the requirements (for a class perhaps?), but at least that bit isn't hard-coded. I would hope that Array isn't modified will-nilly in either case (if not for a class). – user2864740 Oct 11 '14 at 21:37