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


#1

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?


#2

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


#3

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


#4

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.


#5

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


#6

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


#7

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


#8

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


#9

On Feb 4, 2006, at 13:10, Logan C. wrote:

   if block_given?

Aha!

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…


#10

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”?


#11

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. :slight_smile:


#12

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


#13

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.


#14

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?


#15

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.


#16

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?

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


#17

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


#18

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>


#19

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


#20

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