Как удалить повторяющиеся элементы из массива в Perl?

У меня есть массив в Perl:

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

Как удалить дубликаты из массива?


person David    schedule 11.08.2008    source источник


Ответы (11)


Вы можете сделать что-то подобное, как показано в perlfaq4:

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

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

print "@filtered\n";

Выходы:

one two three

Если вы хотите использовать модуль, попробуйте функцию uniq из List::MoreUtils.

person Greg Hewgill    schedule 11.08.2008
comment
пожалуйста, не используйте $a или $b в примерах, так как они являются волшебными глобальными переменными sort() - person szabgab; 17.09.2008
comment
Это лексика my в этой области, так что все в порядке. При этом, возможно, можно было бы выбрать более описательное имя переменной. - person ephemient; 18.01.2010
comment
@ephemient да, но если бы вы добавили сортировку в эту функцию, то она превзошла бы $::a и $::b, не так ли? - person vol7ron; 21.02.2012
comment
@szabgab, если это так, то это невероятно плохое дизайнерское решение для sort использовать нелокальные переменные. - person Brian Vandenberg; 15.06.2012
comment
@BrianVandenberg Добро пожаловать в мир 1987 года, когда он был создан, и почти 100% совместимость бэквордов с Perl, так что его нельзя устранить. - person szabgab; 25.06.2012
comment
sub uniq { my %seen; grep !$seen{$_}++, @_ } — лучшая реализация, поскольку она сохраняет порядок бесплатно. Или, что еще лучше, используйте один из List::MoreUtils. - person ikegami; 06.11.2012
comment
@vol7tron означает обратную совместимость, извините, это меня беспокоило ;-) - person Tyler; 29.08.2015
comment
Perl v5.26.0 и выше, List::Util имеет uniq , поэтому MoreUtils не понадобится. - person Sundeep; 30.10.2020

Документация Perl поставляется с хорошей коллекцией часто задаваемых вопросов. Ваш вопрос часто задают:

% perldoc -q duplicate

Ответ, скопированный и вставленный из вывода приведенной выше команды, появится ниже:

Found in /usr/local/lib/perl5/5.10.0/pods/perlfaq4.pod
 How can I remove duplicate elements from a list or array?
   (contributed by brian d foy)

   Use a hash. When you think the words "unique" or "duplicated", think
   "hash keys".

   If you don't care about the order of the elements, you could just
   create the hash then extract the keys. It's not important how you
   create that hash: just that you use "keys" to get the unique elements.

       my %hash   = map { $_, 1 } @array;
       # or a hash slice: @hash{ @array } = ();
       # or a foreach: $hash{$_} = 1 foreach ( @array );

       my @unique = keys %hash;

   If you want to use a module, try the "uniq" function from
   "List::MoreUtils". In list context it returns the unique elements,
   preserving their order in the list. In scalar context, it returns the
   number of unique elements.

       use List::MoreUtils qw(uniq);

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

   You can also go through each element and skip the ones you've seen
   before. Use a hash to keep track. The first time the loop sees an
   element, that element has no key in %Seen. The "next" statement creates
   the key and immediately uses its value, which is "undef", so the loop
   continues to the "push" and increments the value for that key. The next
   time the loop sees that same element, its key exists in the hash and
   the value for that key is true (since it's not 0 or "undef"), so the
   next skips that iteration and the loop goes to the next element.

       my @unique = ();
       my %seen   = ();

       foreach my $elem ( @array )
       {
         next if $seen{ $elem }++;
         push @unique, $elem;
       }

   You can write this more briefly using a grep, which does the same
   thing.

       my %seen = ();
       my @unique = grep { ! $seen{ $_ }++ } @array;
person John Siracusa    schedule 11.08.2008
comment
perldoc.perl.org/ - person szabgab; 17.09.2008
comment
Джон из маха анзерс крадет у меня репутацию! - person brian d foy; 10.10.2008
comment
Я думаю, вы должны получить бонусные баллы за фактический поиск вопроса. - person Brad Gilbert; 24.10.2008
comment
Мне нравится, что лучший ответ - это 95% копипаст и 3 предложения OC. Чтобы было совершенно ясно, это является лучшим ответом; Я просто нахожу этот факт забавным. - person Parthian Shot; 21.07.2014

Установите List::MoreUtils из CPAN.

Затем в вашем коде:

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);
person Ranguard    schedule 31.08.2008
comment
Тот факт, что List::MoreUtils не связан с perl, как бы вредит переносимости проектов, использующих его :( (я, например, не буду) - person yPhil; 19.03.2012
comment
@Ranguard: @dup_list должно быть внутри вызова uniq, а не @dups - person incutonez; 11.11.2013
comment
@yassinphilip CPAN — одна из тех вещей, которые делают Perl настолько мощным и замечательным, насколько это возможно. Если вы пишете свои проекты, основанные только на основных модулях, вы накладываете огромные ограничения на свой код, а также, возможно, написанный код, который пытается делать то, что некоторые модули делают намного лучше, просто чтобы не использовать их. Кроме того, использование базовых модулей ничего не гарантирует, так как разные версии Perl могут добавлять или удалять базовые модули из дистрибутива, поэтому переносимость по-прежнему зависит от этого. - person Francisco Zarabozo; 27.06.2017
comment
Perl v5.26.0 и выше, List::Util имеет uniq , поэтому MoreUtils не понадобится. - person Sundeep; 30.10.2020

Мой обычный способ сделать это:

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

Если вы используете хэш и добавляете элементы в хэш. У вас также есть бонус: вы знаете, сколько раз каждый элемент появляется в списке.

person Xetius    schedule 11.08.2008
comment
Недостатком этого является то, что исходный порядок не сохраняется, если вам это нужно. - person Nathan Fellman; 18.02.2014
comment
Лучше использовать фрагменты вместо foreach цикла: @unique{@myarray}=() - person Onlyjob; 20.09.2015

Можно сделать с помощью простого однострочного Perl.

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.

Блок PFM делает это:

Данные в @in передаются в MAP. MAP создает анонимный хэш. Ключи извлекаются из хэша и передаются в @out

person Hawk    schedule 09.11.2011

Способ 1: Используйте хэш

Логика: хэш может иметь только уникальные ключи, поэтому перебирайте массив, присваивайте любое значение каждому элементу массива, сохраняя элемент в качестве ключа этого хеша. Возвращайте ключи хэша, это ваш уникальный массив.

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

Метод 2: Расширение метода 1 для повторного использования

Лучше сделать подпрограмму, если мы должны использовать эту функцию несколько раз в нашем коде.

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

Способ 3: Используйте модуль List::MoreUtils

use List::MoreUtils qw(uniq);
my @unique = uniq(@array);
person Kamal Nayan    schedule 09.05.2017

Переменная @array — это список с повторяющимися элементами

%seen=();
@unique = grep { ! $seen{$_} ++ } @array;
person Sreedhar    schedule 23.10.2010

Тот последний был довольно хорош. Я бы немного подкорректировал:

my @arr;
my @uniqarr;

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

Я думаю, что это, вероятно, самый читаемый способ сделать это.

person Community    schedule 23.01.2009

Предыдущие ответы в значительной степени обобщают возможные способы выполнения этой задачи.

Тем не менее, я предлагаю модификацию для тех, кто не заботится о подсчете дубликатов, но заботится о порядке.

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

Обратите внимание, что ранее предложенное grep !$seen{$_}++ ... увеличивает $seen{$_} перед отрицанием, поэтому приращение происходит независимо от того, было ли оно уже %seen или нет. Вышеупомянутое, однако, короткое замыкание, когда $record{$_} истинно, оставляя то, что было услышано однажды, «за пределами %record».

Вы также можете пойти на эту нелепость, которая использует автовивификацию и существование хеш-ключей:

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

Однако это может привести к некоторой путанице.

И если вас не волнует ни порядок, ни количество дубликатов, вы можете использовать другой хак, используя хеш-фрагменты и трюк, о котором я только что упомянул:

...
undef @record{@record};
keys %record; # your record, now probably scrambled but at least deduped
person YenForYang    schedule 02.01.2019
comment
Для сравнивающих: sub uniq{ my %seen; undef @seen{@_}; keys %seen; } Аккуратно. - person stevesliva; 09.05.2019

Попробуйте это, кажется, функция uniq нуждается в отсортированном списке для правильной работы.

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";
person saschabeaumont    schedule 26.05.2015

Использование концепции уникальных хеш-ключей:

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

Выход: а с б д

person Sandeep_black    schedule 30.03.2017