get all combinations

Hello.

The link doesn’t work anymore, so I hope someone posts the handlers that was in Nigel Garvey’s link. :slight_smile:

Here is a quick an dirty solution of my own, to hoover over all combinations of pairs in a set(list/array of elements.


set n to 4
# A combinatorial loop, that hoovers over all combinations of pairs with two elements in a set/list/array of elements.

repeat with i from 1 to (n - 1)
	repeat with j from (i + 1) to n
		log "" & i & " x " & j
	end repeat
end repeat
(*1 x 2*)
(*1 x 3*)
(*1 x 4*)
(*2 x 3*)
(*2 x 4*)
(*3 x 4*)

I still have my original scripts. The first is based directly on an algorithm in a document by Robert Sedgewick, the link to which at the top of the script still works!:

-- A port to AppleScript of "Improved version of Heap's method (recursive)"
-- found in Robert Sedgewick's PDF document "Permutation Generation Methods"
-- <http://www.cs.princeton.edu/~rs/talks/perms.pdf>

-- Sedgewick's version recurses directly to the first three items in the list,
-- whose six possible combinations are added to the collection without further
-- recursion, presumably to combine speed with a convenient recursion exit. Any
-- further items are added into the permutation process as the recursion retreats,
-- each change of value in the current rightmost item being accompanied by all
-- possible arrangements of the items to its left. Changes to the rightmost item
-- are effected thus: at recursion levels handling an odd number of list items, the
-- rightmost item is exchanged with the leftmost item only; at recursion levels
-- handling an even number of list items, the rightmost item is exchanged with each
-- of the preceding items in turn, starting with the leftmost.
-- 
-- This script special-cases lists of less than three items.

on allPermutations(theList)
	
	script o
		property workList : missing value
		property permutations : {}
		property r : count theList -- index of the rightmost item
		
		on prmt(n)
			-- n is the index of the rightmost list item affected by this iteration
			-- and thus also the number of list items affected by this iteration (1 thru n)
			
			if n = 3 then
				-- These six permutations are hard-coded to reduce low-level recursion
				copy my workList to the end of my permutations
				
				set {v1, v2, v3} to my workList
				
				set item 2 of my workList to v1
				set item 1 of my workList to v2
				copy my workList to the end of my permutations
				
				set item 1 of my workList to v3
				set item 3 of my workList to v2
				copy my workList to the end of my permutations
				
				set item 1 of my workList to v1
				set item 2 of my workList to v3
				copy my workList to the end of my permutations
				
				set item 1 of my workList to v2
				set item 3 of my workList to v1
				copy my workList to the end of my permutations
				
				set item 1 of my workList to v3
				set item 2 of my workList to v2
				copy my workList to the end of my permutations
			else
				-- Precalculate some values for the repeat
				set nMinus1 to n - 1 -- parameter for next-level recursions
				set nIsEven to (n mod 2 = 0) -- true if n is even
				set x to 1 -- the default index with which to swap if n is odd
				
				-- Get all permutations of items 1 thru (n - 1) with the current item n
				prmt(nMinus1)
				-- Repeat with successive values of item n
				repeat with i from 1 to nMinus1
					-- If n is even, swap items n and i, otherwise default to swapping items n and 1
					if nIsEven then set x to i
					tell item x of my workList
						set item x of my workList to item n of my workList
						set item n of my workList to it
					end tell
					prmt(nMinus1)
				end repeat
			end if
		end prmt
		
	end script
	
	if o's r < 3 then
		-- Special-case lists of less than three items
		copy theList to the beginning of o's permutations
		if o's r is 2 then set the end of o's permutations to the reverse of the beginning of o's permutations
	else
		-- Otherwise use the recursive handler
		copy theList to o's workList
		o's prmt(o's r)
	end if
	
	return o's permutations
	
end allPermutations

allPermutations({1, 2, 3, 4, 5})

But I prefer this one, a modification which permutes from the right instead of from the left, giving a more ordered appearance to the results (for those of us who normally read from left to right):

-- A port to AppleScript of "Improved version of Heap's method (recursive)"
-- found in Robert Sedgewick's PDF document "Permutation Generation Methods"
-- <http://www.cs.princeton.edu/~rs/talks/perms.pdf>
--
-- This version attempts to produces a slightly more "sorted" result than
-- Sedgewick's, by recursing directly to the *last* three items in the list, whose
-- six possible combinations are added to the collection without further recursion.
-- Any preceding items are added into the permutation process as the recursion
-- retreats, each change of value in the current leftmost item being accompanied by
-- all possible arrangements of the items to its right. Changes to the leftmost
-- item are effected thus: at recursion levels handling an odd number of list
-- items, the leftmost item is exchanged with the rightmost item only; at recursion
-- levels handling an even number of list items, the leftmost item is exchanged
-- with each of the following items in turn, starting with the rightmost.
-- 
-- This script special-cases lists of less than three items.

on allPermutations(theList)
	
	script o
		property workList : missing value
		property permutations : {}
		property r : count theList -- index of the rightmost item
		property m : r - 1 -- index of the middle item of the last three
		
		on prmt(l)
			-- l is the index of the leftmost item affected by this iteration
			set n to r - l + 1 -- n is the number of list items affected by this iteration (l thru r)
			
			if n = 3 then
				-- These six permutations are hard-coded to reduce low-level recursion
				copy my workList to the end of my permutations
				
				set {v1, v2, v3} to items l thru r of my workList
				
				set item m of my workList to v3
				set item r of my workList to v2
				copy my workList to the end of my permutations
				
				set item l of my workList to v2
				set item r of my workList to v1
				copy my workList to the end of my permutations
				
				set item m of my workList to v1
				set item r of my workList to v3
				copy my workList to the end of my permutations
				
				set item l of my workList to v3
				set item r of my workList to v2
				copy my workList to the end of my permutations
				
				set item m of my workList to v2
				set item r of my workList to v1
				copy my workList to the end of my permutations
			else
				-- Precalculate some values for the repeat
				set lPlus1 to l + 1 -- parameter for next-level recursions
				set nIsEven to (n mod 2 = 0) -- true if n is even
				set x to r -- the default index with which to swap if n is odd
				
				-- Get all permutations of items (l +1) thru r with the current item l
				prmt(lPlus1)
				-- Repeat with successive values of item l
				repeat with i from r to lPlus1 by -1
					-- If n is even, swap items l and i, otherwise default to swapping items l and r
					if nIsEven then set x to i
					tell item x of my workList
						set item x of my workList to item l of my workList
						set item l of my workList to it
					end tell
					prmt(lPlus1)
				end repeat
			end if
		end prmt
		
	end script
	
	if o's r < 3 then
		-- Special-case lists of less than three items
		copy theList to the beginning of o's permutations
		if o's r is 2 then set the end of o's permutations to the reverse of the beginning of o's permutations
	else
		-- Otherwise use the recursive handler
		copy theList to o's workList
		o's prmt(1)
	end if
	
	return o's permutations
	
end allPermutations

allPermutations({1, 2, 3, 4, 5})

No doubt Shane will produce an ASObjC version ere long. :wink:

Hello Nigel.

Thank you very much for posting your script. I want to play with it. I prefer lexical permutations as well, where they change the fastest on the right side of it. :slight_smile: In Thomas Calculus Version 10 there is a description of a routine for computing the distance between 2 such permutations. The number of interwening permutations + 1. One day, I am going to order that book! :slight_smile:

Pass. :stuck_out_tongue:

Hmmm. Well here’s my attempt. Much slower than the vanilla above, but at least it works, which is quite gratifying. :slight_smile:

Edits: Script modified in accordance with Shane’s suggestion in the following post and a couple of ideas pinched from Stefan further down the thread.

use AppleScript version "2.3.1"
use scripting additions
use framework "Foundation"

on allPermutations(theList)
	
	script o
		property workArray : missing value -- Formerly 'workList'.
		property permutations : {}
		property r : (count theList) - 1 -- zero-based index of the rightmost item
		property m : r - 1 -- zero-based index of the middle item of the last three
		
		on prmt(l)
			-- l is the zero-based index of the leftmost item affected by this iteration
			set n to r - l + 1 -- n is the number of list items affected by this iteration (l thru r)
			
			if (n is 3) then
				-- These six permutations are hard-coded to reduce low-level recursion
				permutations's addObject:(workArray's |copy|())
				
				workArray's exchangeObjectAtIndex:r withObjectAtIndex:m
				permutations's addObject:(workArray's |copy|())
				
				workArray's exchangeObjectAtIndex:r withObjectAtIndex:l
				permutations's addObject:(workArray's |copy|())
				
				workArray's exchangeObjectAtIndex:r withObjectAtIndex:m
				permutations's addObject:(workArray's |copy|())
				
				workArray's exchangeObjectAtIndex:r withObjectAtIndex:l
				permutations's addObject:(workArray's |copy|())
				
				workArray's exchangeObjectAtIndex:r withObjectAtIndex:m
				permutations's addObject:(workArray's |copy|())
			else
				-- Precalculate some values for the repeat
				set lPlus1 to l + 1 -- parameter for next-level recursions
				set nIsEven to (n mod 2 = 0) -- true if n is even
				set x to r -- the default index with which to swap if n is odd
				
				-- Get all permutations of items (l +1) thru r with the current item l
				prmt(lPlus1)
				-- Repeat with successive values of item l
				repeat with i from r to lPlus1 by -1
					-- If n is even, swap items l and i, otherwise default to swapping items l and r
					if (nIsEven) then set x to i
					(workArray's exchangeObjectAtIndex:x withObjectAtIndex:l)
					prmt(lPlus1)
				end repeat
			end if
		end prmt
		
	end script
	
	if (o's r < 2) then
		-- Special-case lists of less than three items
		copy theList to the beginning of o's permutations
		if (o's r is 1) then set the end of o's permutations to the reverse of theList
	else
		-- Otherwise use the recursive handler
		set o's workArray to current application's NSMutableArray's arrayWithArray:theList
		set o's permutations to current application's NSMutableArray's array()
		o's prmt(0)
	end if
	
	return o's permutations as list
	
end allPermutations

I can see I’m going to have to take up a new hobby :wink:

I ran both versions in a similar environment, and the original was a bit above 3 times as fast for the list of 5 items. But the gap narrows as you add numbers. At 9, I got 143 seconds vs 151. So I tweaked your script a smidge, to use copy() to make the new arrays like this:

 permutations's insertObject:(workArray's |copy|()) atIndex:len

That brought the time down to a whisker under 140. You have a winner :cool:

Thanks, Shane. :slight_smile:

Going right up to 9 items on my machine, my vanilla script takes 300 seconds and my ASObjC attempt 115! Your modification only brings this down to 114 seconds, but it’s still an obvious improvement and I’ve edited my post above accordingly.

For the curious ones:
It so happens that I have implemented such an function in my personal OSAX, which may sound a bit unfair. Obviously, it’s a C solution for AppleScript, but it’s only 3 times faster than Nigel’s Objective-C version (32 seconds, against 105). The bottleneck in Nigel’s vanilla solution is the same as in C, copying 362,880 times to a list and place it in a growing list. The AppleScriptObjC version completely depends on AppleEvents but has no data handling on it’s own. It so happens that for the first time I see that AppleScriptObjC comes close to OSAX in performance.

The permutation in this post is maybe worth to translate to AppleScriptObjC, it could avoid copying and swapping making it run a lot faster.

Hello.

Lexical next R- combination:

This is a very nice and useful thread. Some times, it may take a long time to generate a full set of permutations and combinations, then we may create one at a time, to spread the cost. Here is a handler that delivers the next combination. You have to compute the number of combinations up front, so you don’t exceed the number of possible combinations, you also have to subtract one combination, since you started with one. For instance, if I start with {1,2}, a pair from the set of {1,2,3,4}, I can only iterate 5 times, since the total number of combinations are 6.
you may of course change the algorithm to start the cycle over again, if you so wish.

(* 

	Get next R combination.
	
	Pseudo code from Kenneth. H. Rosen Discrete mathematics and its applications p. 385.

	Returns the next set.
	 a: the former combination
	 v: the set from where the combinations are from
	 r: the number of items in the combination.

*)

on next_R_Combination(a, v, r)
	
	set {i, n} to {r, length of v}
	if i ≥ n then error "next_R_Combination:  Error: Subset greater or equal to superset in length."
	if r ≠ length of a then error "next_R_Combination:  Error: Length of subset not equal to r."
	try
		repeat while (item i of a) = n - r + i
			set i to i - 1
		end repeat
	on error
		error "next_R_Combination:  Error: Passed over the last combination"
	end try
	set item i of a to ((item i of a) + 1)
	repeat with j from (i + 1) to r
		set item j of a to (item i of a) + j - i
	end repeat
	return a
end next_R_Combination
(*
# driver for next_R_Combination

set m to next_R_Combination({1, 2, 5, 6}, {1, 2, 3, 4, 5, 6}, 4)
--> {1, 3, 4, 5}
try
	text 0 of m
on error e
	set ofs to offset of "of " in e
	display dialog (text (ofs + 3) thru -2 of e)
end try
*)

Edited, removed superfluous assignment in the driver/test of the algorithm.
Added some clarity to the description of the handler.

Hello.

Here is the lexical r-permutation counterpart of the handler above, this one, takes the former permutation as an argument, and delivers the next one (in lexical order), as above, it is important to keep track of the number of calls as well, so you don’t overstep the number of possible permutations.

The advantage of this approach, is to spread the cost of creating the permutations, over the run of “something”.

(* 

	Get next R permutation.
	
	Pseudo code from Kenneth. H. Rosen Discrete mathematics and its applications p. 384.

	Returns the next lexical permutation, based on the permutation passed..
	 a: the former permutation
	
*)

on next_R_Permutation(a)
	
	
	set n to (length of a)
	set j to n - 1
	
	try
		repeat while (item j of a) > item (j + 1) of a
			set j to j - 1
		end repeat
	on error
		error "next_R_Permutation:  Error: Passed over the last permutation"
	end try
	# j is now the largest subscript with item j of a < item (j+1) of a	
	set k to n
	
	repeat while item j of a > item k of a
		set k to k - 1
	end repeat
	(*
 
  item k of a is now the smallest integer greater than item j of a
  to the right of item j of a
 
 *)
	local rptmp
	
	set rptmp to item j of a
	set item j of a to item k of a
	set item k of a to rptmp
	
	set r to n
	set s to j + 1
	
	repeat while r > s
		set rptmp to item r of a
		set item r of a to item s of a
		set item s of a to rptmp
		set r to r - 1
		set s to s + 1
	end repeat
	
	return a
end next_R_Permutation


# driver for next_R_permutation
(*
set m to next_R_Permutation({3, 1, 2})
--> {3,2,1}
try
	text 0 of m
on error e
	set ofs to offset of "of " in e
	display dialog (text (ofs + 3) thru -2 of e)
end try
*)

Hi DJ.

I’ve just added a comment about that script to the end of that thread.

Otherwise, I’m not quite sure what you’re saying about it. Are you asking for an AppleScriptObjC version?

Edit: Here is one. You call the ‘permute’ handler with just the original list. It’s not as fast as the script in post #13.
Further edits: Another script improvement from Shane and two from Stefan. :slight_smile:

use AppleScript version "2.3.1"
use scripting additions
use framework "Foundation"

property permutations : missing value

on permute(theList)
	set theArray to current application's NSMutableArray's arrayWithArray:theList
	set permutations to current application's NSMutableArray's array()
	prmt(theArray, 0, (count theList) - 1)
	
	return my permutations as list
end permute

on prmt(theArray, theStart, theEnd)
	if (theStart = theEnd) then
		permutations's addObject:theArray
	else
		repeat with x from theStart to theEnd
			set theCopy to theArray's mutableCopy()
			--swap
			if (x > theStart) then (theCopy's exchangeObjectAtIndex:theStart withObjectAtIndex:x)
			prmt(theCopy, theStart + 1, theEnd)
		end repeat
	end if
end prmt

My timings were rough (and late!), but that’s very interesting. I guess I’m still intrigued by why one version scales better than the other. AppleScript garbage collection is one obvious potential factor, but I wonder if there’s more to it.

Perhaps I misunderstand what you’re getting at, but AppleScriptObjC doesn’t involve any Apple events.

That is interesting. If you were anyone else, I’d tell you to check your code :slight_smile:

FYI, you can use mutableCopy() to produce a mutable copy, and both copy()/mutableCopy() can be called on either mutable or immutable objects. And not just arrays, but also string, sets – any of the classes that have a mutable subclass.

Hi,

here a Objective-C version using the Heap’s algorithm (300 ms for 9 elements)


@import Foundation

NSMutableArray *inputArray, *outputArray;

void heapPermute(n) {
    if (n == 1) {
        [outputArray addObject:[inputArray copy]];
    } else {
        for (int i = 0; i < n; i++) {
            heapPermute(n-1);
            if (n % 2 == 1) {
                [inputArray exchangeObjectAtIndex:0 withObjectAtIndex:n-1];
            } else {
                [inputArray exchangeObjectAtIndex:i withObjectAtIndex:n-1];
            }
        }
    }
}

int main (int argc, const char * argv[]) {
    
    @autoreleasepool {
        outputArray = [NSMutableArray array];
        inputArray = [NSMutableArray arrayWithObjects:@"black", @"dark brown", @"beige", @"blue", @"yellow", @"magenta", @"green", @"red", @"gray", nil];
        heapPermute([inputArray count])
    }
    return 0;
}

and just for fun a Swift version (not measured)


var inputArray = ["black", "dark brown", "beige", "blue", "yellow"]
var outputArray = [Array<String>]()

func swap(i : Int, j : Int) {
    let temp = inputArray[i]
    inputArray[i] = inputArray[j]
    inputArray[j] = temp
}

func heapPermute(n : Int) {
    if n == 1 {
        outputArray.append(inputArray)
    }
    else {
        for i in 0..<n {
            heapPermute(n-1)
            if (n % 2 == 1) {
                swap(0, n-1)
            } else {
                swap(i, n-1)
            }
        }
    }
}
heapPermute(inputArray.count)

Aha! Thanks. Now incorporated into the script above.

So in fact the initial manifestation of ‘theArray’ needn’t be mutable. One difference between this script and my ASObjC version of “Improved Heap” is that one creates and stores mutable arrays and the other immutable ones. Does this have any implications for speed or storage?

Well that’s certainly the fastest so far! :slight_smile: Is it usable from AppleScript?

That’s right.

The best answer I can give is “possibly”. The thing with classes like array is that they are implemented as what are known as “class clusters”. In other words, there are a whole lot of subclasses, optimized for different situations, and there is no guarantee which you get – only that they respond to the same methods. As an example, the subclass used for an array of a dozen items will be quite different to the one used for an array of thousands of items.

And the implementations can (and do) change over time, sometimes dramatically – they are strongly regarded as implementation details, not to be relied upon.

I’ve seen suggestions that mutableCopy differs from copy only in that it marks the new entity as potentially mutable, which would presumably mean negligible impact.

The preference for immutable flavors boils down to safety most times, I think – you can’t accidentally change them.

it’s not far off it. Stefan has actually written it as a command-line app with no interface. But if he put it in a class of its own and saved it as a framework, then yes it would. And if “put it in a class of its own and saved it as a framework” sounds complicated, it’s actually very simple.

This is an example of how such code can be made accessible to AS:

www.macosxautomation.com/applescript/apps/ASObjCExtras.html

It’s just bits of Objective-C that do some common task that AS is slow at, put together in a single class and saved as a framework.

My mistake, what I meant was the ocid class which I referred to as the C data type named AppleEvent, not as in AppleEvents in sending and receiving events from one process to another.

:smiley: … I understand, but you make me uncertain about it I will take a look into that code tomorrow.