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?

Respostas:


82

Você pode usar Set em vez de Array.


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]

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.


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]

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

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)
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.