提取 SKU 值,可以是数字或字母数字,长度必须为 4 到 20 个字符

Extract SKU values which may be numeric or alphanumeric and must be 4 to 20 characters long

除了正则表达式,我愿意包含更多代码。

我正在编写一些代码来拍照,运行几个 Imagick 过滤器,然后 tesseractOCR 通过,以输出文本。

从该文本中,我使用带有 PHP 的正则表达式来提取 SKU(产品型号)并将结果输出到数组中,然后将其插入到 table.

一切都很好,除了我现在使用的表达方式:

\w[^a-z\s\/?!@#-$%^&*():;.,œ∑´®†¥¨ˆøπåß∂ƒ©˙∆˚¬Ω≈ç√∫˜µ≤≥]{4,20}

我仍然会取回一些仅包含字母的字符串。

最终目标:

-可能包含大写字母和数字的字符串,
- 仅包含数字的字符串,
-不只包含字母的字符串,
-不包含任何小写字母的字符串,
-这些字符串必须在 4-20 个字符之间

举个例子:

一个SKU可以是5209,也可以是WRE5472UFG5621.

在正则表达式大师出现之前,像我这样的懒人只会做两轮并保持简单。首先,匹配所有只有 A-Z0-9 的字符串(而不是制作大量的无列表或查找)。然后,使用 preg_grep()PREG_GREP_INVERT 标志来删除所有仅包含 A-Z 的字符串。最后,过滤唯一匹配项以消除重复噪音。

$str = '-9 Cycles 3 Temperature Levels Steam Sanitizet+ -Sensor Dry | ALSO AVAILABLE (PRICES MAY VARY) |- White - 1258843 - DVE45R6100W {+ Platinum - 1501 525 - DVE45R6100P desirable: 1258843 DVE45R6100W';

$wanted = [];

// First round: Get all A-Z, 0-9 substrings (if any)
if(preg_match_all('~\b[A-Z0-9]{6,24}\b~', $str, $matches)) {

    // Second round: Filter all that are A-Z only
    $wanted = preg_grep('~^[A-Z]+$~', $matches[0], PREG_GREP_INVERT);

    // And remove duplicates:
    $wanted = array_unique($wanted);
}

结果:

array(3) {
    [2] · string(7) "1258843"
    [3] · string(11) "DVE45R6100W"
    [4] · string(11) "DVE45R6100P"
}

请注意,我已将匹配长度增加到 {6,24},即使您说的是 4 个字符的匹配,因为您的示例字符串有 4 位数字的子字符串不在您的“理想”列表中。

编辑: 我已将 preg_match_all() 移动到包含剩余操作的条件构造中,并默认将 $wanted 设置为空数组.您可以方便地一次性 捕获匹配 评估是否匹配 (而不是例如有 if(!empty($matches)))。

更新: 按照@mickmackusa 的回答,使用前瞻性的更多 eloquent 正则表达式,我很好奇带有过滤功能的“普通”正则表达式的性能,与. 使用前瞻。然后,test case只有 1 次 3v4l 迭代才能不轰炸他们,使用你自己的服务器进行更多!)。

测试用例使用了 100 个具有潜在匹配项的生成字符串,运行 使用这两种方法进行了 5000 次迭代。返回的匹配结果是相同的。具有前瞻性的单步正则表达式平均花费 0.83 秒,而两步“普通”正则表达式平均花费 0.69 秒。看起来使用前瞻比更“直截了当”的方法成本略高。

好的,你已经接受了一个间接的回答,因为我在问题下的评论中要求改进问题。我将这解释为您无意进一步澄清问题,而其他答案会按需要工作。出于这个原因,我将提供一个单一的正则表达式解决方案,这样您就不需要在进行初始正则表达式提取后使用迭代正则表达式过滤。

对于有限的样本数据,您的要求归结为:

匹配整个“单词”(以空格分隔的可见字符):

  1. 由数字或字母数字字符串和
  2. 组成
  3. 长度在 4 到 20 个字符之间。

如果需要,您随后可以使用 array_unique() 消除重复的匹配字符串。

代码:(Demo)

$str = '-9 Cycles 3 Temperature Levels Steam Sanitizet+ -Sensor Dry | ALSO AVAILABLE (PRICES MAY VARY) |- White - 1258843 - DVE45R6100W {+ Platinum - 1501 525 - DVE45R6100P desirable: 1258843 DVE45R6100W';

if (preg_match_all('~\b(?:[A-Z]{4,20}(*SKIP)(*FAIL)|[A-Z\d]{4,20})\b~', $str, $m)) {
    var_export(array_unique($m[0]));
}

输出:

array (
  0 => '1258843',
  1 => 'DVE45R6100W',
  2 => '1501',
  3 => 'DVE45R6100P',
)

模式分解:

\b             #the zero-width position between a character matched by \W and a character matched by \w
(?:            #start non-capturing group
  [A-Z]{4,20}(*SKIP)(*FAIL) #match and disqualify all-letter words
  |                         #or
  [A-Z\d]{4,20}             #match between 4 and 20 digits or uppercase letters
)              #end non-capturing group
\b             #the zero-width position between a character matched by \W and a character matched by \w

这里有几个替代的正则表达式模式供比较——一个不使用任何环视的模式使用“跳过失败”技术来取消纯字母“单词”的资格。

  • 437 步:\b(?=\S*\d)[A-Z\d]{4,20}\b
  • 325 步:\b(?=[A-Z]*\d)[A-Z\d]{4,20}\b
  • 298 步:\b(?:[A-Z]{4,20}(*SKIP)(*FAIL)|[A-Z\d]{4,20})\b

等效的非正则表达式过程(我不认可)是:(Demo)

foreach (explode(' ', $str) as $word) {
    $length = strlen($word);
    if ($length >= 4                    // has 4 characters or more
        && $length <= 20                // has 20 characters or less
        && !isset($result[$word])       // not yet in result array
        && ctype_alnum($word)           // comprised numbers and/or letters only
        && !ctype_alpha($word)          // is not comprised solely of letters
        && $word === strtoupper($word)  // has no lowercase letters
    ) {
        $result[$word] = $word;
    }
}
var_export(array_values($result));