Ruby - rejecting hashes that are nested within arrays

Viewed 137

I'm trying to reject specific hashes from within an array. I'm getting confused due to the nesting of things

I have a hash that looks like this

hash = {
  "key1":
  [
    {
      "name": "foo",
      "id": 1
    },
    {
      "name": "foo",
      "id": 2
    },
    {
      "name": "bar",
      "id": 3
    }
  ],
  "key2":
  [
    {
      "name": "foo",
      "id": 1
    },
    {
      "name": "foo",
      "id": 2
    },
    {
      "name": "bar",
      "id": 3
    }
  ]
}

I need to iterate over each of the values and modify the arrays to reject any hash that has hash[:name] == "bar" and still keep everything else intact.

I'm trying

hash = hash.each_value do |value|
  value.reject {|v| v[:name] == "bar"}
end
=> {:key1=>[{:name=>"foo", :id=>1}, {:name=>"foo", :id=>2}, {:name=>"bar", :id=>3}],
 :key2=>[{:name=>"foo", :id=>1}, {:name=>"foo", :id=>2}, {:name=>"bar", :id=>3}]}

I can remove them with something like this:

hash.values.flatten.reject {|v| v[:name] == "bar"}
=> [{:name=>"foo", :id=>1}, {:name=>"foo", :id=>2}, {:name=>"foo", :id=>1}, {:name=>"foo", :id=>2}]

But I need the initial hash to remain intact

I expect to derive this:

hash = {
  "key1":
  [
    {
      "name": "foo",
      "id": 1
    },
    {
      "name": "foo",
      "id": 2
    }
  ],
  "key2":
  [
    {
      "name": "foo",
      "id": 1
    },
    {
      "name": "foo",
      "id": 2
    }
  ]
}

I've also tried this route:

hash.reject do |key, value|
  value.each do |r|
    r[:name] == "bar"
  end
end

=> {}

Any help is much appreciated - I'm down for an explanation of where my logic is bad

I know that I'm iterating over things incorrectly, it's just a bit confusing to me with all the nesting

2 Answers

@Schwern has identified the problems with your code so I will merely suggest a more Ruby-like solution.

hash = {
  "key1": [{ "name": "foo", "id": 1 },
           { "name": "foo", "id": 2 },
           { "name": "bar", "id": 3 }],
  "key2": [{ "name": "foo", "id": 1 },
           { "name": "foo", "id": 2 },
           { "name": "bar", "id": 3 }]
}
hash.transform_values { |arr| arr.reject { |h| h[:name] == "bar" } }
  #=> { "key1": [{ "name": "foo", "id": 1 },
  #              { "name": "foo", "id": 2 }],
  #     "key2": [{ "name": "foo", "id": 1 },
  #              { "name": "foo", "id": 2 }]
  #   }

See Hash#transform_values. Note that hash is not mutated. To modify hash in place use Hash#transform_values!.

You're not modifying the hash, but the values in the hash. hash = hash.each_value is doing nothing, (and the return value of each_value isn't documented).

value.reject does reject the values, but not in place. It returns a new array and leaves the original alone.

a = ["foo", "bar", "baz"]

# The modified array: ["foo", "baz"]
p a.reject { |v| v == "bar" }

# The original array is untouched: ["foo", "bar", "baz"]
p a

Use reject! to modify each array in place.

hash.each_value do |value|
  value.reject! {|v| v[:name] == "bar"}
end
Related