基于字符串位置进行替换

7
使用正则表达式是否有一种方法可以替换字符串中的字符,这些字符是基于位置的?
例如,我的一个项目中的重写规则之一是“如果o是倒数第二个元音字母且编号为偶数(从左到右计数),则用ö替换o。”
因此,例如:
- heabatoik将变成heabatöik(o是倒数第二个元音字母,也是第四个元音字母) - habatoik不会改变(o是倒数第二个元音字母,但是是第三个元音字母)
使用preg_replace在PHP中是否可以实现这一点?
4个回答

8

从主题字符串的开头开始,您希望匹配2n + 1个元音字母,后面跟着一个 o,但仅当o后面恰好跟着另外一个元音字母时:

$str = preg_replace(
  '/^((?:(?:[^aeiou]*[aeiou]){2})*)' .  # 2n vowels, n >= 0
    '([^aeiou]*[aeiou][^aeiou]*)' .     # odd-numbered vowel
    'o' .                               # even-numbered vowel is o
    '(?=[^aeiou]*[aeiou][^aeiou]*$)/',  # exactly one more vowel
  '$1$2ö',
  'heaeafesebatoik');

如果要对奇数 o 进行相同的操作,需要匹配 2n 个前导元音而不是 2n+1 个:

$str = preg_replace(
  '/^((?:(?:[^aeiou]*[aeiou]){2})*)' .  # 2n vowels, n >= 0
    '([^aeiou]*)' .                     # followed by non-vowels
    'o' .                               # odd-numbered vowel is o
    '(?=[^aeiou]*[aeiou][^aeiou]*$)/',  # exactly one more vowel
  '$1$2ö',
  'habatoik');

如果没有匹配项,则不执行替换操作,因此如果您想要按顺序运行它们,那么运行它们是安全的。

1
为什么在最后的 [^aeiou]+ 中使用 + 而不是 * - Bart Kiers
我以为是我忽略了什么! :) - Bart Kiers
1
+1 的风格不错,但是说真的,我永远不想维护那个。 - Matteo Riva
1
谢谢,请问如果输入字符串更长的话,为什么字符串的第一部分会被切断?例如 heaeafesebatoik 给出的结果是 fesebatöik - Chris
谢谢,如果我想对奇数进行操作而不与偶数正则表达式冲突,我该怎么做? - Chris

1

您可以使用preg_match_all将字符串分割为元音/非元音部分并进行处理。

例如:

preg_match_all("/(([aeiou])|([^aeiou]+)*/",
    $in,
    $out, PREG_PATTERN_ORDER);

根据您的具体需求,您可能需要修改正则表达式中()*+?的位置。

1

我想在Schmitt的基础上进行扩展。(我没有足够的积分来添加评论,我不想抢他的风头)。我会使用标志PREG_OFFSET_CAPTURE,因为它不仅返回元音字母,还返回它们的位置。这是我的解决方案:

const LETTER = 1;
const LOCATION = 2
$string = 'heabatoik'

preg_match_all('/[aeiou]/', $string, $in, $out, PREG_OFFSET_CAPTURE);

$lastElement = count($out) - 1; // -1 for last element index based 0

//if second last letter location is even
//and second last letter is beside last letter
if ($out[$lastElement - 1][LOCATION] % 2 == 0 &&
    $out[$lastElement - 1][LOCATION] + 1 == $out[$lastElement][LOCATION])
       substr_replace($string, 'ö', $out[$lastElement - 1][LOCATION]);

注意:

print_r(preg_match_all('/[aeiou]/', 'heabatoik', $in, $out, PREG_OFFSET_CAPTURE));
Array
(
    [0] => Array
        (
            [0] => Array
                (
                    [0] => e
                    [1] => 1
                )

            [1] => Array
                (
                    [0] => a
                    [1] => 2
                )

            [2] => Array
                (
                    [0] => a
                    [1] => 4
                )

            [3] => Array
                (
                    [0] => o
                    [1] => 6
                )

            [4] => Array
                (
                    [0] => i
                    [1] => 7
                )
        )
)

0

这是我会做的方式:

$str = 'heabatoik';

$vowels = preg_replace('#[^aeiou]+#i', '', $str);
$length = strlen($vowels);
if ( $length % 2 && $vowels[$length - 2] == 'o' ) {
    $str = preg_replace('#o([^o]+)$#', 'ö$1', $str);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接