Trong Perl, làm cách nào để tạo một băm có các khóa đến từ một mảng nhất định?


80

Giả sử tôi có một mảng và tôi biết mình sẽ làm rất nhiều câu hỏi "Mảng có chứa X không?" Séc. Cách hiệu quả để làm điều này là biến mảng đó thành một hàm băm, trong đó các khóa là các phần tử của mảng và sau đó bạn chỉ cần nói

if ($ băm {X}) {...}

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

Câu trả lời:


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

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

Điều này làm là 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) này được gán cho một hàm băm, các cặp được đánh số lẻ trở thành khóa của hàm băm và các cặp được đánh số chẵn trở thành các giá trị tương ứng.


43
 @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ừ các tài liệu :

Nếu bạn bối rối về lý do tại sao bạn sử dụng '@' ở đó trên một lát băm thay vì '%', hãy nghĩ về nó như thế này. Loại dấu ngoặc nhọn (vuông hoặc xoăn) điều chỉnh việc xem xét nó là một mảng hay một hàm băm. Mặt khác, ký hiệu hàng đầu ('$' hoặc '@') trên mảng hoặc hàm băm cho biết bạn đang nhận lại giá trị số ít (vô hướng) hay số nhiều (danh sách).


1
Chà, tôi chưa bao giờ nghe nói về (hoặc nghĩ đến) cái đó. Cảm ơn! Tôi khó hiểu cách nó 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% băm và tham chiếu đến nó bằng dấu @?
raldi 19-08

2
raldi: nó là một lát cắ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. Xem perldoc.perl.org/perldata.html#Slices - đặc biệt là đoạn cuối cùng của phần
ysth

Bạn nên thêm điều đó vào câu trả lời của bạn!
raldi 21/09/08

Bạn cũng có thể giải thích về RHS? Cảm ơn.
Susheel Javadi

1
(danh sách) x $ number sao chép danh sách $ number lần. Sử dụng một mảng trong ngữ cảnh vô hướng trả về số phần tử, vì vậy (1) x @array là danh sách các 1 có cùng độ dài với @array.
moritz

39
@hash{@keys} = undef;

Cú pháp ở đây mà bạn đang đề cập đến hàm băm với an @là một lát cắt băm. Về cơ bản chúng ta đang nói $hash{$keys[0]}$hash{$keys[1]}$hash{$keys[2]}... là một danh sách ở bên trái của dấu =, một giá trị và chúng ta đang gán cho danh sách đó, danh sách này thực sự đi vào băm và đặt giá trị cho tất cả các khóa đượ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 đều tự động vivify (hiện thực hóa) với các giá trị không xác định. [Đề xuất ban đầu của tôi ở đây là đặt biểu thức = 1, sẽ đặt một khóa đó thành 1 và các khóa khác thành undef. Tôi đã thay đổi nó để nhất quán, nhưng như chúng ta sẽ thấy bên dưới, các giá trị chính xác không quan trọng.]

Khi bạn nhận ra rằng lvalue, biểu thức ở bên trái của dấu =, là một danh sách được xây dựng từ hàm băm, thì bạn sẽ bắt đầu hiểu tại sao chúng ta sử dụng nó @. [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 như một tập hợp. Điều quan trọng không phải là giá trị mà tôi đang ấn đị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à:

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

thay thế:

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

Thực sự hiệu quả hơn nếu chỉ chạy một existskiểm tra hơn là bận tâm đến giá trị trong hàm băm, mặc dù đối với tôi điều quan trọng ở đây chỉ là khái niệm rằng bạn đang đại diện cho một tập hợp chỉ với các khóa của hàm băm. Ngoài ra, ai đó đã chỉ ra rằng bằng cách sử dụng undeflàm giá trị ở đây, chúng ta sẽ sử dụng ít không gian lưu trữ hơn so với việc 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 hàm băm và để lại các phần tử khác undef, và một số giải pháp khác đang biến cartwheels để xây dựng một mảng giá trị băm; hoàn toàn lãng phí nỗ lực).


1
Cái này thích hợp hơn cái kia vì nó không tạo danh sách tạm thời để khởi tạo hàm băm. Điều này sẽ nhanh hơn và tiêu tốn ít bộ nhớ hơn.
Leon Timmermans 18-08

1
Frosty: Trước tiên, bạn phải khai báo "my% hash", sau đó khai báo "@hash {@arr} = 1" (không có "my").
Michael Carman

8
= (), không = undef, chỉ để nhất quán trong việc sử dụng ngầm định undef cho tất cả các giá trị, không chỉ tất cả sau giá trị đầu tiên. (Như đã trình bày trong những nhận xét này, quá dễ dàng để nhìn thấy undefvà nghĩ rằng nó chỉ có thể được thay đổi thành 1 và ảnh hưởng đến tất cả các giá trị băm.)
ysth 19/09/08

2
Vì các giá trị kết thúc là "undef" ở đây (và có lẽ không phải vì lý do bạn nghĩ - như ysth đã chỉ ra), bạn không thể chỉ sử dụng hàm băm trong mã như "if ($ hash {$ value})". Bạn cần "nếu (tồn tại $ băm {$ value})".
Dave Cross

2
Sẽ rất tuyệt nếu bạn chỉnh sửa câu trả lời của mình để chỉ ra rằng nó cần được sử dụng với tồn tại, tồn tại hiệu quả hơn việc kiểm tra độ tin cậy bằng cách thực sự tải giá trị băm và undef đó chiếm ít không gian hơn 1
bhollis

16

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

@hash{@key} = ();

8

Tôi luôn nghĩ rằng

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

ít nhất là đẹp và dễ đọc / có thể bảo trì.


7

Có một giả thiết ở đây, rằng cách hiệu quả nhất để thực hiện nhiều câu hỏi "Mảng có chứa X không?" kiểm tra là chuyển đổi mảng thành một hàm băm. Hiệu quả phụ thuộc vào nguồn tài nguyên khan hiếm, thường là thời gian nhưng đôi khi là không gian và đôi khi là nỗ lực của lập trình viên. Bạn đang tăng ít nhất gấp đôi bộ nhớ được 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 đồng thời. Thêm vào đó, bạn đang viết nhiều mã gốc hơn mà bạn sẽ cần kiểm tra, lập tài liệu, v.v.

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ả chúng đều lấy một khối làm điều kiện và một danh sách làm đối số, tương tự như map()grep():

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

Tôi đã chạy thử nghiệm nhanh, tải một nửa / usr / share / dict / words vào một mảng (25000 từ), sau đó tìm kiếm 11 từ được chọn từ toàn bộ từ điển (mỗi từ thứ 5000) trong mảng, sử dụng cả mảng -to-hash method và any()hàm từ List :: MoreUtils.

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

Tuy nhiên, đó không phải là câu chuyện đầy đủ - quá trình chuyển đổi mảng thành hàm băm mất khoảng 0,04 giây, trong trường hợp này sẽ giết chết hiệu quả thời gian của phương thức mảng thành hàm băm nhanh hơn any()phương pháp này gấp 1,5 lần . Vẫn tốt, nhưng gần như không xuất sắc.

Cảm giác ruột của tôi là phương pháp mảng thành 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 rất nhiều nếu tôi có một số chỉ số chắc chắn hơn (rất nhiều trường hợp thử nghiệm, phân tích thống kê tốt, có thể là một số lớn- O phân tích thuật toán của từng phương pháp, v.v.) Tùy thuộc vào nhu cầu của bạn, List :: MoreUtils có thể là giải pháp tốt hơn; nó chắc chắn linh hoạt hơn và ít yêu cầu mã hóa hơn. Hãy nhớ rằng, tối ưu hóa quá sớm là một tội lỗi ... :)


Điều này không trả lời câu hỏi. Nó cũng bỏ sót điểm ... chuyển đổi mảng thành băm chỉ xảy ra một lần ... tổng cộng 0,04 giây (năm 2008) được thêm vào thời gian chạy của chương trình, trong khi việc tra cứu diễn ra nhiều lần.
Jim Balter

2
Tôi đã cố gắng giải quyết vấn đề cơ bản chứ không chỉ trả lời câu hỏi. List::MoreUtilscó thể là một phương pháp thích hợp hoặc không, tùy thuộc vào trường hợp sử dụng. Trường hợp sử dụng của bạn có thể có nhiều lần tra cứu; những người khác có thể không. Vấn đề là cả chuyển đổi mảng thành băm và List::MoreUtilsgiải quyết vấn đề cơ bản của việc xác định thành viên; biết nhiều cách tiếp cận cho phép bạn chọn phương pháp tốt nhất cho trường hợp sử dụng cụ thể của mình.
arclight


5

Cũng cần lưu ý về tính hoàn chỉnh, phương pháp thông thường của tôi để thực hiện việc này với 2 mảng có độ dài như nhau @keys@valsbạn muốn là một hàm băm ...

my %hash = map { $keys[$_] => $vals[$_] } (0..@keys-1);


4
Các thành ngữ thông thường cho @keys-1$#keys.
Stefan Majewsky

@StefanMajewsky Tôi đã không thấy cái đó thực sự được sử dụng trong một thời gian. Bản thân tôi tránh xa nó - nó xấu xí.
Tamzin Blake

3

Giải pháp của Raldi có thể được thắt chặt đến mức 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 thành băm:

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

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

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

[CHỈNH SỬA để đưa vào]


Một giải pháp khác được cung cấp (mất 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 hoàn toàn là phong cách. 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
Dave Cross

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 ){ ... }

1
Nếu thực hiện nhiều lần cho một mảng lớn, điều đó có thể sẽ chậm hơn rất nhiều.
ysth 19/09/08

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

1

Nếu bạn thực hiện nhiều phép toán lý thuyết tập hợp - 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ẽ xây dựng Set cho bạn - và bạn có thể truy vấn nó với: $s->contains($m).


1

Bạn có thể đặt mã vào một chương trình con, nếu bạn không muốn làm ô 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 chuyển 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;

%hash = map{ $_, undef } @keylist
Brad Gilbert

1
#!/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);

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

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

Một hasref có vẻ hơi bị thổi phồng ở đây.
bobbogo

0

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

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.