在系数之间添加乘号 (*)
Add multiplication signs (*) between coefficients
我有一个程序,用户在其中输入一个函数,例如 sin(x)+1
。我正在使用 ast
来尝试通过将组件列入白名单来确定字符串是否为 'safe',如 this answer 中所示。现在我想解析字符串以在没有它们的系数之间添加乘法 (*
) 符号。
例如:
3x
->3*x
4(x+5)
-> 4*(x+5)
sin(3x)(4)
-> sin(3x)*(4)
(sin
已经在全局变量中,否则将是 s*i*n*(3x)*(4)
有什么有效的算法可以做到这一点吗?我更喜欢 pythonic 解决方案(即不是复杂的正则表达式,不是因为它们是 pythonic,而是因为我也不理解它们并且想要一个我能理解的解决方案。简单的正则表达式就可以了。)
我非常愿意在一个条件下使用 sympy
(对于这类事情来说这看起来真的很容易):安全。显然 sympy
在幕后使用了 eval
。我目前的(部分)解决方案非常安全。如果有人有办法使 sympy
使用不受信任的输入更安全,我也欢迎。
正则表达式是在 vanilla python 中完成工作的最快捷、最干净的方式,我什至会为您解释正则表达式,因为正则表达式是一个非常强大的工具,它很容易理解。
要实现您的目标,请使用以下语句:
import re
# <code goes here, set 'thefunction' variable to be the string you're parsing>
re.sub(r"((?:\d+)|(?:[a-zA-Z]\w*\(\w+\)))((?:[a-zA-Z]\w*)|\()", r"*", thefunction)
我知道它有点长和复杂,但是一个不同的、更简单的解决方案如果没有比这里的正则表达式中的内容更多的 hacky 内容,就不会立即显而易见。但是,这已经针对您的所有三个测试用例进行了测试,并且完全符合您的要求。
作为对这里发生的事情的简要解释: re.sub
的第一个参数是正则表达式,它匹配特定的模式。第二个是我们要用它替换的东西,第三个是要替换的实际字符串。每次我们的正则表达式看到匹配项时,它都会删除它并插入替换项,并使用一些特殊的幕后技巧.
对正则表达式的更深入分析如下:
((?:\d+)|(?:[a-zA-Z]\w*\(\w+\)))((?:[a-zA-Z]\w*)|\()
:匹配数字或函数调用,后跟变量或括号。
((?:\d+)|(?:[a-zA-Z]\w*\(\w+\)))
:第 1 组。注意:括号分隔了一个组,它是一种子正则表达式。捕获组已编入索引以供将来参考;组也可以用修饰符重复(稍后描述)。该组匹配一个数字或一个函数调用。
(?:\d+)
:非捕获组。任何在左括号后紧跟 ?:
的组都不会为其自身分配索引,但仍充当模式的 "section"。前任。 A(?:bc)+
将匹配 "Abcbcbcbc..." 等等,但是您不能访问带有索引的 "bcbcbcbc" 匹配。但是,如果没有这个组,写 "Abc+" 将匹配 "Abcccccccc..."
\d
:匹配任何数字一次。 \d
的正则表达式将单独匹配 "1"
、"2"
和 "123"
的 "3"
。
+
:匹配前一个元素一次或多次次。在这种情况下,前一个元素是 \d
,任意数字。在前面的示例中,“123”上的 \d+
将成功匹配“123”作为单个元素。这对我们的正则表达式至关重要,以确保正确注册多位数。
|
:管道字符,在正则表达式中,它实际上表示 or
:"a|b"
将匹配 "a"
或 "b"
。在这种情况下,它将 "a number" 和 "a function call" 分开;匹配数字或函数调用。
(?:[a-zA-Z]\w*\(\w+\))
:匹配函数调用。也是一个非捕获组,如 (?:\d+)
。
[a-zA-Z]
:匹配函数调用的第一个字母。这里没有修饰符,因为我们只需要确保 first 字符是一个字母即可; A123
在技术上是一个有效的函数名称。
\w
:匹配任何字母数字字符或下划线。确保第一个字母后,后面的字符可以是字母、数字或下划线,仍然可以作为函数名。
*
:匹配前一个元素0次或更多次。虽然最初看起来没有必要,但星号实际上使元素成为 可选 。在本例中,我们修改的元素是 \w
,但一个函数在技术上不需要超过一个字符; A()
是一个有效的函数名。 A
将与 [a-zA-Z]
匹配,使得 \w
变得不必要。另一方面,第一个字母后可以有任意数量的字符,这就是我们需要此修饰符的原因。
\(
:理解这一点很重要:这不是另一个组。这里的反斜杠很像普通字符串中的转义字符。在正则表达式中,任何时候您在特殊字符前加上反斜杠,例如圆括号、+
或 *
,它都像普通字符一样使用它。 \(
匹配 一个左括号 ,用于函数的实际函数调用部分。
\w+
:匹配数字、字母或下划线一次或多次。这确保函数实际上有一个参数进入它。
\)
:类似于 \(
,但匹配 右 括号
((?:[a-zA-Z]\w*)|\()
:第 2 组。匹配变量或左括号。
(?:[a-zA-Z]\w*)
:匹配一个变量。这与我们的函数名称匹配器完全相同。但是,请注意这是在非捕获组中:这很重要,因为 OR 检查的方式。紧随其后的 OR 将这个组视为一个整体。如果不分组,"last object matched" 将是 \w*
,这不足以满足我们的需求。它会说:"match one letter followed by more letters OR one letter followed by a parenthesis"。将此元素放在非捕获组中允许我们控制 OR 注册的内容。
|
: 或字符。匹配 (?:[a-zA-Z]\w*)
或 \(
.
\(
:匹配左括号。一旦我们检查了是否有左括号,就我们的正则表达式而言,我们不需要检查任何超出它的内容。
现在,还记得我们的两个小组,第一组和第二组吗?这些用于替换字符串 "*"
。替换字符串不是真正的正则表达式,但它仍然具有某些特殊字符。在这种情况下,\<number>
将插入该号码的组。所以我们的替换字符串是这样的:"Put group 1 in (which is either our function call or our number), then put in an asterisk (*), then put in our second group (either a variable or a parenthesis)"
我想大概总结一下吧!
我有一个程序,用户在其中输入一个函数,例如 sin(x)+1
。我正在使用 ast
来尝试通过将组件列入白名单来确定字符串是否为 'safe',如 this answer 中所示。现在我想解析字符串以在没有它们的系数之间添加乘法 (*
) 符号。
例如:
3x
->3*x
4(x+5)
->4*(x+5)
sin(3x)(4)
->sin(3x)*(4)
(sin
已经在全局变量中,否则将是s*i*n*(3x)*(4)
有什么有效的算法可以做到这一点吗?我更喜欢 pythonic 解决方案(即不是复杂的正则表达式,不是因为它们是 pythonic,而是因为我也不理解它们并且想要一个我能理解的解决方案。简单的正则表达式就可以了。)
我非常愿意在一个条件下使用 sympy
(对于这类事情来说这看起来真的很容易):安全。显然 sympy
在幕后使用了 eval
。我目前的(部分)解决方案非常安全。如果有人有办法使 sympy
使用不受信任的输入更安全,我也欢迎。
正则表达式是在 vanilla python 中完成工作的最快捷、最干净的方式,我什至会为您解释正则表达式,因为正则表达式是一个非常强大的工具,它很容易理解。
要实现您的目标,请使用以下语句:
import re
# <code goes here, set 'thefunction' variable to be the string you're parsing>
re.sub(r"((?:\d+)|(?:[a-zA-Z]\w*\(\w+\)))((?:[a-zA-Z]\w*)|\()", r"*", thefunction)
我知道它有点长和复杂,但是一个不同的、更简单的解决方案如果没有比这里的正则表达式中的内容更多的 hacky 内容,就不会立即显而易见。但是,这已经针对您的所有三个测试用例进行了测试,并且完全符合您的要求。
作为对这里发生的事情的简要解释: re.sub
的第一个参数是正则表达式,它匹配特定的模式。第二个是我们要用它替换的东西,第三个是要替换的实际字符串。每次我们的正则表达式看到匹配项时,它都会删除它并插入替换项,并使用一些特殊的幕后技巧.
对正则表达式的更深入分析如下:
((?:\d+)|(?:[a-zA-Z]\w*\(\w+\)))((?:[a-zA-Z]\w*)|\()
:匹配数字或函数调用,后跟变量或括号。((?:\d+)|(?:[a-zA-Z]\w*\(\w+\)))
:第 1 组。注意:括号分隔了一个组,它是一种子正则表达式。捕获组已编入索引以供将来参考;组也可以用修饰符重复(稍后描述)。该组匹配一个数字或一个函数调用。(?:\d+)
:非捕获组。任何在左括号后紧跟?:
的组都不会为其自身分配索引,但仍充当模式的 "section"。前任。A(?:bc)+
将匹配 "Abcbcbcbc..." 等等,但是您不能访问带有索引的 "bcbcbcbc" 匹配。但是,如果没有这个组,写 "Abc+" 将匹配 "Abcccccccc..."\d
:匹配任何数字一次。\d
的正则表达式将单独匹配"1"
、"2"
和"123"
的"3"
。+
:匹配前一个元素一次或多次次。在这种情况下,前一个元素是\d
,任意数字。在前面的示例中,“123”上的\d+
将成功匹配“123”作为单个元素。这对我们的正则表达式至关重要,以确保正确注册多位数。
|
:管道字符,在正则表达式中,它实际上表示or
:"a|b"
将匹配"a"
或"b"
。在这种情况下,它将 "a number" 和 "a function call" 分开;匹配数字或函数调用。(?:[a-zA-Z]\w*\(\w+\))
:匹配函数调用。也是一个非捕获组,如(?:\d+)
。[a-zA-Z]
:匹配函数调用的第一个字母。这里没有修饰符,因为我们只需要确保 first 字符是一个字母即可;A123
在技术上是一个有效的函数名称。\w
:匹配任何字母数字字符或下划线。确保第一个字母后,后面的字符可以是字母、数字或下划线,仍然可以作为函数名。*
:匹配前一个元素0次或更多次。虽然最初看起来没有必要,但星号实际上使元素成为 可选 。在本例中,我们修改的元素是\w
,但一个函数在技术上不需要超过一个字符;A()
是一个有效的函数名。A
将与[a-zA-Z]
匹配,使得\w
变得不必要。另一方面,第一个字母后可以有任意数量的字符,这就是我们需要此修饰符的原因。\(
:理解这一点很重要:这不是另一个组。这里的反斜杠很像普通字符串中的转义字符。在正则表达式中,任何时候您在特殊字符前加上反斜杠,例如圆括号、+
或*
,它都像普通字符一样使用它。\(
匹配 一个左括号 ,用于函数的实际函数调用部分。\w+
:匹配数字、字母或下划线一次或多次。这确保函数实际上有一个参数进入它。\)
:类似于\(
,但匹配 右 括号
((?:[a-zA-Z]\w*)|\()
:第 2 组。匹配变量或左括号。(?:[a-zA-Z]\w*)
:匹配一个变量。这与我们的函数名称匹配器完全相同。但是,请注意这是在非捕获组中:这很重要,因为 OR 检查的方式。紧随其后的 OR 将这个组视为一个整体。如果不分组,"last object matched" 将是\w*
,这不足以满足我们的需求。它会说:"match one letter followed by more letters OR one letter followed by a parenthesis"。将此元素放在非捕获组中允许我们控制 OR 注册的内容。|
: 或字符。匹配(?:[a-zA-Z]\w*)
或\(
.\(
:匹配左括号。一旦我们检查了是否有左括号,就我们的正则表达式而言,我们不需要检查任何超出它的内容。
现在,还记得我们的两个小组,第一组和第二组吗?这些用于替换字符串 "*"
。替换字符串不是真正的正则表达式,但它仍然具有某些特殊字符。在这种情况下,\<number>
将插入该号码的组。所以我们的替换字符串是这样的:"Put group 1 in (which is either our function call or our number), then put in an asterisk (*), then put in our second group (either a variable or a parenthesis)"
我想大概总结一下吧!