1

I am trying to generate permutations using list of strings taking one character one time. Below is the code of input and output that I want. Can we simply do it iteratively?. Also I am not finding exact method.

String[] lst = new String[]{"abc", "def", "ghi"}; //Given
String[] permutations = new String[]{ //To Generate
            "adg", "adh", "adi",
            "aeg", "aeh", "aei",
            "afg", "afh", "afi",
            "bdg", "bdh", "bdi",
            "beg", "beh", "bei",
            "bfg", "bfh", "bfi",
            "cdg", "cdh", "cdi",
            "ceg", "ceh", "cei",
            "cfg", "cfh", "cfi",
 };

Update: I am not looking just for the above example with list size=3. It can be of any size and each string may happen to be of different length.

For ex: list = [ "ab", "abc", "defghi", "x", "einsigl"]

2
  • I think you have a mistake in the last column (aei, bei and cei are doubled) Commented May 25, 2020 at 22:01
  • Thanks for pointing out, I have fixed it. Commented May 26, 2020 at 2:13

3 Answers 3

1

In this answer I will walk through how I solved this problem to find an algorithm that works for an array of any length for words which can be any length and are not required to all be the same length.

I will first make a recursive solution, and then transorm it into an iterative one.

The easiest way to answer problems like this is to think of them recursively:

Generating all permutations of [] should return [""] Generating all permutations of a non-empty list means, for each letter c in the first word in the list, return all permutations of the rest of the list with c prepended on the front.

This can be written in Java as follows:

public static List<String> generatePermutationsRecursiveSlow(String[] words) {
    if (words.length == 0)
        // base case
        return Collections.singletonList("");
    else {
        // recursive case

        // result list
        ArrayList<String> permutations = new ArrayList<>();

        // split array into item 0 and items [1..end] 
        String firstWord = words[0];
        String[] otherWords = new String[words.length - 1];
        System.arraycopy(words, 1, otherWords, 0, words.length - 1);

        // recurse to find permutations for items [1..end]
        List<String> otherWordsPermutations = generatePermutationsRecursiveSlow(otherWords);

        // for each character in the first word 
        for (char c : firstWord.toCharArray()) {
            // for each permutation from the recursive call's results
            for (String otherWordsPermutation : otherWordsPermutations) {
                // prepend this character onto the permutation and add it to the results
                permutations.add(c + otherWordsPermutation);
            }
        }
        return permutations;
    }
}
  • Calling generatePermutationsRecursiveSlow(new String[0]) returns [""].
  • Calling generatePermutationsRecursiveSlow(new String[]{"cd"}) will cause the local c variable to be equal to 'c', and it will recurse with an empty array as the argument, making otherWordsPermutations equal to [""], so it will add 'c' + "" (which is "c") to the results, then it will do the same for 'd', adding "d" to the results.
  • Calling generatePermutationsRecursiveSlow(new String[]{"ab", "cd"}) will mean that when c is 'a', it will add to the results list 'a'+"c", then 'a'+"d", and whencis'b', it will add'b'+"c"and'b'+"d"`

A similar but better optimised version which works in the same way can be written like this:

public static List<String> generatePermutationsRecursive(String[] words) {
    ArrayList<String> permutations = new ArrayList<>();
    int wordLen = words.length;

    generatePermutationsRecursive(words, permutations, new char[wordLen], 0);

    return permutations;
}

public static void generatePermutationsRecursive(String[] words, ArrayList<String> permutations, char[] word, int i) {
    if (i == word.length) {
        // base case
        permutations.add(new String(word));
    } else {
        for (int j = 0; j < words[i].length(); j++) {
            // equivalent of prepending
            word[i] = words[i].charAt(j);
            // recurse
            generatePermutationsRecursive(words, permutations, word, i + 1);
        }
    }
}

This is better optimised since it uses the word parameter to avoid the O(n) prepending to the string by instead modifying a character array. It also introduces the parameter i which is the effective start index of the array, making it possible to avoid copying parts of the input array.

This can be transformed into an iterative approach by tracking the variables that change between different recursive calls using a stack (in place of the call stack):

private static List<String> generatePermutationsIterative(String[] words) {
    // in the recursive version, each recursive function call would have its own local copy of `i` and `j`
    // simulate that here with 2 stacks
    ArrayDeque<Integer> i_stack = new ArrayDeque<>(words.length);
    ArrayDeque<Integer> j_stack = new ArrayDeque<>(words.length);

    i_stack.add(0);
    j_stack.add(0);

    char[] word = new char[words.length];
    ArrayList<String> permutations = new ArrayList<>();

    while (!i_stack.isEmpty()) {
        int i = i_stack.removeLast();
        int j = j_stack.removeLast();

        if (i == words.length) {
            // base case
            permutations.add(new String(word));
            continue;
        }

        if (!(j < words[i].length())) {
            // reached end of loop `for (int j = 0; j < words[i].length(); j++)`
            continue;
        }

        // if not reached end of loop `for (int j = 0; j < words[i].length(); j++)` yet,
        // then increment `j` and allow next iteration to happen
        i_stack.add(i);
        j_stack.add(j + 1);

        word[i] = words[i].charAt(j);

        // recurse
        i_stack.add(i + 1);
        j_stack.add(0);
    }

    return permutations;
}

Code here

As a sidenote, look how cool Haskell is with this 2-line solution to the problem here (admittedly its not iterative, but it should have tail-call optimisation, making it as fast as an iterative solution).

Sign up to request clarification or add additional context in comments.

Comments

1

Here's one way to do it that should work for arbitrary number of words of arbitrary length (not including 0).

   String[] lst = new String[] {
        "abc",
        "def",
        "ghi"
    };
    int numWords = lst.length;
    int wordlen = lst[0].length();
    int numPerms = (int) Math.pow(wordlen, numWords);
    char[][] perms = new char[numPerms][numWords];
    char[][] chararr = Arrays.stream(lst)
      .map(String::toCharArray)
      .toArray(i -> new char[i][wordlen]);
    for (int i = 0; i < numWords; i++) {
        double permsLocal = Math.pow(wordlen, i + 1);
        int numRepeats = (int) Math.ceil((numPerms / permsLocal));
        int repeats = (int)(permsLocal / wordlen);
        for (int x = 0; x < repeats; x++) {
            char[] word = chararr[i];
            for (int j = 0; j < wordlen; j++) {
                char c = word[j];
                for (int k = 0; k < numRepeats; k++) {
                    perms[(x * wordlen * numRepeats) + k + j * numRepeats][i] = c;
                }
            }
        }
    }
    String[] permutations = Arrays.stream(perms)
      .map(String::new)
      .toArray(String[]::new);

Output:

[adg, adh, adi, aeg, aeh, aei, afg, afh, afi, bdg, bdh, bdi, beg, beh,
bei, bfg, bfh, bfi, cdg, cdh, cdi, ceg, ceh, cei, cfg, cfh, cfi]

Link to repl.it: https://repl.it/repls/BoilingExcitingAttributes

Comments

0

You can do it as follows:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        String[] lst = new String[] { "abc", "def", "ghi" };
        List<String> list = new ArrayList<>();
        for (char a : lst[0].toCharArray()) {
            for (char b : lst[1].toCharArray()) {
                for (char c : lst[2].toCharArray()) {
                    list.add(new String(new char[] { a, b, c }));
                }
            }
        }

        // Convert to array
        String[] permutations = list.toArray(new String[0]);

        // Display
        System.out.println(Arrays.toString(permutations));
    }
}

Output:

[adg, adh, adi, aeg, aeh, aei, afg, afh, afi, bdg, bdh, bdi, beg, beh, bei, bfg, bfh, bfi, cdg, cdh, cdi, ceg, ceh, cei, cfg, cfh, cfi]

1 Comment

Won't this only work if you know how many words you have? And even if you do know that, it'll still be a ton of work maintaining it

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.