Добавить элемент в массив, если его еще нет

92

У меня есть класс Ruby

class MyClass
  attr_writer :item1, :item2
end

my_array = get_array_of_my_class() #my_array is an array of MyClass
unique_array_of_item1 = []

Я хочу , чтобы подтолкнуть MyClass#item1к unique_array_of_item1, но только если unique_array_of_item1не содержит , что до item1сих пор. Я знаю простое решение: просто выполните итерацию my_arrayи проверьте, unique_array_of_item1содержит ли уже текущий item1или нет.

Есть ли более эффективное решение?

Алан Коромано
источник

Ответы:

82

Вы можете использовать Set вместо Array.

Иржи Поспишил
источник
Хотя верно, что в документации говорится, что наборы не упорядочены, на самом деле они (начиная с Ruby 1.9) упорядочены. Если вы посмотрите на код, основные методы, которые вы использовали бы для получения порядка (например, Set#eachи Set#to_a), делегируются @hash. А по состоянию на Ruby 1.9 Хеши заказаны. «Хеши перечисляют свои значения в том порядке, в котором были вставлены соответствующие ключи». ruby-doc.org/core-1.9.1/Hash.html
phylae
Никогда в новинку не было такого набора. Они потрясающие, спасибо вам большое,
Брэд
123

У @Coorasse есть хороший ответ , хотя он должен быть:

my_array | [item]

И обновить my_arrayна месте:

my_array |= [item]
Джейсон Денни
источник
63
или my_array |= [item]который my_array
обновится
2
Возможно, мне что-то здесь не хватает, но оператор | = мне кажется, что он не работает? Я использую Ruby 2.1.1
Viet
@Viet отлично |=работает в моих тестах с 2.1.1. Опишите свой тестовый пример или откройте новый вопрос.
depquid
Тестирую снова, и теперь он работает. Не знаю, что я делал раньше, так как мой комментарий был сделан много месяцев назад.
Viet
1
В чем сложность этого?
Нобита,
42

Вам не нужно перебирать my_arrayвручную.

my_array.push(item1) unless my_array.include?(item1)

Редактировать:

Как отмечает Томбарт в своем комментарии, использование Array#include?не очень эффективно. Я бы сказал, что влияние на производительность незначительно для небольших массивов, но вы можете захотеть пойти с Setболее крупными.

Doterr
источник
6
ты определенно не хочешь этого делать! array.include?(item)имеет сложность O(n)- это похоже на повторение всего массива. взгляните на этот тест: gist.github.com/deric/4953652
Tombart
Красивое решение :). Удобочитаемый! В моем случае у меня не может быть набора, поэтому я использую это решение.
Виктор
Вы также можете использовать двоичный поиск для повышения производительности, если массив упорядочен. [1, 2, 3, 4, 5].bsearch { |e| e == 3 }
Виктор
32

Вы можете преобразовать item1 в массив и присоединиться к ним:

my_array | [item1]
Coorasse
источник
1
Это должно быть |не ||(см ответа Джейсона)
Сет
1
Моя вина. Сожалею. Отредактировал ответ
coorasse
3

Важно помнить, что класс Set и | Метод (также называемый «Set Union») даст массив уникальных элементов, что отлично, если вы не хотите дублировать, но будет неприятным сюрпризом, если у вас есть неуникальные элементы в исходном массиве по дизайну.

Если у вас есть хотя бы один повторяющийся элемент в исходном массиве, который вы не хотите терять, итерация по массиву с ранним возвратом будет в худшем случае O (n), что не так уж плохо в общей схеме вещей. .

class Array
  def add_if_unique element
    return self if include? element
    push element
  end
end
Elreimundo
источник
0

Я не уверен, что это идеальное решение, но у меня сработало:

    host_group = Array.new if not host_group.kind_of?(Array)
    host_group.push(host)
witkacy26
источник