2011-06-21 18 views
9

有沒有一種有效的方法來用一個Perl哈希中的值替換一堆字符串?來自hash的Perl正則表達式替代

例如,

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

有沒有辦法實現上述在Perl?

+1

如果這些方法都失敗,請嘗試'eval'(http://perldoc.perl.org/functions/eval.html) – Nick

+1

@Nick,那更糟糕的可能建議,儘量爲:for循環內我很關心。它怎麼可能有幫助!? – ikegami

+0

在Perl代碼的任何位置都無法替換變量--eval可讓您在字符串中擴展變量,然後以Perl代碼的形式執行該字符串。 eval「s/$ key/$ regex {$ key}/g」 – Nick

回答

4

第一個問題:你確定你有什麼是效率低下

其次,最明顯的下一步將是拉一切都變成一個單一的正則表達式:

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

然後你就可以做替代:

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

這仍然是「慢「在正則表達式引擎必須不斷重新檢查相同的字母的情況下,有足夠的鍵重疊。你可以使用類似Regexp::Optimizer的東西來消除重疊。但是,優化的成本可能不僅僅是做所有事情的成本,取決於有多少變化(散列中的鍵/值)以及您要修改的線數。過早優化 - !

請注意,當然,您的示例代碼在替換後沒有對文本做任何事情。它不會原地修改文件,所以我假設你正在分開處理。

3

定義匹配任何鍵的正則表達式。

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

通過$regex{$1}更換的$regex任何比賽。

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

省略該程序的執行過程中o改性劑如果$regex變化。

注意,如果有是另一個鍵(例如ffoo),以先到者爲準在加入正則表達式的前綴將被看作是一個匹配(例如f|foo匹配ffoo|f比賽foofoobar)密鑰。如果發生這種情況,您可能需要按照您想贏得的比賽來排序keys %regex。 (感謝ysth指出這一點。),你可能要考慮

+2

如果您有像abc和abcd這樣的按鍵,則通過減少長度進行排序很重要:map {quotemeta} sort {length($ b)<=> length a)}鍵%regex' – ysth

+0

@ysth謝謝,我從來沒有意識到Perl有最左邊的匹配策略,而不是最長的匹配! – 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";          \ 
     ' 

東西是不會行由行的文件,而是處理整個文件一次,並使用/s修改你的正則表達式用於單線模式。

1

你現在的工作原理是什麼,所以目前還不清楚你的要求是什麼。

One catch:您發佈的代碼可能存在雙取代問題,具體取決於%regex和/或$_的內容。例如,

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

解決的辦法是將foreach移入該模式,可以這麼說。

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

while循環不是解決方案!你在哪寫的? – cirne100

+0

@ cirne100,您可以指定您想要如何處理編輯的文本。如果你想寫在某個地方,請繼續。 – ikegami

1

的開始:

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

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

大文件使用:

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

小文件使用方法:

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

爲了證明eval,也出點好奇心,我跑了一些te ST的OP代碼與$regex{$1}方法比較,eval方法。

首先,填充(token|token|...)匹配表達式中的每個可能的標記似乎沒有什麼價值。 Perl需要一次檢查所有的令牌 - 這是多麼有效的問題,而不是簡單地一次檢查每個令牌並用硬編碼的值進行替換。

其次,做$regex{$1}意味着每個匹配都會提取散列映射關鍵字。

無論如何,這裏有一些數字(跑這對草莓5.12,具有100K線4MB的文件):

  1. $regex{$1}方法利用6秒(5秒/反潛/ g的)
  2. tie方法利用10秒
  3. 的OP方法利用有點低於1秒(有/去代替/克)
  4. eval方法利用小於1秒(比OP代碼更快)

這是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

這很有趣,我不希望'$ regex {$ 1}'方法太慢。使用'Regexp :: Optimizer'是否有所作爲?時間如何根據鍵的數量而改變? – Gilles

+0

@Giles,非常好的問題,顯然 - 更不用說平臺(Windows)和Perl分佈可能會有所作爲。任何關於這種分析的幫助都非常值得歡迎 - 從OP中聽到一些信息也很好 - 這些方法中哪一種方法在他/她的環境中效率最高。 – Nick

0

這是一個老問題了,所以我很驚訝沒有還沒有提出明顯的方法:預先編譯每個正則表達式(即散列鍵)。

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

或(IMO)獲得更好的可讀性:

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

如果你知道,只能有每個輸入行一個可能的匹配,那麼跳過與last剩餘的正則表達式匹配成功後,還將幫助如果有很多密鑰。例如

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