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

D

Dave Howell

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

Levin Alexander

T24gMi80LzA2LCBEYXZlIEhvd2VsbCA8Z3JvdXBzQGdyYW5kZmVud2ljay5uZXQ+IHdyb3RlOgo+
IEkgbWVhbiwgdGhpcyBzZWVtcyBzbywgZXIsIG9idmlvdXMhICJQbGVhc2UgZmluZCBvbmUgaW5z
dGFuY2Ugb2YgdGhpcwo+IG9iamVjdCBpbiB0aGUgYXJyYXksIGFuZCByZXR1cm4gdG8gbWUgdGhl
IGFycmF5IHdpdGhvdXQgdGhhdCBvYmplY3QuIElmCj4gdGhlIGFycmF5IGRvZXNuJ3QgaGF2ZSB0
aGF0IG9iamVjdCwgdGhlbiBqdXN0IGdpdmUgdGhlIGFycmF5IGJhY2sgdG8KPiBtZS4iCgogIGNs
YXNzIEFycmF5CiAgICBkZWYgd2l0aG91dF9vbmUoZWxlbSkKICAgICAgbmV3ID0gc2VsZi5kdXAg
IyBtYWtlIGEgY29weSBzbyB0aGF0IHRoZSBvcmlnaW5hbCBpcyBub3QgY2hhbmdlZAogICAgICBu
ZXcuZGVsZXRlX2F0KG5ldy5pbmRleChlbGVtKSkKICAgICAgbmV3CiAgICBlbmQKICBlbmQKCiAg
WzEsMiwzLDQsNCw0LDUsNV0ud2l0aG91dF9vbmUoNCkgIz0+IFsxLDIsMyw0LDQsNSw1XQoKLUxl
dmluCg==
 
J

James Edward Gray II

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 Gray II
 
A

ara.t.howard

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?

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
 
L

Logan Capaldo

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?
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) }
unless index_of_element.nil?
self.delete_at(index_of_element)
end
self
end
end
 
L

Levin Alexander

T24gMi80LzA2LCBXaWxzb24gQmlsa292aWNoIDx3aWxzb25iQGdtYWlsLmNvbT4gd3JvdGU6Cj4g
T24gMi80LzA2LCBEYXZlIEhvd2VsbCA8Z3JvdXBzQGdyYW5kZmVud2ljay5uZXQ+IHdyb3RlOgo+
Cj4gU29tZW9uZSB3aWxsIHByb2JhYmx5IHBvc3QgYSBzb2x1dGlvbiB0aGF0IHVzZXMgaW5qZWN0
LCB0aG91Z2guIEhlaC4KCmNsYXNzIEVudW1lcmFibGUKICBkZWYgZGVsZXRlX29uZV9vZihvYmop
CiAgICBzZWxmLmluamVjdChbXSxmYWxzZSkgeyB8KGFyciwgYWxyZWFkeV9kZWxldGVkKSwgZWxl
bXwKICAgICAgYXJyIDw8IGVsZW0gdW5sZXNzIGVsZW0gPT0gb2JqIGFuZCBub3QgYWxyZWFkeV9k
ZWxldGVkCiAgICAgIGFscmVhZHlfZGVsZXRlZCA9IHRydWUgaWYgZWxlbSA9PSBvYmoKICAgICAg
W2FyciwgYWxyZWFkeV9kZWxldGVkXQogICAgfVswXQogIGVuZAplbmQKCi4uLiB0aGlzIGlzIHVu
dGVzdGVkCgotTGV2aW4sIHNjbnIK
 
S

Simon Kröger

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
 
D

Dave Howell

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

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

class Array
def delete_single(element)
if index=3Dself.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=20=

delete didn't return the array, but rather, everything *but* the=20
array. And it had an optional "yield." I'm mystified as to how to make=20=

THAT part work, since neither ri nor the Pickaxe book gave me any hint=20=

at all as to how to detect IF a block is present, and yield if it is.=20
Just using "yield" got me an error since I wasn't using a block in this=20=

case.

And even the above code took me hours to figure out, and I don't really=20=

understand why it works. Well, that's not true, but I don't understand=20=

it well enough to be able to figure out if there's a shorter way to do=20=

it. I got that far by noticing what Pickaxe said about Array.slice!

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

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

something worked. {shrug}
The shortest solution i came up with is

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

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

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

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

Wow. I don't understand this one at all. The "-" operator also was=20
massively destructive, removing all duplicates when used. I'm going to=20=

have to look up the * operator (as I recall, it's one of those really=20
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*=20
of the "real" array. They are, of course, each unique. Array-subtract=20
the index value for the element you want to remove. Array-subtracting=20
"nil" has no effect but isn't an error. Then use the remaining index=20
values to copy those elements out of the original "real" array.

Although I'm still going to have to look up what the * is=20
accomplishing. :)
 
D

Dave Howell

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....
 
J

Joel VanderWerf

Dave said:
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.
 
D

Dave Howell

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....
class Array
def remove(*args) replace(self - args) end
end

a = %w( a b b )
p a.delete_if{|x| x == "b"}
p(a.delete("b") && a)
p a.select{|x| x != "b"}
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>
 
W

Wilson Bilkovich

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

class Array
def delete_one_element!(value)
return self unless i =3D self.index(value)
self =3D 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.


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

irb(main):013:0> a =3D [1,2,3,8,15,15,8]
=3D> [1, 2, 3, 8, 15, 15, 8]
irb(main):014:0> a.delete_first(15)
=3D> [1, 2, 3, 8, 15, 8]
irb(main):015:0>
 
A

ara.t.howard

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....
class Array
def remove(*args) replace(self - args) end
end

a = %w( a b b )
p a.delete_if{|x| x == "b"}
p(a.delete("b") && a)
p a.select{|x| x != "b"}
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
 
A

Adam Shelly

... . 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 !=3D 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=3D=3Dself
arr.each{|e| if (n=3Dindex(e)) then delete_at(n); end }
self
end
end

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

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

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

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


-Adam
 
W

William James

Dave said:
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."

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
 
S

Simon Kröger

[...]
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
 
P

Patrick Deuster

--------------000301040205060500080400
Content-Type: text/plain; charset=ISO-8859-1; format=flowed
Content-Transfer-Encoding: 7bit

William said:
Dave said:
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."

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

--------------000301040205060500080400--
 
J

James Edward Gray II

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 Gray II
 
A

ara.t.howard

[...]
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 emp= ty,
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 ther= e
was no modification.

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

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: i=
s
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 i=
t
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 i=
s
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 except=
ion
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

--=20
happiness is not something ready-made. it comes from your own actions.
- h.h. the 14th dali lama
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,997
Messages
2,570,239
Members
46,827
Latest member
DMUK_Beginner

Latest Threads

Top