Найти индекс N-го вхождения символа в строку

string perl

1210 просмотра

4 ответа

8753 Репутация автора

Я нашел indexи rindexдля нахождения первого или последнего вхождения символа (или подстроки) в строке. Я также знаю, что у них есть, offsetчто может быть использовано для начала с определенного индекса.

Я хочу знать, есть ли простой способ найти индекс N-го вхождения символа или подстроки в строке. Я предпочел бы не делать это с регулярным выражением и предпочел бы не писать цикл, который просто многократно вызывает indexсо смещением.

РЕДАКТИРОВАТЬ: я не утверждал ограничение хорошо. Причина, по которой я сказал «без цикла», заключается в том, что я ищу встроенный способ сделать это, который существует во многих языках.

Автор: ewok Источник Размещён: 18.07.2016 05:48

Ответы (4)


1 плюс

39274 Репутация автора

Одна возможная реализация:

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
Автор: Hunter McMillen Размещён: 18.07.2016 06:36

1 плюс

41680 Репутация автора

Как уже говорилось, для этого нет встроенного. Вот несколько способов, с помощью split, indexи регулярное выражение.

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; 
    }
}

Это печатает

Найти индекс вхождения 3 из | a | в: | Xab_ab_ab_ab_ |
индекс: 7
сплит: 7
регулярное выражение: 7
Найти индекс вхождения 3 из | ab | в: | Xab_ab_ab_ab_ |
индекс: 7
сплит: 7
регулярное выражение: 7
Найти индекс вхождения 3 из | c | в: | Xab_ab_ab_ab_ |
индекс: нет 3 совпадений
сплит: нет 3 матчей
регулярное выражение: нет 3 совпадений

Примечания

  • В splitкаждом разделителе также возвращается в выходной список, с захватом /($patt)/, для более простой lengthоценки. Таким образом мы считаем 2*$N(а затем и принимаем -1).

  • В регулярном выражении @- arrayиспользуется, @LAST_MATCH_STARTдля позиции последнего успешного матча. Здесь /gв скалярном контексте in whileон переходит от совпадения к следующему при повторных выполнениях и $-[0]задает начальную позицию последнего (предыдущего) такого совпадения.

  • Подводные лодки возвращаются, undefесли не требуется $Nникаких совпадений, в том числе вообще никаких совпадений.

Спасибо Бородину за комментарии returnот сабов и за использование @-вместо @+.

Автор: zdim Размещён: 18.07.2016 07:39

0 плюса

72274 Репутация автора

(Этот ответ не отвечает на ваш вопрос, но он поможет вам принять решение по регулярному выражению.)

То, как вы запрашиваете числовой индекс позиции, звучит так, как будто вы думаете об извлечении данных из строки, как только у вас будет это число, так, как это могли бы сделать программисты на Си.

Например, скажем, у вас была строка

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

и вы хотели извлечь все данные вплоть до первого экземпляра слова «и». Вот способ, которым вы могли бы сделать это, что является своего рода C-способом сделать это с помощью Perl.

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;

С помощью регулярных выражений вы объединяете поиск строки и извлечение данных в одной операции. (Обратите внимание, что оба фрагмента кода игнорируют случай отсутствия «и» для простоты)

Я понимаю, что вы еще не очень хорошо знаете регулярные выражения, и что регулярные выражения поначалу могут быть пугающими, но вам нужно будет понять их как часть изучения Perl, если вы собираетесь добиться успеха с языком.

Автор: Andy Lester Размещён: 18.07.2016 09:34

1 плюс

120875 Репутация автора

Вот два примера того, как бы я решил проблему

Подпрограмма 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
Автор: Borodin Размещён: 18.07.2016 10:01
Вопросы из категории :
32x32