Why does Array#compact! return the array, but uniq! return a count?

I wanted to remove all nils and duplicate values from an array:


@fields.collect{ |field| field.group }.compact!.uniq!
end

I wanted to modify in place, because the return value of collect() is
ephemeral, nobody else can have a reference, no need to duplicate it, I
thought…

But now I have:

all = @fields.collect{ |field| field.group }.compact!
all.uniq!
all
end

Should I just do:

@fields.collect{ |field| field.group }.compact.uniq

Doesn’t this create an array,
create a new array without nils,
throw away the old,
create a new array without duplicates,
and throw away the old
??

Should I not worry about it?

Sam

Oops, sorry, uniq! doesn’t return a count, but it doesn’t return the
resulting array, either, UNLESS there were duplicates…

~/p/ruby/vcard $ irb
irb(main):001:0> [“a”].uniq!
nil
irb(main):002:0> [“a”, “a”].uniq!
[“a”]
irb(main):003:0> [“a”].uniq
[“a”]
irb(main):004:0> [“a”, “a”].uniq
[“a”]

Quoteing sroberts@uniserve.com, on Thu, Feb 13, 2003 at 01:32:42PM +0900:

···

I wanted to remove all nils and duplicate values from an array:


@fields.collect{ |field| field.group }.compact!.uniq!
end

I wanted to modify in place, because the return value of collect() is
ephemeral, nobody else can have a reference, no need to duplicate it, I
thought…

But now I have:

all = @fields.collect{ |field| field.group }.compact!
all.uniq!
all
end

Should I just do:

@fields.collect{ |field| field.group }.compact.uniq

Doesn’t this create an array,
create a new array without nils,
throw away the old,
create a new array without duplicates,
and throw away the old
??

Should I not worry about it?

Sam

Hi,

···

In message “Why does Array#compact! return the array, but uniq! return a count?” on 03/02/13, Sam Roberts sroberts@uniserve.com writes:

I wanted to remove all nils and duplicate values from an array:


@fields.collect{ |field| field.group }.compact!.uniq!
end

I wanted to modify in place, because the return value of collect() is
ephemeral, nobody else can have a reference, no need to duplicate it, I
thought…

But now I have:

all = @fields.collect{ |field| field.group }.compact!
all.uniq!
all
end

Array’s bang methods return nil if it does not modify the receiver, so
that chaining is not encouraged.

						matz.

Hi –

I wanted to remove all nils and duplicate values from an array:


@fields.collect{ |field| field.group }.compact!.uniq!
end

I wanted to modify in place, because the return value of collect() is
ephemeral, nobody else can have a reference, no need to duplicate it, I
thought…

But now I have:

all = @fields.collect{ |field| field.group }.compact!
all.uniq!
all
end

Partial answer/info:

Keep in mind that a lot of the ! methods return nil if there’s no
change:

irb(main):002:0> [1,2,3].compact!
=> nil

So if compact! works, it’s only because there are some nils there.
It’s probably best either to modify in place, or to assign the result
to a variable, but not both.

David

···

On Thu, 13 Feb 2003, Sam Roberts wrote:


David Alan Black
home: dblack@candle.superlink.net
work: blackdav@shu.edu
Web: http://pirate.shu.edu/~blackdav

Ah. I misread the compact! docs, and thought it was different.

Now I understand.

Thanks!

Sam

Quoteing matz@ruby-lang.org, on Thu, Feb 13, 2003 at 01:41:17PM +0900:

···

Hi,

In message “Why does Array#compact! return the array, but uniq! return a count?” > on 03/02/13, Sam Roberts sroberts@uniserve.com writes:

I wanted to remove all nils and duplicate values from an array:


@fields.collect{ |field| field.group }.compact!.uniq!
end

I wanted to modify in place, because the return value of collect() is
ephemeral, nobody else can have a reference, no need to duplicate it, I
thought…

But now I have:

all = @fields.collect{ |field| field.group }.compact!
all.uniq!
all
end

Array’s bang methods return nil if it does not modify the receiver, so
that chaining is not encouraged.

  					matz.

matz@ruby-lang.org (Yukihiro Matsumoto) wrote in message news:1045109926.846613.10554.nullmailer@picachu.netlab.jp

Array’s bang methods return nil if it does not modify the receiver, so
that chaining is not encouraged.

I’m not suggesting a change, but what is the rationale for this?

IMHO, chaining is one of the most useful aspects of ruby – multiple
lines of code can be folded into one. With chaining you’re generally
not interested in the intermediate results (otherwise you’d store them
in a variable somewhere) and so in-place modification is more
efficient since you don’t have to create lots of intermediate objects
that are then immediately discarded.

If you do want to chain, but don’t want to modify the receiver, then
you can just insert an Object#dup.

uniq!, for example, actually does two things: it removes duplicates
from the receiver AND tells you whether the were any duplicates in the
original array. IMHO these should be separate methods.

Therefore I think that the scheme that desctructive updates (!
methods) should always return self, i.e. that Ruby’s behaviour is
wrong. Obviously you’ve thought about this in detail so what am I
missing?

Once again, I’m not suggesting a change (it would break just about
every Ruby program!), but am interested in Matz’s original reasoning.

Cheers,

Tom

Array’s bang methods return nil if it does not modify the receiver, so
that chaining is not encouraged.

I’m not suggesting a change, but what is the rationale for this?

IMHO, chaining is one of the most useful aspects of ruby – multiple
lines of code can be folded into one. With chaining you’re generally
not interested in the intermediate results (otherwise you’d store them
in a variable somewhere) and so in-place modification is more
efficient since you don’t have to create lots of intermediate objects
that are then immediately discarded.

I’m with Tom on this one. The whole thing seems like a Pythonista form of “Do
it this way. There is no other way.” social engineering.

Hi,

Array’s bang methods return nil if it does not modify the receiver, so
that chaining is not encouraged.

I’m not suggesting a change, but what is the rationale for this?

Because their’s no cheaper way to detect modifies, without making
copies.

IMHO, chaining is one of the most useful aspects of ruby – multiple
lines of code can be folded into one. With chaining you’re generally
not interested in the intermediate results (otherwise you’d store them
in a variable somewhere) and so in-place modification is more
efficient since you don’t have to create lots of intermediate objects
that are then immediately discarded.

Use non-bang methods (bit slower, but safer) for chaining. Ugly bangs
are warning mark for side effects.

						matz.
···

In message “Re: Why does Array#compact! return the array, but uniq! return a count?” on 03/02/13, Tom Payne google@tompayne.org writes:

I guess the main question is

How often do I care whether or not the receiver was modified?

For

@fields.collect{ |field| field.group }.compact!.uniq!

we don’t really care whether compact!() or uniq!() do nothing, we’re
interested in ensuring the final state of the array generated by collect().

It just doesn’t seem that often that we’ll need to do this:

unless (arr.uniq!) { 
	# Darn! Do something because arr already contained unique 
	# elements
}

For times when we care about the uniqueness or compactness of the elements
of the array, we could have

uniq?
compact?

if you plan on doing more than just calling uniq! or compact! anyway.

Other ideas:

- change ! methods to always return the receiver, create new methods
  that have the current ! functionality:

	expect_uniq!
	expect_compact!

- leave the old methods alone, add new ones that always return the 
  receiver

	ensure_uniq!
	ensure_compact!

Personally, I’d prefer if by default most methods did modify and return the
receiver, using another symbol to denote creation of a new object

arr.sort<.each {
arr.uniq<.each {

or
arr.sort*.each {
arr.uniq*.each {
or
arr.sort+.each {
arr.uniq+.each {

but that’s a pretty huge change :->

···

On Thu, 13 Feb 2003 22:51:06 +0900 “Mike Campbell” michael_s_campbell@yahoo.com wrote:

I’m with Tom on this one. The whole thing seems like a Pythonista form of “Do
it this way. There is no other way.” social engineering.


Jim Hranicky, Senior SysAdmin UF/CISE Department |
E314D CSE Building Phone (352) 392-1499 |
jfh@cise.ufl.edu http://www.cise.ufl.edu/~jfh |


For times when we care about the uniqueness or compactness of the elements
of the array, we could have

uniq?
compact?

I like this…

if you plan on doing more than just calling uniq! or compact! anyway.

Other ideas:

  • change ! methods to always return the receiver, create new methods
    that have the current ! functionality:

This would break existing code possibly.

I wonder about using an optional parameter to return status
information, instead of having to “overload” the method’s result …

Script started on Thu Feb 13 16:43:44 2003
neelix hgs 11 %> cat test_new_uniq.rb
#!/usr/local/bin/ruby -w

class BoolStatus
def initialize(bool)
@value = bool
end
def true
@value = true
end
def false
@value = false
end
def true?
@value == true ? true : false
end
def false?
@value == false ? true : false
end

def to_s
    @value.to_s
end

end

class Array
alias old_uniq! uniq!

def uniq!(modified=nil)
    if old_uniq!.nil?
        if modified.nil?
            return nil      # same as now
        else
            modified.false
        end
    else
        unless modified.nil?
            modified.true
        end
    end
    return self
end

end

u = [1,2,3,4,5]
v = [1,2,3,4,5]
x = [1,2,2,3,3,4,4,5]
y = [1,2,2,3,3,4,4,5]

z = BoolStatus.new(false)
puts u.uniq!.inspect
puts v.uniq!(z).inspect
puts z.inspect

puts x.uniq!.inspect
puts y.uniq!(z).inspect
puts z.inspect

neelix hgs 12 %> ./test_new_uniq/.rb
nil
[1, 2, 3, 4, 5]
#<BoolStatus:0xe8f98 @value=false>
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
#<BoolStatus:0xe8f98 @value=true>
neelix hgs 13 %> exit
neelix hgs 14 %>
script done on Thu Feb 13 16:44:05 2003

Having the extra parameter to determine behaviour desn’t break
existing code, I think.


Jim Hranicky, Senior SysAdmin UF/CISE Department |

    Hugh
···

On Thu, 13 Feb 2003, James F.Hranicky wrote:

I elided the ctrl-H chars and left in the spurious ‘/’!
neelix hgs 12 %> ./test_new_uniq.rb
was the intended entry there!
Hugh

···

On Fri, 14 Feb 2003, Hugh Sasse Staff Elec Eng wrote:

neelix hgs 12 %> ./test_new_uniq/.rb