Forum: Ruby Quiz #65, Principle of Great Surprise, and Array.delete sled

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Dave H. (Guest)
on 2006-02-04 22:51
(Received via mailing list)
I thought I was actually going to enter my first RubyQuiz, but I've
spent three times as much time trying to get Ruby to stop surprising me
as I have trying to implement my algorithm, and I've had to abandon my
effort since I don't have the time to spend. Sigh.

Can somebody explain to me how I'm supposed to delete a *single*
element from an array based on equivalence with an object? and get back
said array with said element deleted?

In other words....

ar = [1, 2, 3, 8, 15, 15, 8]

ar.delete_one_element!(15)
=> [1, 2, 3, 8, 15, 8]

ar.delete_one_element!(14)
=> [1, 2, 3, 8, 15, 15, 8]

ar.delete_one_element!(nil)
=> [1, 2, 3, 8, 15, 15, 8]

I did find so many different ways of not doing this....

Too enthusiastic....
ar.delete(15)
=> 15
ar
=> [1, 2, 3, 8, 8]

Right results, wrong output...
ar.slice!(ar.index(15))
=> 15
ar
=> [1, 2, 3, 8, 15, 8]

and also surprised me by not handling 'nil' as I wanted...
ar.slice!(ar.index(13))
TypeError: no implicit conversion from nil to integer
         from (irb):8:in `slice!'
         from (irb):8


I mean, this seems so, er, obvious! "Please find one instance of this
object in the array, and return to me the array without that object. If
the array doesn't have that object, then just give the array back to
me."

Why isn't that the Ruby Way? What am I missing here?

Help?
Levin A. (Guest)
on 2006-02-04 23:07
(Received via mailing list)
On 2/4/06, Dave H. <removed_email_address@domain.invalid> wrote:
> I mean, this seems so, er, obvious! "Please find one instance of this
> object in the array, and return to me the array without that object. If
> the array doesn't have that object, then just give the array back to
> me."

  class Array
    def without_one(elem)
      new = self.dup # make a copy so that the original is not changed
      new.delete_at(new.index(elem))
      new
    end
  end

  [1,2,3,4,4,4,5,5].without_one(4) #=> [1,2,3,4,4,5,5]

-Levin
James G. (Guest)
on 2006-02-04 23:10
(Received via mailing list)
On Feb 4, 2006, at 2:50 PM, Dave H. wrote:

> Can somebody explain to me how I'm supposed to delete a *single*
> element from an array based on equivalence with an object? and get
> back said array with said element deleted?

I was just wanting this method yesterday, and not for the first
time.  Is there any good reason we can't have a delete_first() method
for Array?

James Edward G. II
Wilson B. (Guest)
on 2006-02-04 23:10
(Received via mailing list)
On 2/4/06, Dave H. <removed_email_address@domain.invalid> wrote:
>
>
> => 15
> I mean, this seems so, er, obvious! "Please find one instance of this
> object in the array, and return to me the array without that object. If
> the array doesn't have that object, then just give the array back to
> me."
>
> Why isn't that the Ruby Way? What am I missing here?
>
> Help?

If I'm understanding you properly, this is one way:

class Array
  def delete_one_element!(value)
    return self unless i = self.index(value)
    self[i] = nil
    self.compact!
  end
end

irb(main):023:0> ar = [1,2,3,8,15,15,8]
=> [1, 2, 3, 8, 15, 15, 8]
irb(main):024:0> ar.delete_one_element!(15)
=> [1, 2, 3, 8, 15, 8]
irb(main):025:0> ar.delete_one_element!(99)
=> [1, 2, 3, 8, 15, 8]
irb(main):026:0>

Someone will probably post a solution that uses inject, though. Heh.
unknown (Guest)
on 2006-02-04 23:13
(Received via mailing list)
On Sun, 5 Feb 2006, Dave H. wrote:

>
>
> => 15
> I mean, this seems so, er, obvious! "Please find one instance of this object
> in the array, and return to me the array without that object. If the array
> doesn't have that object, then just give the array back to me."
>
> Why isn't that the Ruby Way? What am I missing here?
>
> Help?

   harp:~ > cat a.rb
   class Array
     def remove(*args) replace(self - args) end
   end

   a = %w( a b b )
   p a.delete_if{|x| x == "b"}

   a = %w( a b b )
   p(a.delete("b") && a)

   a = %w( a b b )
   p a.select{|x| x != "b"}

   a = %w( a b b )
   p a - ["b"]

   a = %w( a b b c )
   p a.remove("b", "c")


   harp:~ > ruby a.rb
   ["a"]
   ["a"]
   ["a"]
   ["a"]
   ["a"]

there are probably more ways.

hth.

-a
Logan C. (Guest)
on 2006-02-04 23:13
(Received via mailing list)
On Feb 4, 2006, at 3:50 PM, Dave H. wrote:

>
>
> => 15
> I mean, this seems so, er, obvious! "Please find one instance of
>
require 'enumerator'
class Array
def delete_one_element(x = nil)
        if block_given?
           tester = lambda { |item| yield(item) }
        else
            tester = lambda { |item| item == x }
        end
        index_of_element = self.to_enum(:each_index).find { |i|
tester.call(self[i]) }
        unless index_of_element.nil?
           self.delete_at(index_of_element)
        end
        self
end
end
Levin A. (Guest)
on 2006-02-04 23:22
(Received via mailing list)
On 2/4/06, Wilson B. <removed_email_address@domain.invalid> wrote:
> On 2/4/06, Dave H. <removed_email_address@domain.invalid> wrote:
>
> Someone will probably post a solution that uses inject, though. Heh.

class Enumerable
  def delete_one_of(obj)
    self.inject([],false) { |(arr, already_deleted), elem|
      arr << elem unless elem == obj and not already_deleted
      already_deleted = true if elem == obj
      [arr, already_deleted]
    }[0]
  end
end

... this is untested

-Levin, scnr
Simon Kröger (Guest)
on 2006-02-04 23:25
(Received via mailing list)
> Why isn't that the Ruby Way? What am I missing here?
>
> Help?

You obviously know how to code such a method yourself and i can't answer
why for example Array#- doesn't work that way (had to learn that
myself).

The shortest solution i came up with is

ar = [1, 2, 3, 8, 15, 15, 8]

p ar.values_at(*(0...ar.size).to_a - [ar.index(15)])
=> [1, 2, 3, 8, 15, 8]

p ar.values_at(*(0...ar.size).to_a - [ar.index(14)])
=> [1, 2, 3, 8, 15, 15, 8]

p ar.values_at(*(0...ar.size).to_a - [ar.index(nil)])
=> [1, 2, 3, 8, 15, 15, 8]

cheers

Simon
Dave H. (Guest)
on 2006-02-04 23:43
(Received via mailing list)
On Feb 4, 2006, at 13:22, Simon Kröger wrote:

>
>> Why isn't that the Ruby Way? What am I missing here?
>> Help?
>
> You obviously know how to code such a method yourself and i can't
> answer why for example Array#- doesn't work that way (had to learn
> that myself).

Well, actually, no, I don't. Here's what I bashed together:

class Array
	def delete_single(element)
		if index=self.index(element) then
			self.slice!(index,1)
			self
		else
			nil
		end
	end
end

It was originally going to be similar to .delete until I realized that
.delete didn't return the array, but rather, everything *but* the
array. And it had an optional "yield." I'm mystified as to how to make
THAT part work, since neither ri nor the Pickaxe book gave me any hint
at all as to how to detect IF a block is present, and yield if it is.
Just using "yield" got me an error since I wasn't using a block in this
case.

And even the above code took me hours to figure out, and I don't really
understand why it works. Well, that's not true, but I don't understand
it well enough to be able to figure out if there's a shorter way to do
it. I got that far by noticing what Pickaxe said about Array.slice!

	Equivalent to
		def slice!(*args)
			result = self[*args]
			self[*args] = nil
			result
		end

which basically whispered to me "you have to return something other
than the result of your deletion...." So I tried a couple things until
something worked. {shrug}

> p ar.values_at(*(0...ar.size).to_a - [ar.index(nil)])
> => [1, 2, 3, 8, 15, 15, 8]

Wow. I don't understand this one at all. The "-" operator also was
massively destructive, removing all duplicates when used. I'm going to
have to look up the * operator (as I recall, it's one of those really
strange-and-clever Ruby things) to see what might be going on here.

Oh! Oh oh oh!

My, that's very clever. Create an array containing the *index values*
of the "real" array. They are, of course, each unique. Array-subtract
the index value for the element you want to remove. Array-subtracting
"nil" has no effect but isn't an error. Then use the remaining index
values to copy those elements out of the original "real" array.

Although I'm still going to have to look up what the * is
accomplishing. :)
Dave H. (Guest)
on 2006-02-04 23:46
(Received via mailing list)
On Feb 4, 2006, at 13:10, Logan C. wrote:

>        if block_given?

Aha!

<checks index of Pickaxe>

I see. It's in there, but buried near the end of a section in the
middle of the discussion on blocks.

Time for some more highlighting....
Dave H. (Guest)
on 2006-02-04 23:50
(Received via mailing list)
On Feb 4, 2006, at 13:09, James Edward G. II wrote:

> On Feb 4, 2006, at 2:50 PM, Dave H. wrote:
>
>> Can somebody explain to me how I'm supposed to delete a *single*
>> element from an array based on equivalence with an object? and get
>> back said array with said element deleted?
>
> I was just wanting this method yesterday, and not for the first time.
> Is there any good reason we can't have a delete_first() method for
> Array?

Wouldn't delete_first still ought to return "_obj_ or nil"?
Joel VanderWerf (Guest)
on 2006-02-04 23:53
(Received via mailing list)
Dave H. wrote:
> And it had an optional "yield." I'm mystified as to how to make THAT
> part work, since neither ri nor the Pickaxe book gave me any hint at all
> as to how to detect IF a block is present, and yield if it is. Just
> using "yield" got me an error since I wasn't using a block in this case.

Use Kernel.block_given?, like this:

def one_two_three
  if block_given?; yield 1; yield 2; yield 3;
  else [1,2,3]
  end
end

See the PickAxe, in the chapter on blocks and iterators, page 51.
James G. (Guest)
on 2006-02-04 23:59
(Received via mailing list)
On Feb 4, 2006, at 3:49 PM, Dave H. wrote:

>> time.  Is there any good reason we can't have a delete_first()
>> method for Array?
>
> Wouldn't delete_first still ought to return "_obj_ or nil"?

Good point.  You are probably right.

James Edward G. II
Dave H. (Guest)
on 2006-02-05 00:23
(Received via mailing list)
On Feb 4, 2006, at 13:10, Wilson B. wrote:

> If I'm understanding you properly, this is one way:
>
> class Array
>   def delete_one_element!(value)
>     return self unless i = self.index(value)
>     self[i] = nil
>     self.compact!
>   end
> end
>

You are, but it isn't. I might well have "nil" as a legitimate item in
the array, and .compact would erase all of them. I definitely want a
solution that can _never_ reduce the size of my array by more than one
element.
Dave H. (Guest)
on 2006-02-05 00:29
(Received via mailing list)
On Feb 4, 2006, at 13:10, removed_email_address@domain.invalid wrote:

>> ar.delete_one_element!(15)
>> => [1, 2, 3, 8, 15, 8]
>>
>> ar.delete_one_element!(14)
>> => [1, 2, 3, 8, 15, 15, 8]
>>
>> ar.delete_one_element!(nil)
>> => [1, 2, 3, 8, 15, 15, 8]
>>
>> I did find so many different ways of not doing this....
>>

>
> all return
>   ["a"]

This is just a list of many other ways to not do what I want, yes?
<scratch head>
Wilson B. (Guest)
on 2006-02-05 03:15
(Received via mailing list)
On 2/4/06, Dave H. <removed_email_address@domain.invalid> wrote:
> >   end
> > end
> >
>
> You are, but it isn't. I might well have "nil" as a legitimate item in
> the array, and .compact would erase all of them. I definitely want a
> solution that can _never_ reduce the size of my array by more than one
> element.
>

OK.. Here's a safer version:
class Array
  def delete_first(value)
    return self unless i = self.index(value)
    self.delete_at(i)
    self
  end
end

irb(main):013:0> a = [1,2,3,8,15,15,8]
=> [1, 2, 3, 8, 15, 15, 8]
irb(main):014:0> a.delete_first(15)
=> [1, 2, 3, 8, 15, 8]
irb(main):015:0>
unknown (Guest)
on 2006-02-05 04:48
(Received via mailing list)
On Sun, 5 Feb 2006, Dave H. wrote:

>>> ar = [1, 2, 3, 8, 15, 15, 8]
>>> I did find so many different ways of not doing this....
>>   p a - ["b"]
>>   p a.remove("b", "c")
>>
>> all return
>>   ["a"]
>
> This is just a list of many other ways to not do what I want, yes? <scratch
> head>


sorry.  didn't read closely enough:

     harp:~ > cat a.rb
     class Array
       def remove(elem) delete_at(index(elem)) rescue nil; self end
     end

     a = [1, 2, 3, 8, 15, 15, 8]
     p a.remove(15)


     harp:~ > ruby a.rb
     [1, 2, 3, 8, 15, 8]

though i'll say that not raising an error when the element is not found
is an
amazingly bad idea that would mask many bugs (imagine an algorithm that
should
sometimes remove things but __never__ does)

regards.

-a
Adam S. (Guest)
on 2006-02-05 06:55
(Received via mailing list)
On 2/4/06, Dave H. <removed_email_address@domain.invalid> wrote:
>
>  ... . The "-" operator also was
> massively destructive, removing all duplicates when used.

I ran into that problem too when I was coding the quiz.
Does anyone else find it strange that a + b - b != a  if a contains
duplicates of any element in b?

To finish the quiz, I added this method:

class Array
  def subtract arr
    return clear if arr==self
    arr.each{|e| if (n=index(e)) then delete_at(n); end }
    self
  end
end

a = [1,2,3,3,3,4,5,5,6]
=> [1, 2, 3, 3, 3, 4, 5, 5, 6]

b = [3,4,5]
=> [3, 4, 5]

a.subtract b
=> [1, 2, 3, 3, 5, 6]

a
=> [1, 2, 3, 3, 5, 6]


-Adam
William J. (Guest)
on 2006-02-05 13:30
(Received via mailing list)
Dave H. wrote:
>
>
> => 15
> I mean, this seems so, er, obvious! "Please find one instance of this
> object in the array, and return to me the array without that object. If
> the array doesn't have that object, then just give the array back to
> me."

This may not be of any use to you, but here's a way to delete all
elements but the first:

class Array
  def no_dup!( x )
    (i = index(x)) && delete( x ) && insert(i,x)
    self
  end
end

And here's another version of delete_one_element!.

class Array
  def delete_one_element!( x )
    (i = index(x)) && delete_at( i )
    self
  end
end
Simon Kröger (Guest)
on 2006-02-05 14:28
(Received via mailing list)
removed_email_address@domain.invalid wrote:

> [...]
> not raising an error when the element is not found is an
> amazingly bad idea that would mask many bugs
> [...]

Well, i don't think it's 'amazingly bad'. Array#- doesn't raise an error
in this case, Array#clear doesn't raise an error if the array is already
empty, neither does the Array intersection or any of the Set methods.

Perhaps you can explain why Array#delete doesn't return the array but
the parameter (preventing method chaining).

My first idea was that it should work like Array#flatten /
Array#flatten! both returning the modified array, the bang version
returning nil if there was no modification.

Ok, just my 2 cents, fell free to ignore this post - i just stumbled
about a similar 'inconsistency' myself and wanted to say that.
(no i don't want to change ruby, and there is no offense intended)

cheers

Simon
Patrick D. (Guest)
on 2006-02-05 15:28
(Received via mailing list)
William J. wrote:
>>
>> ar.delete_one_element!(nil)
>> Right results, wrong output...
>>
> class Array
>     (i = index(x)) && delete_at( i )
>     self
>   end
> end
>
>
>
>
And here's yet another version, similar to the ones already posted
though:

class Array
  def delete_one! n
    (i = index(n)) ? delete_at(i) : nil
  end
end
James G. (Guest)
on 2006-02-05 18:02
(Received via mailing list)
On Feb 5, 2006, at 5:28 AM, William J. wrote:

> This may not be of any use to you, but here's a way to delete all
> elements but the first:
>
> class Array
>   def no_dup!( x )
>     (i = index(x)) && delete( x ) && insert(i,x)
>     self
>   end
> end

Array.uniq!

???

James Edward G. II
unknown (Guest)
on 2006-02-05 18:50
(Received via mailing list)
On Sun, 5 Feb 2006, [ISO-8859-1] Simon Kröger wrote:

>
>
> cheers

good points all.  i can only guess

   - Array#- doesn't raise an error since it can partially succeed/fail,
in
     otherwords some of the elements might be removed while some are
not: is
     this success or failure?  also Array#- is defined as 'set
difference' and,
     under that def not raising an error certainly makes good sense -
the
     result is always what you asked for - even if it's empty.

   - Array#clear shouldn't raise an error, since the result after
calling it
     always want you'd hoped for : an empty arrray.

   - Array#delete... well you got me there.  nil is at least useful so
one can
     do

       a.delete(elem) or raise "failed"

     otherwise you have no idea if the operation succeeded or not.


the thing with writing a Array#delete_first method that returns the
array is
that there's absolutely no indication of success or, as in the case of
Array#clear, a guarantee of success.

in general i think methods basically fail into two categories

   - those that always succeed, Array#clear for example.  these can
simply
     return self.

   - those that might fail and need to either return nil or raise an
exception
     to indicate this.  raising is nice because you can chain.

it's not clear to me why 'delete_first' would be in the first category
but i
think this is largely religious.

cheers.

-a
William J. (Guest)
on 2006-02-05 23:13
(Received via mailing list)
James Edward G. II wrote:
> > end
>
> Array.uniq!
>
> ???
>
> James Edward G. II

irb(main):008:0> a=[1,1,2,2,3,3]
=> [1, 1, 2, 2, 3, 3]
irb(main):009:0> a.no_dup!(2)
=> [1, 1, 2, 3, 3]
irb(main):010:0> a.uniq!(3)
ArgumentError: wrong number of arguments (1 for 0)
        from (irb):10:in `uniq!'
        from (irb):10
irb(main):011:0> a.uniq!
=> [1, 2, 3]

Array#uniq is "massively destructive".
Dave H. (Guest)
on 2006-02-06 02:12
(Received via mailing list)
On Feb 5, 2006, at 8:50, removed_email_address@domain.invalid wrote:

> in general i think methods basically fail into two categories
> but i
> think this is largely religious.

I think you're confusing "failure" with something else. If I ask a
method to delete a "22" from an array that contains none of them, then,
as I see it, deleting nothing from the array is a correct, successful
response. If I *care* if there are any such elements in the array in
the first place I can easily (and succinctly)
	if myArray.index(element)
This topic is locked and can not be replied to.