python 正则表达式,其中一组选项在列表中最多出现一次,顺序不限
python regex where a set of options can occur at most once in a list, in any order
我想知道在 python 或 perl 中是否有任何方法可以构建一个正则表达式,您可以在其中定义一组选项,这些选项最多可以按任何顺序出现一次。因此,例如我想要 foo(?: [abc])*
的导数,其中 a
、b
、c
只能出现一次。所以:
foo a b c
foo b c a
foo a b
foo b
都有效,但是
foo b b
不会
您可以将此正则表达式与捕获组和否定前瞻结合使用:
对于 Perl
,您可以将此变体与 forward referencing 一起使用:
^foo((?!.*) [abc])+$
正则表达式详细信息:
^
: 开始
foo
:匹配foo
(
: 启动捕获组#1
(?!.*)
:否定前瞻断言我们在输入的任何地方都不匹配捕获组 #1 中的内容
[abc]
:匹配space后跟[=19=]或b
或c
)+
: 结束捕获组#1。重复此组 1+ 次
$
:结束
如前所述,此正则表达式使用了一个名为 前向引用 的功能,它是对 稍后出现在正则表达式模式中的组的反向引用。 JGsoft、.NET、Java、Perl、PCRE、PHP、Delphi 和 Ruby 允许前向引用,但 Python 不允许。
这是 Python 的相同正则表达式的 解决方法,它不使用前向引用:
^foo(?!.* ([abc]).*)(?: [abc])+$
在这里,我们在重复组之前使用否定前瞻来检查并使匹配失败,如果允许的子字符串有任何重复,即 [abc]
.
您可以使用对之前捕获的组的引用来完成。
foo(?: ([abc]))?(?: (?!)([abc]))?(?: (?!|)([abc]))?$
这很长,有很多选项。如有必要,可以动态生成这样的正则表达式。
def match_sequence_without_repeats(options, seperator):
def prevent_previous(n):
if n == 0:
return ""
groups = "".join(rf"\{i}" for i in range(1, n + 1))
return f"(?!{groups})"
return "".join(
f"(?:{seperator}{prevent_previous(i)}([{options}]))?"
for i in range(len(options))
)
print(f"foo{match_sequence_without_repeats('abc', ' ')}$")
您可以断言 space 和右侧字母的第二个匹配项没有匹配项:
foo(?!(?: [abc])*( [abc])(?: [abc])*)(?: [abc])*
foo
字面匹配
(?!
否定前瞻
(?: [abc])*
匹配 a space 和 b 或 c 的可选重复
( [abc])
捕获组,用于与相同的反向引用进行比较
(?: [abc])*
再次匹配 a space 和 b 或 c
</code> 向后引用组 1</li>
</ul>
</li>
<li><code>)
关闭前瞻
(?: [abc])*
匹配可选重复或 a space 和 b 或 c
如果不想只匹配foo,可以将量词改为1个或多个(?: [abc])+
perl 中的一个变体使用 (?1)
重用第一个子模式,它指的是捕获组 ([abc])
^foo ([abc])(?: (?!)((?1))(?: (?!|)(?1))?)?$
如果字符串的顺序无关紧要,并且你想确保每个字符串只出现一次,你可以将列表变成一个集合 Python:
my_lst = ['a', 'a', 'b', 'c']
my_set = set(lst)
print(my_set)
# {'a', 'c', 'b'}
我假设字符串的元素可以以任意顺序出现并且出现任意次数。例如,'a foo'
应该匹配而 'a foo b foo'
不应该匹配。
您可以通过一系列使用前瞻的交替来做到这一点,每个感兴趣的子字符串一个,但是当有很多字符串需要考虑时,它就变得有些吃力了。假设您想要匹配零个或一个 "foo"
的 and/or 零个或一个 "a"
的。您可以使用以下正则表达式:
^(?:(?!.*\bfoo\b)|(?=(?:(?!\bfoo\b).)*\bfoo\b(?!(.*\bfoo\b))))(?:(?!.*\ba\b)|(?=(?:(?!\ba\b).)*\ba\b(?!(.*\ba\b))))
这匹配,例如,'foofoo'
、'aa'
和 afooa
。如果它们不匹配,请删除分词符 (\b
)。
请注意,此表达式首先断言字符串的开头 (^
),然后是两个正前瞻,一个用于 'foo'
,一个用于 'a'
。还要检查,比如说,'c'
一个人会加入
(?:(?!.*\bc\b)|(?=(?:(?!\bc\b).)*\bc\b(?!(.*\bc\b))))
与
相同
(?:(?!.*\ba\b)|(?=(?:(?!\ba\b).)*\ba\b(?!(.*\ba\b))))
与 \ba\b
更改为 \bc\b
。
如果能够使用反向引用就好了,但我不知道该怎么做。
通过将鼠标悬停在 link 中的正则表达式上,将为表达式的每个元素提供解释。 (如果不清楚,我指的是光标。)
注意
(?!\bfoo\b).
匹配不以单词 'foo'
开头的字符。因此
(?:(?!\bfoo\b).)*
匹配不包含 'foo'
且不以 'f'
后跟 'oo'
.
结尾的子字符串
我会在实践中提倡这种方法,而不是使用简单的字符串方法吗?让我思考一下。
这是 anubhava 答案的修改版本,使用 backreference(在 Python 中有效,至少对我来说更容易理解)而不是前向引用。
在捕获组中使用 [abc]
进行匹配,然后检查捕获组匹配的文本不会再次出现在它之后的任何地方:
^foo(?:( [abc])(?!.*))+$
^
: 开始
foo
:匹配foo
(?:
: 启动非捕获组(?:( [abc])(?!.*))
( [abc])
:捕获第 1 组,匹配 space
后跟 a
、b
或 c
(?!.*)
:否定前瞻,如果第一个捕获组匹配的文本出现在.
匹配的零个或多个字符之后,则匹配失败
)+
:结束非捕获组并匹配1次或多次
$
:结束
如果不必是正则表达式:
import collections
# python >=3.10
def is_a_match(sentence):
words = sentence.split()
return (
(len(words) > 0)
and (words[0] == 'foo')
and (collections.Counter(words) <= collections.Counter(['foo', 'a', 'b', 'c']))
)
# python <3.10
def is_a_match(sentence):
words = sentence.split()
return (
(len(words) > 0)
and (words[0] == 'foo')
and not (collections.Counter(words) - collections.Counter(['foo', 'a', 'b', 'c']))
)
# TESTING
#foo a b c True
#foo b c a True
#foo a b True
#foo b True
#foo b b False
或者用集合和海象运算符:
def is_a_match(sentence):
words = sentence.split()
return (
(len(words) > 0)
and (words[0] == 'foo')
and (
(s := set(words[1:])) <= set(['a', 'b', 'c'])
and len(s) == len(words) - 1
)
)
除了这里有一个不使用后向或前向引用的正则表达式外,上面的答案没什么可补充的。相反,它使用 3 个单独的否定先行断言来确保输入不包含 2 次出现的 a
或 b
或 c
。正则表达式还允许自由使用 spaces.
^foo(?![^a]*a[^a]*a)(?![^b]*b[^b]*b)(?![^c]*c[^c]*c)( +[abc])* *$
^
- 匹配字符串的开头
(?![^a]*a[^a]*a)
- 否定前瞻性断言,接下来的内容不包含两次出现的 a
(?![^b]*b[^b]*b)
- 否定前瞻性断言,随后的内容不包含两次出现的 b
(?![^c]*c[^c]*c)
- 否定前瞻断言,随后的内容不包含两次出现的 c
( +[abc])*
- 匹配 0 次或多次出现:1 次或多次 space 后跟 a
或 b
或 c
*
- 匹配 0 次或多次出现的 space
7 $
- 匹配字符串的结尾
正则表达式看起来“笨拙”,但非常简单明了。输入 foo a b c
时,成功的匹配需要 35 步,输入 foo b b
时,不成功的匹配需要 13 步。 Thich 与其他答案相比有优势。
我想知道在 python 或 perl 中是否有任何方法可以构建一个正则表达式,您可以在其中定义一组选项,这些选项最多可以按任何顺序出现一次。因此,例如我想要 foo(?: [abc])*
的导数,其中 a
、b
、c
只能出现一次。所以:
foo a b c
foo b c a
foo a b
foo b
都有效,但是
foo b b
不会
您可以将此正则表达式与捕获组和否定前瞻结合使用:
对于 Perl
,您可以将此变体与 forward referencing 一起使用:
^foo((?!.*) [abc])+$
正则表达式详细信息:
^
: 开始foo
:匹配foo
(
: 启动捕获组#1(?!.*)
:否定前瞻断言我们在输入的任何地方都不匹配捕获组 #1 中的内容[abc]
:匹配space后跟[=19=]或b
或c
)+
: 结束捕获组#1。重复此组 1+ 次$
:结束
如前所述,此正则表达式使用了一个名为 前向引用 的功能,它是对 稍后出现在正则表达式模式中的组的反向引用。 JGsoft、.NET、Java、Perl、PCRE、PHP、Delphi 和 Ruby 允许前向引用,但 Python 不允许。
这是 Python 的相同正则表达式的 解决方法,它不使用前向引用:
^foo(?!.* ([abc]).*)(?: [abc])+$
在这里,我们在重复组之前使用否定前瞻来检查并使匹配失败,如果允许的子字符串有任何重复,即 [abc]
.
您可以使用对之前捕获的组的引用来完成。
foo(?: ([abc]))?(?: (?!)([abc]))?(?: (?!|)([abc]))?$
这很长,有很多选项。如有必要,可以动态生成这样的正则表达式。
def match_sequence_without_repeats(options, seperator):
def prevent_previous(n):
if n == 0:
return ""
groups = "".join(rf"\{i}" for i in range(1, n + 1))
return f"(?!{groups})"
return "".join(
f"(?:{seperator}{prevent_previous(i)}([{options}]))?"
for i in range(len(options))
)
print(f"foo{match_sequence_without_repeats('abc', ' ')}$")
您可以断言 space 和右侧字母的第二个匹配项没有匹配项:
foo(?!(?: [abc])*( [abc])(?: [abc])*)(?: [abc])*
foo
字面匹配(?!
否定前瞻(?: [abc])*
匹配 a space 和 b 或 c 的可选重复
( [abc])
捕获组,用于与相同的反向引用进行比较(?: [abc])*
再次匹配 a space 和 b 或 c</code> 向后引用组 1</li> </ul> </li> <li><code>)
关闭前瞻(?: [abc])*
匹配可选重复或 a space 和 b 或 c
如果不想只匹配foo,可以将量词改为1个或多个
(?: [abc])+
perl 中的一个变体使用
(?1)
重用第一个子模式,它指的是捕获组([abc])
^foo ([abc])(?: (?!)((?1))(?: (?!|)(?1))?)?$
如果字符串的顺序无关紧要,并且你想确保每个字符串只出现一次,你可以将列表变成一个集合 Python:
my_lst = ['a', 'a', 'b', 'c']
my_set = set(lst)
print(my_set)
# {'a', 'c', 'b'}
我假设字符串的元素可以以任意顺序出现并且出现任意次数。例如,'a foo'
应该匹配而 'a foo b foo'
不应该匹配。
您可以通过一系列使用前瞻的交替来做到这一点,每个感兴趣的子字符串一个,但是当有很多字符串需要考虑时,它就变得有些吃力了。假设您想要匹配零个或一个 "foo"
的 and/or 零个或一个 "a"
的。您可以使用以下正则表达式:
^(?:(?!.*\bfoo\b)|(?=(?:(?!\bfoo\b).)*\bfoo\b(?!(.*\bfoo\b))))(?:(?!.*\ba\b)|(?=(?:(?!\ba\b).)*\ba\b(?!(.*\ba\b))))
这匹配,例如,'foofoo'
、'aa'
和 afooa
。如果它们不匹配,请删除分词符 (\b
)。
请注意,此表达式首先断言字符串的开头 (^
),然后是两个正前瞻,一个用于 'foo'
,一个用于 'a'
。还要检查,比如说,'c'
一个人会加入
(?:(?!.*\bc\b)|(?=(?:(?!\bc\b).)*\bc\b(?!(.*\bc\b))))
与
相同(?:(?!.*\ba\b)|(?=(?:(?!\ba\b).)*\ba\b(?!(.*\ba\b))))
与 \ba\b
更改为 \bc\b
。
如果能够使用反向引用就好了,但我不知道该怎么做。
通过将鼠标悬停在 link 中的正则表达式上,将为表达式的每个元素提供解释。 (如果不清楚,我指的是光标。)
注意
(?!\bfoo\b).
匹配不以单词 'foo'
开头的字符。因此
(?:(?!\bfoo\b).)*
匹配不包含 'foo'
且不以 'f'
后跟 'oo'
.
我会在实践中提倡这种方法,而不是使用简单的字符串方法吗?让我思考一下。
这是 anubhava 答案的修改版本,使用 backreference(在 Python 中有效,至少对我来说更容易理解)而不是前向引用。
在捕获组中使用 [abc]
进行匹配,然后检查捕获组匹配的文本不会再次出现在它之后的任何地方:
^foo(?:( [abc])(?!.*))+$
^
: 开始foo
:匹配foo
(?:
: 启动非捕获组(?:( [abc])(?!.*))
( [abc])
:捕获第 1 组,匹配 spacea
、b
或c
(?!.*)
:否定前瞻,如果第一个捕获组匹配的文本出现在.
匹配的零个或多个字符之后,则匹配失败
)+
:结束非捕获组并匹配1次或多次$
:结束
如果不必是正则表达式:
import collections
# python >=3.10
def is_a_match(sentence):
words = sentence.split()
return (
(len(words) > 0)
and (words[0] == 'foo')
and (collections.Counter(words) <= collections.Counter(['foo', 'a', 'b', 'c']))
)
# python <3.10
def is_a_match(sentence):
words = sentence.split()
return (
(len(words) > 0)
and (words[0] == 'foo')
and not (collections.Counter(words) - collections.Counter(['foo', 'a', 'b', 'c']))
)
# TESTING
#foo a b c True
#foo b c a True
#foo a b True
#foo b True
#foo b b False
或者用集合和海象运算符:
def is_a_match(sentence):
words = sentence.split()
return (
(len(words) > 0)
and (words[0] == 'foo')
and (
(s := set(words[1:])) <= set(['a', 'b', 'c'])
and len(s) == len(words) - 1
)
)
除了这里有一个不使用后向或前向引用的正则表达式外,上面的答案没什么可补充的。相反,它使用 3 个单独的否定先行断言来确保输入不包含 2 次出现的 a
或 b
或 c
。正则表达式还允许自由使用 spaces.
^foo(?![^a]*a[^a]*a)(?![^b]*b[^b]*b)(?![^c]*c[^c]*c)( +[abc])* *$
^
- 匹配字符串的开头(?![^a]*a[^a]*a)
- 否定前瞻性断言,接下来的内容不包含两次出现的a
(?![^b]*b[^b]*b)
- 否定前瞻性断言,随后的内容不包含两次出现的b
(?![^c]*c[^c]*c)
- 否定前瞻断言,随后的内容不包含两次出现的c
( +[abc])*
- 匹配 0 次或多次出现:1 次或多次 space 后跟a
或b
或c
*
- 匹配 0 次或多次出现的 space 7$
- 匹配字符串的结尾
正则表达式看起来“笨拙”,但非常简单明了。输入 foo a b c
时,成功的匹配需要 35 步,输入 foo b b
时,不成功的匹配需要 13 步。 Thich 与其他答案相比有优势。