文字列をあらゆる n 通リに分割する(Ruby)

String#separate です。分割のすべての場合を尽くします。配列で返します。

class String
  def separate(n)
    return [[self]] if n <= 1 or n > self.length
    if n == 2
      st = self
      return [[st]] if st.length == 1
      ar = []
      (st.length - 1).times {|i| ar << [st[0..i], st[i + 1..-1]]}
      ar
    else
      seprt([[self]], n)
    end
  end
  
  private
  def seprt(ar, n)
    if n == 2
      a = []
      ar.each do |ar1|
        b = ar1.dup
        ar1.each_with_index do |st, j|
          next if st.length == 1
          st.separate(2).each do |ar2|
            c = b.dup
            c[j] = ar2
            c.flatten!
            a << c
          end
        end
      end
      a.uniq
    else
      (n - 1).times {ar = seprt(ar, 2)}
      ar
    end
  end
end

実行例。

p "12345678".separate(3)
=begin
[["1", "2", "345678"], ["1", "23", "45678"], ["1", "234", "5678"],
["1", "2345", "678"], ["1", "23456", "78"], ["1", "234567", "8"],
["12", "3", "45678"], ["12", "34", "5678"], ["12", "345", "678"],
["12", "3456", "78"], ["12", "34567", "8"], ["123", "4", "5678"],
["123", "45", "678"], ["123", "456", "78"], ["123", "4567", "8"],
["1234", "5", "678"], ["1234", "56", "78"], ["1234", "567", "8"],
["12345", "6", "78"], ["12345", "67", "8"], ["123456", "7", "8"]]
=end


追記

ブロックがある場合はそれを実行、ない場合は Enumerator を返すようにしました。(2016/3/6)

class String
  def separate(n)
    ar = seprt2(n)
    if block_given?
      ar.each {|x| yield(x)}
    else
      ar.to_enum
    end
  end
  
  def seprt2(n)
    return [[self]] if n <= 1 or n > self.length
    if n == 2
      st = self
      return [[st]] if st.length == 1
      ar = []
      (st.length - 1).times {|i| ar << [st[0..i], st[i + 1..-1]]}
      ar
    else
      seprt([[self]], n)
    end
  end
  private :seprt2
end

p "12345678".separate(3).to_a
p "abcdefgh".separate(4) {|ar| print "#{ar[0].upcase},  "}; puts

 

追記

ここで実装した Array#divide を使えば、こんな具合になります。(2018/2/10)

class String
  def divide(n)
    e = chars.divide(n).with_object([]) {|i, ar| ar << i.map(&:join)}.to_enum
    block_given? ? loop {yield(e.next)} : e
  end
end