Como faço para remover itens duplicados de uma matriz no Perl?

156

Eu tenho uma matriz em Perl:

my @my_array = ("one","two","three","two","three");

Como faço para remover as duplicatas da matriz?

David
fonte

Respostas:

168

Você pode fazer algo assim, como demonstrado no perlfaq4 :

sub uniq {
    my %seen;
    grep !$seen{$_}++, @_;
}

my @array = qw(one two three two three);
my @filtered = uniq(@array);

print "@filtered\n";

Saídas:

one two three

Se você deseja usar um módulo, tente a uniqfunção emList::MoreUtils

Greg Hewgill
fonte
28
por favor não use $ a ou $ b em exemplos como eles são os globals mágicos de sort ()
szabgab
2
É um myléxico neste escopo, então tudo bem. Dito isto, possivelmente um nome de variável mais descritivo poderia ser escolhido.
ephemient
2
@hemphient sim, mas se você adicionasse a classificação nessa função, ela trunfaria $::ae $::bnão seria?
vol7ron
5
@BrianVandenberg Bem-vindo ao mundo de 1987 - quando isso foi criado - e quase 100% de compactação de palavras-chave para perl - para que não possa ser eliminado.
szabgab
18
sub uniq { my %seen; grep !$seen{$_}++, @_ }é uma implementação melhor, pois preserva a ordem sem nenhum custo. Ou melhor ainda, use o da List :: MoreUtils.
Ikegami #
120

A documentação do Perl vem com uma boa coleção de perguntas frequentes. Sua pergunta é freqüente:

% perldoc -q duplicate

A resposta, copie e cole a partir da saída do comando acima, aparece abaixo:

Encontrado em /usr/local/lib/perl5/5.10.0/pods/perlfaq4.pod
 Como posso remover elementos duplicados de uma lista ou matriz?
   (contribuído por brian d foy)

   Use um hash. Quando você acha que as palavras "único" ou "duplicado", pense
   "chaves de hash".

   Se você não se importa com a ordem dos elementos, pode apenas
   crie o hash e extraia as chaves. Não é importante como você
   crie esse hash: apenas use "chaves" para obter os elementos exclusivos.

       meu% hash = mapa {$ _, 1} @array;
       # ou uma fatia de hash: @hash {@array} = ();
       # ou um foreach: $ hash {$ _} = 1 foreach (@array);

       meu @ único = chaves% hash;

   Se você deseja usar um módulo, tente a função "uniq" em
   "Lista :: MoreUtils". No contexto da lista, ele retorna os elementos exclusivos,
   preservando sua ordem na lista. No contexto escalar, ele retorna o
   número de elementos únicos.

       use List :: MoreUtils qw (uniq);

       my @unique = uniq (1, 2, 3, 4, 4, 5, 6, 5, 7); # 1,2,3,4,5,6,7
       meu $ unique = uniq (1, 2, 3, 4, 4, 5, 6, 5, 7); # 7

   Você também pode percorrer cada elemento e pular os que você viu
   antes. Use um hash para acompanhar. A primeira vez que o loop vê um
   elemento, esse elemento não tem chave em% visto. A instrução "next" cria
   a chave e imediatamente usa seu valor, que é "undef", então o loop
   continua com o "push" e incrementa o valor dessa chave. Nas próximas
   Quando o loop vê o mesmo elemento, sua chave existe no hash e
   o valor dessa chave é verdadeiro (já que não é 0 ou "undef"), portanto, o
   next pula essa iteração e o loop passa para o próximo elemento.

       meu @ único = ();
       meu% visto = ();

       foreach meu $ elem (@array)
       {
         próximo se $ seen {$ elem} ++;
         push @unique, $ elem;
       }

   Você pode escrever isso mais rapidamente usando um grep, que faz o mesmo
   coisa.

       meu% visto = ();
       meu @ único = grep {! $ seen {$ _} ++} @array;
John Siracusa
fonte
perldoc.perl.org/…
szabgab 17/09/08
17
John iz em mah anzers roubando mah rep!
Brian d foy #
5
Eu acho que você deve ganhar pontos de bônus por realmente procurar a pergunta.
Brad Gilbert
2
Eu gosto que a melhor resposta seja 95% de copiar e colar e 3 frases de OC. Para ser perfeitamente claro, esta é a melhor resposta; Eu apenas acho esse fato divertido.
Tiro parta
70

Lista de instalação :: MoreUtils from CPAN

Então no seu código:

use strict;
use warnings;
use List::MoreUtils qw(uniq);

my @dup_list = qw(1 1 1 2 3 4 4);

my @uniq_list = uniq(@dup_list);
Ranguard
fonte
4
O fato de essa lista :: moreutils não vem w / Perl de kinda danos a portabilidade de projectos de usá-lo :( (Eu, pelo menos não)
yPhil
3
@Ranguard: @dup_listdeve estar dentro da uniqchamada, não@dups
incutonez
@yassinphilip CPAN é uma das coisas que tornam o Perl o mais poderoso e ótimo possível. Se você está escrevendo seus projetos com base apenas nos módulos principais, está colocando um limite enorme no seu código, juntamente com um código possivelmente escrito que tenta fazer o que alguns módulos fazem muito melhor apenas para evitar usá-los. Além disso, o uso de módulos principais não garante nada, pois diferentes versões do Perl podem adicionar ou remover módulos principais da distribuição, portanto a portabilidade ainda depende disso.
Francisco Zarabozo
24

Minha maneira usual de fazer isso é:

my %unique = ();
foreach my $item (@myarray)
{
    $unique{$item} ++;
}
my @myuniquearray = keys %unique;

Se você usar um hash e adicionar os itens ao hash. Você também tem o bônus de saber quantas vezes cada item aparece na lista.

Xetius
fonte
2
Isso tem a desvantagem de não preservar o pedido original, se você precisar.
Nathan Fellman
É melhor usar fatias em vez de foreachloop:@unique{@myarray}=()
Onlyjob
8

A variável @array é a lista com elementos duplicados

%seen=();
@unique = grep { ! $seen{$_} ++ } @array;
Sreedhar
fonte
7

Pode ser feito com um liner Perl simples.

my @in=qw(1 3 4  6 2 4  3 2 6  3 2 3 4 4 3 2 5 5 32 3); #Sample data 
my @out=keys %{{ map{$_=>1}@in}}; # Perform PFM
print join ' ', sort{$a<=>$b} @out;# Print data back out sorted and in order.

O bloco PFM faz isso:

Os dados em @in são alimentados no MAP. O MAP cria um hash anônimo. As chaves são extraídas do hash e alimentam @out

Falcão
fonte
4

Esse último foi muito bom. Eu apenas ajustaria um pouco:

my @arr;
my @uniqarr;

foreach my $var ( @arr ){
  if ( ! grep( /$var/, @uniqarr ) ){
     push( @uniqarr, $var );
  }
}

Eu acho que essa é provavelmente a maneira mais legível de fazer isso.

jh314
fonte
4

Método 1: usar um hash

Lógica: um hash pode ter apenas chaves exclusivas; portanto, itere sobre a matriz, atribua qualquer valor a cada elemento da matriz, mantendo o elemento como chave desse hash. Retorne as chaves do hash, é sua matriz exclusiva.

my @unique = keys {map {$_ => 1} @array};

Método 2: Extensão do método 1 para reutilização

Melhor criar uma sub-rotina se devemos usar essa funcionalidade várias vezes em nosso código.

sub get_unique {
    my %seen;
    grep !$seen{$_}++, @_;
}
my @unique = get_unique(@array);

Método 3: usar o módulo List::MoreUtils

use List::MoreUtils qw(uniq);
my @unique = uniq(@array);
Kamal Nayan
fonte
1

As respostas anteriores resumem bastante as maneiras possíveis de realizar essa tarefa.

No entanto, sugiro uma modificação para aqueles que não se preocupam contando os duplicados, mas fazer o cuidado sobre a ordem.

my @record = qw( yeah I mean uh right right uh yeah so well right I maybe );
my %record;
print grep !$record{$_} && ++$record{$_}, @record;

Observe que os grep !$seen{$_}++ ...incrementos sugeridos anteriormente $seen{$_}antes de negar, portanto, o incremento ocorre independentemente de já ter sido %seenou não. O exposto acima, no entanto, provoca um curto-circuito quando $record{$_}é verdade, deixando o que foi ouvido uma vez "fora do ar %record".

Você também pode usar esse ridículo, que tira proveito da autovivificação e da existência de chaves de hash:

...
grep !(exists $record{$_} || undef $record{$_}), @record;

Isso, no entanto, pode levar a alguma confusão.

E se você não se importa com pedidos ou contagens duplicadas, pode fazer outro hack usando fatias de hash e o truque que acabei de mencionar:

...
undef @record{@record};
keys %record; # your record, now probably scrambled but at least deduped
YenForYang
fonte
Para aqueles que comparam: sub uniq{ my %seen; undef @seen{@_}; keys %seen; } Neat.
Steveliva # 9/19
0

Tente isso, parece que a função uniq precisa de uma lista classificada para funcionar corretamente.

use strict;

# Helper function to remove duplicates in a list.
sub uniq {
  my %seen;
  grep !$seen{$_}++, @_;
}

my @teststrings = ("one", "two", "three", "one");

my @filtered = uniq @teststrings;
print "uniq: @filtered\n";
my @sorted = sort @teststrings;
print "sort: @sorted\n";
my @sortedfiltered = uniq sort @teststrings;
print "uniq sort : @sortedfiltered\n";
saschabeaumont
fonte
0

Usando o conceito de chaves de hash exclusivas:

my @array  = ("a","b","c","b","a","d","c","a","d");
my %hash   = map { $_ => 1 } @array;
my @unique = keys %hash;
print "@unique","\n";

Saída: acbd

Sandeep_black
fonte