Adicione o elemento a uma matriz se ainda não estiver lá

92

Eu tenho uma aula de 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 = []

Eu quero empurrar MyClass#item1para unique_array_of_item1, mas apenas se unique_array_of_item1ainda não contiver isso item1. Existe uma solução simples que eu conheço: basta iterar my_arraye verificar se unique_array_of_item1já contém o atual item1ou não.

Existe alguma solução mais eficiente?

Alan Coromano
fonte

Respostas:

82

Você pode usar Set em vez de Array.

Jiří Pospíšil
fonte
Embora seja verdade que os documentos dizem que os conjuntos não estão em ordem, eles estão de fato (a partir do Ruby 1.9) ordenados. Se você olhar para o código, os principais métodos que você usaria para obter o pedido (como Set#eache Set#to_a) delegado @hash. E a partir do Ruby 1.9 os hashes são solicitados. "Hashes enumeram seus valores na ordem em que as chaves correspondentes foram inseridas." ruby-doc.org/core-1.9.1/Hash.html
phylae
Nunca houve tal coisa como um conjunto. Eles são incríveis, muito obrigado
Brad
122

@Coorasse tem uma boa resposta , embora devesse ser:

my_array | [item]

E para atualizar my_arrayno local:

my_array |= [item]
Jason Denney
fonte
63
ou my_array |= [item]que será atualizado my_arrayno local
andorov
2
Talvez eu esteja faltando alguma coisa aqui, mas o operador | = não parece funcionar para mim? Estou executando o Ruby 2.1.1
Viet
@Viet |=funciona bem em meus testes com 2.1.1. Descreva seu caso de teste ou abra uma nova pergunta.
depois de
Testando novamente e agora funciona. Não sei o que estava fazendo antes, pois meu comentário foi feito há muitos meses.
Viet
1
Qual é a complexidade disso?
Nobita de
40

Você não precisa iterar my_arraymanualmente.

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

Editar:

Como Tombart aponta em seu comentário, o uso Array#include?não é muito eficiente. Eu diria que o impacto no desempenho é insignificante para pequenos Arrays, mas você pode querer ir com os Setmaiores.

doesterr
fonte
5
você definitivamente não quer fazer isso! array.include?(item)tem complexidade O(n)- então é como iterar todo o array. dê uma olhada neste benchmark: gist.github.com/deric/4953652
Tombart
32

Você pode converter item1 em array e juntá-los:

my_array | [item1]
Coorasse
fonte
1
Este deve ser |não ||(ver resposta de Jason)
Seth
1
Minha culpa. Desculpe. Resposta
editada
3

É importante ter em mente que a classe Set e o | (também chamado de "Set Union") produzirá um array de elementos únicos , o que é ótimo se você não quiser duplicatas, mas que será uma surpresa desagradável se você tiver elementos não únicos em seu array original por design.

Se você tiver pelo menos um elemento duplicado em seu array original que não deseja perder, iterar por meio do array com um retorno inicial é o pior caso O (n), o que não é tão ruim no grande esquema das coisas .

class Array
  def add_if_unique element
    return self if include? element
    push element
  end
end
elreimundo
fonte
0

Não tenho certeza se é a solução perfeita, mas funcionou para mim:

    host_group = Array.new if not host_group.kind_of?(Array)
    host_group.push(host)
witkacy26
fonte