Просмотр массива хэшей для удаления строки, включенной в другую строку в Ruby

57
2

У меня есть массив хешей, построенных так:

grapes_matched << { part: part, grape: grape_match }

Я бы хотел:

    сохранить текущую сортировку массива удалите элементы в массиве, если grape_match.name (элемент Active Record) включает еще один grape_match.name более короткий.

Например, представьте, что мой массив хэшей:

{ part:"toto", grape: AR Grape with name: "Cabernet" }
{ part:"titi", grape: AR Grape with name: "Cabernet Sauvignon" }
{ part:"tutu", grape: AR Grape with name: "Merlot" }

Поскольку второй "Cabernet Sauvignon" включает первый "Cabernet", я хочу удалить первый массив.

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

В то время у меня есть что-то очень уродливое:

grapes_matched.each do |grape_matched|
temp_grape = grape_matched[:grape]
temp_grape_name = I18n.transliterate(temp_grape.name).downcase
# does the temp grape name is included in one of previous grapes
# first grape
grapes_founds << temp_grape if grapes_founds.length == 0
# other grapes
grapes_founds.each do |grape_found|
grapes_founds << temp_grape if !I18n.transliterate(grape_found.name).downcase.include? temp_grape_name
end
end

Я вполне уверен, что это можно сделать с меньшим количеством строк кода в Ruby и сохранением исходного массива хэшей.

Заранее спасибо.

спросил(а) 2017-12-15T19:55:00+03:00 2 года, 10 месяцев назад
1
Решение
58

Моя цель - реализовать разумно-эффективный алгоритм.

Пусть сначала упростит и перестроит массив.

grapes = [{ part:"toto", grape: "Cabernet" },
{ part:"tutu", grape: "Merlot" },
{ part:"titi", grape: "Cabernet Sauvignon" }]

Затем мы можем сделать следующее, чтобы получить желаемый массив разумно эффективным образом.

grapes.each_with_index.
sort_by { |g,_i| -g[:grape].size }.
each_with_object([]) { |(g,i),a| a << [g,i] unless a.any? { |f,_i|
f[:grape].include?(g[:grape]) } }.
sort_by(&:last).
map(&:first)
#=> [{:part=>"tutu", :grape=>"Merlot"},
# {:part=>"titi", :grape=>"Cabernet Sauvignon"}]

Эти шаги заключаются в следующем.

Добавьте индекс в каждый хэш, чтобы их первоначальный порядок в grapes можно было определить позже.

e = grapes.each_with_index
#=> #<Enumerator: [{:part=>"toto", :grape=>"Cabernet"},
# {:part=>"tutu", :grape=>"Merlot"},
# {:part=>"titi", :grape=>"Cabernet Sauvignon"}]:each_with_index>

Сортируйте пары hash/index, уменьшая размер g[:grape].

 b = e.sort_by { |g,_i| -g[:grape].size }
#=> [[{:part=>"titi", :grape=>"Cabernet Sauvignon"}, 2],
# [{:part=>"toto", :grape=>"Cabernet"}, 0],
# [{:part=>"tutu", :grape=>"Merlot"}, 1]]

Добавьте каждую пару хэша/индекса [g,i] в исходно пустой массив a, если f[:grape] включает g[:grape] для хэша f уже в a.

c = b.each_with_object([]) { |(g,i),a| a << [g,i] unless a.any? { |f,_i|
f[:grape].include?(g[:grape]) } }
#=> [[{:part=>"titi", :grape=>"Cabernet Sauvignon"}, 2],
# [{:part=>"tutu", :grape=>"Merlot"}, 1]]

Чтобы получить желаемый порядок хэшей в c, отсортируйте их по их индексам в исходном массиве grapes (что не влияет на этот пример).

d = c.sort_by(&:last)
#=> [[{:part=>"tutu", :grape=>"Merlot"}, 1],
# [{:part=>"titi", :grape=>"Cabernet Sauvignon"}, 2]]

Удалите индексы.

d.map(&:first)
#=> [{:part=>"tutu", :grape=>"Merlot"},
# {:part=>"titi", :grape=>"Cabernet Sauvignon"}]

В зависимости от требований может быть предпочтительнее заменить f[:grape].include?(g[:grape]) с f[:grape].begin_with?(g[:grape]) || f[:grape].end_with?(g[:grape]) f[:grape].begin_with?(g[:grape]) || f[:grape].end_with?(g[:grape]).

Простой тест, сравнивающий решение @Max с моим.

def max_way(grapes_matched)
grapes_matched.select do |grape_matched|
grapes_matched.none? { |gm| gm[:grape] != grape_matched[:grape] &&
grape_matched[:grape].include?(gm[:grape]) }
end
end

def cary_way(grapes)
grapes.each_with_index.
sort_by { |g,_i| -g[:grape].size }.
each_with_object([]) { |(g,i),a| a << [g,i] unless a.any? { |f,_i|
f[:grape].include?(g[:grape]) } }.
sort_by(&:last).
map(&:first)
end

ALPHA = ('a'..'z').to_a
def rnd5
' '.gsub(' ') { ALPHA.sample }
end

def grapes(n, m)
n.times.each_with_object([]) do |i,a|
s1, s2 = rnd5, rnd5
a << { grape: "%s %s" % [s1, s2] }
a << { grape: i.even? ? s1 : s2 } if i < m
end.shuffle
end

require 'fruity'

def bench(n, m)
(grapes_matched = grapes(n, m)).size
compare do
Max { max_way(grapes_matched) }
Cary { cary_way(grapes_matched) }
end
end

bench   95, 5
Running each test once. Test will take about 1 second.
Cary is faster than Max by 3x ± 1.0

bench 950, 50
Running each test once. Test will take about 13 seconds.
Cary is faster than Max by 3x ± 1.0

bench 950, 500
Running each test once. Test will take about 23 seconds.
Cary is faster than Max by 4x ± 0.1

ответил(а) 2017-12-16T10:40:00+03:00 2 года, 10 месяцев назад
58

Это может быть намного короче:

grapes_founds = grapes_matched.select do |grape_matched|
grapes_matched.none? { |gm| gm[:grape] != grape_matched[:grape] && grape_matched[:grape].include?(gm[:grape]) }
end

На английском языке: выберите все винограды, для которых ни один другой виноград не имеет другого имени, которое включено в это виноградное имя.

Мне не совсем ясно, какова структура ваших данных и как ваши строки нормализованы, поэтому вам, возможно, придется массировать это в правильной форме.

ответил(а) 2017-12-15T22:11:00+03:00 2 года, 10 месяцев назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема