1

Mathematics/algorithms was never my strong point (!) so requesting help on this one.

What is the most efficient implementation for a method with the following signature:

/*
 * pairParts.size() > 0
 * pairParts.size() is always an even number
 */
private Set<StringPairGroup> getAllPossibleStringPairGroups(Set<String> pairParts) {
    Set<StringPairGroup> groups = new HashSet<StringPairGroup>();           
    // logic that adds all possible StringPairGroups            
`   return groups;
}

/*
 * StringPair object: first and second values cannot be null.
 * StringPair object: first != second
 * StringPair object: is equal to another if both the first values and both the second values are equal.
 */
public class StringPair {       
    private final String first;
    private final String second;    
    ...
}

/*
 * StringPairGroup object: is equal to another if their StringPair sets exactly match.
 */
public class StringPairGroup {      
   Set<StringPair> stringPairs  
   ...   
}

As an example an input of {'A', 'B'} would return {[AB],[BA]}. An input of {'A', 'B', 'C', 'D'} would return {[AB],[BA],[AC],[CA],[AD],[DA][...], [AB,CD],[BA,CD],[AB,DC],[BA,DC],[AC,DB],[...]}.

I am really just interested in the logic that creates all the possible StringPairGroups for any set of input Strings. I could probably come up with some sort of brute force implementation but would rather know how to do something a lot 'cleverer'.

So any hints as to how I would implement that would be useful.

Edit:

Sorry guys I think I may have missed off something quite important. I am really beggining to confuse myself. This is it:

A StringPairGroup cannot contain a repeated 'pair part' across all of its StringPairs. Does that make sense?

Dan MacBean
  • 1,845
  • 3
  • 14
  • 21

4 Answers4

2

I would do it this way:

  1. Implement a comparison in StringPair that when passed another StringPair compares to see if they share any element: boolean shareElement(StringPair other)
  2. I could create a list of all possible StringPairs [AB], [BA], [AC], [CA]
  3. I would then do the following for i = 1 - (originalList.size() / 2)...

    a. Create combinations of i elements from the unique pair list that do not share any elements.

Combine this solution with @Hemal Pandya's and I think you will have your answer. Meaning, use Hemal's recursive combination of the sets in combination with the shareElement above.

Edit: I would also create a boolean shareElement(StringPair... others)

John B
  • 30,460
  • 6
  • 67
  • 92
1

I don't know if this is efficient, but here's an attempt..

In step 1 Given A, B, C generate [A,B], [A,C], [B, A], [B, C], [C, A], [C, B]

That's easy (right?).

Now, initially you had a set of 3 elements, each of size 1, from which you generated the pairs.

After the first step you have set of 6 elements, each of size 2, so you do the same thing you did with the first one to generate the 2nd order sets.

And so on till you have a set of 1 element.

Make sense?

Looking at your note that the order does not matter I am thinking maybe this is incorrect.

Miserable Variable
  • 27,314
  • 13
  • 69
  • 124
  • This would not prevent duplicate entries in a set such as [AB, AC] – John B Sep 28 '11 at 11:21
  • The input size must be an even number (so A,B,C) is not a valid input. – Dan MacBean Sep 28 '11 at 11:22
  • True, but that does not mean that the idea is not sound. I think the recursive combination of elements from the previous result set is a good idea. It just needs the check to ensure no reuse of elements in a single Group. If that was addressed it would get a +1 from me. – John B Sep 28 '11 at 11:26
  • Thanks @John. I need to go back to what I am paid for but hopefully this will get OP started :) – Miserable Variable Sep 28 '11 at 11:31
1

I understand the question as follows. There is a collection of N different strings (N is even), e.g. A, B, C, D. A pair is any concatenation of two different strings from this collection. So, AB, BA are pairs, but AA is not a pair. A pair is different from another pair if the corresponding concatenated strings are different strings. So, AB is different from BA, and AB equals AB (obiously). There are N*N - N different pairs that can be built from N different strings.

A group of order k is a set of k different pairs. So, [AB], [CD] are groups of order 1 because they both contain 1 pair. [AB, CD], [BA, CD] are groups of order 2, because they both contain 2 pairs. Two groups are equal if and only if they are equal as two sets. So, two equal groups have exactly the same pairs; the order of the pairs does not matter. E.g., [AB, CD] and [BA, CD] are different groups because not all pairs in them are equal. [AB, CD] and [CD, AB] are two equal groups.

All groups of order k can be constructed recursively:

  1. Select any pair P of strings
  2. If k = 1 return the groups built from all these pairs
  3. If k > 1:
    3.1 Remove this pair from the collection C(N) of N strings, leaving a collection C(N-2) of the remaining strings.
    3.2 Construct all groups of order k-1 from C(N-2) and combine them with the pair P.

Here is a Java program (the complete code is on github:gist). An executable program in on ideone.

public static class Pair {
    public String s1, s2;
    public Pair(String s1, String s2) {
        this.s1 = s1; this.s2 = s2;
    }
    public String toString() {
        return s1 + s2;
    }
}

public static class Group {
    public List<Pair> pairs = new ArrayList<Pair>();
    public Group(Pair p) {pairs.add(p);}
}

public static List<Group> getGroups(String[] strings, int order) {
    List<Group> groups = new ArrayList<Group>();
    for (int i = 0; i < strings.length; ++i) {
        for (int j = 0; j < strings.length; ++j) {
            if (i != j) {
                Pair p = new Pair(strings[i], strings[j]);
                if (order == 1) {
                    groups.add(new Group(p));
                }
                else {
                    String[] strings2 = new String[strings.length - 2];
                    for (int k = 0, k2 = 0; k < strings.length; ++k) {
                        if (k != i && k != j) {
                            strings2[k2++] = strings[k];
                        }
                    }
                    List<Group> groups2 = getGroups(strings2, order - 1);
                    for (int k = 0; k < groups2.size(); ++k) {
                        Group g = new Group(p);
                        groups.add(g);
                        Group g2 = groups2.get(k);
                        g.pairs.addAll(g2.pairs);
                    }
                }
            }
        }            
    }
    return groups;
}

There are N/2 possible orders. Contruct the groups for all these orders and append them.

String strings[] = {"A", "B", "C", "D", "E", "F"};
List<Group> groups = new ArrayList<Group>();
for (int order = 1; order <= strings.length/2; ++order) {
    List<Group> groups2 = getGroups(strings, order); 
    groups.addAll(groups2);
}           

The recursive solution is well understandable but less efficient. If your N is large then you would need a faster iterative solution. The iterative solution is less illustrative than the recursive one and would not be suitable for the presentation here. You can consult e.g. Knuth: The Art of Computer Programming, Vol. 4A: Combinatorial Algorithms.

Jiri Kriz
  • 8,866
  • 3
  • 26
  • 35
  • Wow! This looks great. Will have a proper look at it tomorrow, or over the weekend. Will come back with some comments after that. Thanks. – Dan MacBean Sep 29 '11 at 12:18
  • Thanks again. Understand it now, although have realised how big the numbers get when N is large so going to look into a faster iterative solution. Do you have any hints as how to approach this? – Dan MacBean Oct 06 '11 at 10:44
  • This is a large topic. Yo can search for "all combinations algorithm" (and similar) on the Web. You will find e.g. this [overwiew](http://stackoverflow.com/questions/127704/algorithm-to-return-all-combinations-of-k-elements-from-n/127856#127856). You can also look into the Knuth's book, that I mentioned - but it is rather difficult and you would need to tranform his algorithms into Java. In any case, I think that you will need to adapt any found algorithm to your specific problem. My recursive solution gave you hopefully the idea and a benchmark. – Jiri Kriz Oct 06 '11 at 11:08
0

I would do it on two stages. First I would create an Iterable implementation that would have a constructor that would take in a Set as a parameter.

Then I would convert the Set into an array (I used Set rather than any other collection to guarantee uniqueness of the input set). You don't really need to store it into the array if you can guarantee no concurrent access.

Then based on the size of the array I calculate the number of possible permutations and store it into a final variable. The formula should be something like N!/2 I think.

I then have two indexes (or iterators) created. First one p is on the first element the second one q is on the second element.

Each time iterate is called, I would check if q is at the end if it is, reset q back to the first element and increment p by 1. If p is also at the end then indicate there is no more elements. Else return the new string pair of p and q.

I can then use this iterable to create a new set, array, etc of string pairs.

The separation would allow you to save memory because you don't store the string pair groups unless you need it.

Archimedes Trajano
  • 22,850
  • 10
  • 113
  • 154