【问题标题】:possible ways of splitting a string into n substrings将字符串拆分为 n 个子字符串的可能方法
【发布时间】:2017-12-03 22:28:19
【问题描述】:

我正在尝试编写一个将“abcd”作为输入并返回的方法:

["a b c d", "a b cd", "a bc d", "a bcd", "ab c d", "ab cd", "abc d", "abcd"]

因此,将字符串拆分为 n 个子字符串的所有可能方法,当您连接 s1 + s2 + s3 + ... 时,您将返回原始字符串。

我已经这样解决了,但我觉得应该有一个更快更直接的方法来做到这一点。

def sequence(n)
  [true, false].repeated_permutation(n).to_a
end

def breakdown4(string)
  guide = sequence(string.length-1)
  arr = []
  guide.each do |i|
    s = string.dup
    counter = 0
    i.each do |j|
      if j
        s.insert(counter+1, " ")
        p counter
        counter += 2
      else
        counter += 1
      end
    end
    arr.push(s)
  end
  arr
end

有什么建议吗?

【问题讨论】:

标签: arrays ruby string


【解决方案1】:

一个也使用repeated_permutation 的单线器(我刚从你那里学到的 :-):

s = 'abcd'

[' ', ''].repeated_permutation(s.size - 1).map { |j| s.chars.zip(j).join }
=> ["a b c d", "a b cd", "a bc d", "a bcd", "ab c d", "ab cd", "abc d", "abcd"]
  • repeated_permutation 产生关节,例如 [" ", "", " "]
  • zip 将它们与字母配对,例如 [["a", " "], ["b", ""], ["c", " "], ["d", nil]]
  • join 将所有部分转换为字符串并将它们连接起来,例如 "a bc d"

(注意nil 变成了空字符串,join works recursively,有效地压平了整个结构)。


在我不知道repeated_permutation之前想出的以前的解决方案:

[s[0]].product(*s[1..-1].chars.flat_map { |c| [[' ', ''], [c]] }).map(&:join)
=> ["a b c d", "a b cd", "a bc d", "a bcd", "ab c d", "ab cd", "abc d", "abcd"]

s[1..-1].chars.reduce([s[0]]) { |m, c| m.product([' ', ''], [c]) }.map(&:join)
=> ["a b c d", "a b cd", "a bc d", "a bcd", "ab c d", "ab cd", "abc d", "abcd"]

[''].product(*([[' ', '']] * s.size.pred)).map { |j| s.gsub('') { j.shift } }
=> ["a b c d", "a b cd", "a bc d", "a bcd", "ab c d", "ab cd", "abc d", "abcd"]

(0...2**s.size).step(2).map { |i| s.gsub(/(?!^)/) { ' ' * (1 & i /= 2) } }
=> ["abcd", "a bcd", "ab cd", "a b cd", "abc d", "a bc d", "ab c d", "a b c d"]

它们的基本思想是这样的(为了清楚起见,使用硬编码的字符串):

['a'].product([' ', ''], ['b'], [' ', ''], ['c'], [' ', ''], ['d']).map(&:join)
=> ["a b c d", "a b cd", "a bc d", "a bcd", "ab c d", "ab cd", "abc d", "abcd"]

【讨论】:

  • 这是迄今为止最漂亮的答案! (repeated_permutation one),但我不太明白它为什么会起作用......
  • 非常聪明,斯特凡。一个细节:您可以将s.chars.zip(j).join 替换为arr.zip(j).join,其中arr 之前已定义为arr = s.chars
  • @NavidFallad 我添加了一些解释,希望现在清楚了吗?您可以随时使用它,例如将块更改为{ |j| p s.chars.zip(j) },看看发生了什么。当然,文档也有帮助 :-)。
  • @CarySwoveland 是的,这让它更快,但它仍然属于较慢的,所以我不是想赢得速度奖,只是一个简洁+清晰奖:-)
【解决方案2】:
s = 'abcd'

s[1..-1].each_char.reduce([s[0]]) do |arr, c|
  space_c = " #{c}" 
  arr.flat_map { |str| [str + c, str + space_c] }
end
  # => ["abcd", "abc d", "ab cd", "ab c d", "a bcd", "a bc d", "a b cd", "a b c d"]

这是另一种方式(在字符串中填充空格)。

arr = (1..s.size-1).to_a
  #=> [1, 2, 3]
s.size.times.flat_map do |n|
  arr.combination(n).map do |locs|
    scopy = s.dup
    locs.reverse_each { |idx| scopy.insert(idx, ' ') }
    scopy
  end
end  
  #=> ["abcd", "a bcd", "ab cd", "abc d", "a b cd", "a bc d", "ab c d", "a b c d"]

【讨论】:

    【解决方案3】:

    以下是不同方法之间的一些比较:

    #navid
    def sequence(n)
      [true, false].repeated_permutation(n).to_a
    end
    
    def breakdown4(string)
      guide = sequence(string.length-1)
      arr = []
      guide.each do |i|
        s = string.dup
        counter = 0
        i.each do |j|
          if j
            s.insert(counter+1, " ")
            #p counter
            counter += 2
          else
            counter += 1
          end
        end
        arr.push(s)
      end
      arr
    end
    
    
    #tom
    def powerset(arr)
      a = [[]] 
      for i in 0...arr.size do
        len = a.size; j = 0;
        while j < len
          a << (a[j] + [arr[i]])
          j+=1
        end
      end
      a
    end
    
    def breakdown(string)
      indexes_lists = powerset((1..string.length-1).to_a)
      indexes_lists.map(&:reverse).map do |indexes_list|
        result = string.dup
        indexes_list.each { |i| result.insert(i, " ")}
        result
      end
    end
    
    #stefan
    def stefan1 s
      [s[0]].product(*s[1..-1].chars.flat_map { |c| [[' ', ''], [c]] }).map(&:join)
    end
    
    def stefan2 s
      s[1..-1].chars.reduce([s[0]]) { |m, c| m.product([' ', ''], [c]) }.map(&:join)
    end
    
    def stefan3 s
      [''].product(*([[' ', '']] * s.size.pred)).map { |j| s.gsub('') { j.shift } }
    end
    
    def stefan4 s
      (0...2**s.size).step(2).map { |i| s.gsub(/(?!^)/) { ' ' * (1 & i /= 2) } }
    end
    
    def stefan5 s
      [' ', ''].repeated_permutation(s.size - 1).map { |j| s.chars.zip(j).join }
    end
    
    #cary
    def cary s
      s[1..-1].each_char.reduce([s[0]]) do |arr, c|
        space_c = ' ' + c 
        arr.flat_map { |str| [str + c, str + space_c] }
      end
    end
    
    #cary2
    def cary2 s
      arr = (1..s.size-1).to_a
        #=> [1, 2, 3]
      s.size.times.flat_map do |n|
        arr.combination(n).map do |locs|
          scopy = s.dup
          locs.reverse_each { |idx| scopy.insert(idx, ' ') }
          scopy
        end
    end  
    end
    

    结果

    require 'fruity'
    str = 'abcd'
    
    compare do
      navid    { s = str.dup; breakdown4(s) }
      tom      { s = str.dup; breakdown(s).sort }
      stefan_1 { s = str.dup; stefan1(s) }
      stefan_2 { s = str.dup; stefan2(s) }
      stefan_3 { s = str.dup; stefan3(s) }  
      stefan_4 { s = str.dup; stefan4(s).sort }
      stefan_5 { s = str.dup; stefan5(s) }
      cary_s   { s = str.dup; cary(s).reverse  }  
      cary_s2  { s = str.dup; cary2(s).sort  }  
    end
    
    #Running each test 64 times. Test will take about 1 second.
    #cary_s is faster than navid by 2x ± 1.0
    #navid is similar to tom
    #tom is similar to cary_s2
    #cary_s2 is similar to stefan_1
    #stefan_1 is similar to stefan_2
    #stefan_2 is faster than stefan_3 by 2x ± 1.0
    #stefan_3 is similar to stefan_5
    #stefan_5 is similar to stefan_4
    

    使用更长的字符串:

    str = 'abcdefghijklm'
    
    #Running each test once. Test will take about 4 seconds.
    #cary_s is faster than stefan_1 by 9x ± 1.0
    #stefan_1 is similar to cary_s2
    #cary_s2 is similar to navid
    #navid is similar to tom
    #tom is similar to stefan_2
    #stefan_2 is faster than stefan_3 by 2x ± 1.0
    #stefan_3 is similar to stefan_5
    #stefan_5 is similar to stefan_4
    

    【讨论】:

    • 对于stefan_3,您使用了stefan2。顺便说一句,我添加了第四个,现在我不知道哪个是哪个:-)。我认为排序不应该包含在基准中,这可能会对时代做出重大贡献。甚至可能会以不同的方式损害不同的解决方案,具体取决于它们的结果已经排序。
    • @StefanPochmann 好的,已更新。我必须对汤姆的答案进行排序,否则结果会有所不同。
    • 这是一个社区维基回答。如果有人认为测试需要改进,请在答案中链接测试代码。
    • 哈...在您的测试代码中,我终于注意到 Navid 使用了repeated_permutation。这给了我迄今为止最漂亮的解决方案......
    • @CarySwoveland 不需要sortreverse 就足够了,而且开销也少得多。或者他可以使用[str + space_c, str + c] 直接生成“正确”的订单。顺便说一句,虽然这个“果味”输出看起来很可爱,但我实际上更喜欢冷硬数字,而不仅仅是“相似”。尤其是当几个“相似”被链接起来时,因为相似性听起来不具有传递性。
    【解决方案4】:

    这实际上是一个相当棘手的问题......但经过深思熟虑,这是我想出的一个聪明的方法。它可能不是最紧凑(单线)的解决方案;但它很有启发性,可以很容易地用任何语言重写,而且不浪费。

    首先要认识到,给定长度为n 的输入字符串,有n-1 可能的位置可以插入空格。所以,我们需要做的就是:

    1. 生成那些n-1位置的所有可能的组合,然后
    2. 相应地插入这些空格。

    对于第(1)部分,我使用了一个简单的算法来generate the powerset(1..n-1).to_a,其中n 是输入字符串的长度:

    def powerset(arr)
      a = [[]] 
      for i in 0...arr.size do
        len = a.size; j = 0;
        while j < len
          a << (a[j] + [arr[i]])
          j+=1
        end
      end
      a
    end
    

    例如:

    powerset([1,2,3])
    #=> [[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]]
    

    现在,我们可以简单地在这些索引处向原始字符串插入空格。我添加的一个小技巧是从 更大 索引开始,它不会弄乱其他索引 - 即您需要以 降序 使用每个列表。

    这是最终代码:

    def powerset(arr)
      a = [[]] 
      for i in 0...arr.size do
        len = a.size; j = 0;
        while j < len
          a << (a[j] + [arr[i]])
          j+=1
        end
      end
      a
    end
    
    def breakdown(string)
      indexes_lists = powerset((1..string.length-1).to_a)
      indexes_lists.map(&:reverse).map do |indexes_list|
        result = string.dup
        indexes_list.each { |i| result.insert(i, " ")}
        result
      end
    end
    

    用法:

    breakdown("abcde")
    # => ["abcde", "a bcde", "ab cde", "a b cde", "abc de",
    #     "a bc de", "ab c de", "a b c de", "abcd e", "a bcd e",
    #     "ab cd e", "a b cd e", "abc d e", "a bc d e", "ab c d e",
    #     "a b c d e"]
    

    【讨论】:

    • 旁注:breakdown(string).length == 2**(string.length-1)。例如,breakdown("abcdefghijklmnopqrstuvwxyz").length == 33554432!!该算法呈指数增长(这并不奇怪,因为它是基于幂集计算的)——因此为大量输入计算此列表很快变得不切实际。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-25
    相关资源
    最近更新 更多