【问题标题】:What is the best way to split a string to get all the substrings by Ruby?拆分字符串以通过 Ruby 获取所有子字符串的最佳方法是什么?
【发布时间】:2010-12-22 10:12:42
【问题描述】:

比如“stack”这个词,我想得到一个数组,比如:

['s', 'st', 'sta', ... 'stack', 't', 'ta', ... , 'c', 'ck', 'k']

我是通过这样的代码做到的:

def split_word(str)
  result = []
  chas = str.split("")
  len = chas.size
  (0..len-1).each do |i|
    (i..len-1).each do |j|
      result.push(chas[i..j].join)
    end
  end
  result.uniq
end

有没有更好更干净的方法来做到这一点?谢谢。

【问题讨论】:

  • 一般建议:最好使用 map(函数式)而不是遵循“init empty + iterate + push + return”(命令式)模式
  • @tokland:您所描述的不是 map(Ruby 中的 collect),而是 fold 或更一般的 catamorphism(Ruby 中的injectreduce)。
  • @Jörg,我所说的模式不是折叠:“a = []; [1,2,3].each { |x| a
  • 好吧,地图总是可以写成折叠的......无论如何,把术语放在一边我希望前面评论中的例子能说明我的意图。问题是类型,用 Haskell 术语来说,“a -> [a]”,所以对我来说,地图似乎是最自然的解决方案。
  • @tokland:嗯,map 只是fold 的一个特例:-) 基本上,map f = foldr (λx y → f x : y) []。您在评论中给出的一般描述描述了fold 的一般情况,但对于您正确的具体示例,这当然是map。特别是当init empty是一个与原始集合相同类型的集合,而iterate只是应用了一个转换函数,而push包含所有元素,那么它就是一个@987654334 @。实际上,fold 是通用的:any 迭代(mapselectcount 等,甚至是each)都可以表示为fold

标签: ruby string


【解决方案1】:
def split_word s
  (0..s.length).inject([]){|ai,i|
    (1..s.length - i).inject(ai){|aj,j|
      aj << s[i,j]
    }
  }.uniq
end

你也可以考虑使用Set而不是Array作为结果。

PS:这是另一个基于数组乘积的想法:

def split_word s
  indices = (0...s.length).to_a
  indices.product(indices).reject{|i,j| i > j}.map{|i,j| s[i..j]}.uniq
end

【讨论】:

  • 根据用户@steel 的建议修复了第二个解决方案
  • 输出应该是a,b,c,ab,bc,ac,abc
【解决方案2】:

我会写:

def split_word(s)
  0.upto(s.length - 1).flat_map do |start| 
    1.upto(s.length - start).map do |length| 
      s[start, length]
    end
  end.uniq
end

groups = split_word("stack")
# ["s", "st", "sta", "stac", "stack", "t", "ta", "tac", "tack", "a", "ac", "ack", "c", "ck", "k"]

使用map(函数式)而不是模式init empty + each + append + return(命令式)通常更清晰、更紧凑。

【讨论】:

  • 是的,你说得对。我也更喜欢功能模式而不是命令模式。谢谢你的建议!您认为哪种模式的性能更好?
  • @pake007:好吧,我说它通常更快,但在这里我不太确定,因为 Ruby 数组的扁平化和非惰性。无论如何,不​​要为性能而出汗,差异可能很小。
  • flat_map 有必要吗?如果是这样,flat_map 需要什么情况?我问是因为用 map 替换 flat_map 会产生相同的结果
  • @kraftydevil 如果没有 flat_map,您应该得到一个嵌套数组。列表理解的规则:在除最后一次之外的所有迭代中使用 flat_map。
【解决方案3】:
def substrings(str)
  output = []
  (0...str.length).each do |i|
    (i...str.length).each do |j|
      output << str[i..j]
    end
  end
  output
end

这只是你的方法的一个清理版本,它可以用更少的步骤工作 =)

【讨论】:

    【解决方案4】:

    不这么认为。

    这是我尝试的版本:

    def split_word(str)
      length = str.length - 1
      [].tap do |result|
        0.upto(length) do |i|
          length.downto(i) do |j|
            substring = str[i..j]
            result << substring unless result.include?(substring)
          end
        end
      end
    end
    

    【讨论】:

      【解决方案5】:
      def substrings(str)
        (0...str.length).map do |i|
           (i...str.length).each { |j| str[i..j]}
        end
      end
      

      这只是另一种方式,对我来说读起来更清楚。

      【讨论】:

        【解决方案6】:

        这是获取所有可能子字符串的递归方式。

        def substrings str
          return [] if str.size < 1
          ((0..str.size-1).map do |pos|
            str[0..pos]
          end) + substrings(str[1..])
        end
        

        【讨论】:

          【解决方案7】:

          稍后,但这是我从重新格式化您的代码中得到的。

          def substrings(string)
            siz = string.length
            answer = []
          
            (0..siz-1).each do |n|
              (n..siz-1).each do |i|
                answer << string[n..i]
              end
            end
            answer
          end
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-07-27
            • 2016-04-28
            • 2020-07-11
            • 2023-03-06
            • 1970-01-01
            • 2020-10-05
            相关资源
            最近更新 更多