Làm cách nào để xóa các mục trùng lặp khỏi một mảng trong Perl?


156

Tôi có một mảng trong Perl:

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

Làm thế nào để tôi loại bỏ các bản sao từ mảng?

Câu trả lời:


168

Bạn có thể làm một cái gì đó như thế này như được thể hiện trong perlfaq4 :

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

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

print "@filtered\n";

Đầu ra:

one two three

Nếu bạn muốn sử dụng một mô-đun, hãy thử uniqchức năng từList::MoreUtils


28
vui lòng không sử dụng $ a hoặc $ b trong các ví dụ vì chúng là những quả cầu ma thuật sắp xếp ()
szabgab

2
Đó là một mytừ vựng trong phạm vi này, vì vậy nó ổn. Điều đó đang được nói, có thể một tên biến mô tả nhiều hơn có thể được chọn.
ephemient

2
@ephemient có, nhưng nếu bạn đã thêm sắp xếp trong chức năng này sau đó nó sẽ trump $::a$::b, đúng không?
vol7ron

5
@BrianVandenberg Chào mừng bạn đến với thế giới năm 1987 - khi điều này được tạo ra - và gần như 100% khả năng trả lời từ khóa cho perl - vì vậy nó không thể bị loại bỏ.
szabgab

18
sub uniq { my %seen; grep !$seen{$_}++, @_ }là một thực hiện tốt hơn vì nó bảo tồn trật tự miễn phí. Hoặc thậm chí tốt hơn, sử dụng một từ Danh sách :: MoreUtils.
ikegami

120

Tài liệu Perl đi kèm với một bộ Câu hỏi thường gặp. Câu hỏi của bạn thường được hỏi:

% perldoc -q duplicate

Câu trả lời, sao chép và dán từ đầu ra của lệnh trên, xuất hiện bên dưới:

Tìm thấy trong /usr/local/lib/perl5/5.10.0/pods/perlfaq4.pod
 Làm cách nào để xóa các phần tử trùng lặp khỏi danh sách hoặc mảng?
   (đóng góp bởi brian d foy)

   Sử dụng một hàm băm. Khi bạn nghĩ các từ "độc nhất" hoặc "trùng lặp", hãy nghĩ
   "Khóa băm".

   Nếu bạn không quan tâm đến thứ tự của các yếu tố, bạn có thể
   tạo hàm băm sau đó giải nén các phím. Nó không quan trọng như thế nào bạn
   tạo hàm băm đó: chỉ cần bạn sử dụng "khóa" để lấy các phần tử duy nhất.

       % hash = map {$ _, 1} @array;
       # hoặc một lát băm: @hash {@array} = ();
       # hoặc một foreach: $ hash {$ _} = 1 foreach (@array);

       @unique của tôi = khóa% băm;

   Nếu bạn muốn sử dụng một mô-đun, hãy thử chức năng "uniq" từ
   "Danh sách :: MoreUtils". Trong ngữ cảnh danh sách, nó trả về các phần tử duy nhất,
   giữ gìn trật tự của họ trong danh sách Trong bối cảnh vô hướng, nó trả về
   số lượng các yếu tố duy nhất.

       sử dụng Danh sách :: MoreUtils qw (uniq);

       @unique của tôi = 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

   Bạn cũng có thể đi qua từng yếu tố và bỏ qua những yếu tố bạn đã thấy
   trước. Sử dụng một hàm băm để theo dõi. Lần đầu tiên vòng lặp nhìn thấy một
   phần tử, phần tử đó không có khóa trong% Seen. Câu lệnh "tiếp theo" tạo ra
   khóa và ngay lập tức sử dụng giá trị của nó, đó là "undef", do đó, vòng lặp
   tiếp tục "đẩy" và tăng giá trị cho khóa đó. Tiếp theo
   Thời gian vòng lặp nhìn thấy cùng một phần tử, khóa của nó tồn tại trong hàm băm và
   giá trị của khóa đó là đúng (vì nó không phải là 0 hoặc "undef"), vì vậy
   bỏ qua lần lặp đó và vòng lặp đi đến phần tử tiếp theo.

       @unique của tôi = ();
       % tôi thấy = ();

       quy định $ elem của tôi (@array)
       {
         tiếp theo nếu $ thấy {$ elem} ++;
         đẩy @unique, $ elem;
       }

   Bạn có thể viết ngắn gọn hơn bằng cách sử dụng grep, thao tác này cũng tương tự
   Điều.

       % tôi thấy = ();
       @unique của tôi = grep {! $ thấy {$ _} ++} @array;


17
John iz trong mah anzers ăn cắp mah rep!
brian d foy

5
Tôi nghĩ bạn nên nhận được điểm thưởng khi thực sự tìm kiếm câu hỏi.
Brad Gilbert

2
Tôi thích câu trả lời tốt nhất là 95% copy-paste và 3 câu OC. Để được hoàn toàn rõ ràng, đây câu trả lời tốt nhất; Tôi chỉ thấy thực tế đó thú vị.
Bắn Parthian

70

Danh sách cài đặt :: MoreUtils từ CPAN

Sau đó, trong mã của bạn:

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);

4
Thực tế là List :: MoreUtils không được đóng gói w / perl kinda làm hỏng tính di động của các dự án sử dụng nó :( (Tôi cho một người sẽ không)
yPhil

3
@Ranguard: @dup_listnên ở trong uniqcuộc gọi, không@dups
incutonez

@yassinphilip CPAN là một trong những điều làm cho Perl trở nên mạnh mẽ và vĩ đại nhất có thể. Nếu bạn đang viết các dự án của mình chỉ dựa trên các mô-đun cốt lõi, thì bạn đang đặt một giới hạn rất lớn cho mã của mình, cùng với mã được viết có thể cố gắng làm những gì một số mô-đun làm tốt hơn chỉ để tránh sử dụng chúng. Ngoài ra, sử dụng các mô-đun lõi không đảm bảo bất cứ điều gì, vì các phiên bản Perl khác nhau có thể thêm hoặc xóa mô-đun lõi khỏi phân phối, do đó tính di động vẫn phụ thuộc vào điều đó.
Francisco Zarabozo

24

Cách thông thường của tôi để làm điều này là:

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

Nếu bạn sử dụng hàm băm và thêm các mục vào hàm băm. Bạn cũng có phần thưởng khi biết số lần mỗi mục xuất hiện trong danh sách.


2
Điều này có nhược điểm là không giữ được trật tự ban đầu, nếu bạn cần.
Nathan Fellman

Tốt hơn là sử dụng các lát thay vì foreachvòng lặp:@unique{@myarray}=()
Onlyjob

8

Biến @array là danh sách có các phần tử trùng lặp

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

7

Có thể được thực hiện với một lớp lót Perl đơn giản.

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.

Khối PFM thực hiện điều này:

Dữ liệu trong @in được đưa vào MAP. MAP xây dựng một hàm băm ẩn danh. Các khóa được trích xuất từ ​​hàm băm và đưa vào @out


4

Đó là cuối cùng là khá tốt. Tôi chỉ cần chỉnh nó một chút:

my @arr;
my @uniqarr;

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

Tôi nghĩ rằng đây có lẽ là cách dễ đọc nhất để làm điều đó.


4

Phương pháp 1: Sử dụng hàm băm

Logic: Một hàm băm chỉ có thể có các khóa duy nhất, do đó lặp qua mảng, gán bất kỳ giá trị nào cho từng phần tử của mảng, giữ phần tử làm khóa của hàm băm đó. Trả về các khóa của hàm băm, mảng duy nhất của bạn.

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

Phương pháp 2: Mở rộng phương pháp 1 cho khả năng sử dụng lại

Tốt hơn là tạo một chương trình con nếu chúng ta phải sử dụng chức năng này nhiều lần trong mã của chúng tôi.

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

Phương pháp 3: Sử dụng mô-đun List::MoreUtils

use List::MoreUtils qw(uniq);
my @unique = uniq(@array);

1

Các câu trả lời trước khá nhiều tóm tắt các cách có thể để hoàn thành nhiệm vụ này.

Tuy nhiên, tôi đề nghị sửa đổi đối với những người không quan tâm đến đếm các bản sao, nhưng làm chăm sóc về trật tự.

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

Lưu ý rằng các grep !$seen{$_}++ ...mức tăng được đề xuất trước đó $seen{$_}trước khi phủ định, do đó, mức tăng xảy ra bất kể nó đã được %seenhay chưa. Tuy nhiên, ở trên, ngắn mạch khi $record{$_}là đúng, để lại những gì đã nghe một lần 'tắt %record'.

Bạn cũng có thể đi vì sự lố bịch này, lợi dụng tự động hóa và sự tồn tại của các khóa băm:

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

Điều đó, tuy nhiên, có thể dẫn đến một số nhầm lẫn.

Và nếu bạn quan tâm đến việc không đặt hàng hoặc đếm trùng lặp, bạn có thể cho một vụ hack khác bằng cách sử dụng các lát băm và mẹo tôi vừa đề cập:

...
undef @record{@record};
keys %record; # your record, now probably scrambled but at least deduped

Đối với những người so sánh: sub uniq{ my %seen; undef @seen{@_}; keys %seen; } Neat.
stevesliva

0

Hãy thử điều này, có vẻ như hàm uniq cần một danh sách được sắp xếp để hoạt động đúng.

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";

0

Sử dụng khái niệm khóa băm duy nhất:

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

Đầu ra: acbd

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.