Returning part of a hash

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.

On Jul 11, 12:28 pm, barjunk [email protected] wrote:

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.

On Jul 11, 2:28 pm, barjunk [email protected] wrote:

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

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.

On 7/11/07, Ezra Z. [email protected] wrote:

           "key20" => "value20" }

hash.select {|key, value| key == “key1” }

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

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

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 Z.
– Lead Rails Evangelist
[email protected]
– Engine Y., Serious Rails Hosting
– (866) 518-YARD (9273)

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

=> {:two=>2, :three=>3}

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 Z.
– Lead Rails Evangelist
[email protected]
– Engine Y., Serious Rails Hosting
– (866) 518-YARD (9273)

On 7/11/07, Craig D. [email protected] wrote:

           "key2" => "value2",
           "key7" => "value7" }

end

To answer my own question (wasn’t sure I’d have time before leaving),
there’s no conflict. The methods worked as advertised in my irb session
just
now.

Craig

Ezra Z. 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

On Thu, Jul 12, 2007 at 06:34:31AM +0900, Stefan R. 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. 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

Gregory S. wrote:

On Thu, Jul 12, 2007 at 06:34:31AM +0900, Stefan R. 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(mn) 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 :slight_smile:

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

Regards
Stefan

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.

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

likely longer than the keys-array.

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

2007/7/11, barjunk [email protected]:

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.

irb(main):001:0> h={}
=> {}
irb(main):002:0> 20.times {|i| h[“key#{i}”]=“val#{i}”}
=> 20

irb(main):008:0> h2 = Hash[*h.select {|k,v| %w{key1 key5
key8}.include? k}.flatten]
=> {“key1”=>“val1”, “key5”=>“val5”, “key8”=>“val8”}

irb(main):013:0> h.dup.delete_if {|k,v| not %w{key1 key5 key8}.include?
k}
=> {“key1”=>“val1”, “key5”=>“val5”, “key8”=>“val8”}

Kind regards

robert

On Jul 11, 11:57 pm, “Robert K.” [email protected]
wrote:

           "key20" => "value20" }

hash.select {|key, value| key == “key1” }
key8}.include? k}.flatten]
=> {“key1”=>“val1”, “key5”=>“val5”, “key8”=>“val8”}

irb(main):013:0> h.dup.delete_if {|k,v| not %w{key1 key5 key8}.include? k}
=> {“key1”=>“val1”, “key5”=>“val5”, “key8”=>“val8”}

Kind regards

robert

WOW!

Thanks for all the options…I didn’t understand the O(n^2)
conversation other than that there is a possibility for the time it
takes to get the pieces would get bigger as the array got bigger.

Mike B.

From: Nobuyoshi N. [mailto:[email protected]]

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

i’m glad v1.9 hash#select returns hash.
how about hash#shift?

thank you for the update
-botp

On 12.07.2007 19:52, barjunk wrote:

           ...

irb(main):001:0> h={}

Kind regards

robert

WOW!

Thanks for all the options…I didn’t understand the O(n^2)
conversation other than that there is a possibility for the time it
takes to get the pieces would get bigger as the array got bigger.

Not sure what you mean by this. The effort for the solutions I proposed
above is O(n*m) because of the linear search in the key select array.
This is typically not an issue if the array is small. If it can be
large then it’s worthwhile to use a Set which has O(1) lookup (hash
internally) and you get O(n) (n = size of Hash).

Kind regards

robert

On Jul 11, 4:30 pm, barjunk [email protected] wrote:

Thanks for any help.

class Hash

# Hash intersection. Two hashes intersect
# when their pairs are equal.
#
#   {:a=>1,:b=>2} & {:a=>1,:c=>3}  #=> {:a=>1}
#
# A hash can also be intersected with an array
# to intersect by keys only.
#
#   {:a=>1,:b=>2} & [:a,:c]  #=> {:a=>1}
#
# The later form is similar to #pairs_at. This differs only
# in that #pairs_at will return a nil value for a key
# not in the hash, but #& will not.

def &(other)
  case other
  when Array
    k = (keys & other)
    Hash[*(k.zip(values_at(*k)).flatten)]
  else
    Hash.new[*(to_a & other.to_a).flatten]
  end
end

end

T.


http://facets.rubyforge.org

Trans wrote:

def &(other)
  case other
  when Array
    k = (keys & other)
    Hash[*(k.zip(values_at(*k)).flatten)]
  else
    Hash.new[*(to_a & other.to_a).flatten]
  end
end

end

T.


http://facets.rubyforge.org

Hash[*something.flatten] will destroy any nested arrays (and if you’re
so unlucky to get an even amount of elements, it won’t even raise).

Regards
Stefan

On Jul 12, 3:24 pm, Stefan R. [email protected] wrote:

end

T.


http://facets.rubyforge.org

Hash[*something.flatten] will destroy any nested arrays (and if you’re
so unlucky to get an even amount of elements, it won’t even raise).

Ah, nice catch thanks. I fixed it. But in the process, well, I just
have to get this off my chest…

Arrggghh!!! Where’s flatten(x)!? I know David Black asked for that
like YEARS AND YEARS ago and we still we don’t have it? Come on!
Please, please put it in 1.9. I even wrote the C code myself two years
ago. I’ll post it up on ruby-core. Okay? Okay?

Okay. Sorry for the digression… In any case, I worked around:

def &(other)
  case other
  when Array
    k = (keys & other)
    Hash[*(k.zip(values_at(*k)).flatten)]
  else
    r = (to_a & other.to_a).inject([]) do |a, kv|
      a.concat kv; a
    end
    Hash[*r]
  end
end

Better? That last version had a bug anyway --Hash.new[] should have
been Hash[].

Thanks Stefan,
T.