Returning part of a hash

B

barjunk

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

barjunk

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

Chris Shea

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
 
E

Ezra Zygmuntowicz

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
# >> {:eek:ne => 1, :two => 2, :three => 3}.pass:)one)
# => {:eek:ne=>1}
def pass(*keys)
self.reject { |k,v| ! keys.include?(k) }
end

# blocks the keys in the arguments
# >> {:eek:ne => 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
-- (e-mail address removed)
-- Engine Yard, Serious Rails Hosting
-- (866) 518-YARD (9273)
 
F

Florian Gross

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

Stefan Rusterholz

Ezra said:
Here are two handy methods for doing what you want:

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

# blocks the keys in the arguments
# >> {:eek:ne => 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
 
E

Ezra Zygmuntowicz

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
-- (e-mail address removed)
-- Engine Yard, Serious Rails Hosting
-- (866) 518-YARD (9273)
 
G

Gregory Seidman

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
 
S

Stefan Rusterholz

Gregory said:
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.

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

Ezra Zygmuntowicz

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
 
N

Nobuyoshi Nakada

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.
 

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,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top