【问题标题】:How do you use split and scan to parse a URI in ruby?如何使用 split 和 scan 来解析 ruby​​ 中的 URI?
【发布时间】:2015-09-25 04:29:59
【问题描述】:

假设我在 Ruby 中有这个字符串

str = "/server/ab/file.html

我想得到一个包含

的数组

["/server/", "/ab/", "file.html"]

有没有办法使用 split 或 scan 来获取这个数组?我已经尝试了各种组合,但没有任何东西与我想要的完全匹配。我不能使用任何外部库。有任何想法吗?谢谢。

【问题讨论】:

  • 没有。在"/server/" 的末尾和"/ab/" 的开头,用splitscan 的单遍是不可能的。
  • 有没有办法检查开头的斜杠,如果没有,将其添加到字符串中?
  • 这会给你"/file.html",而不是"file.html"。顺便说一句,"/ab/" 前面有斜线但"file.html" 没有斜线的逻辑是什么?
  • @sawa 可以在返回的迭代器上使用单个 scan 传递和 map

标签: ruby parsing split uri


【解决方案1】:

正如@sawa 所说,问题在于需要您操作字符串的双“/”。

我能想到的最直接的解决办法是:

# removes the '/' at the beginning of the string
# and splits the string to an array
a = str.sub(/^\//, '').split('/') # => ["server", "ab", "file.html"]

# iterates through the array objects EXCEPT the last one,
# (notice three dots '...' instead of two '..'),
# and adds the missing '/'
a[0...-1].each {|s| s << '/'; s.insert(0 , '/')} # => ["/server/", "/ab/"]

a # => ["/server/", "/ab/", "file.html"]

编辑 2

跟进@mudasobwa 的概念、想法和输入,如果您知道第一个字符始终是'/',这将是迄今为止最快的解决方案(请参阅编辑的基准):

        a = str[1..-1].split('/')
        a << (a.pop.tap { a.map! {|s| "/#{s}/" } } )

祝你好运。

基准测试

阅读@mudasobwa 的回答后,我印象非常深刻。我想知道他的解决方案有多快......

...我很惊讶地发现,尽管他的解决方案看起来更优雅,但速度要慢得多。

我不知道为什么,但在这种情况下,使用 gsub 或 scan 的 Regexp 查找似乎比较慢。

以下是基准,供任何感兴趣的人参考(每秒迭代次数 - 数字越大越好):

require 'benchmark/ips'

str = "/server/ab/file.html"
Benchmark.ips do |b|

    b.report("split") do
        a = str.sub(/^\//, '').split('/')
        a[0...-1].each {|s| s << '/'; s.insert(0 , '/')}
    end
    b.report("updated split") do
        a = str[1..-1].split('/')
        a[0...-1].each {|s| s << '/'; s.insert(0 , '/')}
    end
    b.report("scan") do
        str.scan(/(?<=\/)([\w.]+)(\/)?/).map { |(val,slash)| slash ? "/#{val}/" : val }
    end
    b.report("gsub") do
        str.gsub(/(?<=\/)([\w.]+)(\/)?/).map { |m| "#{$2 && '/'}#{m}" }
    end
    b.report("mudasobwa's varient") do
        a = str[1..-1].split('/')
        [*a[0..-2].map { |e| "/#{e}/"}, a[-1]]
    end
    b.report("mudasobwa's tap concept") do
        a = str[1..-1].split('/')
        a << (a.pop.tap { a.map! {|s| "/#{s}/" } })
    end

end; nil

# results:
#
# Calculating -------------------------------------
#                split    39.378k i/100ms
#        updated split    45.530k i/100ms
#                 scan    23.910k i/100ms
#                 gsub    18.006k i/100ms
#  mudasobwa's varient    47.389k i/100ms
# mudasobwa's tap concept
#                         51.895k i/100ms
# -------------------------------------------------
#                split    517.487k (± 2.9%) i/s -      2.599M
#        updated split    653.271k (± 6.4%) i/s -      3.278M
#                 scan    268.048k (± 6.9%) i/s -      1.339M
#                 gsub    202.457k (± 3.2%) i/s -      1.026M
#  mudasobwa's varient    656.734k (± 4.8%) i/s -      3.317M
# mudasobwa's tap concept
#                         761.914k (± 3.2%) i/s -      3.840M

【讨论】:

    【解决方案2】:
    ▶ str.gsub(/(?<=\/)([\w.]+)(\/)?/).map { |m| "#{$2 && '/'}#{m}" } 
    #⇒ [ "/server/", "/ab/", "file.html" ]
    

    或者,使用scan,这更符合语义:

    ▶ str.scan(/(?<=\/)([\w.]+)(\/)?/).map { |(val,slash)| slash ? "/#{val}/" : val }
    

    可能是最快的解决方案:

    ▶ a = str[1..-1].split('/')
    ▶ [*a[0..-2].map { |e| "/#{e}/"}, a[-1]]
    #⇒ ["/server/", "/ab/", "file.html"]
    

    完成就地数组更改(嘿,美学家):

    ▶ a = str[1..-1].split('/')
    ▶ a.pop.tap do |e| 
    ▷   a.map! do |e| 
    ▷     [-1, 0].each do |i| 
    ▷       e.insert(i, '/')
    ▷     end
    ▷     e
    ▷   end.push e
    ▷ end
    ▶ puts a
    #⇒ ["/server/", "/ab/", "file.html"]
    

    【讨论】:

    • 我喜欢你的解决方案,看起来超级优雅......我非常喜欢它,我测试了它与我自己的性能对比。我不知道为什么它更慢,但它肯定更漂亮:-)
    • @Myst 因为全局扫描。您可以通过将第一个 sub 更改为:a = str[1..-1].split('/') 来提高您的速度。
    • @Myst 您介意将我的第三个变体(刚刚发布)添加到您的基准测试吗?
    • @mudasobwa 好收获!我没有这样做是因为担心字符串可能不会真正以“/”符号开头,但你完全正确!我检查了基准测试,使用str[...] 会更快——即使使用if 语句,它也快两倍!谢谢:-)
    • @Myst:我添加了需要零额外内存的解决方案:数组和字符串都在原地修改:)
    【解决方案3】:
    str = str[1..-1].split('/')
    => ["server", "ab", "file.html"]
    str[0...-1].map!{|e| "/#{e}/"} << str[-1]
    => ["/server/", "/ab/", "file.html"]
    

    【讨论】:

    • 不错的解决方案。但是,为了继续我们之前的对话,您正在重新创建一个数组和大部分字符串,从而在您可以简单地编辑现有对象时创建新对象;-) .... 由于这不是真正的长时间迭代,它可能不会事情。但是,如果代码进入解析请求的服务器应用程序,您可能希望避免这种情况。考虑使用map! 或使用each 编辑字符串。有时创建更多对象很好。在我的解决方案中,我创建了一个新的临时数组以避免在我的迭代中使用 if 语句。视情况而定,仅供参考。
    猜你喜欢
    • 1970-01-01
    • 2015-05-19
    • 2016-10-27
    • 2013-01-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多