How to generate combinations in chunks - algorithm

I have algorithm which performs calculations for every combination of input elements, e.g. for every 5-element subset out of 100-element set. I am porting it to GPU, and now I have initial version of it ready. In order to speed it up I would like to load data from local memory, which is limited (e.g. 32KB) and can hold something like 20 input elements out of 100. So I have to somehow partition my work and generate combinations in chunks. And now this is the hard part, how to do this. Most probably I have to first load data for 20 elements and perform calculations for 5-elements subsets of these 20 elements. After this I would have to replace some (or all) of them with new ones and perform calculations for them, then rinse and repeat. Could you tell me how should I choose replaced elements in local memory, to avoid duplicate work? So far I came to conclusion that I would have to replace at least 16 of them at once to avoid duplicated work problem.
Edit: here is example for generating of 2-element combinations out of 5 elements. Here is full list of all possible cases:
1, 2
1, 3
1, 4
1, 5
2, 3
2, 4
2, 5
3, 4
3, 5
4, 5
Local memory on GPU is limited - lets assume it can hold 3 elements only. So I have to somehow divide my problem into generating combinations of 2 elements out of 3. I have to repeat this multiple times, until I get all combinations from above list. As a first step I can load elements 1, 2, 3 into local memory, so I will get following combinations:
1, 2
1, 3
2, 3
Now I have to load another set of elements and calculate combinations for them. It can be 1, 4, 5. It will produce following combinations:
1, 4
1, 5
4, 5
On the other hand set 1, 2, 4 is invalid - it would result in duplicated combination:
1, 2 // duplicate
1, 4 // ok, new
2, 4 // ok, new
After this step there are 4 more combinations to be generated (list is below). Algorithm has to be able to generate another 2-element combination out of 3 elements, and to somehow handle last (10th) combination.
2, 4
2, 5
3, 4
3, 5
By splitting work in this way, I would be able to process all combinations of original input set using limited local memory which can hold only part of it.

Say e.g. you have 100 elements, you can hold 20 in memory, and you need all the 5-element combinations, of which there are C(100,5) = 75,287,520.
In order to be able to generate all the combinations, every combination of 5 elements has to be in memory at some point. This can be done by dividing the elements into groups of 20/5 = 4 elements; there are 25 of these groups in the input, and C(25,5) = 53,130 combinations of 5 groups.
For every combination of groups, we will start by generating the combinations with one element from each of the five groups; this gives us 53,130 x 45 = 54,405,120 unique combinations.
We now have the combinations where each element comes from a different group, which is the partition [1,1,1,1,1]. We still have to find the combinations for the partitions [2,1,1,1], [2,2,1], [3,1,1], [3,2] and [4,1]. The easiest way will be to do this in seperate stages, but the fastest way will of course be to incorporate these into the first stage for [1,1,1,1,1], because all the combinations of groups we'll ever need are loaded in memory at some point during the first stage.
For the partition [2,1,1,1], we'd load every group in turn as the group with 2 elements, and then load every combination of 3 groups from the remaining 24 groups and take an element from each of them. That would require 25 x C(24,3) = 50,600 steps, each resulting in C(4,2) x 43 = 384 combinations, or a total of 19,430,400.
A partition like [2,2,1] is a bit different, because we'd load every group in turn to be the first group with 2 elements, but only the groups that come after it as the second group with 2 elements, to avoid duplicates. Then for each of these, we'd load each of the other 23 groups to get the final element. That would require C(25,2)/2 x 23 = 6,900 steps, each resulting in C(4,2) x C(4,2) x C(4,1) = 144 combinations, for a total of 993,600.
The partition [3,1,1] requires 25 x C(24,2) = 25 x 276 = 6,900 steps, each resulting in C(4,3) x 42 = 64 combinations, for a total of 441,600.
The partition [3,2] requires 25 x 24 = 600 steps, each resulting in C(4,3) x C(4,2) = 24 combinations, for a total of 14,400.
The partition [4,1] requires 25 x 24 = 600 steps, each resulting in C(4,4) x C(4,1) = 4 combinations, for a total of 2,400.
So we have a total of:
[1,1,1,1,1] -> 54,405,120
[2,1,1,1] -> 19,430,400
[2,2,1] -> 993,600
[3,1,1] -> 441,600
[3,2] -> 14,400
[4,1] -> 2,400
----------
75,287,520 combinations
As you'll have noticed, the partitions [3,2] and [4,1] both require every combination of two groups, so they can be easily integrated into one stage. Of course, if you integrate them all into the first stage for [1,1,1,1,1], you'll only have to load 53,130 combinations of groups into memory, which is the absolute minimum.
(If it's faster to load only one new group of elements into memory at each step, instead of running through the combinations of groups in lexicographical order, check out this answer.)
Integration of different stages
The simplest way to run through all the group combinations for the partition [1,1,1,1,1] is to load groups 1 to 21 as group A, then all groups after A up to 22 as group B, all groups after B up to 23 as group C, all groups after C up to 24 as group D, and all groups after D up to 25 as group E.
A B C D E
1 2 3 4 5 <- ABCDE
1 2 3 4 6 <- ABCDE
...
1 2 3 4 25 <- ABCDE
1 2 3 5 6 <- ABCDE
...
1 2 3 24 25 <- ABCDE
1 2 4 5 6 <- ABCDE
...
1 2 23 24 25 <- ABCDE
1 3 4 5 6 <- ABCDE
...
1 22 23 24 25 <- ABCDE
2 3 4 5 6 <- ABCDE
...
21 22 23 24 25 <- ABCDE
The partitions with four parts can be integrated by taking [2,1,1,1], [1,2,1,1], [1,1,2,1] and [1,1,1,2] elements from these combinations of four groups:
A B C D E
1 2 3 4 5 <- ABCD ABCE ABDE ACDE BCDE
1 2 3 4 6 <- ABCE ABDE ACDE BCDE
...
1 2 3 4 25 <- ABCE ABDE ACDE BCDE
1 2 3 5 6 <- ABDE ACDE BCDE
...
1 2 3 24 25 <- ABDE ACDE BCDE
1 2 4 5 6 <- ACDE BCDE
...
1 2 23 24 25 <- ACDE BCDE
1 3 4 5 6 <- BCDE
...
1 22 23 24 25 <- BCDE
2 3 4 5 6 <- none
...
21 22 23 24 25 <- none
The partitions with three parts can be integrated by taking [2,2,1], [2,1,2], [1,2,2], [3,1,1], [1,3,1] and [1,1,3] elements from these combinations of three groups:
A B C D E
1 2 3 4 5 <- ABC ABD ACD BCD ABE ACE BCE ADE BDE CDE
1 2 3 4 6 <- ABE ACE BCE ADE BDE CDE
...
1 2 3 4 25 <- ABE ACE BCE ADE BDE CDE
1 2 3 5 6 <- ADE BDE CDE
...
1 2 3 24 25 <- ADE BDE CDE
1 2 4 5 6 <- CDE
...
1 2 23 24 25 <- CDE
1 3 4 5 6 <- none
...
21 22 23 24 25 <- none
The partitions with two parts can be integrated by taking [2,3], [3,2], [4,1] and [1,4] elements from these combinations of two groups:
A B C D E
1 2 3 4 5 <- AB AC BC AD BD CD AE BE CE DE
1 2 3 4 6 <- AE BE CE DE
...
1 2 3 4 25 <- AE BE CE DE
1 2 3 5 6 <- DE
...
1 2 3 24 25 <- DE
1 2 4 5 6 <- none
...
21 22 23 24 25 <- none
In general
There are e elements, m elements can be loaded in memory, and you want all combinations of k elements. Use k groups of size g = m/k.
Generate all partitions of k with parts limited to size g:
[1,1,1 ... 1] [2,1,1 ... 1] [2,2,1 ... 1] ... [k] (if k <= g)
[1,1,1 ... 1] [2,1,1 ... 1] [2,2,1 ... 1] ... [g,k-g] (if k > g)
For each of these, generate all unique permutations, e.g.:
[3,2,2,1] -> [3,2,2,1] [3,2,1,2] [3,1,2,2]
[2,3,2,1] [2,3,1,2] [1,3,2,2]
[2,2,3,1] [2,1,3,2] [1,2,3,2]
[2,2,1,3] [2,1,2,3] [1,2,2,3]
Sort the permutations per number of parts, e.g.:
k: [1,1,1 ... 1]
k-1: [2,1 ... 1] [1,2 ... 1] ... [1,1 ... 2]
...
2: [g,k-g] [k-g,g]
Load the first k groups into memory, e.g.:
A B C D E F
1 2 3 4 5 6
For every length of partition p, generate every set of groups of size p, e.g.:
p=k: ABCDEF C(k,k) sets
p=k-1: ABCDE ABCDF ABCEF ABDEF ACDEF BCDEF C(k,k-1) sets
p=k-2: ABCD ABCE ABCF ABDE ABDF ... CDEF C(k,k-2) sets
...
p=2: AB AC AD AE AF BC BD BE BF ... EF C(k,2) sets
For each of these sets, generate the combinations for the partitions with the corresponding number of parts, e.g.:
p=k-1: ABCDE [2,1,1,1,1] -> [a,a,b,c,d,e] C(g,2)*C(g,1)^4 combinations
[1,2,1,1,1] -> [a,b,b,c,d,e]
[1,1,2,1,1] -> [a,b,c,c,d,e]
[1,1,1,2,1] -> [a,b,c,d,d,e]
[1,1,1,1,2] -> [a,b,c,d,e,e]
ABCDE [2,1,1,1,1] -> [a,a,b,c,d,f]
[1,2,1,1,1] -> [a,b,b,c,d,f]
[1,1,2,1,1] -> [a,b,c,c,d,f]
[1,1,1,2,1] -> [a,b,c,d,d,f]
[1,1,1,1,2] -> [a,b,c,d,f,f]
...
BCDEF [2,1,1,1,1] -> [b,b,c,d,e,f]
[1,2,1,1,1] -> [b,c,c,d,e,f]
[1,1,2,1,1] -> [b,c,d,d,e,f]
[1,1,1,2,1] -> [b,c,d,e,e,f]
[1,1,1,1,2] -> [b,c,d,e,f,f]
From the list of sets, remove the sets which do not contain the last group (F):
p=k: ABCDEF
p=k-1: ABCDF ABCEF ABDEF ACDEF BCDEF
p=k-2: ABCF ABDF ABEF ACDF ACEF ADEF BCDF BCEF BDEF CDEF
...
p=2: AF BF CF DF EF
Load the next groups up to e/g into memory as group F, e.g.:
A B C D E F
1 2 3 4 5 7
...
1 2 3 4 5 e/g
Again, for each of these, and for each of the sets, generate the combinations for the partitions with the corresponding number of parts.
From the list of sets, remove the sets which do not contain the two last groups (EF):
p=k: ABCDEF
p=k-1: ABCEF ABDEF ACDEF BCDEF
p=k-2: ABEF ACEF ADEF BCEF BDEF CDEF
...
p=2: EF
Load the next groups up to e/g-1 into memory as group E, and for each of these, load the groups after E up to e/g into memory as group F, e.g.:
A B C D E F
1 2 3 4 6 7
...
1 2 3 4 e/g-1 e/g
Again, for each of these, and for each of the sets, generate the combinations for the partitions with the corresponding number of parts.
From the list of sets, remove the sets which do not contain the three last groups (DEF):
p=k: ABCDEF
p=k-1: ABDEF ACDEF BCDEF
p=k-2: ADEF BDEF CDEF
...
p=2: none
Load the next groups up to e/g-2 into memory as group D, and for each of these, load the groups after D up to e/g-1 into memory as group E, and for each of these, load the groups after E up to e/g into memory as group F, e.g.:
A B C D E F
1 2 3 5 6 7
...
1 2 3 e/g-2 e/g-1 e/g
Again, for each of these, and for each of the sets, generate the combinations for the partitions with the corresponding number of parts.
And so on, until you reach:
A B C D E F
e/g-5 e/g-4 e/g-3 e/g-2 e/g-1 e/g
with only:
p=k: ABCDEF
Run-through for 21,9,3
number of elements: e = 21
elements: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21]
size of combinations: k = 3 elements
size of memory: m = 9 elements
Preparations:
number of groups in memory: 3 (k)
group size: g = m/k = 3 elements
number of groups: e/g = 7
groups: 1:[1,2,3] 2:[4,5,6] 3:[7,8,9] 4:[10,11,12] 5:[13,14,15] 6:[16,17,18] 7:[19,20,21]
number of element sets loaded into memory: C(e/g,k) = C(7,3) = 35
partitions of k with max part g: [1,1,1] [2,1] [3]
permutations: 3:{[1,1,1]} 2:{[1,2],[2,1]} 1:{[3]}
group sets: 3:{[A,B,C]} 2:{[A,B],[A,C],[B,C]} 1:{[A],[B],[C]}
Phase 1:
group sets: 3:{[A,B,C]} 2:{[A,B],[A,C],[B,C]} 1:{[A],[B],[C]} (all)
A B C
1 2 3 -> elements in memory: [1,2,3] [4,5,6] [7,8,9] -> 84 combinations
3: [1,1,1]:[A,B,C] -> [a,b,c] -> [1,4,7] [1,4,8] [1,4,9] [1,5,7] [1,5,8] [1,5,9] [1,6,7] [1,6,8] [1,6,9]
[2,4,7] [2,4,8] [2,4,9] [2,5,7] [2,5,8] [2,5,9] [2,6,7] [2,6,8] [2,6,9]
[3,4,7] [3,4,8] [3,4,9] [3,5,7] [3,5,8] [3,5,9] [3,6,7] [3,6,8] [3,6,9]
2: [1,2]:[A,B] -> [a,b,b] -> [1,4,5] [1,4,6] [1,5,6] [2,4,5] [2,4,6] [2,5,6] [3,4,5] [3,4,6] [3,5,6]
[1,2]:[A,C] -> [a,c,c] -> [1,7,8] [1,7,9] [1,8,9] [2,7,8] [2,7,9] [2,8,9] [3,7,8] [3,7,9] [3,8,9]
[1,2]:[B,C] -> [b,c,c] -> [4,7,8] [4,7,9] [4,8,9] [5,7,8] [5,7,9] [5,8,9] [6,7,8] [6,7,9] [6,8,9]
[2,1]:[A,B] -> [a,a,b] -> [1,2,4] [1,3,4] [2,3,4] [1,2,5] [1,3,5] [2,3,5] [1,2,6] [1,3,6] [2,3,6]
[2,1]:[A,C] -> [a,a,c] -> [1,2,7] [1,3,7] [2,3,7] [1,2,8] [1,3,8] [2,3,8] [1,2,9] [1,3,9] [2,3,9]
[2,1]:[B,C] -> [b,b,c] -> [4,5,7] [4,6,7] [5,6,7] [4,5,8] [4,6,8] [5,6,8] [4,5,9] [4,6,9] [5,6,9]
1: [3]:[A] -> [a,a,a] -> [1,2,3]
[3]:[B] -> [b,b,b] -> [4,5,6]
[3]:[C] -> [c,c,c] -> [7,8,9]
Phase 2:
group sets: 3:{[A,B,C]} 2:{[A,C],[B,C]} 1:{[C]} (sets without C removed)
A B C
1 2 4 -> elements in memory: [1,2,3] [4,5,6] [10,11,12] -> 64 combinations
3: [1,1,1]:[A,B,C] -> [a,b,c] -> [1,4,10] [1,4,11] [1,4,12] [1,5,10] [1,5,11] [1,5,12] [1,6,10] [1,6,11] [1,6,12]
[2,4,10] [2,4,11] [2,4,12] [2,5,10] [2,5,11] [2,5,12] [2,6,10] [2,6,11] [2,6,12]
[3,4,10] [3,4,11] [3,4,12] [3,5,10] [3,5,11] [3,5,12] [3,6,10] [3,6,11] [3,6,12]
2: [1,2]:[A,C] -> [a,c,c] -> [1,10,11] [1,10,12] [1,11,12] [2,10,11] [2,10,12] [2,11,12] [3,10,11] [3,10,12] [3,11,12]
[1,2]:[B,C] -> [b,c,c] -> [4,10,11] [4,10,12] [4,11,12] [5,10,11] [5,10,12] [5,11,12] [6,10,11] [6,10,12] [6,11,12]
[2,1]:[A,C] -> [a,a,c] -> [1,2,10] [1,3,10] [2,3,10] [1,2,11] [1,3,11] [2,3,11] [1,2,12] [1,3,12] [2,3,12]
[2,1]:[B,C] -> [b,b,c] -> [4,5,10] [4,6,10] [5,6,10] [4,5,11] [4,6,11] [5,6,11] [4,5,12] [4,6,12] [5,6,12]
1: [3]:[C] -> [c,c,c] -> [10,11,12]
A B C
1 2 5 -> elements in memory: [1,2,3] [4,5,6] [13,14,15] -> 64 combinations
1 2 6 -> elements in memory: [1,2,3] [4,5,6] [16,17,18] -> 64 combinations
1 2 7 -> elements in memory: [1,2,3] [4,5,6] [19,20,21] -> 64 combinations
Phase 3:
group sets: 3:{[A,B,C]} 2:{[B,C]} (sets without B removed)
A B C
1 3 4 -> elements in memory: [1,2,3] [7,8,9] [10,11,12] -> 45 combinations
3: [1,1,1]:[A,B,C] -> [a,b,c] -> [1,7,10] [1,7,11] [1,7,12] [1,8,10] [1,8,11] [1,8,12] [1,9,10] [1,9,11] [1,9,12]
[2,7,10] [2,7,11] [2,7,12] [2,8,10] [2,8,11] [2,8,12] [2,9,10] [2,9,11] [2,9,12]
[3,7,10] [3,7,11] [3,7,12] [3,8,10] [3,8,11] [3,8,12] [3,9,10] [3,9,11] [3,9,12]
2: [1,2]:[B,C] -> [b,c,c] -> [7,10,11] [7,10,12] [7,11,12] [8,10,11] [8,10,12] [8,11,12] [9,10,11] [9,10,12] [9,11,12]
[2,1]:[B,C] -> [b,b,c] -> [7,8,10] [7,9,10] [8,9,10] [7,8,11] [7,9,11] [8,9,11] [7,8,12] [7,9,12] [8,9,12]
A B C
1 3 5 -> elements in memory: [1,2,3] [7,8,9] [13,14,15] -> 45 combinations
1 3 6 -> elements in memory: [1,2,3] [7,8,9] [16,17,18] -> 45 combinations
1 3 7 -> elements in memory: [1,2,3] [7,8,9] [19,20,21] -> 45 combinations
1 4 5 -> elements in memory: [1,2,3] [7,8,9] [13,14,15] -> 45 combinations
1 4 6 -> elements in memory: [1,2,3] [7,8,9] [16,17,18] -> 45 combinations
1 4 7 -> elements in memory: [1,2,3] [7,8,9] [19,20,21] -> 45 combinations
1 5 6 -> elements in memory: [1,2,3] [7,8,9] [16,17,18] -> 45 combinations
1 5 7 -> elements in memory: [1,2,3] [7,8,9] [19,20,21] -> 45 combinations
1 6 7 -> elements in memory: [1,2,3] [7,8,9] [19,20,21] -> 45 combinations
Phase 4:
group sets: 3:{[A,B,C]} (sets without A removed)
A B C
2 3 4 -> elements in memory: [4,5,6] [7,8,9] [10,11,12] -> 27 combinations
3: [1,1,1]:[A,B,C] -> [a,b,c] -> [4,7,10] [4,7,11] [4,7,12] [4,8,10] [4,8,11] [4,8,12] [4,9,10] [4,9,11] [4,9,12]
[5,7,10] [5,7,11] [5,7,12] [5,8,10] [5,8,11] [5,8,12] [5,9,10] [5,9,11] [5,9,12]
[6,7,10] [6,7,11] [6,7,12] [6,8,10] [6,8,11] [6,8,12] [6,9,10] [6,9,11] [6,9,12]
A B C
2 3 5 -> elements in memory: [4,5,6] [7,8,9] [13,14,15] -> 27 combinations
2 3 6 -> elements in memory: [4,5,6] [7,8,9] [16,17,18] -> 27 combinations
2 3 7 -> elements in memory: [4,5,6] [7,8,9] [19,20,21] -> 27 combinations
2 4 5 -> elements in memory: [4,5,6] [10,11,12] [13,14,15] -> 27 combinations
2 4 6 -> elements in memory: [4,5,6] [10,11,12] [16,17,18] -> 27 combinations
2 4 7 -> elements in memory: [4,5,6] [10,11,12] [19,20,21] -> 27 combinations
2 5 6 -> elements in memory: [4,5,6] [13,14,15] [16,17,18] -> 27 combinations
2 5 7 -> elements in memory: [4,5,6] [13,14,15] [19,20,21] -> 27 combinations
2 6 7 -> elements in memory: [4,5,6] [16,17,18] [19,20,21] -> 27 combinations
3 4 5 -> elements in memory: [7,8,9] [10,11,12] [13,14,15] -> 27 combinations
3 4 6 -> elements in memory: [7,8,9] [10,11,12] [16,17,18] -> 27 combinations
3 4 7 -> elements in memory: [7,8,9] [10,11,12] [19,20,21] -> 27 combinations
3 5 6 -> elements in memory: [7,8,9] [13,14,15] [16,17,18] -> 27 combinations
3 5 7 -> elements in memory: [7,8,9] [13,14,15] [19,20,21] -> 27 combinations
3 6 7 -> elements in memory: [7,8,9] [16,17,18] [19,20,21] -> 27 combinations
4 5 6 -> elements in memory: [10,11,12] [13,14,15] [16,17,18] -> 27 combinations
4 5 7 -> elements in memory: [10,11,12] [13,14,15] [19,20,21] -> 27 combinations
4 6 7 -> elements in memory: [10,11,12] [16,17,18] [19,20,21] -> 27 combinations
5 6 7 -> elements in memory: [13,14,15] [16,17,18] [19,20,21] -> 27 combinations
Results:
Phase 1: 84 = 84 combinations
Phase 2: 4 x 64 = 256 combinations
Phase 3: 10 x 45 = 450 combinations
Phase 4: 20 x 27 = 540 combinations
----
1330 combinations = C(21,3)

Depending on how heavy is the calculation you do for each combination, the fastest way might be to partition the range 0..C(n,k) into a bunch of subranges to be processed in parallel, and within each subrange generate the respective combinations directly on the GPU, using an unranking function to generate the first one and the classical algorithm for finding the next combination to generate subsequent ones.

Related

How to obtain efficiently a matrix that adds all elements up to/from (i,j) in Julia

P is a given matrix (i.g., 2-dimensional array). The size of P is I×J.
I want to have two matrices, A and B, such that
The sizes of A and B are the same as the size of P.
Then, I wrote the following code in Julia 1.6.
f(P) = cumsum(cumsum(P,dims=1),dims=2)
g(P) = f( reverse!(reverse!(P,dims=1),dims=2))[end:-1:begin,end:-1:begin]
It works. Here is an example.
P = rand(1:6,3,4)
#3×4 Matrix{Int64}:
# 4 1 1 4
# 2 2 6 6
# 3 1 5 2
f(P)
#3×4 Matrix{Int64}:
# 4 5 6 10
# 6 9 16 26
# 9 13 25 37
g(P)
#3×4 Matrix{Int64}:
# 37 28 24 12
# 27 22 19 8
# 11 8 7 2
Does anybody have a better idea? If there is a faster method, I'd like to know.
Context
I want to expand the above functions f and g to tensors, that it, P is a tensor (i.g., d-dimensional array) whose size is I1×…×Id. I want to make two arrays, A and B, such that

Algorithm help :Given a num, can it finally be 1

Given a number, you can divide it or its contiguous part by 2 , or multiply it or its continuous part by 2. Can the number finally be 1?
For example : 13
3 is a part of 13, first we take 3 * 2 = 6, the num turn to be 16,
second we can operate the whole num 16, 16 / 2 = 8, the num is 8 now,
8/2 = 4, num is 4 now,
4/2 = 2, num is 2 now,
2/2 = 1, num is 1 now.
finally we can say 13 can turn into 1, and the path is 13->16->8->4->2->1, we can use a List to store the path.
Example :27
first we operate the whole num 27, 27 * 2 = 54;
then we take 4 as the part of 54, 4 / 2 = 2 , so the 4 is 2 now, num becomes 52;
operate 52, 52 / 2 = 26, num is 26 now;
operate 26, 26 / 2 = 13, num is 13 now;
we just analyzed 13, so 27 can turn into 1 finally.
How to analyze such problem? What's the main idea of solving such type problem?
Sorry about the confusing description, let's take a more complex example: 316
16 is a contiguous part, let 16 / 2 = 8 , so the num is 38 now,
then take 8 / 2 = 4 , the num is 34,
take 4 / 2 = 2, the num is 32,
now take the whole num 32 / 2 = 16,
16 / 2 = 8, num is 8,
8 / 2 = 4, num is 4,
4 / 2 = 2, num is 2,
finally 2 / 2 = 1.
We say, original num 316 can turn into 1 finally after above conversion.
And the contiguous part means, if the input num is 12345,
then 123, 234,345,12,2345 and so on, they are all contiguous parts.
Any continuous subset of num is fine,including head or tail is NOT necessary.
The question is :
How to judge such a num? And if the num can turn into 1, print the path.
Can you find the shortest way?
I got some hints from interviewer (The interview is over):
Most of numbers are eligible, that means nums which are NOT eligible, these characteristics are obvious.
Brute fore way's time complexity is too high, we should pruning timely. (Slide window + pruning ?)
Here is a simple and unoptimized breadth-first search.
def shortest_digit_path (n):
path_from = {n: None}
queue = [n]
count = 0
while True:
m = queue.pop(0)
count += 1
if 0 == count %1000:
print((count, m))
if m == 1:
break
x = str(m)
for i in range(len(x)):
for j in range(i+1, len(x) + 1):
y = x[0:i]
z = x[i:j]
w = x[j:]
if z[0] == '0':
continue # The continuous section is not a proper number.
# Try half of z
if z[-1] in ['2', '4', '6', '8']:
next_m = int(y + str(int(z)//2) + w)
if next_m not in path_from:
path_from[next_m] = m
queue.append(next_m)
# Try doubling z
next_m = int(y + str(int(z)*2) + w)
if next_m not in path_from:
path_from[next_m] = m
queue.append(next_m)
path = []
while m is not None:
path.append(m)
m = path_from[m]
return list(reversed(path))
After playing around with this for a bit, I came up with the following observations.
If the number ends in 0 or 5, there is no path to having any other digit at the end, and therefore you can't get to 1. (The above function will just run forever.
For anything else we can find a path just dealing with 1-2 digits at a time.
Here are the special cases for observation #2. Our first goal is to get to just 0, 1, and 5 as digits.
0: 0
1: 1
2: 2 -> 1
3: 3 -> 6 -> 12 -> 24 -> 28 -> 56 -> 112 -> 16 -> 8 -> 4 -> 2 -> 1
4: 4 -> 2 -> 1
5: 5
6: 6 -> 12 -> 24 -> 28 -> 56 -> 112 -> 16 -> 8 -> 4 -> 2 -> 1
7: 7 -> 14 -> 28 -> 56 -> 112 -> 16 -> 8 -> 4 -> 2 -> 1
8: 8 -> 4 -> 2 -> 1
9: 9 -> 18 -> 28 -> 56 -> 112 -> 16 -> 8 -> 4 -> 2 -> 1
And now from the start of the number we have to deal with the following cases that reduce the number of digits and get back to our desired form.
10: 10 -> 5
11: 11 -> 22 -> 24 -> 28 -> 56 -> 112 -> 16 -> 8 -> 4 -> 2 -> 1
15: 15 -> 110 -> 220 -> 240 -> 280 -> 560 -> 1120 -> 160 -> 80 -> 40 -> 20 -> 10 -> 5
50: 50 -> 25 -> 15 -> 110 -> 220 -> 240 -> 280 -> 560 -> 1120 -> 160 -> 80 -> 40 -> 20 -> 10 -> 5
51: 51 -> 52 -> 26 -> 16 -> 8 -> 4 -> 2 -> 1
55: 55 -> 510 -> 520 -> 260 -> 160 -> 80 -> 40 -> 20 -> 10 -> 5
With this set of rules we can first normalize the number to a standard form, then we can shorten it one digit at a time. This lets us essentially instantly come up with a path. Almost certainly not the shortest one, but definitely a path.
Writing that function is left as an exercise to the reader.
Now back to the shortest path. The algorithm for the breadth-first search can be made much faster if we start with a breadth-first search from both ends and meet in the middle. For this you'd need to also have a path_to that is initialized with {1: None}, a queue containing elements of the form (m, is_rising) and initialize it with [(1, True), (n: False)]. You'd then have to branch on is_rising and before entering values into path_from/path_to check for whether it is in path_to/path_from. If it is, you've met in the middle. Now work out both halves of the path and join them together.
The approach is tricker. But it will let you find the shortest path in the square root of the number of steps that the current approach takes.

Split a subset with a constraint

Today, while practicing some Algorithm questions I found an interesting question.
The question is
You have to divide 1 to n (with one missing value x ) into two equal
halfs such that sum of the two halfs are equal.
Example:
If n = 7 and x = 4
The solution will be {7, 5} and {1, 2, 3, 6}
I can answer it with brute force method but i want an efficient solution
Can any one help me out?
If the sum of the elements 1→N without x is odd then there is no solution.
Otherwise you can find your solution in O(N) with balanced selection.
4 in a row
First let us consider that any sequence of four contiguous numbers can be split in two sets with equal sum given that:
[x, x+1, x+2, x+3] → [x+3, x];[x+2, x+1]
Thus selecting them and placing them in sets A B B A balances sets A and B.
4 across
Moreover, when we have two couples across an omitted value, it can hold a similar property:
[x-2, x-1, x+1, x+2] → [x+2, x-2]; [x+1, x-1]
so still A B B A
At this point we can fix the following cases:
we have a quadruplet: we split it as in case 1
we have 2 numbers, x and other 2 numbers: we split as in case 2
Alright, but it can happen we have 3 numbers, x and other 3 numbers, or other conditions. How can we select in balanced manner anyway?
+2 Gap
If we look again at the gap across x:
[x-1, x+1]
we can notice that somehow if we split the two neighbors in two separate sets we must balance a +2 on the set with bigger sum.
Balancing Tail
We can do this by using the last four numbers of the sequence:
[4 3 2 1] → [4, 2] ; [3, 1] → 6 ; 4
Finally we have to consider that we might not have one of them, so let's build the other case:
[3 2 1] → [2] ; [3, 1] → 2 ; 4
and let us also realize we can do the very same at the other end of the sequence with an A B A B (or B A B A) pattern - if our +2 stands on B (or A);
4 across +
It is amazing that 4 across still holds if we jump h (odd!) numbers:
[x+3, x+2, x-2, x-3] → [x+3, x-3]; [x+2, x-2]
So, exploring the array we can draw the solution step by step
An example:
11 10 9 8 7 6 5 4 3 2 1
the sum it's even, so x can be only an even number:
x = 10
11 - 9 | 8 7 6 5 | 4 3 2 1 → (+2 gap - on A) (4 in a row) (balancing tail)
A B A B B A B A B A
x = 8
11 10 | 9 - 7 | 6 5 | 4 3 2 1 → (4 across +) (+2 gap - on A) (balancing tail)
a b A B | b a | B A B A
x = 6
11 10 9 8 | 7 - 5 | 4 3 2 1 → (4 in a row) (+2 gap - on A) (balancing tail)
A B B A A B A B B B
x = 4 we have no balancing tail - we have to do that with head
11 10 9 8 | 7 6 | 5 - 3 | 2 1 → (balancing head) (4 across +) (+2 gap)
A B A B A B | b a | B A
x = 2
11 10 9 8 | 7 6 5 4 | 3 - 1 → (balancing head) (4 in a row) (+2 gap)
A B A B A B B A B A
It is interesting to notice the symmetry of the solutions. Another example.
10 9 8 7 6 5 4 3 2 1
the sum it's odd, so x can be only an odd number, and the number of elements now is odd.
x = 9
10 - 8 | 7 6 5 4 | 3 2 1 → (+2 gap - on A) (4 in a row) (balancing tail)
A B A B B A B A B
x = 7
10 9 | 8 - 6 | 5 4 | 3 2 1 → (4 across +) (+2 gap - on A) (balancing tail)
a b | A B | b a B A B
x = 5
10 9 8 7 | 6 - 4 | 3 2 1 → (4 in a row) (+2 gap - on A) (balancing tail)
A B B A A B B A B
x = 3
10 9 8 7 | 6 5 | 4 - 2 | 1 → (balancing head) (4 across + virtual 0) (+2 gap)
A B A B B A | a b | A
x = 1
10 9 8 7 | 6 5 4 3 | 2 → (balancing head) (4 in a row) (+2 gap virtual 0)
A B A B A B B A B
Finally it is worth to notice we can switch from A to B whenever we have a full balanced segment (i.e. 4 in a row or 4 across)
Funny said - but the property requesting the sum([1 ... N]-x) to be even makes the cases quite redundant if you try yourself.
I am pretty sure this algorithm can be generalized - I'll probably provide a revised version soon.
This problem can be solved by wrapping the standard subset sum problem of dynamic programming with preprocessing steps. These steps are of O(1) com
Algorithm (n, x):
sum = n * (n+1) / 2
neededSum = sum - x
If (neededSum % 2 != 0): return 0
create array [1..n] and remove x from it
call standard subsetsum(arr, 0, neededSum/2, [])
Working python implementation of subsetsum algorithm - printing all subsets is given below.
def subsetsum(arr, i, sum, ss):
if i >= len(arr):
if sum == 0:
print ss
return 1
else:
return 0
ss1 = ss[:]
count = subsetsum(arr, i + 1, sum, ss1)
ss1.append(arr[i])
count += subsetsum(arr, i + 1, sum - arr[i], ss1)
return count
arr = [1, 2, 3, 10, 5, 7]
sum = 14
a = []
print subsetsum(arr, 0, sum, a)
Hope it helps!

Algorithm to calculate the price volatility of a commodity

I am trying to design an algorithm to calculate how volatile the price fluctuations of a commodity are.
The way I would like this to work is that if the price of a commodity constantly goes up and down, it should have a higher score than if the price of the commodity gradually increases and then falls in price rapidly.
Here is an example of what I mean:
Commodity A: 1 -> 2 -> 3 -> 2 -> 1 -> 3 -> 4 -> 2 -> 1
Commodity B: 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 2
Commodity C: 1 -> 2 -> 3 -> 4 -> 5 -> 4 -> 3 -> 2-> 1
Commodity A has a 'wave' like pattern in that its price goes up and falls down on a regular basis.
Commodity B has a 'cliff' like pattern in that the price goes up gradually and then falls steeply.
Commodity C has a 'hill' like pattern in that the price rises gradually and then falls gradually.
A should receive the highest ranking, followed by C, followed by B. The more of a wave pattern the price of the commodity follows, the higher a ranking it should have.
Does have any suggestions for an algorithm that could do this?
Thanks!
My Approach looks something like this.
For my algorithm, I am considering the above example.
A: 1 -> 2 -> 3 -> 2 -> 1 -> 3 -> 4 -> 2 -> 1
B: 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 2
C: 1 -> 2 -> 3 -> 4 -> 5 -> 4 -> 3 -> 2-> 1
Now I will squash these list, by squash i mean taking the start value and end value of an increasing or decreasing sequence.
So, after squashing the list will look something like this.
A: 1 -> 3 -> 1 -> 4 -> 1
B: 1 -> 8 -> 2
C: 1 -> 5 -> 1
Now once this it done, I take the difference between i and i+1 element and then take the average and based on the average, I give them the rank.
So the difference between i and i+1 element will look something like this
2 2 3 3
A: 1 --> 3 --> 1 --> 4 --> 1
7 6
B: 1 --> 8 --> 2
4 4
C: 1 --> 5 --> 1
Now let's sum this difference and take the average.
A: (2+2+3+3)/4 = 2.5
B: (7+6)/2 = 6.5
C: (4+4)/2 = 4
Now we can assign ranks based on this average value where
A < C < B
Hope this helps!

Finding the root value of a binary tree?

I have an array which stores the relations of values, which makes several trees something like:
So, in this case, my array would be (root, linked to)
(8,3)
(8,10)
(3,1)
(3,6)
(6,4)
(6,7)
(10,14)
(14,13)
And i'd like to set all the root values in the array to the main root in the tree (in all trees):
(8,3)
(8,1)
(8,6)
(8,4)
(8,7)
(8,10)
(8,14)
(8,13)
What algorithm should i investigate?
1) Make a list of all the unique first elements of the tuples.
2) Remove any that also appear as the second element of a tuple.
3) You'll be left with the root (8 here). Replace the first elements of all tuples with this value.
EDIT:
A more complicated approach that will work with multiple trees would be as follows.
First, convert to a parent lookup table:
1 -> 3
3 -> 8
4 -> 6
6 -> 3
7 -> 6
10 -> 8
13 -> 14
14 -> 10
Next, run "find parent with path compression" on each element:
1)
1 -> 3 -> 8
gives
1 -> 8
3 -> 8
4 -> 6
...
3)
3 -> 8
4)
4 -> 6 -> 3 -> 8
gives
1 -> 8
3 -> 8
4 -> 8
6 -> 8
7 -> 6
...
6)
6 -> 8 (already done)
7)
7 -> 6 -> 8
etc.
Result:
1 -> 8
3 -> 8
4 -> 8
6 -> 8
7 -> 8
...
Then convert this back to the tuple list:
(8,1)(8,3)(8,4)...
The find parent with path compression algorithm is as find_set would be for disjoint set forests, e.g.
int find_set(int x) const
{
Element& element = get_element(x);
int& parent = element.m_parent;
if(parent != x)
{
parent = find_set(parent);
}
return parent;
}
The key point is that path compression helps you avoid a lot of work. In the above, for example, when you do the lookup for 4, you store 6 -> 8, which makes later lookups referencing 6 faster.
So assume you have a list of tuples representing the points:
def find_root(ls):
child, parent, root = [], [], []
for node in ls:
parent.append(node[0])
child.append(node[1])
for dis in parent:
if (!child.count(dis)):
root.append(dis)
if len(root) > 1 : return -1 # failure, the tree is not formed well
for nodeIndex in xrange(len(ls)):
ls[nodeIndex] = (root[0], ls[nodeIndex][1])
return ls

Resources