Proposal for Hash#invert

H

Han Holl

Hello,

The current Hash#invert implementation will silently throw away keys that
have the same value associated with them.
Because I often have many to one hashes, but have preferred keys, I'd like
to propose having an optional arbitrating block, that gets called with
the alternative valuse in an array. Something like this:

class Hash
def invert(&blk)
rsl = {}
arbitrate = {}
each_pair do |k, v|
if rsl.has_key?(v) && block_given?
(arbitrate[v] ||= [rsl[v]]) << k
else
rsl[v] = k
end
end
arbitrate.each_pair do |k,v|
rsl[k] = yield v
end
rsl
end
end

You can call this with something like:
hash.invert {|x| x.find {|el| fields.include?(el)} ||
x.sort_by{|el| el.size}[-1] }

or even the simple:
hash.invert { raise 'This hash is not invertible' }

Does this sound generally useful, or is it just me ?

Cheers,

Han Holl
 
N

nobu.nokada

Hi,

At Thu, 8 Jul 2004 21:42:34 +0900,
Han Holl wrote in [ruby-talk:105571]:
You can call this with something like:
hash.invert {|x| x.find {|el| fields.include?(el)} ||
x.sort_by{|el| el.size}[-1] }

or even the simple:
hash.invert { raise 'This hash is not invertible' }

Does this sound generally useful, or is it just me ?

Sounds good, but the block can't get the key?
 
R

Robert Klemme

Han Holl said:
Hello,

The current Hash#invert implementation will silently throw away keys that
have the same value associated with them.
Because I often have many to one hashes, but have preferred keys, I'd like
to propose having an optional arbitrating block, that gets called with
the alternative valuse in an array. Something like this:

class Hash
def invert(&blk)

Don't need the blk as far as I can see.
rsl = {}
arbitrate = {}
each_pair do |k, v|
if rsl.has_key?(v) && block_given?
(arbitrate[v] ||= [rsl[v]]) << k
else
rsl[v] = k
end
end


A check for block_given? is missing here:
arbitrate.each_pair do |k,v|
rsl[k] = yield v
end
rsl
end
end

You can call this with something like:
hash.invert {|x| x.find {|el| fields.include?(el)} ||
x.sort_by{|el| el.size}[-1] }

or even the simple:
hash.invert { raise 'This hash is not invertible' }

Does this sound generally useful, or is it just me ?

It does indeed sound useful but I would implement it differently in order
to save the extra hash which is only needed in certain cases. I'd hand
over all info and let the block decide what to do with it:

class Hash

def invert_test(&b)
reverse = self.class.new

each do |key, val|
if b and reverse.has_key? val
b.call reverse, val, key
else
reverse[val] = key
end
end

reverse
end

end


h = {
"a" => 1,
"b" => 2,
"c" => 3,
"d" => 3,
}

puts "original"
p h

puts "overwrite"
p h.invert_test

puts "ignore"
p h.invert_test {}

puts "modify key"
p h.invert_test {|hash, key, val| hash["duplicate #{key}"] = val}

puts "store in array if more than one value"
p h.invert_test {|hash, key, val|
oldval = hash[key]

case oldval
when Array
oldval << val
else
hash[key] = [oldval, val]
end
}


Yields:

17:45:21 [ruby]: ./hash-reverse.rb
original
{"a"=>1, "b"=>2, "c"=>3, "d"=>3}
overwrite
{1=>"a", 2=>"b", 3=>"d"}
ignore
{1=>"a", 2=>"b", 3=>"c"}
modify key
{"duplicate 3"=>"d", 1=>"a", 2=>"b", 3=>"c"}
store in array if more than one value
{1=>"a", 2=>"b", 3=>["c", "d"]}

As a nice side effect the implementation of #invert becomes simpler. What
do you think?

Kind regards

robert
 
H

Han Holl

Robert Klemme said:
A check for block_given? is missing here:
No, I don't think so. arbitrate will be empty if there is no block
class Hash

def invert_test(&b)
reverse = self.class.new

each do |key, val|
if b and reverse.has_key? val
b.call reverse, val, key
else
reverse[val] = key
end
end

reverse
end

end

The trouble with this approach is that you get called on each duplicate
value. Sometimes I find I can only decide on the most appropriate
candidate if I've seen them all, but in most cases your implementation would
work as well.

Cheers,

Han Holl
 
R

Robert Klemme

Han Holl said:
"Robert Klemme" <[email protected]> wrote in message
No, I don't think so. arbitrate will be empty if there is no block

Oops, yes you're right.
class Hash

def invert_test(&b)
reverse = self.class.new

each do |key, val|
if b and reverse.has_key? val
b.call reverse, val, key
else
reverse[val] = key
end
end

reverse
end

end

The trouble with this approach is that you get called on each duplicate
value. Sometimes I find I can only decide on the most appropriate
candidate if I've seen them all, but in most cases your implementation would
work as well.

Well, in that case you can remember them all and act on them later. The
advantage of my implementation is that you have to pay the overhead of the
second hash only if you really need it.

Regards

robert
 

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

Forum statistics

Threads
473,770
Messages
2,569,584
Members
45,075
Latest member
MakersCBDBloodSupport

Latest Threads

Top