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.

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

···

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.

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)

···

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.

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

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

···

2007/7/11, barjunk <barjunk@attglobal.net>:

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.

class Hash

    # Hash intersection. Two hashes intersect
    # when their pairs are equal.

···

On Jul 11, 4:30 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.

    #
    # {: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

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.

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 7/11/07, Ezra Zygmuntowicz <ezmobius@gmail.com> wrote:

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

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-forum.com/\.

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

···

From: Nobuyoshi Nakada [mailto:nobu@ruby-lang.org]
#
# 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

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.

···

On Jul 11, 11:57 pm, "Robert Klemme" <shortcut...@googlemail.com> wrote:

2007/7/11, barjunk <barj...@attglobal.net>:

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

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

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

···

--
Posted via http://www.ruby-forum.com/\.

there's no conflict. The methods worked as advertised in my irb session just
now.

Craig

···

On 7/11/07, Craig Demyanovich <cdemyanovich@gmail.com> wrote:

On 7/11/07, Ezra Zygmuntowicz <ezmobius@gmail.com> wrote:

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

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

To answer my own question (wasn't sure I'd have time before leaving),

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)

···

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

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

···

On Thu, Jul 12, 2007 at 06:34:31AM +0900, Stefan Rusterholz wrote:

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 12.07.2007 19:52, barjunk wrote:

On Jul 11, 11:57 pm, "Robert Klemme" <shortcut...@googlemail.com> > wrote:

2007/7/11, barjunk <barj...@attglobal.net>:

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.

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

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.

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.

···

On Jul 12, 3:24 pm, Stefan Rusterholz <apei...@gmx.net> wrote:

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

Gregory Seidman 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 :slight_smile:

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

Regards
Stefan

···

On Thu, Jul 12, 2007 at 06:34:31AM +0900, Stefan Rusterholz wrote:

--
Posted via http://www.ruby-forum.com/\.

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

···

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 :slight_smile:

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

Regards
Stefan