2008-09-18 25 views
71

Hãy nói rằng tôi có một mảng, và tôi biết tôi sẽ làm rất nhiều "Liệu mảng có chứa X?" kiểm tra. Cách hiệu quả để làm điều này là biến mảng đó thành băm, trong đó các phím là các phần tử của mảng, và sau đó bạn chỉ có thể nói Trong Perl, làm cách nào để tạo một băm có khóa đến từ một mảng nhất định?

if($hash{X}) { ... }

Có cách nào dễ dàng để thực hiện chuyển đổi mảng-băm này không? Lý tưởng nhất, nó phải đủ linh hoạt để lấy một mảng ẩn danh và trả về một băm ẩn danh.

Trả lời

106
%hash = map { $_ => 1 } @array; 

Nó không phải là ngắn là "@hash {@array} = ..." giải pháp, nhưng những người yêu cầu băm và mảng đã được định nghĩa ở một nơi khác, trong khi một này có thể mất một mảng vô danh và trả về một băm ẩn danh.

Điều này thực hiện lấy từng phần tử trong mảng và ghép nối nó với "1". Khi danh sách các cặp (khóa, 1, khóa, 1, khóa 1) được gán cho một băm, các số lẻ được đánh số sẽ trở thành các khóa của băm và các số chẵn sẽ trở thành các giá trị tương ứng.

34
@hash{@keys} = undef; 

Cú pháp ở đây mà bạn đang đề cập đến băm với @ là một lát băm. Về cơ bản, chúng tôi đang nói $hash{$keys[0]} AND $hash{$keys[1]} AND $hash{$keys[2]} ... là danh sách ở phía bên tay trái của =, một giá trị và chúng tôi sẽ gán cho danh sách đó, thực sự đi vào băm và đặt giá trị cho tất cả các phím được đặt tên. Trong trường hợp này, tôi chỉ chỉ định một giá trị, để giá trị đó đi vào $hash{$keys[0]} và các mục nhập băm khác tất cả tự động sinh động (đi vào cuộc sống) với các giá trị không xác định. [Đề xuất ban đầu của tôi ở đây đã được đặt biểu thức = 1, điều này sẽ đặt một khóa thành 1 và những người khác thành undef. Tôi đã thay đổi nó cho sự nhất quán, nhưng như chúng ta sẽ thấy dưới đây, các giá trị chính xác không quan trọng.]

Khi bạn nhận ra rằng giá trị, biểu thức ở phía bên tay trái của =, là danh sách được xây dựng từ băm, sau đó nó sẽ bắt đầu làm cho một số ý nghĩa lý do tại sao chúng tôi đang sử dụng mà @. [Ngoại trừ tôi nghĩ điều này sẽ thay đổi trong Perl 6.]

Ý tưởng ở đây là bạn đang sử dụng hàm băm làm bộ. Điều quan trọng không phải là giá trị mà tôi chỉ định; nó chỉ là sự tồn tại của các phím. Vì vậy, những gì bạn muốn làm không phải là một cái gì đó như:

if ($hash{$key} == 1) # then key is in the hash 

thay vì:

if (exists $hash{$key}) # then key is in the set 

Nó thực sự hiệu quả hơn để chỉ cần chạy một kiểm tra hơn là bận tâm với giá trị trong băm exists, mặc dù đối với tôi điều quan trọng ở đây chỉ là khái niệm mà bạn đại diện cho một tập hợp chỉ với các khóa của băm. Ngoài ra, ai đó đã chỉ ra rằng bằng cách sử dụng undef làm giá trị ở đây, chúng tôi sẽ tiêu thụ không gian lưu trữ ít hơn chúng tôi sẽ chỉ định một giá trị. (Và cũng tạo ra ít nhầm lẫn hơn, vì giá trị không quan trọng, và giải pháp của tôi sẽ chỉ gán giá trị cho phần tử đầu tiên trong băm và để các giá trị khác undef và một số giải pháp khác đang chuyển cartwheel để xây dựng một mảng giá trị vào băm, hoàn toàn lãng phí công sức).

+1

này một là một lợi thế trong khác vì nó không tạo danh sách tạm thời để khởi tạo băm. Điều này sẽ nhanh hơn và tiêu thụ ít bộ nhớ hơn. –

+0

Điều này không hoạt động khi được kiểm tra: kiểm tra.pl: @arr = ("foo", "bar", "baz"); @hash {@arr} = 1; lỗi cú pháp tại dòng test.pl 2, gần "@hash {" – Frosty

+1

Frosty: Trước tiên bạn phải khai báo "băm% của tôi", sau đó thực hiện "@hash {@arr} = 1" (không "của tôi"). –

2

giải pháp Raldi có thể được thắt chặt đến này ('=>' từ bản gốc là không cần thiết):

my %hash = map { $_,1 } @array; 

Kỹ thuật này cũng có thể được sử dụng để chuyển danh sách văn bản vào bảng băm:

my %hash = map { $_,1 } split(",",$line) 

Ngoài ra nếu bạn có một dòng của các giá trị như thế này: "foo = 1, thanh = 2, baz = 3" bạn có thể làm điều này:

my %hash = map { split("=",$_) } split(",",$line); 

[EDIT để bao gồm]


Một giải pháp được cung cấp (trong đó có hai dòng) là:

my %hash; 
#The values in %hash can only be accessed by doing exists($hash{$key}) 
#The assignment only works with '= undef;' and will not work properly with '= 1;' 
#if you do '= 1;' only the hash key of $array[0] will be set to 1; 
@hash{@array} = undef; 
+1

Sự khác biệt giữa $ _ => 1 và $ _, 1 là hoàn toàn theo kiểu. Cá nhân tôi thích => vì nó dường như chỉ ra liên kết khóa/giá trị rõ ràng hơn. Giải pháp @hash {@array} = 1 của bạn không hoạt động. Chỉ một trong các giá trị (giá trị được liên kết với khóa đầu tiên trong @array) được đặt thành 1. –

+0

Cảm ơn bạn đã làm rõ. Tôi đã chỉnh sửa câu trả lời. – Frosty

38
@hash{@array} = (1) x @array; 

Đó là một lát băm, một danh sách các giá trị từ băm, vì vậy nó được danh sách-y @ ở phía trước.

Từ the docs:

Nếu bạn đang nhầm lẫn về việc tại sao bạn sử dụng một '@' có trên một lát băm thay của một '%', nghĩ về nó như thế này. Loại khung hình (hình vuông hoặc quăn) chi phối cho dù đó là một mảng hay một băm đang được xem xét. Trên mặt khác , ký hiệu hàng đầu ('$' hoặc '@') trên mảng hoặc băm cho biết liệu bạn đang lấy lại một giá trị số ít (vô hướng) hoặc số nhiều (danh sách).

+1

Wow, tôi chưa bao giờ nghe nói về (hoặc nghĩ đến) cái đó. Cảm ơn! Tôi đang gặp sự cố khi hiểu cách hoạt động. Bạn có thể thêm một lời giải thích? Đặc biệt, làm thế nào bạn có thể lấy một băm có tên% hash và tham chiếu nó bằng một dấu @? – raldi

+1

raldi: đó là một lát băm, một danh sách các giá trị từ băm, vì vậy nó sẽ có danh sách-y @ ở phía trước. Xem http://perldoc.perl.org/perldata.html#Slices - đặc biệt là đoạn cuối cùng của phần – ysth

+0

Bạn nên thêm phần đó vào câu trả lời của mình! – raldi

2

Bạn cũng có thể sử dụng Perl6::Junction.

use Perl6::Junction qw'any'; 

my @arr = (1, 2, 3); 

if(any(@arr) == 1){ ... } 
+0

Nếu thực hiện nhiều lần cho một mảng lớn, điều đó có khả năng sẽ chậm hơn rất nhiều. – ysth

+0

Thực sự làm điều đó một lần chậm hơn rất nhiều. nó phải tạo ra một đối tượng. Sau đó ngay sau đó, nó sẽ phá hủy vật thể đó. Đây chỉ là một ví dụ về những gì có thể. –

5

Trong perl 5.10, có đóng cửa-to-magic ~~ điều hành:

sub invite_in { 
    my $vampires = [ qw(Angel Darla Spike Drusilla) ]; 
    return ($_[0] ~~ $vampires) ? 0 : 1 ; 
} 

Xem ở đây: http://dev.perl.org/perl5/news/2007/perl-5.10.0.html

+0

Nếu thực hiện nhiều lần cho một mảng lớn, điều đó có khả năng sẽ chậm hơn rất nhiều. – ysth

+1

Đó là "nhà điều hành khớp thông minh" :) –

14

Lưu ý rằng nếu gõ if (exists $hash{ key }) không phải là quá nhiều công việc cho bạn (mà tôi thích sử dụng vì vấn đề quan tâm thực sự là sự hiện diện của một khóa hơn là tính trung thực của giá trị của nó), sau đó bạn có thể sử dụng

@hash{@key} =(); 
ngắn gọn và ngọt ngào
7

Có một giả định ở đây, rằng cách hiệu quả nhất để thực hiện rất nhiều "Liệu mảng có chứa X?" kiểm tra là chuyển đổi mảng thành băm. Hiệu quả phụ thuộc vào tài nguyên khan hiếm, thường là thời gian nhưng đôi khi không gian và đôi khi là nỗ lực lập trình. Bạn đang ít nhất tăng gấp đôi bộ nhớ tiêu thụ bằng cách giữ một danh sách và một băm của danh sách xung quanh cùng một lúc. Thêm vào đó bạn đang viết code hơn ban đầu mà bạn sẽ cần phải kiểm tra, tài liệu, vv

Là một thay thế, nhìn vào các module Danh sách :: MoreUtils, đặc biệt là chức năng any(), none(), true()false().Tất cả họ đều có một khối như các điều kiện và một danh sách như là đối số, tương tự như map()grep():

print "At least one value undefined" if any { !defined($_) } @list;

Tôi chạy một thử nghiệm nhanh, tải trong một nửa số/usr/share/dict/words để một mảng (25000 từ), sau đó tìm mười một từ được chọn từ trên toàn bộ từ điển (mỗi từ thứ 5000) trong mảng, sử dụng cả phương pháp mảng-tới-băm và hàm any() từ Danh sách :: MoreUtils.

On Perl 5.8.8 xây dựng từ nguồn, phương pháp mảng-to-băm chạy gần như 1100x nhanh hơn so với phương pháp any() (1300x nhanh hơn dưới Ubuntu 6.06 của đóng gói Perl 5.8.7.)

Đó không phải là Tuy nhiên, câu chuyện đầy đủ - việc chuyển đổi mảng thành băm mất khoảng 0,04 giây trong trường hợp này làm giảm hiệu quả thời gian của phương pháp mảng thành băm nhanh hơn 1.5x-2x so với phương pháp any(). Vẫn tốt, nhưng không gần như là sao.

Cảm giác ruột của tôi là phương pháp mảng-băm sẽ đánh bại any() trong hầu hết các trường hợp, nhưng tôi sẽ cảm thấy tốt hơn nếu tôi có một số chỉ số vững chắc hơn (nhiều trường hợp kiểm tra, phân tích thống kê hợp lý) , có thể một số phân tích thuật toán lớn-O của từng phương pháp, v.v.) Tùy thuộc vào nhu cầu của bạn, Danh sách :: MoreUtils có thể là giải pháp tốt hơn; nó chắc chắn linh hoạt hơn và đòi hỏi ít mã hóa hơn. Hãy nhớ rằng, tối ưu hóa sớm là một tội lỗi ... :)

+0

Danh sách :: MoreUtils là một mẹo tuyệt vời, cảm ơn. – SquareCog

0

Bạn cũng có thể muốn xem Tie::IxHash, triển khai các mảng liên kết được sắp xếp. Điều đó sẽ cho phép bạn thực hiện cả hai loại tra cứu (băm và chỉ mục) trên một bản sao dữ liệu của bạn.

1

Nếu bạn thực hiện rất nhiều thao tác lý thuyết - bạn cũng có thể sử dụng Set::Scalar hoặc mô-đun tương tự. Sau đó, $s = Set::Scalar->new(@array) sẽ tạo Bộ cho bạn - và bạn có thể truy vấn nó bằng: $s->contains($m).

6

Tôi luôn luôn nghĩ rằng

foreach my $item (@array) { $hash{$item} = 1 } 

ít nhất là tốt đẹp và có thể đọc/duy trì.

1

Bạn có thể đặt mã vào chương trình con, nếu bạn không muốn gây ô nhiễm không gian tên của mình.

my $hash_ref = 
    sub{ 
    my %hash; 
    @hash{ @{[ qw'one two three' ]} } = undef; 
    return \%hash; 
    }->(); 

Hoặc thậm chí tốt hơn:

sub keylist(@){ 
    my %hash; 
    @hash{@_} = undef; 
    return \%hash; 
} 

my $hash_ref = keylist qw'one two three'; 

# or 

my @key_list = qw'one two three'; 
my $hash_ref = keylist @key_list; 

Nếu bạn thực sự muốn vượt qua một tham chiếu mảng:

sub keylist(\@){ 
    my %hash; 
    @hash{ @{$_[0]} } = undef if @_; 
    return \%hash; 
} 

my @key_list = qw'one two three'; 
my $hash_ref = keylist @key_list; 
+0

'% hash = map {$ _, undef} @ keylist' –

0
#!/usr/bin/perl -w 

use strict; 
use Data::Dumper; 

my @a = qw(5 8 2 5 4 8 9); 
my @b = qw(7 6 5 4 3 2 1); 
my $h = {}; 

@{$h}{@a} = @b; 

print Dumper($h); 

đưa ra (lưu ý lặp đi lặp lại các phím có được giá trị ở vị trí cao nhất trong mảng - tức là 8-> 2 và không 6)

$VAR1 = { 
      '8' => '2', 
      '4' => '3', 
      '9' => '1', 
      '2' => '5', 
      '5' => '4' 
     }; 
+0

Một hasref có vẻ nhiều hơn một chút bị thổi phồng ở đây. – bobbogo

2

Cũng đáng chú ý cho đầy đủ, phương pháp thông thường của tôi để làm điều này với 2 mảng cùng một độ dài @keys@vals mà bạn muốn là một hash ...

my %hash = map { $keys[$_] => $vals[$_] } ([email protected]);

+4

Thành ngữ thông thường cho '@ keys-1' là' $ # keys'. –

+0

@StefanMajewsky Tôi chưa từng thấy cái này thực sự được sử dụng trong một thời gian. Tôi tránh xa điều đó - nó xấu xí. –

Các vấn đề liên quan