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

156

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

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

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

Дэвид
источник

Ответы:

168

Вы можете сделать что-то вроде этого, как показано в 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

Грег Хьюгилл
источник
28
пожалуйста, не используйте $ a или $ b в примерах, так как они являются волшебными глобалами sort ()
szabgab
2
Это myлексическое в этом смысле, так что все в порядке. При этом, возможно, можно было бы выбрать более описательное имя переменной.
Эфимент
2
@ephemient да, но если бы вы добавили сортировку в эту функцию, то это было бы лучше, $::aи $::bне так ли?
vol7ron
5
@BrianVandenberg Добро пожаловать в мир 1987 года - когда он был создан - и почти 100% -ную совместимость с обратным словом для Perl - поэтому его нельзя устранить.
Сабгаб
18
sub uniq { my %seen; grep !$seen{$_}++, @_ }это лучшая реализация, поскольку она сохраняет порядок без затрат. Или даже лучше, используйте тот из List :: MoreUtils.
икегами
120

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

% perldoc -q duplicate

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

Найдено в /usr/local/lib/perl5/5.10.0/pods/perlfaq4.pod
 Как я могу удалить дубликаты элементов из списка или массива?
   (предоставлено Брайаном Д. Фой)

   Используйте хэш. Когда вы думаете, слова «уникальный» или «дублированный», подумайте
   "ключи хеша".

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

       мой% hash = map {$ _, 1} @array;
       # или фрагмент хеша: @hash {@array} = ();
       # или foreach: $ hash {$ _} = 1 foreach (@array);

       мой @unique = keys% hash;

   Если вы хотите использовать модуль, попробуйте функцию "uniq" из
   "Список :: MoreUtils". В контексте списка он возвращает уникальные элементы,
   сохраняя их порядок в списке. В скалярном контексте возвращает
   количество уникальных элементов.

       использовать 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

   Вы также можете пройти через каждый элемент и пропустить те, которые вы видели
   перед. Используйте хеш для отслеживания. Первый раз, когда цикл видит
   элемент, этот элемент не имеет ключа в% Seen. «Следующее» утверждение создает
   ключ и сразу же использует его значение, которое является "undef", поэтому цикл
   продолжает нажимать и увеличивает значение для этой клавиши. Следующий
   Когда цикл видит тот же элемент, его ключ существует в хеше и
   значение для этого ключа является истинным (так как это не 0 или "undef"), поэтому
   next пропускает эту итерацию, и цикл переходит к следующему элементу.

       my @unique = ();
       мой% видел = ();

       foreach мой $ elem (@array)
       {
         далее, если $ seen {$ elem} ++;
         push @unique, $ elem;
       }

   Вы можете написать это более кратко, используя grep, который делает то же самое
   вещь.

       мой% видел = ();
       мой @unique = grep {! $ seen {$ _} ++} @array;
Джон Сиракуза
источник
17
Джон из Ма-Анзерс крадет Мах-Реп!
Брайан д Фой
5
Я думаю, что вы должны получить бонусные баллы за то, что действительно посмотрите на вопрос.
Брэд Гилберт
2
Мне нравится, что лучший ответ - 95% копирование-вставка и 3 предложения OC. Чтобы быть совершенно ясно, это является лучшим ответом; Я просто нахожу этот факт забавным.
Парфянский выстрел
70

Список установки :: 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);
Ranguard
источник
4
Тот факт, что List :: MoreUtils не связан с perl своего рода, повреждает переносимость проектов, использующих его :( (я, например, не буду)
yPhil
3
@Ranguard: @dup_listдолжен быть внутри uniqзвонка, а не@dups
incutonez
@yassinphilip CPAN - это то, что делает Perl настолько мощным и мощным, насколько это возможно. Если вы пишете свои проекты, основанные только на базовых модулях, вы накладываете огромный предел на свой код, а также, возможно, на плохо написанный код, который пытается сделать то, что некоторые модули делают намного лучше, просто чтобы избежать их использования. Кроме того, использование основных модулей ничего не гарантирует, поскольку различные версии Perl могут добавлять или удалять основные модули из дистрибутива, поэтому переносимость все еще зависит от этого.
Франциско Сарабосо
24

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

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

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

Xetius
источник
2
У этого есть недостаток - не сохранять первоначальный заказ, если он вам нужен.
Натан Феллман
Лучше использовать ломтики вместо foreachцикла:@unique{@myarray}=()
Onlyjob
8

Переменная @array - это список с дублирующимися элементами.

%seen=();
@unique = grep { ! $seen{$_} ++ } @array;
SREEDHAR
источник
7

Может быть сделано с простым 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

ястреб
источник
4

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

my @arr;
my @uniqarr;

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

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

jh314
источник
4

Способ 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);
Камаль Наян
источник
1

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

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

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
YenForYang
источник
Для тех, кто сравнивает: sub uniq{ my %seen; undef @seen{@_}; keys %seen; } аккуратно.
стевеслива
0

Попробуйте, кажется, что для правильной работы функции 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";
saschabeaumont
источник
0

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

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

Выход: acbd

Sandeep_black
источник