4

This is a follow up on this thread, which main issue was to iterate over all permutations of an array, that is given ["a","b","c"], obtain ["bca","acb".. etc], using an iterator.

Thanks to Martin R's insights, and also his inputs in another thread, I came up with another possible solution for the 'Sequence-Based enumeration of permutations' problem using iterators. The issue is that I'm not sure I have all permutations although there are good indications they're all there. The algorithm is guaranteed to provide n! permutations at the most, with no duplicates.

The idea behind this approach is the following, say there's an array a=["a","b","c"...], of size n. Listing all permutations can be viewed as picking elements from bags:

■
■
■
■       ■  
■       ■  ■
■ ...   ■  ■  ■

0 ...  n-3 n-2 n-1

So the algorithm takes the initial array, and removes a row, pass it recursively until there is no row left. At this point, if an iterator can be found, all individual permutations can be addressed independently. The iterator is hidden in FactorialSequence below, where a method next() allows to move from adjacents points.

public struct FactorialSequence : Sequence, IteratorProtocol {

    private var current: [Int]

    public init(size:Int) {
        self.current = Array(repeating: 0, count: size)
    }

    public mutating func next() -> [Int]? {
        return self.__next();
    }

    private mutating func __next() -> [Int]? {
        var next = current
        defer {
            current = next;
        }

        for i in self.current.indices.reversed() {
            if next[i] < current.count - i - 1  {
                next[i] += 1
                return next;
            }
            next[i] = 0
        }
        return nil
    }
}

func permute(seq:[String],at:[Int]) -> String? {
    if seq.count > 0 {
        var ss = seq;
        let uu = seq[at.first!]
        var cc = at;
        _ = ss.remove(at: cc.first!)
        _ = cc.remove(at: 0);
        return uu + (permute(seq:ss,at:cc) ?? "")
    }
    return nil ;
}

the permute() function is called passing the iterator (an array) calculated from FactorialSequence:

var fs = FactorialSequence(size: 3)
print("\(fs.current):\(permute(seq:["a","b","c"], at: fs.current)!)")

while let uu = fs.next() {
    print("\(uu):\(permute(seq:["a","b","c"], at: uu)!)")
}

and gives (in flatten string format):

   [-0.000][-0.000][171]    [0, 0, 0]:abc
   [0.0009][0.0009][174]    [0, 1, 0]:acb
   [0.0016][0.0007][174]    [1, 0, 0]:bac
   [0.0024][0.0008][174]    [1, 1, 0]:bca
   [0.0032][0.0008][174]    [2, 0, 0]:cab
   [0.0040][0.0008][174]    [2, 1, 0]:cba

Note on 'no duplicates': Since permutations are accessed using an array (the iterator), if two iterators differ by one elements, they point to two different permutations. Although a little thin, I take this as an argument for not having duplicates.

The only question remaining is 'are they all there?'. One could say that there are n! distinct arrays pointing at a given permutation, but I'm not too sure about the validity of that argument, since it comes from a 'drawing'... Pointers welcome.

I didn't thoroughly scrub SO to check if this had been already formulated this way or in a similar way (although links in the original thread use other approaches). Apologies if it did.

Alex
  • 1,581
  • 1
  • 11
  • 27
  • Well, do you get the correct number of results, and are they unique? This shouldn't be hard to check. – m69's been on strike for years Sep 01 '18 at 14:04
  • So far they are to the limits of my tests, but that's hardly a proof. The point of this post was to ask if there was any generic proof at all, as I suspect the method described above was most certainly used before. I just couldn't find where. – Alex Sep 02 '18 at 07:40

1 Answers1

3

For a given size N the FactorialSequence produces a sequence of all arrays

[ i.0, i.1, ..., i.(N-1) ]

such that

0 <= i.0 < N, 0 <= i.1 < N-1, ..., 0 <= i.(N-1) < 1

that are exactly

N * (N-1) * ... * 1 = N!

elements. The permute() function then picks the element with index i.0 from the given array with N elements, then the element with i.1 from the remaining N-1 elements, and so on.

So yes, this indeed produces all possible permutations of the array.

However, the code can be simplified a bit. First, FactorialSequence does not return the initial array [ 0, ..., 0 ], corresponding to the identity permutation. Also the separate __next() method seems unnecessary.

If we change the code to

public struct FactorialSequence : Sequence, IteratorProtocol {

    private var current: [Int]
    private var firstIteration = true

    public init(size:Int) {
        self.current = Array(repeating: 0, count: size)
    }

    public mutating func next() -> [Int]? {
        if firstIteration {
            firstIteration = false
            return current
        }

        for i in self.current.indices.reversed() {
            if current[i] < current.count - i - 1  {
                current[i] += 1
                return current;
            }
            current[i] = 0
        }
        return nil
    }
}

then all permutations are returned (including the initial identity), and the defer statement is no longer necessary.

The permute() function can be simplified slightly, and made generic:

func permute<E>(array: [E], at: [Int]) -> [E] {
    if at.isEmpty { return [] }
    var at = at
    var array = array
    let firstIndex = at.remove(at: 0)
    let firstElement = array.remove(at: firstIndex)
    return [firstElement] + permute(array: array, at: at)
}

Now

let array = ["a", "b", "c"]
let fs = FactorialSequence(size: 3)
for p in fs {
    print(permute(array: array, at: p).joined())
}

produces the expected output.

Note however that permute() produces a lot of intermediate arrays, therefore I assume it to be less efficient than the other methods that you referenced.

An alternative would be to swap the picked element to its new place, this avoids recursion and temporary arrays:

func permute<E>(array: [E], at: [Int]) -> [E] {
    var result = array
    for (i, j) in at.enumerated() {
        result.swapAt(i, i + j)
    }
    return result
}

(It produces the permutations in a different order, though.)

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • Thanks for the feedback, much appreciated. Yes, it's a lot less efficient (at least twice as slow than the method you referred to), and creates a gazillon of intermediate arrays (n! to be precise, while only n are active at a time), that's why I originally produced permutations in string format. The method is easier to understand though. I'm wondering if there isn't a way to create a master array from which all subarrays will be taken from? It really is the memory allocation that's killing the algorithm... – Alex Sep 02 '18 at 12:25
  • Actually, I'm pretty sure that there's a better way.... If we look at the algorithm, it creates a bijection between a permutation and the iterator. At step 'i' it tells 'which' element of the array to skip, because that element was 'removed' at step 'i - 1', and which element to pick to fill in the permutation. So the intermediate arrays are in my view not needed if we can find a way 'carry' those two pieces of information from one step to the next. This + a LinkedList would do the job IMO. What do you think? – Alex Sep 02 '18 at 12:46
  • 1
    @Alex: I have added a variant which does not need temporary arrays. I am sure that more good approaches exist. – Martin R Sep 02 '18 at 15:18
  • Oh boy... Has this algorithm ever been coded before? If not, I think you ought to give it a catchy name, and post it on code review. Maybe there's something more 'obvious', but I doubt more optimised. Your code need ~n*n! operations to find all permutations, while Knuth's requires ~3n*n! (worst case) – Alex Sep 02 '18 at 17:57
  • 1
    @Alex: I am sure that it is not new. – Note also that the Knuth method and this one differ if the array elements are not all different: For [1, 1, 2] the Knuth method produces only the “distinct” permutations [1, 1, 2], [1, 2, 1], [2, 1, 1], whereas this method produces all 6 permutations. – I would also suggest do to some benchmarking with large arrays in order to decide which method is more efficient. – Martin R Sep 02 '18 at 18:14
  • @Alex: Actually this is quite similar to [Heap's algorithm](https://en.wikipedia.org/wiki/Heap%27s_algorithm). A (non-sequence) version in Swift can be found here: https://stackoverflow.com/a/34969212/1187415. – I have written a sequence-base version of that algorithm (which seems to be efficient) and will post it on Code Review. – Martin R Sep 02 '18 at 19:01
  • @Alex: https://codereview.stackexchange.com/questions/202994/sequence-based-enumeration-of-permutations-with-heaps-algorithm. – Martin R Sep 02 '18 at 19:40
  • Knuth's algorithm is indeed faster, I was over-optimistic and clearly neglected the array copy costs.... It's also true that the algorithm only creates distinct permutations, which in good scenario, leads to extra optimisation. But looses info about 'what's the most probable configuration?', giving incorrect results whenever ensemble statistics are under considerations (important in my case). Anyway, w/r the code review post, that's standard Heap's algorithm, and permutations are no longer random-accessible. – Alex Sep 03 '18 at 05:37
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/179295/discussion-between-martin-r-and-alex). – Martin R Sep 03 '18 at 06:58