如何在大型JSON数组中搜索并通过多个键查找记录

时间:2019-10-29 19:36:47

标签: ruby-on-rails json ruby

我有一个非常大的数据集,其组织方式如下:

users = [
    {
        username: "Bill",
        gender: "Male",
        details: {
            city: "NY"
        }
    },
    {
        username: "Mary",
        gender: "Female",
        details: {
            city: "LA"
        }
    }
]

我需要一种快速的方法来通过多个键中的多个值搜索多个记录。

我有点分隔的键列表:

keys = ["gender", "details.city"]

我需要执行以下操作(用伪代码编写):

my_users = users.any? {|user|
  keys.each do |key|
    user.key == "NY"
  end
}

我知道这行不通。无法使用的原因之一是我的键列表是点分隔的,因此我可以将其拆分为键数组,例如['gender']['details']['city'],或转换用户哈希像这样用点分隔对象:

def to_o
  JSON.parse to_json, object_class: OpenStruct
end

4 个答案:

答案 0 :(得分:2)

我希望这种方法像您想要的那样工作

//search places implementation
 'com.google.android.libraries.places:places:2.0.0'

答案 1 :(得分:2)

对于线性搜索,德米尔的解决方案是一个很好的解决方案。

对于“必须快速”角度,您可能会发现通过用户数组进行O(n)扫描太慢。为了减轻这种情况,您可能需要创建一个索引:

require "set"
class Index
  def initialize(dataset)
    @index = make_index(dataset)
  end

  def find(conditions = {})
    conditions.inject(Set.new) { |o, e| o | @index[e.join(".")] }.to_a
  end

  private

  def make_keys(record, prefix = [])
    record.flat_map do |key, val|
      case val
      when Hash
        make_keys val, [key]
      else
        (prefix + [key, val]).join(".")
      end
    end
  end

  def make_index(dataset)
    dataset.each_with_object({}) do |record, index|
      make_keys(record).each { |key| (index[key] ||= []) << record }
    end
  end
end

index = Index.new(users)
p index.find("gender" => "Male", "details.city" => "NY")
# => [{:username=>"Bill", :gender=>"Male", :details=>{:city=>"NY"}}]

这需要O(n)时间,并且一次创建索引需要花费额外的内存,但是随后对数据集的每次搜索都应该在O(1)时间内进行。如果您一次设置完数据集后进行了一系列搜索,则可能会选择类似的选项。

答案 2 :(得分:1)

您可以使用Ruby 2.3.0中引入的Hash#dig动态遍历哈希:

def select_users(users, conditions)
  users.select do |user|
    conditions.select do |key, value|
      user.dig(*key.to_s.split(".").map(&:to_sym)) == value
    end.length == conditions.length
  end
end

这假定条件的输入是哈希,例如:

{ "gender" => "Male", "details.city" => "NY" }

并且经过优化,可在一次线性通过中测试多个条件。您还可以支持数组以测试包含性:

def select_users(users, conditions)
  users.select do |user|
    conditions.select do |key, value|
      actual = user.dig(*key.to_s.split(".").map(&:to_sym))
      if value.is_a?(Array)
        value.includes?(actual)
      else
        actual == value
      end
    end.length == conditions.length
  end
end
# get users where city is "NY", "Detroit" or "Los Angeles"
select_users(dataset, { "gender" => "Male", "details.city" => ["NY", "Detroit", "Los Angeles"] })

答案 3 :(得分:0)

问题中的代码(尤其是<a class=\"c-item_foot.*?>(.*?)<\/a><\/div>)建议该对象确定是否针对any?中的任何哈希h

users

或存在哈希值h[:gender] == city #=> true

g

代码

g = h[:details]
g[:city] == city   #=> true

示例

对于问题中给出的def city_present?(users, *key_groups, city) key_arr = key_groups.map { |s| s.split('.').map(&:to_sym) } users.any? { |h| key_arr.any? { |keys| h.dig(*keys) == city } } end

users

说明

请参见Hash#dig。发现city_present?(users, "gender", "details.city", 'NY') #=> true city_present?(users, "gender", "details.city", 'LA') #=> true city_present?(users, "gender", "details.city", 'TO') #=> false 等于:

key_arr

重复搜索

采纳@ChrisHeald的建议,如果[[:gender], [:details, :city]] 很大并且要对不同的值进行重复搜索,则创建与users相关的一组值将是有意义的。可以按照以下步骤进行。

key_groups

require 'set'

def values_present(users, *key_groups)
  key_arr = key_groups.map { |s| s.split('.').map(&:to_sym) }
  users.each_with_object(Set.new) do |h,set|
    key_arr.each do |keys|
      v = h.dig(*keys)
      set << v unless v.nil?
    end
  end
end