Perl中如何快速统计字符串中连续1的最大个数及总数量?
Great question! Let's tackle this efficiently—since you're dealing with huge strings (525k chars) and lots of them, performance is key.
First, let's confirm: your use of tr/1// for counting total 1s is already the best possible choice in Perl. tr is implemented at a low level, with zero regex engine overhead, so it's unbeatable for single-character counting. No need to change that part.
For the longest consecutive 1s, your original regex replace approach works but modifying the string is unnecessary (and adds overhead for large inputs). Here are two cleaner, faster approaches that avoid altering your input:
Approach 1: Global Regex Match (Clean & Fast)
This scans the string once with a global regex to find all runs of 1s, tracking the longest one:
my $str = "000000001111111111000000011111111111111111111111111111111"; # Total 1s (unchanged, still optimal) my $total_ones = $str =~ tr/1//; # Longest consecutive 1s my $max_consec = 0; while ($str =~ /(1+)/g) { my $current_len = length $1; $max_consec = $current_len if $current_len > $max_consec; } print "Total 1s: $total_ones\n"; print "Longest consecutive 1s: $max_consec\n";
- No string modification, so no extra memory copies for large inputs.
- The
gmodifier makes the regex engine scan the string in one pass, finding all runs of 1s efficiently.
Approach 2: Split on Zeros (Perfect for Your "More 0s Than 1s" Scenario)
Since your strings have way more 0s than 1s, splitting on sequences of 0s gives you exactly the runs of 1s (plus empty strings for leading/trailing 0s, which we can ignore):
my $max_consec = 0; foreach my $block (split /0+/, $str) { my $block_len = length $block; $max_consec = $block_len if $block_len > $max_consec; }
- This skips over all the long stretches of 0s directly, focusing only on the parts that matter (the 1s blocks). In practice, this can be slightly faster than the regex approach for your specific data distribution.
Performance Comparison
Let's update your benchmark to include these methods. Here's what you might see (results vary by Perl version and hardware, but trends hold):
use strict; use warnings; use Benchmark ':all'; use String::Random; my $test_regex = '[0][0][0][0][0][0-1][0-1][0-1][0-1][0-1]' x 52560; my $pass = String::Random->new; my $string = $pass->randregex($test_regex); cmpthese(-1, { regex_global => sub { my $max = 0; while ($string =~ /(1+)/g) { my $len = length($1); $max = $len if $len > $max; } }, split_method => sub { my $max = 0; foreach my $block (split /0+/, $string) { my $len = length $block; $max = $len if $len > $max; } }, original_hack => sub { my $match = ""; while ($string =~ /(${match}1+)/g) { $match = $1; } length $match } });
Typical output will show regex_global and split_method outperforming original_hack by a wide margin—your hack approach is redundant because it forces the regex engine to re-scan for longer runs each time, instead of finding all runs in one pass.
Key Takeaways
- Stick with
tr/1//for total counts: It's Perl's fastest way to count single characters. - Avoid modifying the input string: String mutations (like
s///) add unnecessary memory overhead for large strings. - Choose between regex or split based on data: For your "more 0s" case, split is slightly better; for balanced data, the global regex is just as fast and maybe more readable.
内容的提问来源于stack exchange,提问作者Mark Arnold

