我需要在程序中的split
符号之前和之后将正则表达式中的Ruby告诉+ - * /
。
示例:
我需要将"1+12"
变成[1.0, "+", 12.0]
和"6/0.25"
加入[6.0, "/", 0.25]
可能会出现像"3/0.125"
这样的情况,但不太可能。如果上面列出的前两个满意,那应该是好的。
在Ruby文档上,"hi mom".split(%r{\s*}) #=> ["h", "i", "m", "o", "m"]
我查了cheat-sheet以尝试理解%r{\s*}
,我知道%r{}
中的内容(例如\s
)会被跳过,\ s表示空白正则表达式。
答案 0 :(得分:2)
'1.0+23.7'.scan(/(((\d\.?)+)|[\+\-\*\/])/)
答案 1 :(得分:2)
而不是拆分,匹配与捕获组来解析您的输入:
(?<operand1>(?:\d+(?:\.\d+)?)|(?:\.\d+))\s*(?<operator>[+\/*-])\s*(?<operand2>(?:\d+(?:\.\d+)?)|(?:\.\d+))
说明:
(?<groupName>regex)
但是他们没有必要,可能只是()
- 无论哪种方式,子捕获仍然可用如1,2和3.还要注意(?:regex)
构造仅用于分组 而不记得&#34;记住&#34;任何东西,并且不会搞砸你的捕获物)(?:\d+(?:\.\d+)?)|(?:\.\d+))
第一个数字:前导数字后跟可选的小数点和数字,或者是前导小数点后跟数字\s*
介于零或多个空格[+\/*-]
运算符:字符类,表示加号,除号,减号或乘号。\s*
介于零或多个空格(?:\d+(?:\.\d+)?)|(?:\.\d+)
第二个号码:与第一个号码相同的模式。regex demo输出:
答案 2 :(得分:2)
我来到这个派对的时间有点晚了,发现许多好的答案已经被采纳了。因此,我开始略微扩展主题,并比较每个解决方案的性能和稳健性。今天早上似乎是一种有趣的娱乐方式。
除了问题中给出的3个示例之外,我还为四个运算符中的每一个添加了测试用例,以及一些新的边缘情况。这些边缘情况包括处理操作数之间的负数和任意空格,以及每种算法如何处理预期的失败。
答案围绕3种方法展开:split
,scan
和match
。我还使用这3种方法中的每一种编写了新的解决方案,特别是尊重我在这里添加的其他边缘情况。我针对这整套测试用例运行了所有算法,最后得到了一个通过/失败结果表。
接下来,我创建了一个基准测试,创建了1,000,000个测试字符串,每个解决方案都能够正确解析,并针对该样本集运行每个解决方案。
在第一次基准测试时,Cary Swoveland的解决方案表现远远优于其他解决方案,但没有通过增加的测试用例。我对他的解决方案进行了非常小的改动,以产生支持负数和任意空格的解决方案,并将该测试包括为Swoveland+
。
从控制台打印的最终结果在这里(注意:水平滚动以查看所有结果):
| Test Case | match | match | scan | scan |partition| split | split | split | split |
| | Gaskill | sweaver | Gaskill | techbio |Swoveland| Gaskill |Swoveland|Swoveland+| Lilue |
|------------------------------------------------------------------------------------------------------|
| "1+12" | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
| "6/0.25" | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
| "3/0.125" | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
| "30-6" | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
| "3*8" | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
| "20--4" | Pass | -- | Pass | -- | Pass | Pass | -- | Pass | Pass |
| "33+-9" | Pass | -- | Pass | -- | Pass | Pass | -- | Pass | Pass |
| "-12*-2" | Pass | -- | Pass | -- | Pass | Pass | -- | Pass | Pass |
| "-72/-3" | Pass | -- | Pass | -- | Pass | Pass | -- | Pass | Pass |
| "34 - 10" | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
| " 15+ 9" | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
| "4*6 " | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
| "b+0.5" | Pass | Pass | Pass | -- | -- | -- | -- | -- | -- |
| "8---0.5" | Pass | Pass | Pass | -- | -- | -- | -- | -- | -- |
| "8+6+10" | Pass | -- | Pass | -- | -- | -- | -- | -- | -- |
| "15*x" | Pass | Pass | Pass | -- | -- | -- | -- | -- | -- |
| "1.A^ff" | Pass | Pass | Pass | -- | -- | -- | -- | -- | -- |
ruby 2.2.5p319 (2016-04-26 revision 54774) [x86_64-darwin14]
============================================================
user system total real
match (Gaskill): 4.770000 0.090000 4.860000 ( 5.214996)
match (sweaver2112): 4.640000 0.040000 4.680000 ( 4.911849)
scan (Gaskill): 7.360000 0.080000 7.440000 ( 7.719646)
scan (techbio): 12.930000 0.140000 13.070000 ( 13.791613)
partition (Swoveland): 5.390000 0.050000 5.440000 ( 5.648762)
split (Gaskill): 5.150000 0.100000 5.250000 ( 5.455094)
split (Swoveland): 3.860000 0.060000 3.920000 ( 4.040774)
split (Swoveland+): 4.240000 0.040000 4.280000 ( 4.537570)
split (Lilue): 7.540000 0.090000 7.630000 ( 8.022252)
为了防止这篇文章太长,我在https://gist.github.com/mgaskill/96f04e7e1f72a86446f4939ac690759a
包含了此测试的完整代码稳健性测试用例可以在上面的第一个表中找到。 Swoveland+
解决方案是:
f,op,l = formula.split(/\b\s*([+\/*-])\s*/)
return [f.to_f, op, l.to_f]
这包括在分割操作符之前的\b
元字符确保前一个字符是单词字符,从而支持第二个操作数中的负数。 \s*
元字符表达式支持操作数和运算符之间的任意空格。这些变化导致额外稳健性的性能开销低于10%。
我提供的解决方案在这里:
def match_gaskill(formula)
return [] unless (match = formula.match(/^\s*(-?\d+(?:\.\d+)?)\s*([+\/*-])\s*(-?\d+(?:\.\d+)?)\s*$/))
return [match[1].to_f, match[2], match[3].to_f]
end
def scan_gaskill(formula)
return [] unless (match = formula.scan(/^\s*(-?\d+(?:\.\d+)?)\s*([+*\/-])\s*(-?\d+(?:\.\d+)?)\s*$/))[0]
return [match[0][0].to_f, match[0][1], match[0][2].to_f]
end
def split_gaskill(formula)
match = formula.split(/(-?\d+(?:\.\d+)?)\s*([+\/*-])\s*(-?\d+(?:\.\d+)?)/)
return [match[1].to_f, match[2], match[3].to_f]
end
match
和scan
解决方案非常相似,但执行方式有很大不同,这非常有趣,因为它们使用完全相同的正则表达式来完成工作。 split
解决方案稍微简单一些,并且只对整个表达式进行拆分,分别捕获每个操作数和运算符。
请注意,split
个解决方案都无法正确识别故障。添加此支持需要额外解析操作数,这会显着增加解决方案的开销,通常运行速度大约慢3倍。
对于性能和稳健性,match
是明显的赢家。如果稳健性不是问题,但性能是,请使用split
。另一方面,scan
提供了完整的稳健性,但比同等的match
解决方案慢了50%。
另请注意,使用有效的方法将解决方案中的结果提取到结果数组中对于性能与所选算法同样重要。将结果数组捕获到多个变量(在Woveland
中使用)的技术大大优于map
解决方案。早期测试表明map
提取解决方案甚至可以使性能最高的解决方案的运行时间增加一倍以上,因此Lilue
的运行时数极高。
答案 3 :(得分:1)
我认为这可能有用:
"1.2+3.453".split('+').flat_map{|elem| [elem, "+"]}[0...-1]
# => ["1.2", "+", "3.453"]
"1.2+3.453".split('+').flat_map{|elem| [elem.to_f, "+"]}[0...-1]
# => [1.2, "+", 3.453]
显然这只适用于+
。但您可以更改split
character
。
编辑:
此版本适用于每个操作员
"1.2+3.453".split(%r{(\+|\-|\/|\*)}).map do |x|
unless x =~ /(\+|\-|\/|\*)/ then x.to_f else x end
end
# => [1.2, "+", 3.453]
答案 4 :(得分:1)
R = /
(?<=\d) # match a digit in a positive lookbehind
[^\d\.] # match any character other than a digit or period
/x # free-spacing regex definition mode
def split_it(str)
f,op,l = str.delete(' ').partition(R)
[convert(f), op, convert(l)]
end
def convert(str)
(str =~ /\./) ? str.to_f : str.to_i
end
split_it "1+12"
#=> [1, "+", 12]
split_it "3/ 5.2"
#=> [3, "/", 5.2]
split_it "-4.1 * 6"
#=> [-4.1, "*", 6]
split_it "-8/-2"
#=> [-8, "/", -2]
正则表达式当然可以用传统方式编写:
R = /(?<=\d)[^\d\.]/