2008-09-01 36 views
25

Gần đây tôi đã "cần" một hàm zip trong Perl 5 (trong khi tôi đang suy nghĩ về How do I calculate relative time?), tức là một hàm lấy hai danh sách và "nén" chúng lại với nhau thành một danh sách, xen kẽ các phần tử.Có một zip trang nhã để xen kẽ hai danh sách trong Perl 5 không?

(Pseudo) ví dụ:

@a=(1, 2, 3); 
@b=('apple', 'orange', 'grape'); 
zip @a, @b; # (1, 'apple', 2, 'orange', 3, 'grape'); 

Haskell has zip in the PreludePerl 6 has a zip operator xây dựng trong, nhưng làm thế nào để bạn làm điều đó một cách thanh lịch trong Perl 5?

+0

Mã zip của Haskell không phải là những gì bạn đang tìm kiếm: nó trả về danh sách các cặp tương ứng, không phải danh sách các phần tử xen kẽ. –

+0

Bạn nói đúng; Danh sách Haskell chứa các phần tử nếu một loại duy nhất. Tôi đã không suy nghĩ khi tôi gọi Haskell ở đây. – asjo

+2

Thường thì khi người ta nghĩ rằng họ muốn có một zip, nó là để tạo ra một băm từ hai danh sách. Trong trường hợp đó tốt hơn để sử dụng một lát băm. '@hash {@keys} = @ values'. Nếu đó không phải là trường hợp ở đây, thì xin lỗi vì tiếng ồn. –

Trả lời

36

Giả sử bạn có chính xác hai danh sách và họ là chính xác cùng độ dài, đây là một giải pháp ban đầu của Merlyn (Randal Schwartz), người gọi nó là phần châm Perlish:

sub zip2 { 
    my $p = @_/2; 
    return @_[ map { $_, $_ + $p } 0 .. $p - 1 ]; 
} 

gì xảy ra ở đây là cho một Danh sách 10 phần tử, trước tiên, chúng tôi tìm thấy điểm pivot ở giữa, trong trường hợp này là 5 và lưu nó trong $p. Sau đó chúng ta tạo một danh sách các chỉ số cho đến thời điểm đó, trong trường hợp này là 0 1 2 3 4. Tiếp theo chúng ta sử dụng map để ghép mỗi chỉ mục với một chỉ mục khác ở cùng khoảng cách với điểm pivot khi chỉ mục đầu tiên là từ đầu, cho chúng tôi (trong trường hợp này) 0 5 1 6 2 7 3 8 4 9. Sau đó, chúng tôi lấy một lát từ @_ bằng cách sử dụng đó làm danh sách các chỉ mục. Điều này có nghĩa là nếu 'a', 'b', 'c', 1, 2, 3 được chuyển đến zip2, nó sẽ trả về danh sách được sắp xếp lại thành 'a', 1, 'b', 2, 'c', 3.

này có thể được viết bằng một biểu thức duy nhất dọc theo các đường ysth của như vậy:

sub zip2 { @_[map { $_, $_ + @_/2 } 0..(@_/2 - 1)] } 

Cho dù bạn muốn sử dụng một trong hai biến thể phụ thuộc vào việc bạn có thể nhìn thấy mình nhớ cách họ làm việc, nhưng đối với tôi, đó là một tâm trí giãn nở.

+0

wow, rõ ràng và súc tích !!! –

+0

+++ 1 thông minh & ngắn – Viet

+0

tâm trí của tôi bị thổi bay! – Richard

27

Module List::MoreUtils có chức năng zip/lưới mà nên làm các trick:

use List::MoreUtils qw(zip); 

my @numbers = (1, 2, 3); 
my @fruit = ('apple', 'orange', 'grape'); 

my @zipped = zip @numbers, @fruit; 

Đây là nguồn gốc của các chức năng lưới:

sub mesh (\@\@;\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@) { 
    my $max = -1; 
    $max < $#$_ && ($max = $#$_) for @_; 

    map { my $ix = $_; map $_->[$ix], @_; } 0..$max; 
} 
+0

Tôi không biết làm thế nào tôi quản lý để bỏ qua mô-đun đó - cảm ơn! – asjo

+2

Những ký hiệu đã thoát là gì? – dreeves

+1

Nguyên mẫu, nói rằng phải mất hai đến 32 tham số mảng và phụ sẽ ngầm nhận chúng dưới dạng mảngrefref. – ysth

1
 
my @l1 = qw/1 2 3/; 
my @l2 = qw/7 8 9/; 
my @out; 
push @out, shift @l1, shift @l2 while (@l1 || @l2); 

Nếu danh sách là một chiều dài khác nhau, điều này sẽ đặt 'undef' trong các khe phụ nhưng bạn có thể dễ dàng khắc phục điều này nếu bạn không muốn làm điều này. Một cái gì đó như (@ l1 [0] & & shift @ l1) sẽ làm điều đó.

Hy vọng điều này sẽ hữu ích!

+1

Giải pháp tốt, tôi có lẽ đã bày tỏ sở thích của mình vì không sửa đổi hai danh sách đầu vào :-) – asjo

2

Algorithm::Loops thực sự tuyệt vời nếu bạn làm được nhiều điều như vậy.

mã riêng của tôi:

sub zip { @_[map $_&1 ? $_>>1 : ($_>>1)+($#_>>1), [email protected]_] } 
+0

Sử dụng các thay đổi bit có thể nhanh hơn trong C nhưng chỉ là obfuscation không cần thiết trong Perl. Tốt hơn được viết như vậy: @_ [bản đồ {$ _, $ _ + @ _/2} 0 .. (@ _/2 - 1)] Ngắn hơn, quá. –

+1

Nó không phải là một vấn đề cho câu hỏi ở đây, nhưng zip của tôi được thiết kế để làm việc cho số lẻ của các yếu tố quá. – ysth

0

này là hoàn toàn không phải là một giải pháp tao nhã, cũng không phải là giải pháp tốt nhất bởi bất kỳ căng của trí tưởng tượng. Nhưng nó rất thú vị!

package zip; 

sub TIEARRAY { 
    my ($class, @self) = @_; 
    bless \@self, $class; 
} 

sub FETCH { 
    my ($self, $index) = @_; 
    $self->[$index % @$self][$index/@$self]; 
} 

sub STORE { 
    my ($self, $index, $value) = @_; 
    $self->[$index % @$self][$index/@$self] = $value; 
} 

sub FETCHSIZE { 
    my ($self) = @_; 
    my $size = 0; 
    @$_ > $size and $size = @$_ for @$self; 
    $size * @$self; 
} 

sub CLEAR { 
    my ($self) = @_; 
    @$_ =() for @$self; 
} 

package main; 

my @a = qw(a b c d e f g); 
my @b = 1 .. 7; 

tie my @c, zip => \@a, \@b; 

print "@c\n"; # ==> a 1 b 2 c 3 d 4 e 5 f 6 g 7 

Làm thế nào để xử lý STORESIZE/PUSH/POP/SHIFT/UNSHIFT/SPLICE là một bài tập còn lại để người đọc.

10

Đối với mảng của cùng một chiều dài:

my @zipped = (@a, @b)[ map { $_, $_ + @a } (0 .. $#a) ]; 
+0

Giải pháp thực sự tốt đẹp. Phải mất nhiều thời gian để hiểu nó cho tôi. –

+3

Điều này có vấn đề đối với mảng có kích thước không bằng nhau. –

12

tôi tìm ra giải pháp sau đây đơn giản và dễ đọc:

@a = (1, 2, 3); 
@b = ('apple', 'orange', 'grape'); 
@zipped = map {($a[$_], $b[$_])} (0 .. $#a); 

Tôi tin rằng nó cũng nhanh hơn so với các giải pháp tạo mảng trong một sai lầm đặt hàng trước và sau đó sử dụng slice để sắp xếp lại hoặc giải pháp sửa đổi @a@b.

+1

Điều này có vấn đề đối với mảng có kích thước không bằng nhau. –

+1

@briandfoy nhận xét của bạn trợ giúp, nhưng bạn biết mình đã quên làm gì không? chỉ ra cái nào * làm * hoạt động cho các mảng có kích thước không bằng nhau. (Tôi đang mua một cái cần làm điều này) –

+0

Tôi không quên. Vấn đề đó phụ thuộc vào những gì bạn muốn làm với các yếu tố còn lại. Bạn nên hỏi một câu hỏi khác và chỉ rõ ràng buộc của bạn. –

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