Maximal partition - algorithm

Given an integer n, and 2 real sequences {a_1, ..., a_n} and {b_1, ..., b_n}, with a_i, b_i > 0, for all i. For a given fixed m < n let {P_1, ..., P_m} be a partition of the set {1, ..., n} as in P_1 U ... U P_n = {1, ..., n}, with the P_i's pairwise disjoint (empty intersection). I wish to find a partition of size m that maximizes the expression
The number of partitions of the set is n choose m, prohibitively large to do by brute force. Is there an iterative or approximate solution that does better?
For insight into this problem the code block at the end solves via brute-force. For realistic size problems (n ~ 1e6, k ~ 20) it is unusable as is, but easily distrubuted.
Edit: Presorting a, b by the values of a^2/b always gives increasing partition indices:
a = rng.uniform(low=0.0, high=10.0, size=NUM_POINTS)
b = rng.uniform(low=0.0, high=10.0, size=NUM_POINTS)
ind = np.argsort(a/b)
(a,b) = (seq[ind] for seq in (a,b))
a sample run with
NUM_POINTS = 16
PARTITION_SIZE = 3
gives an optimal partition of
[[0, 1, 2, 3, 4, 5, 6, 7], [8, 9], [10, 11]]
which is monotonic in the indices. I think I can prove this. If so, the brute-force search could be improved to n choose k-1 time, still long, but a significant savings.
import numpy as np
import multiprocessing
import concurrent.futures
from functools import partial
from itertools import islice
rng = np.random.RandomState(55)
def knuth_partition(ns, m):
def visit(n, a):
ps = [[] for i in range(m)]
for j in range(n):
ps[a[j + 1]].append(ns[j])
return ps
def f(mu, nu, sigma, n, a):
if mu == 2:
yield visit(n, a)
else:
for v in f(mu - 1, nu - 1, (mu + sigma) % 2, n, a):
yield v
if nu == mu + 1:
a[mu] = mu - 1
yield visit(n, a)
while a[nu] > 0:
a[nu] = a[nu] - 1
yield visit(n, a)
elif nu > mu + 1:
if (mu + sigma) % 2 == 1:
a[nu - 1] = mu - 1
else:
a[mu] = mu - 1
if (a[nu] + sigma) % 2 == 1:
for v in b(mu, nu - 1, 0, n, a):
yield v
else:
for v in f(mu, nu - 1, 0, n, a):
yield v
while a[nu] > 0:
a[nu] = a[nu] - 1
if (a[nu] + sigma) % 2 == 1:
for v in b(mu, nu - 1, 0, n, a):
yield v
else:
for v in f(mu, nu - 1, 0, n, a):
yield v
def b(mu, nu, sigma, n, a):
if nu == mu + 1:
while a[nu] < mu - 1:
yield visit(n, a)
a[nu] = a[nu] + 1
yield visit(n, a)
a[mu] = 0
elif nu > mu + 1:
if (a[nu] + sigma) % 2 == 1:
for v in f(mu, nu - 1, 0, n, a):
yield v
else:
for v in b(mu, nu - 1, 0, n, a):
yield v
while a[nu] < mu - 1:
a[nu] = a[nu] + 1
if (a[nu] + sigma) % 2 == 1:
for v in f(mu, nu - 1, 0, n, a):
yield v
else:
for v in b(mu, nu - 1, 0, n, a):
yield v
if (mu + sigma) % 2 == 1:
a[nu - 1] = 0
else:
a[mu] = 0
if mu == 2:
yield visit(n, a)
else:
for v in b(mu - 1, nu - 1, (mu + sigma) % 2, n, a):
yield v
n = len(ns)
a = [0] * (n + 1)
for j in range(1, m + 1):
a[n - m + j] = j - 1
return f(m, n, 0, n, a)
def Bell_n_k(n, k):
''' Number of partitions of {1,...,n} into
k subsets, a restricted Bell number
'''
if (n == 0 or k == 0 or k > n):
return 0
if (k == 1 or k == n):
return 1
return (k * Bell_n_k(n - 1, k) +
Bell_n_k(n - 1, k - 1))
NUM_POINTS = 13
PARTITION_SIZE = 4
NUM_WORKERS = multiprocessing.cpu_count()
INT_LIST= range(0, NUM_POINTS)
REPORT_EACH = 10000
partitions = knuth_partition(INT_LIST, PARTITION_SIZE)
# Theoretical number of partitions, for accurate
# division of labor
num_partitions = Bell_n_k(NUM_POINTS, PARTITION_SIZE)
bin_ends = list(range(0,num_partitions,int(num_partitions/NUM_WORKERS)))
bin_ends = bin_ends + [num_partitions] if num_partitions/NUM_WORKERS else bin_ends
islice_on = list(zip(bin_ends[:-1], bin_ends[1:]))
# Have to consume it; can't split work on generator
partitions = list(partitions)
rng.shuffle(partitions)
slices = [list(islice(partitions, *ind)) for ind in islice_on]
return_values = [None] * len(slices)
futures = [None] * len(slices)
a = rng.uniform(low=0.0, high=10.0, size=NUM_POINTS)
b = rng.uniform(low=0.0, high=10.0, size=NUM_POINTS)
ind = np.argsort(a/b)
(a,b) = (seq[ind] for seq in (a,b))
def start_task():
print('Starting ', multiprocessing.current_process().name)
def _task(a, b, partitions, report_each=REPORT_EACH):
max_sum = float('-inf')
arg_max = -1
for ind,part in enumerate(partitions):
val = 0
for p in part:
val += sum(a[p])**2/sum(b[p])
if val > max_sum:
max_sum = val
arg_max = part
if not ind%report_each:
print('Percent complete: {:.{prec}f}'.
format(100*len(slices)*ind/num_partitions, prec=2))
return (max_sum, arg_max)
def reduce(return_values):
return max(return_values, key=lambda x: x[0])
task = partial(_task, a, b)
with concurrent.futures.ThreadPoolExecutor() as executor:
for ind,slice in enumerate(slices):
futures[ind] = executor.submit(task, slice)
return_values[ind] = futures[ind].result()
reduce(return_values)

I'm trying to simply re-phrase the problem with sample input, let me know if I missed anything.
A = [1, 3, 2, 1, 4]
B = [2, 1, 5, 3, 1]
n = length(A) = length(B) = 5
We have two lists with positive integers.
We need to find a set of indices S (a subset of N = {1,2,3,..n}), let's assume it's {2,3,5}. Now, we get a new set S' = N - S = {1, 4}
For S and S', (sum(A[S]))^2/(sum(B[S'])) needs to be maximized.
As you said, the approximation solution will work too. One of the heuristics we can use is we need to choose such S so that the values of A list is high and values of B list is
low.
As we take the square of the sum on the subset of A, let's just sort A and choose a sublist so that we get the max score.
import numpy as np
A = np.array([1, 2, 3, 4, 1, 2, 3])
B = np.array([3, 3, 1, 2, 1, 3, 1])
sorted_idx = sorted(range(len(A)), key=lambda k: A[k]) # also other sorting strategy can be used, A[k]/B[k]
A_p = A[sorted_idx]
B_p = B[sorted_idx]
max_s = 0
part_ans = -1
for i in range(len(A_p)):
cur_s = (sum(A_p[:i])**2)/sum(B_p[i:])
if cur_s >= max_s:
print(cur_s)
max_s = cur_s
part_ans = i
print(f'The partitions are: {sorted_idx[:i]} and {sorted_idx[i:]}')

Related

Find all numbers whose digit counts are in descending order

Give a base b and a length n, I'd like to find through all integers in base b, with leading zeroes to reach length n, that satisfy:
For digits i and j, if i < j then the count of i's is >= the count of j's.
E.g., base 3, length 4:
0000, 0001, 0010, 0011, 0012, 0021, 0100,
0101, 0102, 0110, 0120, 0201, 0210 1000,
1001, 1002, 1010, 1020, 1100, 1200, 2001,
2010, 2100
My current approach is to increment through all integers in the range in base 10, convert to base b, count digits, and reject if the digit counts fail our criterion. This is slow.
I think the language I'm using is irrelevant but if it matters, it's Rust.
This problem is equivalent to generating integer partitions of value n into b parts, then using every partition elements as counts of digits and applying permutations (last stage alike Shridhar R Kulkarni approach, but another combinatorial object is used)
For n=7 and b=4 some intermediate parition of 7 into 4 parts is [3, 2, 2, 0] that denotes digit combination [0, 0, 0, 1, 1, 2, 2], then we permute the last one in lexicographic order. partitions function provides non-increasing parts order, so if i < j then the count of i's is >= the count of j's. condition is fulfilled.
Ideone code to play with.
def next_permutation(arr):
#https://www.nayuki.io/page/next-lexicographical-permutation-algorithm
i = len(arr) - 1
while i > 0 and arr[i - 1] >= arr[i]:
i -= 1
if i <= 0:
return False
j = len(arr) - 1
while arr[j] <= arr[i - 1]:
j -= 1
arr[i - 1], arr[j] = arr[j], arr[i - 1]
arr[i : ] = arr[len(arr) - 1 : i - 1 : -1]
return True
def partitions(Sum, K, lst, Minn = 0):
if K == 0:
if Sum == 0:
#print(lst) [3, 1, 0] denotes three zeros and one 1
arr = []
for i in range(len(lst)):
if lst[i]:
arr.extend([i]*lst[i])
#transform [3, 1, 0] to [0,0,0,1]
print(arr)
while next_permutation(arr):
print(arr)
return
for i in range(Minn, min(Sum + 1, Sum + 1)):
partitions(Sum - i, K - 1, [i] + lst, i)
b = 3
n = 4
partitions(n, b, [])
result
[0, 0, 0, 0] [0, 0, 0, 1] [0, 0, 1, 0] [0, 1, 0, 0]
[1, 0, 0, 0] [0, 0, 1, 1] [0, 1, 0, 1] [0, 1, 1, 0]
[1, 0, 0, 1] [1, 0, 1, 0] [1, 1, 0, 0] [0, 0, 1, 2]
[0, 0, 2, 1] [0, 1, 0, 2] [0, 1, 2, 0] [0, 2, 0, 1]
[0, 2, 1, 0] [1, 0, 0, 2] [1, 0, 2, 0] [1, 2, 0, 0]
[2, 0, 0, 1] [2, 0, 1, 0] [2, 1, 0, 0]
This problem can be solved with dynamic programming. Here is one approach (using Python):
from functools import lru_cache
from collections import Counter
from itertools import product
def naive(base, length):
result = 0
for tup in product(range(base), repeat=length):
ctr = Counter(tup)
is_valid = all(ctr[i] >= ctr[i+1] for i in range(base))
if is_valid:
result += 1
return result
#lru_cache(None)
def binom(n, k):
# compute binomial coefficient
if n == 0:
if k == 0:
return 1
else:
return 0
return binom(n - 1, k) + binom(n - 1, k - 1)
def count_seq(base, length):
#lru_cache(None)
def count_helper(base, length, max_repeats):
if base < 0 or length < 0:
return 0
elif length == 0:
return 1
return sum(binom(length, k) * count_helper(base - 1, length - k, k)
for k in range(max_repeats+1))
return count_helper(base, length, length)
assert all(count_seq(base, length) == naive(base, length)
for base in range(7) for length in range(7))
print(count_seq(100, 60))
#21047749425803338154212116084613212619618570995864645505458031212645031666717071397
The key function is count_helper(base, length, max_repeats) that counts the number of valid sequences s.t. the most common digit does not repeat more than max_repeats times. Ignoring the base case, this function satisfies a recurrence relation:
count_helper(base, length, max_repeats) = sum(
binom(length, k) * count_helper(base - 1, length - k, k)
for k in range(max_repeats+1))
At this point, we are deciding how many copies of digit base to insert into the sequence. We can choose any number k between 0 and max_repeats inclusive. For a given, value of k, there are length choose k ways to insert the digit we are adding. Each choice of k leads to a recursive call to a subproblem where base is reduced by 1, length is reduced by k and max_repeats is set to k.
When base = 3 and length = 4, the answer would be
['0000', '0001', '0010', '0011', '0012', '0021', '0100', '0101', '0102', '0110', '0120', '0201', '0210', '1000', '1001', '1002', '1010', '1020', '1100', '1111', '1112', '1121', '1122', '1200', '1211', '1212', '1221', '2001', '2010', '2100', '2111', '2112', '2121', '2211', '2222']
We can observe that all the numbers in the answer would be permutations of ['0000', '0001', '0011', '0012', '1111', '1112', '1122', '2222']. Let us call them unique_numbers.
So, our solution is easy and simple. Generate all the unique_numbers and add their permutations to the result.
from itertools import permutations
base = 3
length = 4
unique_numbers = []
def getUniqueNumbers(curr_digit, curr_count, max_count, curr_num):
#Add the curr_num to unique_numbers
if len(curr_num) == length:
unique_numbers.append(curr_num)
return
#Try to include the curr_digit again
if curr_count + 1 <= max_count:
getUniqueNumbers(curr_digit, curr_count + 1, max_count, curr_num + str(curr_digit))
#Try to include the next digit
if curr_digit + 1 < base:
getUniqueNumbers(curr_digit+1, 1, curr_count, curr_num + str(curr_digit+1))
#Try generating unique numbers starting with every digit
for i in range(base):
getUniqueNumbers(i, 0, length, "")
result = set()
for num in unique_numbers:
permList = permutations(num)
for perm in list(permList):
result.add(''.join(perm))
print(result)

ascending order check recursively

What I try to do is to check a given array is ordered in an ascending manner by divide-and-conquer approach.
I wonder what the logic behind the additional return case (a⌊n/2⌋−1 ≤ a⌊n/2⌋) is to reach to the final result. I attempted to solve the problem without looking at its solution but I couldn't figure out how the author discovers/devises a⌊n/2⌋−1 ≤ a⌊n/2⌋ case. It is really hard for me to unearth the case.
Actually, Why not a⌊n/2⌋ ≤ a⌊n/2⌋+1 instead? And the base case case, why I'm stackoverflowed when I remove equality from the base case, that is h<l?
with trial and error approach, I tried to write the following.
def co(a, l, h):
if h <= l:
return True
mid = l + ((h-l)//2)
cl = co(a, l, mid-1)
rl = co(a, mid+1, h)
return rl and cl and a[mid] < a[mid+1]
# how is a[mid] < a[mid+1] devised ??
# why not a[mid-1] < a[mid] ??
#c = [3, 5, 7, 9, 11,12]
c = [3, 5]
print(co(c, 0, len(c) - 1))
And the base case case, why I'm stackoverflowed when I remove equality
from the base case, that is h<l?
Assume c=[3, 5]. If you replace h<=l with h<l, then when you compute co(a, 1, 1), then mid = 1+0 ... then rl = co (a, 1+1, 1) and a[2] gives you stackoverflow.
How is a[mid] <= a[mid+1] devised ??
You need to compare the most-right element of subproblem1 with the most-left element of subproblem2. The order of these two elements are not taken into account in subproblem1 and subproblem2.
Be careful with Python indexing. 1) When you split the list into a[l:mid-1] and a[mid+1,h], you leave out a[mid-1] and a[mid]. 2) When you write co(c, 0, len(c) - 1) you leave out the last element of c (see Comment4).
There are some mistakes in your code, see my comments.
def co(a, l, h):
if h <= l:
return True
mid = l + ((h-l)//2)
cl = co(a, l, mid-1)
rl = co(a, mid+1, h)
return rl and cl and a[mid] < a[mid+1] ### Comment1: this misses checking a[mid-1] <= a[mid]
# how is a[mid] < a[mid+1] devised ?? ### Comment2: you should use <= rather than <
# why not a[mid-1] < a[mid] ??
#c = [12, 3, 5, 7, 9, 11,12] ### Comment3: your code returns True for this unordered list!
#c = [3, 5, 7, 9, 11,12]
c = [3, 5]
print(co(c, 0, len(c) - 1)) ### Comment4: len(c)-1 should be len(c) otherwise it's not the whole list
Below, I fixed the list indexing in your code. Note that the test becomes h <= l+1 because in Python the list a[mid:mid+1] contains one element.
def co(a, l, h):
if h <= l+1:
return True
mid = l + ((h-l)//2)
cl = co(a, l, mid)
rl = co(a, mid, h)
return rl and cl and a[mid-1] <= a[mid]

backtracking n staircases at most k steps in a single jump

You need to climb a staircase that has n steps, and you decide to get some extra exercise by jumping up the steps. You can cover at most k steps in a single jump. Return all the possible sequences of jumps that you could take to climb the staircase, sorted.
My implementation is obviously giving me the wrong answer.
def climbingStaircase(n, k):
final_res=[]
final_res.append(CSR(n,k,[]))
return final_res
def CSR(n,k,res):
if n == 0:
return res
else:
for i in range(1,k+1):
if n-i>=0:
res.append(i)
n=n-i
res=CSR(n,i,res)
return res
For n = 4 and k = 2, the output should be
[[1, 1, 1, 1],
[1, 1, 2],
[1, 2, 1],
[2, 1, 1],
[2, 2]]
Actual output:
[[1,1,1,1,2,1]]
Can someone point out which part I'm missing?
One huge problem is in the code below: you deduct the quantity of steps for each possibility within the step range.
n=n-i
res=CSR(n,i,res)
When you're done exploring what you can do with a 1-step jump, you need to backtrack and try from the same starting point (this instance's original value of n) with a 2-step jump. Change the code to:
res = CSR(n-i, i, res)
This keeps the n value intact as you go through the loop.
In addition, you can't limit future jumps to the max of what you just took. Change that second parameter, too:
res = CSR(n-i, k, res)
That should get you moving. Also try this lovely debug blog for help. At least insert one or two tracing statements, such as
print n, k, res
at the top of your routine.
CAVEAT
This is not all of your trouble. The largest remaining problem is that CSR returns only one solution: every step you take is appended to the same list. You need a way to gather the completed solutions as separate lists; the append in climbingStaircase is executed only once, after CSR is entirely finished.
You need to recognize a completed solution at n==0.
DEBUGGING HELP
Here is a version of your program with the recursion parameters fixed, and debugging traces inserted.
indent = ""
def climbingStaircase(n, k):
final_res = []
final_res.append(CSR(n, k, []))
return final_res
def CSR(n, k, res):
global indent
indent += " "
print indent, n, k, res
if n == 0:
print "SOLUTION", res
else:
for i in range(1, k+1):
if n-i >= 0:
CSR(n-i, k, res + [i])
indent = indent[:-2]
print climbingStaircase(4, 2)
Note the use of "indent" to help visualize your recursion and backtracking. The critical part here is that, instead of updating res globally, I've left it as a local variable. I've also removed the return value for now, simply dumping to output the solutions as they're found. You can see how it works:
4 2 []
3 2 [1]
2 2 [1, 1]
1 2 [1, 1, 1]
0 2 [1, 1, 1, 1]
SOLUTION [1, 1, 1, 1]
0 2 [1, 1, 2]
SOLUTION [1, 1, 2]
1 2 [1, 2]
0 2 [1, 2, 1]
SOLUTION [1, 2, 1]
2 2 [2]
1 2 [2, 1]
0 2 [2, 1, 1]
SOLUTION [2, 1, 1]
0 2 [2, 2]
SOLUTION [2, 2]
[None]
With this stuff in place, I'm hopeful you can trace your logic and figure out how to capture the sequence of solutions at a level of your choosing.
Successfully implemented Prune's answer.
def climbingStaircase(n, k):
res=[]
CSR(n,k,[],res)
return res
def CSR(n,k,str_, res):
if n == 0:
res.append(str_)
else:
for i in range(1,k+1):
if n-i>=0:
CSR(n-i,k,str_+[i],res)
A quick Java version of this solution:
int[][] climbingStaircase(int n, int k) {
List<ArrayList<Integer>> list = new ArrayList<>();
climb(n, k, new ArrayList<Integer>(), list);
// convert to int[][]
int[][] result = new int[list.size()][];
for (int i=0; i<list.size(); i++) {
List<Integer> l = list.get(i);
int [] arr = new int[l.size()];
for (int j=0; j<l.size(); j++)
arr[j] = l.get(j);
result[i] = arr;
}
return result;
}
void climb(int n, int k, ArrayList<Integer> prev, List<ArrayList<Integer>> list) {
if (n==0) { // no more stairs, done climbing
list.add(prev);
} else {
for (int i=1; i<=k; i++) { // climb remaining stairs in intervals from 1 to k steps
if (i <= n) { // no need to test intervals larger than remaining # of stairs
ArrayList<Integer> branch = new ArrayList<>(prev);
branch.add(i);
climb(n-i, k, branch, list);
}
}
}
}
In Swift 5.5
func solution(n: Int, k: Int) -> [[Int]] {
var res_final = [[Int]]()
SRC(n: n, k: k, res: [], &res_final)
return res_final
}
var indent: String = ""
func SRC(n: Int, k: Int, res: [Int], _ res_final: inout [[Int]]) {
indent += " "
print(indent, n, k, res)
if n == .zero {
res_final.append(res)
print("Solution", res)
} else {
for i in 1...k {
if n-i >= .zero {
SRC(n: n-i, k: k, res: res + [i], &res_final)
}
}
}
indent = " "
}
solution(n: 4, k: 2)

Display all positive solutions of equation x1+x2+...+xk = n

Below is the program that displays all positive solutions of equation x1+x2+...+xk = n, where k and n are positive integers:
func solution(k: Int, n: Int) {
if k > n || k <= 0 {
print("No solution")
} else
if k==1 {
print(n)
} else {
for i in 1...(n-k+1) {
print(i, terminator:"")
solution(k-1, n: n-i)
print("")
}
}
}
solution(4, n: 4)
This program runs well with n = 4 and k = 1,2,4, but it displays incorrectly when k = 3. Can somebody helps find the mistake?
The problem is for n = 4 and case k = 1, 2, 4, there is only one solution for each i, so your print(i, terminator:"") work correctly.
However, for case k = 3, for example, after printing 1 at k = 3, so there are more than one correct cases: (1 , 2, 1) or ( 1, 1, 2), which means, just one command print(1, terminator:"") at k = 1 will not be sufficient.
Image the printing routine will be smt like:
at k = 3, i = 1, print 1
at k = 2, i = 1, print 1
at k = 1, i = 2, print 2
So, at this time, we have (1, 1, 2), looks good.
However, when we backtrack to k = 2, i = 2, print 2
at k = 1, i = 1, print 1,
So, we only have (2, 1), which is not correct.
One simple way to fix this is rather than printing at each recursive step, you just store all result in one array, and print this array when k reaches 0

How to check if this algorithm may not terminate?

Let x denote a vector of p values (i.e. a data point in p dimensional space).
I have two sets: set A of n elements A = {xi, .., xn} and a set B of m elements B = {xj, .., xm}, where |A| > 1 and |B| > 1. Given an integer k > 0, let dist(x, k, A) a function which returns the mean Euclidean distance from x to its k nearest points in A; and dist(x, k, B) the mean Euclidean distance from x to its k nearest points in B.
I have the following algorithm:
Repeat
{
A' = { x in A, such that dist(x, k, A) > dist(x, k, B) }
B' = { x in B, such that dist(x, k, A) < dist(x, k, B) }
A = { x in A such that x not in A' } U B'
B = { x in B such that x not in B' } U A'
}
Until CONDITION == True
Termination: CONDITION is True when no more elements move from A to B or from B to A (that is A' and B' becomes empty), or when |A| or |B| becomes less than or equals to 1.
1) Is it possible to prove that this algorithm terminates ?
2) And if so, is it also possible to have an upper bound for the number of iterations required to terminate ?
Note: the k nearest points to x in a set S, means: the k points (others than x) in S, having the smallest Euclidean distance to x.
It looks like this algorithm can loop forever, oscillating between two or more states. I determined this experimentally using the following Python program:
def mean(seq):
if len(seq) == 0:
raise IndexError("didn't expect empty sequence for mean")
return sum(seq) / float(len(seq))
def dist(a,b):
return abs(a-b)
def mean_dist(x, k, a):
neighbors = {p for p in a if p != x}
neighbors = sorted(neighbors, key=lambda p: dist(p,x))
return mean([dist(x, p) for p in neighbors[:k]])
def frob(a,b,k, verbose = False):
def show(msg):
if verbose:
print msg
seen_pairs = set()
iterations = 0
while True:
iterations += 1
show("Iteration #{}".format(iterations))
a_star = {x for x in a if mean_dist(x, k, a) > mean_dist(x,k,b)}
b_star = {x for x in b if mean_dist(x, k, a) < mean_dist(x,k,b)}
a_temp = {x for x in a if x not in a_star} | b_star
b_temp = {x for x in b if x not in b_star} | a_star
show("\tA`: {}".format(list(a_star)))
show("\tB`: {}".format(list(b_star)))
show("\tA becomes {}".format(list(a_temp)))
show("\tB becomes {}".format(list(b_temp)))
if a_temp == a and b_temp == b:
return a, b
key = (tuple(sorted(a_temp)), tuple(sorted(b_temp)))
if key in seen_pairs:
raise Exception("Infinite loop for values {} and {}".format(list(a_temp),list(b_temp)))
seen_pairs.add(key)
a = a_temp
b = b_temp
import random
#creates a set of random integers, with the given number of elements.
def randSet(size):
a = set()
while len(a) < size:
a.add(random.randint(0, 10))
return a
size = 2
k = 1
#p equals one because I don't feel like doing vector math today
while True:
a = randSet(size)
b = randSet(size)
try:
frob(a,b, k)
except IndexError as e:
continue
except Exception as e:
print "infinite loop detected for initial inputs {} and {}".format(list(a), list(b))
#run the algorithm again, but showing our work this time
try:
frob(a,b,k, True)
except:
pass
break
Result:
infinite loop detected for initial inputs [10, 4] and [1, 5]
Iteration #1
A`: [10, 4]
B`: [1, 5]
A becomes [1, 5]
B becomes [10, 4]
Iteration #2
A`: [1, 5]
B`: [10, 4]
A becomes [10, 4]
B becomes [1, 5]
Iteration #3
A`: [10, 4]
B`: [1, 5]
A becomes [1, 5]
B becomes [10, 4]
In this case, the loop never terminates because A and B continually switch entirely. While experimenting with larger set sizes, I found a case where only some elements switch:
infinite loop detected for initial inputs [8, 1, 0] and [9, 4, 5]
Iteration #1
A`: [8]
B`: [9]
A becomes [0, 1, 9]
B becomes [8, 4, 5]
Iteration #2
A`: [9]
B`: [8]
A becomes [0, 1, 8]
B becomes [9, 4, 5]
Iteration #3
A`: [8]
B`: [9]
A becomes [0, 1, 9]
B becomes [8, 4, 5]
Here, elements 8 and 9 move back and forth while the other elements stay in place.

Resources