Como UNIQ um caso insensível variedade
Pergunta
Tanto quanto eu sei, o resultado de
["a", "A"].uniq
é
["a", "A"]
A minha pergunta é:
Como posso fazer [ "a", "A"]. UNIQ me dar tanto [ "a"] ou [ "A"]
Solução
Apenas fazer o caso consistente em primeiro lugar.
por exemplo:
["a","A"].map{|i| i.downcase}.uniq
Editar: Se como MikeJ sugere, os elementos retornado deve ser exatamente o mesmo que na matriz original, então isso vai fazer isso para você:
a.inject([]) { |result,h| result << h unless result.map{|i| i.downcase}.include?(h.downcase); result }
Edit2 Solução que deve satisfazer MikeJ: -)
downcased = []
a.inject([]) { |result,h|
unless downcased.include?(h.downcase);
result << h
downcased << h.downcase
end;
result}
Outras dicas
Há uma outra maneira você pode fazer isso. Você pode realmente passar um bloco para uniq
ou uniq!
que pode ser usado para avaliar cada elemento.
["A", "a"].uniq { |elem| elem.downcase } #=> ["A"]
ou
["A", "a"].uniq { |elem| elem.upcase } #=> ["A"]
Neste caso, porém, tudo vai ser maiúsculas e minúsculas, por isso sempre retornará a matriz ["A"]
pode construir um mapeamento (Hash) entre os (por exemplo downcased) valores de caso-normalizadas e o valor real e, em seguida, tomar apenas os valores a partir do hash:
["a", "b", "A", "C"]\
.inject(Hash.new){ |h,element| h[element.downcase] = element ; h }\
.values
seleciona a última ocorrência de uma determinada palavra (maiúsculas e minúsculas):
["A", "b", "C"]
Se você quer a primeira ocorrência:
["a", "b", "A", "C"]\
.inject(Hash.new){ |h,element| h[element.downcase] = element unless h[element.downcase] ; h }\
.values
["a", "A"].map{|x| x.downcase}.uniq
=> ["a"]
ou
["a", "A"].map{|x| x.upcase}.uniq
=> ["A"]
Se você estiver usando ActiveSupport, você pode usar uniq_by . Ela não afeta o caso da saída final.
['A','a'].uniq_by(&:downcase) # => ['A']
Um pouco mais eficiente e maneira é fazer uso das teclas uniq em hashes, de modo a verificar o seguinte:
["a", "A"].inject(Hash.new){ |hash,j| hash[j.upcase] = j; hash}.values
retornará o último elemento, neste caso
["A"]
enquanto usando || = como operador de atribuição:
["a", "A"].inject(Hash.new){ |hash,j| hash[j.upcase] ||= j; hash}.values
retornará primeiro elemento, neste caso
["a"]
especialmente para grandes matrizes isso deve ser mais rápido, já que não procurar a matriz cada vez usando inclui?
aplausos ...
Uma solução mais geral (embora não o mais eficiente):
class EqualityWrapper
attr_reader :obj
def initialize(obj, eq, hash)
@obj = obj
@eq = eq
@hash = hash
end
def ==(other)
@eq[@obj, other.obj]
end
alias :eql? :==
def hash
@hash[@obj]
end
end
class Array
def uniq_by(eq, hash = lambda{|x| 0 })
map {|x| EqualityWrapper.new(x, eq, hash) }.
uniq.
map {|x| x.obj }
end
def uniq_ci
eq = lambda{|x, y| x.casecmp(y) == 0 }
hash = lambda{|x| x.downcase.hash }
uniq_by(eq, hash)
end
end
O método uniq_by
leva um lambda que verifica a igualdade, e um lambda que retorna um hash, e remove objectos duplicadas, tal como definido pelos dados.
Implementado em cima disso, o método uniq_ci
remove duplicatas string usando caso comparações insensíveis.