Atualmente, no Swift, você simplesmente digita Set( yourArray )
para tornar uma matriz única. (Ou um conjunto ordenado, se necessário.)
Antes que isso fosse possível, como foi feito?
Eu posso ter uma matriz que se parece com o seguinte:
[1, 4, 2, 2, 6, 24, 15, 2, 60, 15, 6]
Ou, realmente, qualquer sequência de partes do mesmo tipo de dados. O que eu quero fazer é garantir que haja apenas um de cada elemento idêntico. Por exemplo, a matriz acima se tornaria:
[1, 4, 2, 6, 24, 15, 60]
Observe que as duplicatas de 2, 6 e 15 foram removidas para garantir que houvesse apenas um de cada elemento idêntico. O Swift fornece uma maneira de fazer isso facilmente, ou terei que fazer isso sozinho?
arrays
swift
standard-library
Altair357
fonte
fonte
NSSet
, NSSet é uma coleção não ordenada de objetos, se necessário, para manter a ordem NSOrderedSet.$.uniq(array)
github.com/ankurp/Dollar#uniq---uniqSet
da Swift? Você poderá fornecer uma lista de elementos não ordenados e exclusivos.Respostas:
Você pode fazer o seu próprio, por exemplo, desta forma ( atualizado para o Swift 1.2 com Set ):
Versão Swift 3:
E como uma extensão para
Array
:fonte
var addedDict = [T:Bool](); return filter(source) { addedDict(true, forKey: $0) == nil }
updateValue(true, forKey: $0)...
vez deaddedDict(true, forKey: $0)...
return filter(source) { addedDict.updateValue(true, forKey: $0) == nil }
como você diz.let uniques = Array(Set(vals))
Você pode converter para um conjunto e retornar para uma matriz novamente com bastante facilidade:
Isso não é garantido para manter a ordem original da matriz.
fonte
originals
não foremHashable
; somenteHashable
tipos de dados podem ser adicionados a um conjunto, mas qualquer tipo de dados pode ser adicionado a uma matriz.Muitas respostas estão disponíveis aqui, mas eu perdi essa extensão simples, adequada para o Swift 2 e superior:
Torna super simples. Pode ser chamado assim:
Filtragem com base nas propriedades
Para filtrar uma matriz com base nas propriedades, você pode usar este método:
Você pode chamar da seguinte maneira:
fonte
extension Array where Element: Equatable
) está sendo substituída por stackoverflow.com/a/36048862/1033581, que oferece uma solução mais poderosa (extension Sequence where Iterator.Element: Equatable
).O(n²)
desempenho de tempo, o que é realmente ruim para matrizes grandes.O(n²)
complexidade terrível de volta aO(n)
Swift 3.0
fonte
array
não foremHashable
; somenteHashable
tipos de dados podem ser adicionados a um conjunto, mas qualquer tipo de dados pode ser adicionado a uma matriz.Se você colocar as duas extensões no seu código, a
Hashable
versão mais rápida será usada quando possível e aEquatable
versão será usada como fallback.Se a ordem não for importante, você sempre poderá usar este inicializador de conjunto .
fonte
O(n²)
desempenho de tempo, o que é realmente ruim para matrizes grandes.editar / atualizar Swift 4 ou posterior
Também podemos estender o
RangeReplaceableCollection
protocolo para permitir que ele seja usado comStringProtocol
tipos também:Método de mutação:
Para Swift 3 clique aqui
fonte
reduce
implementação, agora a complexidade é diferente.O(n)
tempo), enquanto a versão baseada em mapa plano leva 7,47x mais para 8 milhões de entradas exclusivas que 1 milhão, sugerindo que a versão baseada em mapa plano é melhor . De alguma forma, a versão baseada em flatmap se sai um pouco melhor que oO(n)
tempo!Swift 4
cada tentativa de
insert
também irá retornar uma tupla:(inserted: Bool, memberAfterInsert: Set.Element)
. Veja a documentação .O uso do valor retornado ajuda a evitar o loop ou a execução de qualquer outra operação.
fonte
O(n^2)
, e ninguém percebeu.Swift 4
Garantido para continuar solicitando.
fonte
reduce
: em geral, é apenas mais uma linha em todo o seu projeto de escrever a sua função como:var unique: [Iterator.Element] = []; for element in self where !unique.contains(element) { unique.append(element) }; return unique
. Admito que ainda não testei as performances relativas.O(n²)
desempenho de tempo, o que é realmente ruim para matrizes grandes.O(n²)
. Não há nada rápido nisso.reduce
oureduce(into:)
não faria uma diferença crítica. Reescrever isso para não chamar repetidamentecontains
faria uma diferença MUITO maior.Aqui está uma categoria na
SequenceType
qual preserva a ordem original da matriz, mas usa aSet
para fazer ascontains
pesquisas para evitar oO(n)
custo nocontains(_:)
método da matriz .Se você não é Hashable ou Equatable, pode passar um predicado para fazer a verificação de igualdade:
Agora, se você não tem Hashable, mas é Equatable, você pode usar este método:
Por fim, você pode adicionar uma versão do caminho da chave uniqued como esta:
Você pode colocar os dois no seu aplicativo, o Swift escolherá o caminho certo, dependendo do
Iterator.Element
tipo da sua sequência .fonte
O(n)
solução. A propósito, você pode combinar as operações de "verificação" e "inserção" do conjunto em uma. Veja stackoverflow.com/a/46354989/3141234Inspirados em https://www.swiftbysundell.com/posts/the-power-of-key-paths-in-swift , podemos declarar uma ferramenta mais poderosa que é capaz de filtrar a unicidade em qualquer keyPath. Graças aos comentários de Alexander sobre várias respostas relacionadas à complexidade, as soluções abaixo devem estar próximas do ideal.
Solução não mutante
Estendemos uma função que é capaz de filtrar a unicidade em qualquer keyPath:
Nota: no caso em que seu objeto não está em conformidade com RangeReplaceableCollection, mas está em conformidade com Sequence, você pode ter essa extensão adicional, mas o tipo de retorno sempre será uma matriz:
Uso
Se queremos unicidade para os próprios elementos, como na pergunta, usamos o keyPath
\.self
:Se queremos unicidade para outra coisa (como para
id
uma coleção de objetos), usamos o keyPath de nossa escolha:Solução mutante
Estendemos uma função de mutação capaz de filtrar a unicidade em qualquer keyPath:
Uso
Se queremos unicidade para os próprios elementos, como na pergunta, usamos o keyPath
\.self
:Se queremos unicidade para outra coisa (como para
id
uma coleção de objetos), usamos o keyPath de nossa escolha:fonte
keyPath
padrão\.self
, porque essa é provavelmente a maioria dos casos de uso.Element
sempreHashable
. Uma alternativa para um valor padrão é a adição de uma sobrecarga simples, sem parâmetros:extension Sequence where Element: Hashable { func unique() { ... } }
Uma solução alternativa (se não ótima) a partir daqui usando tipos imutáveis em vez de variáveis:
Incluído para contrastar a abordagem imperativa de Jean-Pillippe com uma abordagem funcional.
Como um bônus, esta função funciona tanto com strings quanto com arrays!
Editar: Esta resposta foi escrita em 2014 para o Swift 1.0 (antes
Set
estava disponível no Swift). Não requer conformidade com Hashable e é executado em tempo quadrático.fonte
contains
e array acrescentam execução em O (n). Embora ele tenha o benefício de exigir apenas equações, não é lavável.filter
. É O (n ^ 2) (que é necessário se você não deseja exigirHashable
conformidade), mas pelo menos convide explicitamente issoveloz 2
com resposta da função uniq :
usar:
fonte
Bool
valor obviamente é redundante, pois seu código nunca o lê. Use um emSet
vez de umDictionary
e você receberá meu voto positivo.No Swift 5
Saída será
fonte
Mais uma solução Swift 3.0 para remover duplicatas de uma matriz. Esta solução aprimora muitas outras soluções já propostas por:
Dada a matriz inteira:
Código funcional:
Código de extensão da matriz:
Esse código aproveita o resultado retornado pela
insert
operação onSet
, que é executada emO(1)
, e retorna uma tupla indicando se o item foi inserido ou se já existe no conjunto.Se o item estava no conjunto, ele
filter
será excluído do resultado final.fonte
defer
o código faria a operação de teste definido duas vezes, uma comcontains
e outra cominsert
. Depois de ler a documentação do Swift, descobri queinsert
retorna uma tupla indicando se o elemento foi inserido ou não, então simplifiquei o código removendo acontains
verificação.extension Sequence where Iterator.Element: Hashable { ... }
insert
econtains
têmO(1)
complexidade.O(1) + O(1) = O(1)
. Essas duas operações são executadasn
vezes (uma vez por chamada do fechamento passada parafilter
, que é chamada uma vez por elemento) Ou seja, se uma operação leva uma quantidade constante de tempo, independentemente do tamanho da entrada, fazê-lo duas vezes ainda faz com que seja necessário um tempo constante isso é independentemente do tamanho da entrada. A complexidade total disso éO(n)
.Swift 4.x:
uso:
ou
fonte
O(n^2)
. Não faça isso.Swift 5
fonte
extension Sequence { // Returns distinct elements based on a key value. func distinct<key: Hashable>(by: ((_ el: Iterator.Element) -> key)) -> [Iterator.Element] { var existing = Set<key>() return self.filter { existing.insert(by($0)).inserted } } }
Bool
quando o único valor que você usa étrue
. Você está buscando um "tipo de unidade" (um tipo com apenas um valor possível). O tipo de unidade de Swift éVoid
, cujo único valor é()
(também conhecido como tupla vazia). Então você pode apenas usar[T: Void]
. Embora você não deva fazer isso, porque basicamente inventouSet
. Use emSet
vez disso. Consulte stackoverflow.com/a/55684308/3141234 Exclua esta resposta.Pense como um programador funcional :)
Para filtrar a lista com base se o elemento já ocorreu, você precisa do índice. Você pode usar
enumerated
para obter o índice emap
retornar à lista de valores.Isso garante o pedido. Se você não se importa com o pedido, a resposta existente
Array(Set(myArray))
é mais simples e provavelmente mais eficiente.ATUALIZAÇÃO: Algumas notas sobre eficiência e correção
Algumas pessoas comentaram sobre a eficiência. Definitivamente, estou na escola de escrever código correto e simples primeiro e depois descobrir gargalos mais tarde, embora eu aprecie que seja discutível se isso é mais claro do que isso
Array(Set(array))
.Este método é muito mais lento que
Array(Set(array))
. Conforme observado nos comentários, ele preserva a ordem e trabalha em elementos que não são Hashable.No entanto, o método do @Alain T também preserva a ordem e também é muito mais rápido. Portanto, a menos que o seu tipo de elemento não seja lavável, ou você só precise de um liner rápido, sugiro ir com a solução deles.
Aqui estão alguns testes em um MacBook Pro (2014) no Xcode 11.3.1 (Swift 5.1) no modo Release.
A função de criação de perfil e dois métodos para comparar:
E uma pequena variedade de entradas de teste:
Dá como saída:
fonte
Array(Set(myArray))
, isso funciona para coisas que não sãoHashable
Array(Set(myArray))
da ordem da sua matriz, é mantida.lastIndex(of:)
. Discordo totalmente sobre o ponto de clareza versus otimização neste caso. Não acho que essa implementação seja particularmente clara, especialmente se comparada a uma solução simples baseada em conjunto. De qualquer forma, esse código deve ser extraído para uma função de extensão. Esse algoritmo se torna basicamente inutilizável, mesmo com um tamanho de entrada baixo, como nos milhares a dezenas de milhares. Não é difícil encontrar esses conjuntos de dados, as pessoas podem ter milhares de músicas, arquivos, contatos etc.Para matrizes onde os elementos não são hashable nem comparáveis (por exemplo, objetos complexos, dicionários ou estruturas), essa extensão fornece uma maneira generalizada de remover duplicatas:
Você não precisa se preocupar em criar valores Hashable e permite usar diferentes combinações de campos para obter exclusividade.
Nota: para uma abordagem mais robusta, consulte a solução proposta por Coeur nos comentários abaixo.
stackoverflow.com/a/55684308/1033581
[EDIT] alternativa Swift 4
Com o Swift 4.2, você pode usar a classe Hasher para criar um hash muito mais fácil. A extensão acima pode ser alterada para aproveitar isso:
A sintaxe de chamada é um pouco diferente porque o fechamento recebe um parâmetro adicional que contém uma função para hash de um número variável de valores (que deve ser Hashable individualmente)
Ele também funcionará com um único valor de exclusividade (usando $ 1 e ignorando $ 0).
fonte
"\()"
, pois pode não fornecer valores exclusivos, como em conformidade com oHashable
should. Por exemplo, se todos os seus elementos estiverem em conformidadePrintable
, retornando o mesmodescription
, sua filtragem falhará.T
a serHashable
.Você pode usar diretamente uma coleção de conjuntos para remover duplicados e convertê-los novamente em uma matriz
Depois, você pode solicitar seu array como quiser
fonte
Versão de sintaxe um pouco mais sucinta da resposta Swift 2 de Daniel Krom , usando um nome à direita e argumento abreviado, que parece basear-se na resposta original de Airspeed Velocity :
Exemplo de implementação de um tipo personalizado que pode ser usado com
uniq(_:)
(que deve estar em conformidade eHashable
, portantoEquatable
, porqueHashable
se estendeEquatable
):No código acima ...
id
, conforme usado na sobrecarga de==
, pode ser qualquerEquatable
tipo (ou método que retorna umEquatable
tipo, por exemplo,someMethodThatReturnsAnEquatableType()
). O código comentado demonstra a extensão da verificação de igualdade, ondesomeOtherEquatableProperty
há outra propriedade de umEquatable
tipo (mas também pode ser um método que retorna umEquatable
tipo).id
, conforme usado nahashValue
propriedade computada (necessária para conformidadeHashable
), pode ser qualquerHashable
(e, portantoEquatable
) propriedade (ou método que retorna umHashable
tipo).Exemplo de uso
uniq(_:)
:fonte
Bool
quando o único valor que você usa étrue
. Você está buscando um "tipo de unidade" (um tipo com apenas um valor possível). O tipo de unidade de Swift éVoid
, cujo único valor é()
(também conhecido como tupla vazia). Então você pode apenas usar[T: Void]
. Embora você não deva fazer isso, porque basicamente inventouSet
. Use emSet
vez disso. Veja stackoverflow.com/a/55684308/3141234Caso você precise de valores classificados, isso funciona (Swift 4)
let sortedValues = Array(Set(array)).sorted()
fonte
.sorted()
serve o final. Saudações.[2, 1, 1]
? Ele sairia[1, 2]
, que não está ordenado: p[2, 1, 1]
. A primeira aparição de elementos únicos, em ordem é[2, 1]
. Essa é a resposta correta. Mas, usando o seu algoritmo (incorreto), você obtém[1, 2]
, que é classificado, mas não está na ordem correta e original.array
não foremHashable
; somenteHashable
tipos de dados podem ser adicionados a um conjunto, mas qualquer tipo de dados pode ser adicionado a uma matriz.Aqui está uma solução que
NS
tipos herdadosO(n)
fonte
aqui eu fiz uma solução O (n) para objetos. Solução não de poucas linhas, mas ...
fonte
Set
com um costumeDistinctWrapper
, você deve usar umDictionary
atributo de distinctAttributes para objetos. Ao seguir essa lógica, você acabaria implementando [Dictionary.init(_:uniquingKeysWith:)
] pastebin.com/w90pVe0p(https://developer.apple.com/documentation/… , que agora está embutido na biblioteca padrão. pastebin.com/w90pVe0pUsei a resposta de Jean-Philippe Pellet e fiz uma extensão Array que realiza operações de conjunto em matrizes, mantendo a ordem dos elementos.
fonte
Bool
quando o único valor que você usa étrue
. Você está buscando um "tipo de unidade" (um tipo com apenas um valor possível). O tipo de unidade de Swift éVoid
, cujo único valor é()
(também conhecido como tupla vazia). Então você pode apenas usar[T: Void]
. Embora você não deva fazer isso, porque basicamente inventouSet
. Use emSet
vez disso. Veja stackoverflow.com/a/55684308/3141234Esta é apenas uma implementação muito simples e conveniente. Uma propriedade calculada em uma extensão de uma matriz que possui elementos equáveis.
fonte
O(n^2)
.Uso:
fonte
O(n²)
.Feito....
Exemplo
saída de arrayWithoutDuplicates - [1,2,4,6,8]
fonte
Uma versão ligeiramente reduzida, com base na resposta de extensão de matriz de Jean-Philippe Pellet:
fonte
insert
retorna uma tupla que informa se o elemento já estava lá ou foi adicionado pela primeira vez. stackoverflow.com/a/55684308/3141234 Exclua esta resposta.Você sempre pode usar um dicionário, porque um dicionário pode conter apenas valores exclusivos. Por exemplo:
Como você pode ver, a matriz resultante nem sempre estará em 'ordem'. Se você deseja classificar / ordenar a matriz, adicione isto:
.
fonte
A maneira mais fácil seria usar o NSOrderedSet, que armazena elementos exclusivos e preserva a ordem dos elementos. Gostar:
fonte