Как я могу найти элементы, содержащиеся в массиве хэшей? - PullRequest
1 голос
/ 19 марта 2011

Вот что у меня так далеко:

pages = []
pages << { :uri => 'a', :page => 'd' }
pages << { :uri => 'b', :page => 'e' }
pages << { :uri => 'c', :page => 'f' }

pages.each do |page|
  puts page.has_value? 'b'
end

Но то, что я хочу, это просто верный или ложный ответ, например, есть ли у вас 'e' или 'f'?

Ответы [ 2 ]

6 голосов
/ 19 марта 2011
pages.any?{|e| e.has_value? 'b'} #=> returns true or false
2 голосов
/ 19 марта 2011

Такая структура может привести к медленным поискам, как только она станет большой. Если ваш набор данных будет большим, и у вас нет внутренней базы данных для хранения вещей вместе с индексированными полями для поиска, вы можете сократить поиск в памяти, предварительно составив список значения и поиск его, а не перебирать массив и заглядывать в хэши. Сделайте это один раз, когда массив хэшей будет создан и стабилен.

vals = pages.inject([]){ |m,h| m += h.values; m } #=> ["a", "d", "b", "e", "c", "f"]

Присвоение этой переменной ускорит задачу проверки наличия у вас рассматриваемого значения, если вам приходится многократно выполнять поиск:

vals.include?('a') #=> true
vals.include?('z') #=> false

Если в ваших хэш-значениях будет много дубликатов, возможно, стоит использовать набор вместо массива.

require 'set'
pages.inject([].to_set){ |m,h| m += h.values; m } #=> #<Set: {"a", "d", "b", "e", "c", "f"}>

Преимущество набора состоит в том, что он содержит только одну копию любого конкретного элемента; Дубликаты игнорируются, сохраняя ваш список поиска как можно меньше. Недостатком является то, что Set имеет немного больше накладных расходов при создании, что замедляет время его создания. Наборы основаны на хэшах, поэтому они работают быстрее, чем последовательный поиск.

Чтобы показать, насколько медленной может быть итерация по массиву, вот тест, выполняющий поиск по 52 хэшам, ища либо «A», либо «z», первый или последний элемент соответственно. Вторые два строят список значений, затем проверяют на включение. Последние два делают то же самое, только используя наборы вместо массивов.

require 'benchmark'
require 'set'

puts "RUBY_VERSION=#{ RUBY_VERSION }" 

# build a 52-element array.
pages = ( ('A'..'Z').to_a + ('a'..'z').to_a ).each_slice(2).inject([]) { |m,a| m << { uri:a[0], page:a[1] }; m }

n = 500_000
Benchmark.bm(20) do |x|
  x.report("AoH traversal A")   { n.times { pages.any?{ |h| h.has_value?('A') } } }
  x.report("AoH traversal z")   { n.times { pages.any?{ |h| h.has_value?('z') } } }
  x.report("array inclusion A") { vals = pages.inject([]){ |m,h| m += h.values; m }; n.times { vals.include?('A') }  }
  x.report("array inclusion z") { vals = pages.inject([]){ |m,h| m += h.values; m }; n.times { vals.include?('z') }  }
  x.report("set inclusion A")   { vals = pages.inject([].to_set){ |m,h| m += h.values; m }; n.times { vals.include?('A') }  }
  x.report("set inclusion z")   { vals = pages.inject([].to_set){ |m,h| m += h.values; m }; n.times { vals.include?('z') }  }
end

# >> RUBY_VERSION=1.9.2
# >>                           user     system      total        real
# >> AoH traversal A       1.140000   0.000000   1.140000 (  1.140952)
# >> AoH traversal z      19.130000   0.010000  19.140000 ( 19.135050)
# >> array inclusion A     0.450000   0.000000   0.450000 (  0.443042)
# >> array inclusion z     5.600000   0.010000   5.610000 (  5.605876)
# >> set inclusion A       0.490000   0.000000   0.490000 (  0.492484)
# >> set inclusion z       0.480000   0.000000   0.480000 (  0.479374)

EDIT:

каждый раз, когда я делаю вставку, мне сначала нужно сделать проверку. Это часть веб-паука. Я буду рассматривать БД в будущем.

Посмотрите на результаты теста.

Выбранный вами ответ и предположительно реализованное решение в среднем в 20 раз медленнее, чем использование набора для поиска. Вы могли бы поддерживать Набор, И свою структуру и все еще быть далеко впереди, потому что скорость поиска Сета будет ухудшаться намного медленнее. Даже поддержание массива отдельно примерно в 10 раз быстрее.

Например, проверьте комплект на попадание или промах. Если это хит, перейдите на следующую страницу. Если вы пропустите push информацию в вашем массиве хэшей, то добавьте необходимую информацию о попадании в набор для следующего цикла. НЕ перестраивайте полностью каждый раз, только добавляйте новую информацию.

В быстром и грязном пауке я бы использовал Hash, где ключами были URL-адреса, которые я сканировал. Я бы нормализовал их, удалив из них все запросы, сеансы и другие данные, оставив только базовый URL для страницы. Таким образом, я мог бы отследить, если я уже видел страницу и пропустить ее, в противном случае вы можете в конечном итоге попасть на одну и ту же страницу несколько раз при изменении запросов. Клавиши Hash указывали на структуры, содержащие всю информацию, которую я почерпнул при сканировании страницы, поэтому в конце обработки я мог вывести результаты для каждой страницы, пройдя по клавишам Hash.

Та же тактика применяется при использовании базы данных, в противном случае вы можете заполнить таблицу избыточным просмотром страниц, вместо того, чтобы видеть уникальные страницы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...