2011-06-21 22 views
9

Có cách nào hiệu quả để thay thế một chuỗi các chuỗi sử dụng các giá trị từ một băm Perl không?Perl regex thay thế từ băm

Ví dụ,

$regex{foo} = "bar"; 
$regex{hello} = "world"; 
$regex{python} = "perl"; 

open(F, "myfile.txt"); 
while (<F>) { 
     foreach $key (keys %regex) { 
      s/$key/$regex{$key}/g; 
     } 
} 
close(F); 

Có cách nào để thực hiện được nêu trên trong Perl?

+1

Nếu vẫn thất bại, hãy thử 'eval' (http://perldoc.perl.org/functions/eval.html) – Nick

+1

@Nick, Đó là những lời khuyên tồi tệ hơn càng tốt, càng xa càng Tôi lo lắng. Làm thế nào nó có thể giúp !? – ikegami

+0

Biến thay thế không thể ở bất kỳ đâu trong mã perl - 'eval' cung cấp cho bạn khả năng mở rộng các biến trong chuỗi và sau đó thực thi chuỗi đó dưới dạng mã perl, ví dụ: eval "s/$ key/$ regex {$ key}/g" – Nick

Trả lời

4

Câu hỏi đầu tiên: bạn có chắc chắn rằng những gì bạn có là không hiệu quả?

Thứ hai, bước tiếp theo rõ ràng nhất sẽ được để kéo tất cả mọi thứ vào một regex duy nhất:

my $check = join '|', keys %regex; 

Và sau đó bạn có thể làm thay như:

s/($check)/$regex{$1}/g; 

này vẫn có thể được "chậm "với sự chồng chéo đầy đủ của các phím mà động cơ regex phải kiểm tra lại các chữ cái liên tục. Bạn có thể sử dụng một cái gì đó như Regexp::Optimizer để loại bỏ sự chồng chéo. Nhưng chi phí tối ưu hóa có thể nhiều hơn chi phí chỉ làm mọi thứ, tùy thuộc vào số lượng thay đổi (khóa/giá trị trong băm của bạn) và số lượng dòng bạn đang sửa đổi. Tối ưu hóa sớm--!

Lưu ý rằng, tất nhiên, mã ví dụ của bạn không làm bất cứ điều gì với văn bản sau khi thay thế. Nó sẽ không sửa đổi các tập tin tại chỗ, vì vậy tôi giả sử bạn đang xử lý một cách riêng biệt.

3

Xác định regexp khớp với bất kỳ phím nào.

$regex = join("|", map {quotemeta} keys %regex); 

Thay thế bất kỳ trận đấu nào là $regex bởi $regex{$1}.

s/($regex)/$regex{$1}/go; 

Bỏ qua o sửa đổi nếu $regex thay đổi trong khi thực hiện chương trình.

Lưu ý rằng nếu có phím mà là một tiền tố của một chìa khóa (ví dụ ffoo), nào đến trước trong regexp tham gia sẽ được xem như một trận đấu (ví dụ f|foo trận f nhưng foo|f trận foo trong foobar). Nếu điều đó có thể xảy ra, bạn có thể cần phải sắp xếp keys %regex theo đó bạn muốn thắng. (Nhờ ysth cho trỏ này ra.)

+2

Nếu bạn có các khóa như abc và abcd, điều quan trọng là sắp xếp bằng cách giảm độ dài:' map {quotemeta} sắp xếp {length ($ b) <=> length ($ a)} phím% regex' – ysth

+0

@ysth Cảm ơn, tôi chưa bao giờ nhận ra rằng Perl có chính sách đối sánh tận cùng bên trái, không phải là trận đấu dài nhất! – Gilles

1
perl -e '               \ 
      my %replace = (foo=>bar, hello=>world, python=>perl); \ 
      my $find = join "|", sort keys %replace;   \ 
      my $str  = "foo,hello,python";      \ 
      $str  =~ s/($find)/$replace{$1}/g;    \ 
      print "$str\n\n";          \ 
     ' 

Something bạn có thể muốn xem xét không được đi line-by-line của tập tin, nhưng thay vì xử lý toàn bộ tập tin cùng một lúc và sử dụng modifier /s trên của bạn regex cho chế độ một đường.

1

Những gì bạn có hiệu quả, vì vậy, không rõ yêu cầu của bạn là gì.

Một lần đánh bắt: Mã bạn đăng có thể gặp sự cố với thay thế kép tùy thuộc vào nội dung của %regex và/hoặc $_. Ví dụ:

my %regex = (
    foo => 'bar', 
    bar => 'foo', 
); 

Giải pháp là chuyển foreach vào mẫu, để nói.

my $pat = 
    join '|', 
    map quotemeta, # Convert text to regex patterns. 
    keys %regex; 

my $re = qr/$pat/; # Precompile for efficiency. 

my $qfn = 'myfile.txt' 
open(my $fh, '<', $qfn) or die "open: $qfn: $!"; 
while (<$fh>) { 
    s/($re)/$regex{$1}/g; 
    ... do something with $_ ... 
} 
+0

chu kỳ trong khi không phải là giải pháp! Bạn viết ở đâu? – cirne100

+0

@ cirne100, bạn chỉ định những gì bạn muốn làm với văn bản đã chỉnh sửa. Nếu bạn muốn viết nó ở đâu đó, hãy tiếp tục. – ikegami

1

Các bắt đầu:

#!/usr/bin/perl 
use strict; 
use Tie::File; 

my %tr=( 'foo' => 'bar', 
      #(...) 
     ); 
my $r =join("|", map {quotemeta} keys %tr); 
$r=qr|$r|; 

với các tập tin lớn sử dụng:

tie my @array,"Tie::File",$ARGV[0] || die; 
for (@array) { 
    s/($r)/$tr{$1}/g; 
} 
untie @array; 

với các tập tin nhỏ sử dụng:

open my $fh,'<',$ARGV[0] || die; 
local $/ = undef; 
my $t=<$fh>; 
close $fh; 
$t=~s/($r)/$tr{$1}/g; 
open $fh,'>',$ARGV[0] || die; 
print $fh $t; 
close $fh; 
3

Để chứng minh quan điểm của eval và cũng ra của tò mò, tôi chạy một số te sts với mã OP so với phương pháp $regex{$1} so với cách tiếp cận eval.

Trước hết, dường như có ít giá trị trong việc nhồi nhét mọi mã thông báo có thể có trong biểu thức đối sánh (token|token|...). Perl cần phải kiểm tra đối với tất cả các thẻ cùng một lúc - điều gây tranh cãi là hiệu quả hơn bao nhiêu so với việc kiểm tra mọi mã thông báo tại một thời điểm và thực hiện thay thế bằng một giá trị mã hóa cứng.

Thứ hai, thực hiện $regex{$1} nghĩa là khóa băm được trích xuất trên mọi kết quả phù hợp.

Dù sao, đây là một số số (chạy này trên dâu 5,12, với một file 4MB bộ 100K dòng):

  1. Cách tiếp cận $regex{$1} mất 6 giây (5 giây với/đi thay vì/g)
  2. cách tiếp cận tie mất 10 giây
  3. cách tiếp cận OP mất một chút dưới 1 giây (với/đi thay vì/g)
  4. Cách tiếp cận eval mất ít hơn 1 giây (nhanh hơn so với mã OP)

Đây là phương pháp eval:

$regex{foo} = "bar"; 
$regex{hello} = "world"; 
$regex{python} = "perl"; 
$regex{bartender} = "barista"; 

$s = <<HEADER; 
\$start = time; 
open(F, "myfile.txt"); 
while (<F>) { 
HEADER 

foreach $key (keys %regex) { 
    $s .= "s/$key/$regex{$key}\/go;\n" 
} 

$s .= <<FOOTER; 
print \$_; 
} 
close(F); 
print STDERR "Elapsed time (eval.pl): " . (time - \$start) . "\r\n"; 
FOOTER 

eval $s; 
+0

Điều đó thật thú vị, tôi đã không nghĩ rằng phương pháp '$ regex {$ 1}' quá chậm. Việc sử dụng 'Regexp :: Optimizer' có tạo sự khác biệt không? Thời gian thay đổi tùy thuộc vào số lượng khóa như thế nào? – Gilles

+0

@Giles, câu hỏi rất hay, rõ ràng - chưa kể đến nền tảng (cửa sổ) và phân phối perl có thể tạo sự khác biệt. Bất kỳ sự giúp đỡ nào về loại lược tả này đều được chào đón hơn - nó cũng sẽ tốt khi nghe điều gì đó từ OP - cái nào trong những cách tiếp cận này là hiệu quả nhất đối với môi trường của người đó. – Nick

0

Đây là một câu hỏi cũ, vì vậy tôi ngạc nhiên không người ta chưa gợi ý rõ ràng: biên dịch trước từng regexps (tức là các khóa băm).

$regex{qr/foo/} = 'bar'; 
$regex{qr/hello/} = 'world'; 
$regex{qr/python/} = 'perl'; 

open(F, "myfile.txt"); 
while (<F>) { 
     foreach $key (keys %regex) { 
      s/$key/$regex{$key}/g; 
     } 
} 
close(F); 

hoặc for (IMO) dễ đọc hơn:

%regex = (
    qr/foo/ => 'bar', 
    qr/hello/ => 'world', 
    qr/python/ => 'perl', 
); 

Nếu bạn biết rằng chỉ có thể có một trận đấu tốt mỗi dòng đầu vào sau đó bỏ qua regexps còn lại với last sau một trận đấu thành công cũng sẽ giúp nếu có rất nhiều chìa khóa. ví dụ. bên trong vòng lặp for:

s/$key/$regex{$key}/g && last;