How to get permutations of a string in Ruby without shifting order?

Viewed 91

I'm trying to take a string, and return all possible permutations without shifting order. For example:

string_array = ["abc"]
empty_array = []
string_array.each do |element|
    for i in (0..(element.length-1)) do
        empty_array.push(element[0..i])
        i += 1
    end
end

something like this would give ['a', 'ab', 'abc'].

However, I need ['a', 'ab', 'abc', 'b', 'bc', 'c'].

Is there anyway of modifying my code to include this?

6 Answers

You can use repeated_combination to generate the first and last index of each substring:

[0, 1, 2].repeated_combination(2).to_a
#=> [[0, 0], [0, 1], [0, 2], [1, 1], [1, 2], [2, 2]]

To actually extract the substrings:

str = 'abc'
(0...str.size).to_a.repeated_combination(2).map { |i, j| str[i..j] }
#=> ["a", "ab", "abc", "b", "bc", "c"]

Let's first create a helper method:

def one_pass(str)
  (1..str.size).map { |n| str[0,n] }
end
one_pass("abc")
  #=> ["a", "ab", "abc"]
one_pass("bc")
  #=> ["b", "bc"]
one_pass("c")
  #=> ["c"]

The main method is now straightforward:

def doit(str)
  str.size.times.flat_map { |i| one_pass(str[i..]) }
end 
doit("abc")
  #=> ["a", "ab", "abc", "b", "bc", "c"]
doit("abcd")
  #=> ["a", "ab", "abc", "abcd", "b", "bc", "bcd", "c", "cd", "d"]

See String#[] and Enumerable#flat_map.


If you insist on combining the two methods:

def doit(str)
  str.size.times.flat_map do |i|
    s = str[i..-1]
    (1..s.size).map { |n| s[0,n] }
  end
end 

I'm going to propose splitting the string into an array of characters, then using #each_cons to get groups of those. If we flat_map over the different lengths possible (1 .. str.size), we can generate all of the possibilities.

1.upto(str.size).flat_map { |i| str.split('').each_cons(i).map(&:join) }

Output:

["a", "b", "c", "ab", "bc", "abc"]

Or to make this maybe a little more efficient:

chars = str.split('')
1.upto(str.size).flat_map { |i| chars.each_cons(i).map(&:join) }
string_array.each do |element|
    for z in (0..element.length-1) do
        for i in (0..(element.length-1)) do
            empty_array.push(element[z..i])
            i += 1
        end
    end
end

I solved this by nesting the for loop in another for loop, but if anyone else has a more efficient solution I would appreciate seeing it

The idea for the following code is to repeat the process that gives you ['a', 'ab', 'abc'] from 'abc' for 'bc' and then 'c':

string_array = ["abc"]
empty_array = []
string_array.each do |element|
    for i in (0..(element.length-1)) do
        for j in (i..(element.length-1)) do
            empty_array.push(element[i..j])
        end
    end
end

Maybe we could use the permutation method from ruby https://www.rubydoc.info/stdlib/core/Array:permutation

def custom_permutation
  string = 'abc'
  string_array = string.chars
  result = []
  1.upto(string_array.size) do |i|
    result << string_array.permutation(i).map(&:join)
  end 
  # We could change the default sort or remove it
  result.flatten.sort
end 


And this will return something like this:

["a", "ab", "abc", "ac", "acb", "b", "ba", "bac", "bc", "bca", "c", "ca", "cab", "cb", "cba"]
Related