I'm currently trying to make a Set
of all possible combinations from an Array
of Strings
, were each element contains only one letter.
The Array
itself can contain the same letter twice or even more and they should only be used as often as they occur.
The Set
should later contain all combinations from a minimum of 2 letters up to the length of the given Array
.
I searched here on stackoverflow, but only found permutation functions that ignore the fact, that each letter should only be used as often as they occur.
This is my first Swift 2 project, so please forgive my greenhornish-ness :)
What I want
var array = ["A", "B", "C","D"]
var combinations: Set<String>
... <MAGIC> ...
print(combinations)
// "AB", "ABC", "ABD", "ABCD", "ABDC", "AC", "ACB", "ACD", "ACBD", "ACDB", and so on ...
My current approach
func permuation(arr: Array<String>) {
for (index, elementA) in arr.enumerate() {
//1..2..3..4
var tmpString = elementA
var tmpArray = arr
tmpArray.removeAtIndex(index)
for (index2, elementB) in tmpArray.enumerate() {
// 12..13..14
var tmpString2 = tmpString + elementB
var tmpArray2 = tmpArray
//[3,4]
tmpArray2.removeAtIndex(index2)
results.append(tmpString2)
}
}
}
permuation(array)
print(results)
// "["AB", "AC", "AD", "BA", "BC", "BD", "CA", "CB", "CD", "DA", "DB", "DC"]"
I know, this is so terribly wrong in so many ways, but I'm stuck with this code, and don't know how to add a recursive functionality.
Try this.
The general algorithm is to have a
fromList
containing the letters you haven't used yet and atoList
that is the string you've built up so far. This uses recursion to build up all possible strings and adds them to the set when the length is 2 or greater:Faster Answer:
As @MartinR pointed out in his post, the solution above is a little slow because of all of the creation and copying of sets. I had originally written this using an
inout
variable for set, but changed it to the more functional interface to make it nice to call.Here is my original (faster) implementation, plus I embedded it in a
permute
that takes just an[String]
and returns aSet<String>
. It does the work of creating theset
and thetoList
array and then calls the inner version ofpermute
to do the real work:Edit: I added a
minStringLen
parameter (with default value of2
) instead of hard coding that value.See @MartinR's answer for performance comparisons.
Swift 3 and Swift 4:
This is quite similar to @vacawama's answer, but hopefully different enough that it deserves a separate answer :)
Here, an array with all combinations is built (explaining comments inline):
Then you can filter the strings with at least two letters, and convert it to a
Set
:I made a simple performance comparison (compiled in Release mode on a Macbook Pro):
The result depends on the size of the input array, but @vacawama's updated method is the fastest:
Here's a Swift 3 function that is a bit faster. It is based on an extension to the Array type that can be used on arrays with any element type.
In my tests, it ran about 40x faster than vacawama's second version on 7 letters.
[EDIT] I realized later that this function produces combinations (as requested in the OP) where vacawama's function produces permutations. I tested an equivalent algorithm for permutations and it was merely 55% faster than vacawama's.
In your output example it wasn't clear what you really want, either:
all combinations and permutations of them:
just all combinations:
I can recommend @oisdk's great Swift library: SwiftSequence for both of them, it has lots of useful functions. In the Combinations section he even shows an example of its usage with the Power Set, which is almost exactly what you're looking for in case 1. Upon importing the files of his library, you can create the
powerSet
function onCollectionType
s like this:This method evaluates lazily, which means that it's only evaluated when it really needs to. Now you mentioned that you only want to have combinations of at least 2 elements. This is easily done with the
filter
method:For case 2. where you need permutations of those as well, you can do this:
You can convert these to a
Set
ofString
s or anArray
:But I strongly recommend to use the lazy version ;) And yes, to remove duplicates you can just use
Set(["A", "B", "C", "D"])
instead of just["A", "B", "C", "D"]
You can also do case 2. in one go like this: