【问题标题】:Find the index of the Nth occurrence of a character in a string查找字符串中第 N 次出现的字符的索引
【发布时间】:2016-11-21 10:21:27
【问题描述】:

我发现 indexrindex 用于查找字符串中字符(或子字符串)的第一次或最后一次出现。我也知道他们有一个offset,可以用来从某个索引开始。

我想知道是否有一种简单的方法可以找到字符串中第 N 次出现的字符或子字符串的索引。我不想用正则表达式来做,也不想写一个循环来重复调用 index 并带有偏移量。

编辑:我没有很好地说明限制。我说“无循环”的原因是我正在寻找一种内置的方法来做到这一点,它存在于许多语言中。

【问题讨论】:

  • 为什么要使用任意的“无循环”/“无正则表达式”解决方案?简单的解决方案使用循环。事实上,任何合理的解决方案都可能涉及循环。
  • 您的限制有点极端和随意。为什么要避免正则表达式,以及循环有什么问题?我希望您意识到任何解决方案都必须涉及循环,无论您是否可以在 Perl 代码中看到它?没有其他方法可以计算字符的出现次数。
  • 没有内置的方法可以做到这一点,您需要自己编写。其他提供方法的语言只是对你隐藏循环。
  • 之所以没有内置的方法是因为 Perl 有正则表达式,这是完成这项工作的正确工具
  • 我很好奇,什么语言有内置的功能来查找字符串中第 n 次出现的字符或子字符串?

标签: string perl


【解决方案1】:

一种可能的实现方式:

use strict; 
use warnings; 

use feature qw(say);

my $string    = 'the quick brown fox jumped over the lazy dog';
my $substring = 'o';
my $n         = 4;

sub nth_index {
   my ($string, $substring, $n) = @_;

   my ($times, $index) = (0, 0);
   while ( $times < $n && $index != -1 ) {
      $index = index(
         $string, 
         $substring, 
         $times == 0 
            ? 0 
            : $index + length($substring),
      );
      $times++;
   }

   return $index; 
}

say nth_index($string, $substring, $n); # 42

【讨论】:

  • 我不确定这是否比基于正则表达式的解决方案更容易维护。
  • 以什么方式?这是一个非常基本的字符串匹配算法
  • 这是编写index 迭代的一种极大扩展方式。你有两倍多的变量。看看我自己的解决方案。
  • @Borodin 我很困惑,我有两个$times$index。您还有两个$i$len(我真的应该预先计算长度)。您指的是用于创建测试用例的变量吗?
  • @HunterMcMillen:我选择预先计算子字符串的长度以避免在每次找到匹配项时调用length,但这不是必需的。我的观点是,您同时拥有$n(这是参数的副本,因此可能会被修改)以及$times,这是不必要的。很大程度上是因为布局,我发现你的代码有一段时间很难掌握。
【解决方案2】:

以下是我如何解决问题的两个示例

子程序nth_index1 使用index,而nth_index2 使用正则表达式。两者都涉及循环,因为任何解决方案都必须这样做

我相信正则表达式解决方案更具可读性,而index 解决方案可能要快一些。但它们都非常快,几乎不可能造成瓶颈,而且可读性始终是最重要的

use strict; 
use warnings 'all';

my $s    = 'the quick brown fox jumps over the lazy dog';
my $ss   = 'o';

for my $n ( 1 .. 4 ) {
    printf "%d %d\n",
        nth_index1($s, $ss, $n),
        nth_index2($s, $ss, $n);
}


sub nth_index1 {
   my ($s, $ss, $n) = @_;

   my $i;
   my $len = length $ss;

   while ( $n-- ) {
      $i = index($s, $ss, $i ? $i + $len : 0 );
      return if $i < 0;
   }

   $i; 
}


sub nth_index2 {
   my ($s, $ss, $n) = @_;

   while ( $s =~ /$ss/g ) {
        return $-[0] unless --$n;
   }

   return;
}

输出

12 12
17 17
26 26
41 41

【讨论】:

    【解决方案3】:

    如上所述,没有内置的。这里有几种方法,使用splitindex 和正则表达式。

    use warnings;
    use strict;
    use feature qw(say);
    
    my $str = "Xab_ab_ab_ab_";  # 'Xab_ab';  # test failed (3) matches
    my $N = 3;  
    
    foreach my $patt qw(a ab c) {      
      say "Find index of occurrence $N of |$patt| in: |$str|";
      say "index: ", ( ind_Nth_match_1($str, $patt, $N) // "no $N matches" ); #/
      say "split: ", ( ind_Nth_match_2($str, $patt, $N) // "no $N matches" ); #/
      say "regex: ", ( ind_Nth_match_3($str, $patt, $N) // "no $N matches" ); #/
    }
    
    sub ind_Nth_match_1 {
        my ($str, $patt, $N) = @_; 
        my ($pos, $cnt) = (0, 0); 
        while ($pos = index($str, $patt, $pos) + 1) {  # != 0
            return $pos-1  if ++$cnt == $N; 
        }
        return;
    }
    
    sub ind_Nth_match_2 {
        my ($str, $patt, $N) = @_; 
        my @toks = split /($patt)/, $str; 
        return if @toks < 2*$N;
        return length( join '', @toks[0..2*$N-1] ) - length($patt);
    }
    
    sub ind_Nth_match_3 {
        my ($str, $patt, $N) = @_; 
        my $cnt = 0;
        while ($str =~ m/$patt/g) {
            return $-[0]  if ++$cnt == $N; 
        }
    }
    

    打印出来

    查找 |a| 的出现 3 的索引在:|Xab_ab_ab_ab_| 指数:7 分裂:7 正则表达式:7 查找 |ab| 的出现索引 3在:|Xab_ab_ab_ab_| 指数:7 分裂:7 正则表达式:7 查找 |c| 的出现索引 3在:|Xab_ab_ab_ab_| 索引:没有 3 个匹配项 分裂:没有 3 场比赛 正则表达式:没有 3 个匹配项

    注意事项

    • split 中,每个定界符也会在输出列表中返回,并捕获/($patt)/,以便更简单地估计length。因此我们计算2*$N(然后计算-1)。

    • 在正则表达式中,@- array 使用 @LAST_MATCH_START 表示最后一次成功匹配的位置。这里 while 中标量上下文中的 /g 使它在重复执行中从一个匹配跳到下一个匹配,$-[0] 给出最后一个(前一个)这样的匹配的起始位置。

    • 如果不需要$N 匹配,则潜艇返回undef,包括根本没有匹配。

    感谢Borodin 来自潜艇的return 的cmets 以及使用@- 而不是@+

    【讨论】:

    • 如果length($ch) 始终为1,则$+[0] - 1$-[0] 相同。此外,return 0 不建议作为错误返回,因为如果 $N 为 1,它是一个有效索引
    • @Borodin 非常感谢,已更正(连同其他更新)。
    • 不应该是return if @toks &lt;= $N 或类似的吗?
    【解决方案4】:

    (此答案不能回答您的问题,但可以帮助您在接受正则表达式解决方案的道路上前进。)

    您要求位置的数字索引的方式,听起来就像您正在考虑在获得该数字后从字符串中提取数据,采用 C 程序员可能会这样做的方式。

    例如,假设你有字符串

    my $str = "My daddy left home when I was three and he didn't leave much for ma and me";
    

    并且您想要提取直到单词“and”的第一个实例的所有数据。这是你可以做到的方式,这是一种使用 Perl 的 C 方式。

    my $pos = find_index_of_first_occurrence( $str, 'and' );
    # Note that find_index_of_first_occurrence() is a hypothetical function.
    print substr( $str, 0, $pos );
    # Prints "My daddy left home when I was three "
    

    在 Perl 中使用正则表达式的方式要简单得多。

    $str =~ /^(.*?)and/;
    print $1;
    

    使用正则表达式,您可以在一个操作中组合搜索字符串和提取数据。 (注意,为了简单起见,两个代码sn-ps都忽略了根本找不到“and”的情况)

    我知道你对正则表达式还不是很了解,而且一开始正则表达式可能会让人望而生畏,但如果你想在这门语言上取得成功,你需要将它们理解为学习 Perl 的一部分。

    【讨论】:

      猜你喜欢
      • 2011-02-04
      • 2010-12-25
      • 1970-01-01
      • 2010-09-16
      • 1970-01-01
      • 1970-01-01
      • 2013-09-26
      相关资源
      最近更新 更多