[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

Returning part of a hash

barjunk

7/11/2007 8:28:00 PM

I have hash that has about 20 keys. I'd like to create a new variable
with just three of those keys. Example:

hash = { "key1" => "value1",
"key2" => "value2",
...
"key20" => "value20" }

And a function like:
newhash = hash.slice("key2","key5","key7")

Which creates:

newhash = { "key2" => "value1",
"key5" => "value5",
"key7" => "value7" }

hash.select {|key, value| key == "key1" }

I could do the above multiple times, but this returns an array not the
hash pair.

Thanks for any help.

Mike B.

17 Answers

barjunk

7/11/2007 8:43:00 PM

0

On Jul 11, 12:28 pm, barjunk <barj...@attglobal.net> wrote:
> I have hash that has about 20 keys. I'd like to create a new variable
> with just three of those keys. Example:
>
> hash = { "key1" => "value1",
> "key2" => "value2",
> ...
> "key20" => "value20" }
>
> And a function like:
> newhash = hash.slice("key2","key5","key7")
>
> Which creates:
>
> newhash = { "key2" => "value1",
> "key5" => "value5",
> "key7" => "value7" }
>
> hash.select {|key, value| key == "key1" }
>
> I could do the above multiple times, but this returns an array not the
> hash pair.
>
> Thanks for any help.
>
> Mike B.


This is backwards, but works:
["key1","key2","key3","key2"].each {|item| hash.delete_if { |
key,value| key == item} }

This would get rid of the keys I don't want.

I could probably do something like:

(hash.keys - ["keys", "I", "want"]).each {|item| hash.delete_if { |
key,value| key == item} }


The only bummer is that directly modifies the hash...but that can be
fixed too.

I'm still new at this...is there a more succinct way?

Mike B.

Chris Shea

7/11/2007 8:45:00 PM

0

On Jul 11, 2:28 pm, barjunk <barj...@attglobal.net> wrote:
> I have hash that has about 20 keys. I'd like to create a new variable
> with just three of those keys. Example:
>
> hash = { "key1" => "value1",
> "key2" => "value2",
> ...
> "key20" => "value20" }
>
> And a function like:
> newhash = hash.slice("key2","key5","key7")
>
> Which creates:
>
> newhash = { "key2" => "value1",
> "key5" => "value5",
> "key7" => "value7" }
>
> hash.select {|key, value| key == "key1" }
>
> I could do the above multiple times, but this returns an array not the
> hash pair.
>
> Thanks for any help.
>
> Mike B.

This works:

class Hash
def slice(*args)
sliced = self.dup
sliced.delete_if {|k,v| not args.include?(k)}
end
end

Or you could do this:

class Hash
def slice(*args)
ret = {}
args.each {|key| ret[key] = self[key]}
ret
end
end

I'm sure there are other ways.

HTH,
Chris

Ezra Zygmuntowicz

7/11/2007 9:00:00 PM

0


On Jul 11, 2007, at 1:30 PM, barjunk wrote:

> I have hash that has about 20 keys. I'd like to create a new variable
> with just three of those keys. Example:
>
> hash = { "key1" => "value1",
> "key2" => "value2",
> ...
> "key20" => "value20" }
>
> And a function like:
> newhash = hash.slice("key2","key5","key7")
>
> Which creates:
>
> newhash = { "key2" => "value1",
> "key5" => "value5",
> "key7" => "value7" }
>
> hash.select {|key, value| key == "key1" }
>
> I could do the above multiple times, but this returns an array not the
> hash pair.
>
> Thanks for any help.
>
> Mike B.


Hey Mike-

Here are two handy methods for doing what you want:

class Hash
# lets through the keys in the argument
# >> {:one => 1, :two => 2, :three => 3}.pass(:one)
# => {:one=>1}
def pass(*keys)
self.reject { |k,v| ! keys.include?(k) }
end

# blocks the keys in the arguments
# >> {:one => 1, :two => 2, :three => 3}.block(:one)
# => {:two=>2, :three=>3}
def block(*keys)
self.reject { |k,v| keys.include?(k) }
end

end


Cheers-

-- Ezra Zygmuntowicz
-- Lead Rails Evangelist
-- ez@engineyard.com
-- Engine Yard, Serious Rails Hosting
-- (866) 518-YARD (9273)



Florian Groß

7/11/2007 9:01:00 PM

0

> I'm still new at this...is there a more succinct way?

There's also Hash#reject which is the same as h.dup.delete_if.


Stefan Rusterholz

7/11/2007 9:35:00 PM

0

Ezra Zygmuntowicz wrote:
> Here are two handy methods for doing what you want:
>
> class Hash
> # lets through the keys in the argument
> # >> {:one => 1, :two => 2, :three => 3}.pass(:one)
> # => {:one=>1}
> def pass(*keys)
> self.reject { |k,v| ! keys.include?(k) }
> end
>
> # blocks the keys in the arguments
> # >> {:one => 1, :two => 2, :three => 3}.block(:one)
> # => {:two=>2, :three=>3}
> def block(*keys)
> self.reject { |k,v| keys.include?(k) }
> end
> end

I wonder, are you aware that those are O(n^2)?

Regards
Stefan

--
Posted via http://www.ruby-....

Ezra Zygmuntowicz

7/11/2007 9:36:00 PM

0


On Jul 11, 2007, at 2:27 PM, Craig Demyanovich wrote:

>> class Hash
>> # lets through the keys in the argument
>> # >> {:one => 1, :two => 2, :three => 3}.pass(:one)
>> # => {:one=>1}
>> def pass(*keys)
>> self.reject { |k,v| ! keys.include?(k) }
>> end
>>
>> # blocks the keys in the arguments
>> # >> {:one => 1, :two => 2, :three => 3}.block(:one)
>> # => {:two=>2, :three=>3}
>> def block(*keys)
>> self.reject { |k,v| keys.include?(k) }
>> end
>>
>> end
>
>
> Ezra,
>
> Since you're adding the #pass and #block methods to Hash and Hash
> already
> has a #keys method, is there a conflict between the *keys parameter
> to #pass
> and #block and the #keys method on Hash?
>
> Thanks,
> Craig


There is not a conflict as it does do what's advertised, but it's
probably a good idea to change the name anyways, thanks for pointing
that out.


Cheers-
-- Ezra Zygmuntowicz
-- Lead Rails Evangelist
-- ez@engineyard.com
-- Engine Yard, Serious Rails Hosting
-- (866) 518-YARD (9273)



Gregory Seidman

7/11/2007 10:45:00 PM

0

On Thu, Jul 12, 2007 at 06:34:31AM +0900, Stefan Rusterholz wrote:
> Ezra Zygmuntowicz wrote:
> > Here are two handy methods for doing what you want:
> >
> > class Hash
> > # lets through the keys in the argument
> > # >> {:one => 1, :two => 2, :three => 3}.pass(:one)
> > # => {:one=>1}
> > def pass(*keys)
> > self.reject { |k,v| ! keys.include?(k) }
> > end
> >
> > # blocks the keys in the arguments
> > # >> {:one => 1, :two => 2, :three => 3}.block(:one)
> > # => {:two=>2, :three=>3}
> > def block(*keys)
> > self.reject { |k,v| keys.include?(k) }
> > end
> > end
>
> I wonder, are you aware that those are O(n^2)?

Actually, they are O(n*m) since the size of the arguments array is (almost
certainly) different from and smaller than the size of the hash. That said,
if you really want to make it O(n+m) (or so, and that's a + instead of a
*) you put the arguments list in a hash (which makes the include? call
O(1) instead of O(m)). That probably isn't a win until m is more than three
(or maybe more, it would require benchmarking to find the magic number),
though.

> Regards
> Stefan
--Greg


Stefan Rusterholz

7/12/2007 12:19:00 AM

0

Gregory Seidman wrote:
> On Thu, Jul 12, 2007 at 06:34:31AM +0900, Stefan Rusterholz wrote:
>> >
>> > # blocks the keys in the arguments
>> > # >> {:one => 1, :two => 2, :three => 3}.block(:one)
>> > # => {:two=>2, :three=>3}
>> > def block(*keys)
>> > self.reject { |k,v| keys.include?(k) }
>> > end
>> > end
>>
>> I wonder, are you aware that those are O(n^2)?
>
> Actually, they are O(n*m) since the size of the arguments array is
> (almost
> certainly) different from and smaller than the size of the hash.

IIRC we generally refered to O(m*n) as O(n^2) too, but can be that I
remember wrongly. Anyway, O(m*n) is certainly more precise.

> That said,
> if you really want to make it O(n+m) (or so, and that's a + instead of a
> *) you put the arguments list in a hash (which makes the include? call
> O(1) instead of O(m)). That probably isn't a win until m is more than
> three
> (or maybe more, it would require benchmarking to find the magic number),
> though.
>
>> Regards
>> Stefan
> --Greg

Since this is a generic method one can't know how it will be used, so
I'd go for scalability over speed in some anticipated cases. But that's
me.
You can do it in O(n) (n = keys.length) and I'd even assume that way
will be faster than the O(m*n) for small n's since the hash is most
likely longer than the keys-array.
The O(n) variant simply iterates over the keys and builds up the hash
from that.

But actually I really just wondered if he was aware about the complexity
of his algorithm :)

Excuse any bad english please, it's a bit late here and english isn't my
first language.

Regards
Stefan

--
Posted via http://www.ruby-....

Ezra Zygmuntowicz

7/12/2007 1:21:00 AM

0


On Jul 11, 2007, at 5:18 PM, Stefan Rusterholz wrote:

>> That said,
>> if you really want to make it O(n+m) (or so, and that's a +
>> instead of a
>> *) you put the arguments list in a hash (which makes the include?
>> call
>> O(1) instead of O(m)). That probably isn't a win until m is more than
>> three
>> (or maybe more, it would require benchmarking to find the magic
>> number),
>> though.
>>
>>> Regards
>>> Stefan
>> --Greg
>
> Since this is a generic method one can't know how it will be used, so
> I'd go for scalability over speed in some anticipated cases. But
> that's
> me.
> You can do it in O(n) (n = keys.length) and I'd even assume that way
> will be faster than the O(m*n) for small n's since the hash is most
> likely longer than the keys-array.
> The O(n) variant simply iterates over the keys and builds up the hash
> from that.
>
> But actually I really just wondered if he was aware about the
> complexity
> of his algorithm :)
>
> Excuse any bad english please, it's a bit late here and english
> isn't my
> first language.
>
> Regards
> Stefan


Yeah I was aware of the complexity. But for what I use it for it's
actually faster then the other way. I mostly use these methods in web
apps to reject or accept keys out of the params hash> Like for
logging the params but blocking any password fields. So the rejected
keys are usually only one or two in number and the whole has usually
has ~10 items in it.


Benchmarking this you will see that block1 is faster then block2 for
the cases I use it for:

class Hash

def block1(*rejected)
self.reject { |k,v| rejected.include?(k) }
end

def block2(*rejected)
hsh = rejected.inject({}){|m,k| m[k] = true; m}
self.reject { |k,v| hsh[k] }
end

end

require 'benchmark'

hash = {:foo => 'foo',
:bar => 'bar',
:baz => 'baz',
:foo1 => 'foo1',
:bar1 => 'bar1',
:baz1 => 'baz1',
:foo2 => 'foo2',
:bar2 => 'bar2',
:baz2 => 'baz2'
}

n = 50000
Benchmark.bm do |x|
puts "block1"
x.report { n.times{ hash.block1(:foo) } }
x.report { n.times{ hash.block1(:foo,:bar) } }
x.report { n.times{ hash.block1(:foo, :bar, :baz) } }
x.report { n.times{ hash.block1(:foo, :bar, :baz,:foo1) } }
x.report { n.times{ hash.block1(:foo, :bar, :baz,:foo1, :bar2) } }
x.report { n.times{ hash.block1
(:foo, :bar, :baz,:foo1, :bar2, :baz2) } }
x.report { n.times{ hash.block1
(:foo, :bar, :baz,:foo1, :bar2, :baz2, :foo3) } }
x.report { n.times{ hash.block1
(:foo, :bar, :baz,:foo1, :bar2, :baz2, :foo3, :bar3) } }
x.report { n.times{ hash.block1
(:foo, :bar, :baz,:foo1, :bar2, :baz2, :foo3, :bar3, :baz3) } }

puts "block2"
x.report { n.times{ hash.block2(:foo) } }
x.report { n.times{ hash.block2(:foo,:bar) } }
x.report { n.times{ hash.block2(:foo, :bar, :baz) } }
x.report { n.times{ hash.block2(:foo, :bar, :baz,:foo1) } }
x.report { n.times{ hash.block2(:foo, :bar, :baz,:foo1, :bar2) } }
x.report { n.times{ hash.block2
(:foo, :bar, :baz,:foo1, :bar2, :baz2) } }
x.report { n.times{ hash.block2
(:foo, :bar, :baz,:foo1, :bar2, :baz2, :foo3) } }
x.report { n.times{ hash.block2
(:foo, :bar, :baz,:foo1, :bar2, :baz2, :foo3, :bar3) } }
x.report { n.times{ hash.block2
(:foo, :bar, :baz,:foo1, :bar2, :baz2, :foo3, :bar3, :baz3) } }
end



Cheers-
-- Ezra



Nobuyoshi Nakada

7/12/2007 1:55:00 AM

0

Hi,

At Thu, 12 Jul 2007 05:30:03 +0900,
barjunk wrote in [ruby-talk:258922]:
> I have hash that has about 20 keys. I'd like to create a new variable
> with just three of those keys. Example:
>
> hash = { "key1" => "value1",
> "key2" => "value2",
> ...
> "key20" => "value20" }
>
> And a function like:
> newhash = hash.slice("key2","key5","key7")

Hash#slice doesn't exist but Hash#select in recent 1.9 works
similarly.

keys = %w"key2 key5 key7"
newhash = hash.select {|k,v| keys.include?(k)}

It would be easy to define Hash#slice with this.

--
Nobu Nakada