对于这样一个微不足道的任务,有更好的解决方案吗?
给定一个字符串数组,如下所示:
roles = [
"id=Accountant,id=TOTO,id=client",
"id=Admin,id=YOYO,id=custom",
"id=CDI,id=SC"
]
要根据角色值id
值提取角色值,我使用以下正则表达式来匹配它:
r =~ /id=Admin/
愚蠢的简单解决方案只是迭代roles
数组,分配匹配的值并返回如下:
role = nil
roles.each do |r|
role = 'admin' if r =~ /id=Admin/
role = 'national' if r =~ /id=National/
role = 'local' if r =~ /id=Local/
end
role
有没有更好的解决方案?
您可以定义一个正则表达式来同时匹配多个角色。这是一个简单的:
/id=(Admin|National|Local)/
括号充当角色名称的捕获组。您可能希望添加锚点,例如仅匹配每行中的第一个id=value
对。或者,如果这些值可能不明确,请确保匹配整个值,而不仅仅是开头。
然后可以将模式传递给返回匹配行的grep
:
roles.grep(/id=(Admin|National|Local)/)
#=> ["id=Admin,id=YOYO,id=custom"]
将块传递给grep
允许我们转换匹配项:($1
是指第一个捕获组(
roles.grep(/id=(Admin|National|Local)/) { $1.downcase }
#=> ["admin"]
要获取first
角色,请执行以下操作:
roles.grep(/id=(Admin|National|Local)/) { $1.downcase }.first
#=> "admin"
如果您的数组很大,您可以使用lazy
枚举器,该枚举器将在第一次匹配后停止遍历:
roles.lazy.grep(/id=(Admin|National|Local)/) { $1.downcase }.first
#=> "admin"
我认为显而易见的方法是简单地解析整个角色数组:
roles = [
"id=Accountant,id=TOTO,id=client",
"id=Admin,id=YOYO,id=custom",
"id=CDI,id=SC"
]
user_roles = roles.join(',').split(',').map { |r| r.split('=', 2).last.downcase }
user_roles
变成:
["accountant", "toto", "client", "admin", "yoyo", "custom", "cdi", "sc"]
现在,您可以简单地执行以下操作:
user_roles.include?('admin')
或者查找任何"管理员"、"国家"、"本地"事件:
# array1 AND array2, finds the elements that occur in both:
> %w(admin national local) & user_roles
=> ["admin"]
或者,也许只是要找出用户是否具有以下任何角色:
# When there are no matching elements, it will return an empty array
> (%w(admin national local) & user_roles).empty?
=> false
> (["megaboss", "superadmin"] & user_roles).empty?
=> true
这是一个更完整的示例,其中包含常量和方法以及所有!
SUPERVISOR_ROLES = %w(admin national local)
def is_supervisor?(roles)
!(SUPERVISOR_ROLES & roles).empty?
end
def parse_roles(raw_array)
raw_array.flat_map { |r| r.split(',').map { |r| r.split('=', 2).last.downcase } }
end
roles = [
"id=Accountant,id=TOTO,id=client",
"id=Admin,id=YOYO,id=custom",
"id=CDI,id=SC"
]
raise "you no boss :(" unless is_supervisor?(parse_roles(roles))
当然,如果数据集很大,这可能效率低下,但比执行这样的正则表达式更干净,甚至更安全,例如,有人可以创建一个名为AdminHack
的角色,该角色仍将与/id=Admin/
正则表达式匹配,并且如果您想出于其他目的检查其他角色,通过编写这样的通用角色解析器可能会变得有用。
(是的,显然这个解决方案创建了大量的中间数组和其他 insta 丢弃的对象,并且有足够的优化空间(
如果我们想从一组牢房中找到一个特定的间谍,我们只需要从所有牢房中收集所有间谍,并按顺序检查他们,直到找到罪魁祸首。
这里的等效方法是从给定数组中join
字符串以形成单个字符串,然后在该字符串中搜索给定的子字符串:
str = roles.join(' ').downcase
#=> "id=accountant,id=toto,id=client id=admin,id=yoyo,id=custom id=cdi,id=sc"
join
的参数可以是空格、换行符、逗号或其他几个字符串中的任何一个(我用了一个空格(。
然后,我们简单地使用方法 String#[] 和正则表达式查找匹配项:
r = /
(?<=id=) # match 'id=' in a positive lookbehind
(?:admin|national|local) # match 'admin', 'national' or 'local'
(?!w) # do not match a word character (negative lookahead)
/x # free-spacing regex definition mode
在正常(非自由间距模式(下,这是:
/(?<=id=)(?:admin|national|local)(?!w)/
'id='
,处于积极的回望状态,不包括在比赛中。负前瞻(?!w)
确保匹配项不会紧跟单词字符。例如,这阻止了"管理"一词的匹配。
我们现在只需提取匹配项,如果有的话:
str[r] #=> "admin"
如果没有比赛,nil
就会被退回。
我们本可以在最后缩小大小写:
str = roles.join(' ')
str[/(?<=id=)([aA]dmin|[nN]ational|[lL]ocal)(?!w)/i].downcase
我喜欢Stefen 的回答,但不喜欢如果角色列表真的很大,它可以在退出grep
之前运行很长时间来抓取id
值。我也不喜欢这种模式,因为它没有锚定到搜索字符串的开头,迫使引擎做更多的工作。
我宁愿看到代码在第一次点击时停止,所以这是第一次尝试:
roles = [
"id=Accountant,id=TOTO,id=client",
"id=Admin,id=YOYO,id=custom",
"id=CDI,id=SC"
]
found_role = nil
roles.each do |i|
r = i[/^id=(Admin|National|Local)/]
if r
found_role = r.downcase
break
end
end
found_role # => "id=admin"
想到这里,我一直在唠叨我太啰嗦了,所以这个突然冒出来了:
roles = [
"id=Accountant,id=TOTO,id=client",
"id=Admin,id=YOYO,id=custom",
"id=CDI,id=SC"
]
roles.find { |i| i[/^id=(Admin|National|Local)/] }.downcase[/^(id=w+),/, 1]
# => "id=admin"
分解一下,以下是亮点:
i[/^id=(Admin|National|Local)/]
返回匹配的字符串"id=Admin..."
并退出循环。downcase[/^(id=w+),/, 1]
抓住第一对并归还。
然后,和我一样肛门,我想downcase
也会做太多的工作,所以发生了这样的事情:
roles.find { |i| i[/^id=(Admin|National|Local)/] }[/^(id=w+),/, 1].downcase
这是非常神秘的Ruby,我们真的不应该以这种方式编写代码,但我曾经写过C和Perl,所以对我来说似乎是合理的。
有趣的部分:
require 'fruity'
roles = [
"id=Accountant,id=TOTO,id=client",
"id=Admin,id=YOYO,id=custom",
"id=CDI,id=SC"
]
compare do
numero_uno {
found_role = nil
roles.each do |i|
r = i[/^id=(Admin|National|Local)/]
if r
found_role = r.downcase
break
end
end
found_role
}
numero_dos { roles.find { |i| i[/^id=(Admin|National|Local)/] }.downcase[/^(id=w+),/, 1] }
numero_tres { roles.find { |i| i[/^id=(Admin|National|Local)/] }[/^(id=w+),/, 1].downcase }
end
# >> Running each test 2048 times. Test will take about 1 second.
# >> numero_uno is similar to numero_tres
# >> numero_tres is faster than numero_dos by 10.000000000000009% ± 10.0%