Understanding Count Triplets HackerRank - arrays

I have been working on this challenge: Count Triplets, and after a lot of hard work, my algorithm did not work out for every test case.
Since in the discussion, I have seen a code and tried to find out the real functionality of the code, I am still not able to understand how, this code works.
Solution:
from collections import defaultdict
arr = [1,3,9,9,27,81]
r = 3
v2 = defaultdict(int)
v3 = defaultdict(int)
count = 0
for k in arr:
count += v3[k]
v3[k*r] += v2[k]
v2[k*r] += 1
print(count)
The above code works for every test case perfectly. I have tested for value of k, v2, v3 to understand but still don't understand how the code works so smooth with the counting triplets. I cannot think of that solution in my dreams too. I wonder how people are so smart to work out this solution. Nevertheless, I would be glad if I would get the proper explanation. Thanks
Output for k,v2,v3
from collections import defaultdict
arr = [1,3,9,9,27,81]
r = 3
v2 = defaultdict(int)
v3 = defaultdict(int)
count = 0
for k in arr:
count += v3[k]
v3[k*r] += v2[k]
v2[k*r] += 1
print(k, count, v2, v3)
OUTPUT
1 0 defaultdict(<class 'int'>, {1: 0, 3: 1}) defaultdict(<class 'int'>, {1: 0, 3: 0})
3 0 defaultdict(<class 'int'>, {1: 0, 3: 1, 9: 1}) defaultdict(<class 'int'>, {1: 0, 3: 0, 9: 1})
9 1 defaultdict(<class 'int'>, {27: 1, 1: 0, 3: 1, 9: 1}) defaultdict(<class 'int'>, {27: 1, 1: 0, 3: 0, 9: 1})
9 2 defaultdict(<class 'int'>, {27: 2, 1: 0, 3: 1, 9: 1}) defaultdict(<class 'int'>, {27: 2, 1: 0, 3: 0, 9: 1})
27 4 defaultdict(<class 'int'>, {27: 2, 1: 0, 3: 1, 81: 1, 9: 1}) defaultdict(<class 'int'>, {27: 2, 1: 0, 3: 0, 81: 2, 9: 1})
81 6 defaultdict(<class 'int'>, {1: 0, 3: 1, 243: 1, 81: 1, 9: 1, 27: 2}) defaultdict(<class 'int'>, {1: 0, 3: 0, 243: 1, 81: 2, 9: 1,
27: 2})

1. The problem
The function has two parameters, namely:
arr: an array of integers
r: an integer, the common ratio
So, the input can be something like
arr: [1, 2, 2, 4]
r: 2
The goal is to return the count of triplets that form a geometric progression.
2. How to solve it
To solve it there's variou ways. For instances, from SagunB based on the comment from RobertsN
Can be done in O(n) -> single pass through data
No division necessary and single multiplications by R are all that's needed
Using map(C++) or dict(Java, Python) is a must -> can be unordered map (saves O(logN))
Try to think forward when reading a value -> will this value form part of a triplet later?
No need to consider (R == 1) as a corner case
from collections import Counter
# Complete the countTriplets function below.
def countTriplets(arr, r):
r2 = Counter()
r3 = Counter()
count = 0
for v in arr:
if v in r3:
count += r3[v]
if v in r2:
r3[v*r] += r2[v]
r2[v*r] += 1
return count
Or like you said
from collections import defaultdict
# Complete the countTriplets function below.
def countTriplets(arr, r):
v2 = defaultdict(int)
v3 = defaultdict(int)
count = 0
for k in arr:
count += v3[k]
v3[k*r] += v2[k]
v2[k*r] += 1
return count
3. End result
Both cases will pass all the current 13 Test cases in HackerRank
4. Explanation of your case
Comments from RobertsN pretty much explain your code (which is very similar to yours). Still, for a better clarification to understand how the code works, just print the what happens to count, v2 and v3.
Assuming you'll have as input
4 2
1 2 2 4
The expected output is
2
Also, we know that by definition both v2 and v3 will look like
defaultdict(<class 'int'>, {})
which leaves the for loop left to understand. What can cause some confusion there is the operator += but that was already addressed by me in another answer.
So, now to understand the rest we can change the loop to
for k in arr:
print(f"Looping...")
print(f"k: {k}")
print(f"v3_before_count: {v3}")
count += v3[k]
print(f"count: {count}")
print(f"k*r: {k*r}")
print(f"v3_before: {v3}")
v3[k*r] += v2[k]
print(f"v3[k*r]: {v3[k*r]}")
print(f"v2[k]: {v2[k]}")
print(f"v3_after: {v3}")
print(f"v2_before: {v2}")
v2[k*r] += 1
print(f"v2_after: {v2}")
print(f"v2[k*r]: {v2[k*r]}")
Will allow you to see
Looping...
k: 1
v3_before_count: defaultdict(<class 'int'>, {})
count: 0
k*r: 2
v3_before: defaultdict(<class 'int'>, {1: 0})
v2_before_v3: defaultdict(<class 'int'>, {1: 0})
v3[k*r]: 0
v2[k]: 0
v3_after: defaultdict(<class 'int'>, {1: 0, 2: 0})
v2_before: defaultdict(<class 'int'>, {1: 0})
v2_after: defaultdict(<class 'int'>, {1: 0, 2: 1})
v2[k*r]: 1
Looping...
k: 2
v3_before_count: defaultdict(<class 'int'>, {1: 0, 2: 0})
count: 0
k*r: 4
v3_before: defaultdict(<class 'int'>, {1: 0, 2: 0})
v2_before_v3: defaultdict(<class 'int'>, {1: 0, 2: 0})
v3[k*r]: 1
v2[k]: 1
v3_after: defaultdict(<class 'int'>, {1: 0, 2: 0, 4: 1})
v2_before: defaultdict(<class 'int'>, {1: 0, 2: 1})
v2_after: defaultdict(<class 'int'>, {1: 0, 2: 1, 4: 1})
v2[k*r]: 1
Looping...
k: 2
v3_before_count: defaultdict(<class 'int'>, {1: 0, 2: 0, 4: 1})
count: 0
k*r: 4
v3_before: defaultdict(<class 'int'>, {1: 0, 2: 0, 4: 1})
v2_before_v3: defaultdict(<class 'int'>, {1: 0, 2: 0, 4: 1})
v3[k*r]: 2
v2[k]: 1
v3_after: defaultdict(<class 'int'>, {1: 0, 2: 0, 4: 2})
v2_before: defaultdict(<class 'int'>, {1: 0, 2: 1, 4: 1})
v2_after: defaultdict(<class 'int'>, {1: 0, 2: 1, 4: 2})
v2[k*r]: 2
Looping...
k: 4
v3_before_count: defaultdict(<class 'int'>, {1: 0, 2: 0, 4: 2})
count: 2
k*r: 8
v3_before: defaultdict(<class 'int'>, {1: 0, 2: 0, 4: 2})
v2_before_v3: defaultdict(<class 'int'>, {1: 0, 2: 0, 4: 2})
v3[k*r]: 2
v2[k]: 2
v3_after: defaultdict(<class 'int'>, {1: 0, 2: 0, 4: 2, 8: 2})
v2_before: defaultdict(<class 'int'>, {1: 0, 2: 1, 4: 2})
v2_after: defaultdict(<class 'int'>, {1: 0, 2: 1, 4: 2, 8: 1})
v2[k*r]: 1
and extract the desired illations. What can we observe from that?
count increases in the last loop from 0 to 2.
k goes through all values of the arr - so it'll be 1, 2, 2 and 4.
in the initial loop, v3_before_count is {} and v3_before is {1:0}
etc.
Most likely this process will lead to questions and answering them will leave you closer to understand it.

So the code is tracking potential pairs and triplets as it walks through the array.
For each value in the array:
// Increment count by the number of triplets that end with k
count += v3[k]
// Increment the number of potential triplets that will end with k*r
v3[k*r] += v2[k]
// Increment the number of potential pairs that end with k*r
v2[k*r] += 1
The number of triplets for any given k is the number of pairs for any given k/r that we've encountered up to this point.
Note throughout the loop, v3[k] and v2[k] will often be zero, until they hit our predicted k*r value from a previous iteration.

I have been trying to make sense of it and finally, this C# code should be clear to follow
static long countTriplets(List<long> arr, long r)
{
//number of times we encounter key*r
var doubles = new Dictionary<long, long>();
//number of times we encounter a triplet
var triplets = new Dictionary<long, long>();
long count = 0;
foreach (var key in arr)
{
long keyXr = key * r;
if (triplets.ContainsKey(key))
count += triplets[key];
if (doubles.ContainsKey(key))
{
if (triplets.ContainsKey(keyXr))
triplets[keyXr] += doubles[key];
else
triplets.Add(keyXr, doubles[key]);
}
if (doubles.ContainsKey(keyXr))
doubles[keyXr]++;
else
doubles.Add(keyXr, 1);
}
return count;
}

from collections import defaultdict
arr = [1,3,9,9,27,81]
r = 3
v2 = defaultdict(int) #if miss get 0
v3 = defaultdict(int) #if miss get 0
count = 0`enter code here`
for k in arr:
#updating the count, starts propagating with delay=2 elements
count += v3[k]
# number of triplets with last component ending
# on index i of k in array
v3[k*r] += v2[k]
# number of pairs with last component ending
# on index i of k in array
v2[k*r] += 1
print(count)
Best to understand it on example - suppose we have array 11111,
and we are on i=3, so 111>1<1.
v2 has currently count for 111, 11>1< there are two pairs ending with >1< generally n-1 for length(array)=n.
Now at v3 we construct count recursively from v2, as follows: for each pair created and counted with v2 we assign last component there are n such options
for #pairs = n.
So for i=3:
11.1 (v2=1) //this pair remains by sum
+
.111 (v2=2) //these are new
1.11 (v2=2)
Hope this helps!

Potential value of number X : the number of triplets there are if any number uses X as the precedence to completely form a triplet.
Let take an example: 1 2 4 with r = 2.
S1: with 1: no triplet cause 1/2=0.5 and 1/2/2=0.25 not available. Add 1 to hashmap.
S2: with 2: 1 potential triplet can be formed if the final number is reached (the 4). Add 2 to hashmap.
S3: with 4: 1 potential triplet can be form if the final number is reached (the 8). Add 4 to hashmap. At the same time we have 1 triplet because 4/2 & 4/2/2 exist in the hashmap.
But how do we know there only 1? Because in order to reach to number 4 of a triplet, you must go through number 2, and we only has 1 number 2 before number 4.
So total is 1. Easy.
What if the input is 1 2 2 2 4?
we have the potential: 1: 0; 2: 1 number 1; 4: 3 number 2 => 3 triplets
Let add 1 to the input, we have: 1 1 2 2 2 4 with r=2
With the 1st 2, we have 2 potential triplet because there are 2 number 1 before it.
With the 2nd 2, we have double
With the 3rd 2, we have triple
So the total is 2(number 1) x 3 (number 2) = 6 potential triplet
And when the index reached the number 4, similar to Step 3 above, we have total triplet is 6.
This is demonstration of 2 hashmap we iterated through the array:
Input
Potential
Count
1 1 2 2 2 4
{1:0, 2:6, 4:3}
{1:2, 2:3, 4:1}
1 1 2 2 2 4 8 16
{1:0, 2:6, 4:3, 8:1, 16:1}
{1:2, 2:3, 4:1, 8:1, 16:1 }
As the second input in table above, we can say:
with triplet number (1,2,4) we have 6 triplets (potential at number 2)
with triplet number (2,4,8) we have 3 triplets (potential at number 4)
with triplet number (4,8,16) we have 1 triplets (potential at number 8)
SO total is 10 triplets.
And this is my javascript solution
function countTriplets(arr, r) {
const numberAtThisPointOf = {};
const potentialTripletOf = {};
let total = 0;
for (let i = 0; i < arr.length; i++) {
const key = arr[i];
if (numberAtThisPointOf[key] === undefined) {
potentialTripletOf[key] = 0;
numberAtThisPointOf[key] = 0;
}
// if key is final part of a triplet, mean the other 2 smaller numbers exist, the `key % r === 0` & `(key/r) % r === 0` to avoid decimal number in javascript
if (key % r === 0 && numberAtThisPointOf[key/r] !== undefined & numberAtThisPointOf[key/r/r] !== undefined && (key/r) % r === 0) {
total += potentialTripletOf[key/r];
}
// update potential case of current key
if (numberAtThisPointOf[key/r] !== undefined && key % r === 0) {
potentialTripletOf[key] += numberAtThisPointOf[key/r];
}
numberAtThisPointOf[key]++;
}
return total;
}

Can be thoughts like below.
Geometric progression is form of : A, AR , ARR,....
Now consider if element in arr is :
element == ARR or third term of triplet means we have completed the triplet hence update the count.
element == AR or second term of triplet so the next element in GP will be(element multiplied by R ) ARR and to be updated in ARR or r3 dictionary .
element == A or first term so next element in GP will be (element multiplied R) AR hence to be updated in AR or r2 dictionary.

Related

ClickHouse array - find a longest chain of repeating number in array

In Clickhouse I have a column with array of Int16 elements. I'm looking for a way to find a longest chain of repeating number 1.
For example, in array [0,1,1,1,5,1,1,1,1,1,2] longest chain of repeating 1 is 5 elements. Is there any way do do it with existing functions ?
Try this query:
SELECT
/* The source number. */
data.1 AS number,
/* The source array. */
data.2 AS array,
/* Number the values ​​in each chain. */
arrayCumSumNonNegative((x, index) -> x = number ? 1 : -index, array, arrayEnumerate(array)) AS partiallySumArray,
arrayReduce('max', partiallySumArray) AS result
FROM
(
/* test data set */
SELECT arrayJoin([
/**/
(1, []),
(1, [0, 2, 2, 2, 5]),
(1, [0, 1, 1, 1, 5, 1, 1, 1, 1, 1,2]),
(1, [1, 1, 1, 2, 3, 4, 5, 1, 1]),
(1, [-5, 100, 1, 1, 0, 1, 1, 1]),
(1, [1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0]),
/**/
(5, []),
(5, [0, 2, 2, 2, 55]),
(5, [5, 5, 10, 300, 5, 77, 5])
]) AS data
)
FORMAT Vertical
/* Result:
Row 1:
──────
number: 1
array: []
partiallySumArray: []
result: 0
Row 2:
──────
number: 1
array: [0,2,2,2,5]
partiallySumArray: [0,0,0,0,0]
result: 0
Row 3:
──────
number: 1
array: [0,1,1,1,5,1,1,1,1,1,2]
partiallySumArray: [0,1,2,3,0,1,2,3,4,5,0]
result: 5
Row 4:
──────
number: 1
array: [1,1,1,2,3,4,5,1,1]
partiallySumArray: [1,2,3,0,0,0,0,1,2]
result: 3
Row 5:
──────
number: 1
array: [-5,100,1,1,0,1,1,1]
partiallySumArray: [0,0,1,2,0,1,2,3]
result: 3
Row 6:
──────
number: 1
array: [1,1,0,1,1,1,1,1,1,0,0]
partiallySumArray: [1,2,0,1,2,3,4,5,6,0,0]
result: 6
Row 7:
──────
number: 5
array: []
partiallySumArray: []
result: 0
Row 8:
──────
number: 5
array: [0,2,2,2,55]
partiallySumArray: [0,0,0,0,0]
result: 0
Row 9:
───────
number: 5
array: [5,5,10,300,5,77,5]
partiallySumArray: [1,2,0,0,1,0,1]
result: 2
*/

Dictionary assignment overwritten by static values in the loop

I'm trying to calculate the absolute differences between factors of two separate lists. After cross-calculation, the thought process is to save them down into a overall dictionary "kk".
kk = {}
ks = [4,5]
ks2 = [1,2,3,4]
for i in ks:
diff = {abs(i-j):j for j in ks2}
kk[i] = diff
This is what I got:
{4: {1: 4, 2: 3, 3: 2, 4: 1}, 5: {1: 4, 2: 3, 3: 2, 4: 1}}
As we can see, it is incorrect, as "kk"'s values for both keys are the same...

median of the two sorted array

I was not able to understand the below base case 5 and 6 for calculating the median of two sorted arrays. N and M are the two array length.
Base cases:
The smaller array has only one element
Case 0: N = 0, M = 2
Case 1: N = 1, M = 1.
Case 2: N = 1, M is odd
Case 3: N = 1, M is even
The smaller array has only two elements
Case 4: N = 2, M = 2
Case 5: N = 2, M is odd
Case 6: N = 2, M is even
Case 0: There are no elements in first array, return median of second array. If second array is also empty, return -1.
Case 1: There is only one element in both arrays, so output the average of A[0] and B[0].
Case 2: N = 1, M is odd
Let B[5] = {5, 10, 12, 15, 20}
First find the middle element of B[], which is 12 for above array. There are following 4 sub-cases.
…2.1 If A[0] is smaller than 10, the median is average of 10 and 12.
…2.2 If A[0] lies between 10 and 12, the median is average of A[0] and
12.
…2.3 If A[0] lies between 12 and 15, the median is average of 12 and
A[0].
…2.4 If A[0] is greater than 15, the median is average of 12 and 15.
In all the sub-cases, we find that 12 is fixed. So, we need to find the median of B[ M / 2 – 1 ], B[ M / 2 + 1], A[ 0 ] and take its average with B[ M / 2 ].
Case 3: N = 1, M is even
Let B[4] = {5, 10, 12, 15}
First find the middle items in B[], which are 10 and 12 in above example. There are following 3 sub-cases.
…3.1 If A[0] is smaller than 10, the median is 10.
…3.2 If A[0] lies between 10 and 12, the median is A[0].
…3.3 If A[0] is greater than 12, the median is 12.
So, in this case, find the median of three elements B[ M / 2 – 1 ], B[ M / 2] and A[ 0 ].
Case 4: N = 2, M = 2
There are four elements in total. So we find the median of 4 elements.
Case 5: N = 2, M is odd
Let B[5] = {5, 10, 12, 15, 20}
The median is given by median of following three elements: B[M/2], max(A[0], B[M/2 – 1]), min(A[1], B[M/2 + 1]).
Case 6: N = 2, M is even
Let B[4] = {5, 10, 12, 15}
The median is given by median of following four elements: B[M/2], B[M/2 – 1], max(A[0], B[M/2 – 2]), min(A[1], B[M/2 + 1])
I was referring the below URL for understanding the median of two sorted arrays.
http://www.geeksforgeeks.org/median-of-two-sorted-arrays-of-different-sizes/
The simplest way to see how this works is ... with a pencil and paper. Draw two lists of numbers (both short ...), one with an odd number of elements, the other even. Now, look very carefully at the slight-but-important differences between the solutions for Case 5 vs. Case 6.
Since the logic as-presented uses the MAX() function several times, with your pencil-and-paper construct situations where first one, then the other of the two parameters to MAX() are "biggest."
In fifteen minutes or so with that piece of paper and that number-two pencil, you'll be able to convince yourself that the algorithm does (or, doesn't?) work.
... (and, yeah, this is a perfectly serious recommendation. This is exactly how I work such things out!)

3d array traversal originating from center

I'm trying to find a traversal order for a 3d array with uniform dimension n.The traversal order should hereby be sorted ascending by it's distance to the cube's center (order of cells with equal indices is arbitrary).
Example for a 2d-Array:
7 4 8
3 0 1
6 2 5
Which basically is the distance in Manhattan metric:
2 1 2
1 0 1
2 1 2
Traversal as relative coordinates with respect to the origin:
[ 0, 0]
[ 1, 0]
[ 0,-1]
[-1, 0]
[ 0, 1]
[ 1,-1]
[-1,-1]
[-1, 1]
[ 1, 1]
I know of some ways to solve this, for instance precalculating all indices and sorting them according to their distance to the origin. However, since this algorithm is intended to perform on GPU, there are some restrictions:
No recursion (I'm aware of the possibility resolving recursion into
an iterative algorithm - maintaining a stack is however not a
suitable solution in my experience)
No offline calculation (= calculation on the CPU and transferring the result to the GPU). The solution needs to be as flexible as
possible
While searching for solutions I stumbled upon this question, which is exactly the problem I tend to solve, the accepted answer albeit envolves a tree structure, which does not fit the specified requirements: 3D Array Traversal in Different Order
I also thought of a way to create the indices using spherical coordinates, which unfortunately does not yield the correct order. What is an appropriate algorithm to generate the given traversal order for 3d-arrays?
Edit: Stormwind provided an excellent alternative description for the given Problem: "[The problem] is actually about converting addressing from one space to another. Converting between a 1-dimensional and 2-dimensional is simple, like 1,2,3,... to (1,1),(1,2)(2,1)... But this is more like converting from an ascending 1-dimensional (or at least square) to an "ascending octahedron layered" space, when "ascending" means "innermost layer first" in addition to an existing (though arbitrary) incrementing order at each layer surface."
After thinking for a while I've came up with an idea to represent 3d array as a sequence of nodes with directions: +i, -i, +j, -j, +k, -k.
Approach
For 2-dimensional array it would be sufficient to have only three rules:
Each iteration over each node moves it along its axis in its direction, i.e. node +j will increment 2nd index, node -i will decrement 1st index.
There are two kinds of nodes: Main and Secondary. Main axis have one index 0. Each iteration over Main i and j axis nodes (I'll call them I and J) produces Secondary node rotated clockwise 90 degrees:
+I -> -j
-J -> -i
-I -> +j
+J -> +i
Each node has lifetime which decrements every iteration. Lifetime of the node equals (n-1)/2 for odd values of n (for even values see code below). After lifetime goes to 0 the node should be removed.
To enable 3rd dimension another rule should be applied:
Third type of nodes with direction along k axis (here - depth) produces set of I and J axis in each iteration:
+K -> +I, -J, -I, +J
-K -> +I, -J, -I, +J
Here is how it will look:
With such approach elemnts will be automatically sorted by Manhattan distance as in Arturo Menchaca solution.
Implementation
Here is python code that does what I've described. There is a lot of space for improvements this is just a proof of concept. It has no sorting, no recursion and I don't see any offline calculations.
It also contains few tests. Run
NO = ( 0, 0, 0, 2, 0)
Pi = (+1, 0, 0, 0, 0)
PI = (+1, 0, 0, 0, 1)
Pj = ( 0,+1, 0, 0, 0)
PJ = ( 0,+1, 0, 0, 1)
PK = ( 0, 0,+1, 0, 2)
Mi = (-1, 0, 0, 1, 0)
MI = (-1, 0, 0, 1, 1)
Mj = ( 0,-1, 0, 1, 0)
MJ = ( 0,-1, 0, 1, 1)
MK = ( 0, 0,-1, 1, 2)
# i j k ^ ^
# | Id for comparison
# Lifetime index
PRODUCE = {
PI: [ Mj ], # +I -> -j
MJ: [ Mi ], # -J -> -i
MI: [ Pj ], # -I -> +j
PJ: [ Pi ], # +J -> +i
NO: [ NO ],
Pi: [ NO ],
Pj: [ NO ],
Mi: [ NO ],
Mj: [ NO ],
PK: [ PI, MI, PJ, MJ ], # +K -> +I, -J, -I, +J
MK: [ PI, MI, PJ, MJ ], # -K -> +I, -J, -I, +J
}
class Index:
LastDistance = 0
NumberOfVisits = 0
MinIndex = 0
MaxIndex = 0
def __init__(self, i, j, k, lifetime, direction):
self.globalLifetime = lifetime
self.direction = direction
# Assign parent's position
self.i = i
self.j = j
self.k = k
# Step away from parent
self.lifetime = lifetime[direction[3]]
self.step()
def isLive(self):
return self.lifetime > 0
def visit(self):
Index.NumberOfVisits += 1
distance = self.distance()
if distance < Index.LastDistance:
raise NameError("Order is not preserved")
Index.LastDistance = distance
Index.MinIndex = min(self.i, Index.MinIndex)
Index.MinIndex = min(self.j, Index.MinIndex)
Index.MinIndex = min(self.k, Index.MinIndex)
Index.MaxIndex = max(self.i, Index.MaxIndex)
Index.MaxIndex = max(self.j, Index.MaxIndex)
Index.MaxIndex = max(self.k, Index.MaxIndex)
print("[{}, {}, {}]".format(self.i, self.j, self.k))
def step(self):
# Move in your direction
self.i += self.direction[0]
self.j += self.direction[1]
self.k += self.direction[2]
def iterate(self):
self.lifetime -= 1
def produce(self, result):
for direction in PRODUCE[self.direction]:
self.create(direction, result)
def create(self, direction, result):
index = Index(self.i, self.j, self.k, self.globalLifetime, direction)
if index.isLive():
result.append(index)
def distance(self):
# Manhattan Distance
return abs(self.i) + abs(self.j) + abs(self.k)
def Traverse(N):
TotalNumber = N*N*N
halfA = halfB = (N-1)/2
if N % 2 == 0:
halfA = N/2
halfB = N/2-1
MinIndex = -min(halfB, halfA)
MaxIndex = max(halfB, halfA)
lifetime = (halfA, halfB, 0)
SecondaryNodes = []
MainNodes = []
KNodes = []
# visit center
center = Index(0, 0, 0, lifetime, NO)
center.visit()
center.create(PI, MainNodes)
center.create(MI, MainNodes)
center.create(PJ, MainNodes)
center.create(MJ, MainNodes)
center.create(PK, KNodes)
center.create(MK, KNodes)
while len(SecondaryNodes) + len(MainNodes) + len(KNodes) > 0:
# First - visit all side nodes
temp = []
for m in SecondaryNodes:
m.visit()
m.step()
m.iterate()
# Save node only if it is alive
if m.isLive():
temp.append(m)
SecondaryNodes = temp
# Second - visit all main nodes as they may produce secondary nodes
temp = []
for m in MainNodes:
m.visit() # 1 - Visit
m.produce(SecondaryNodes) # 2 - Produce second
m.step() # 3 - Step
m.iterate() # 4 - Lose a life
if m.isLive():
temp.append(m)
MainNodes = temp
# Third - visit all K nodes as they may produce main nodes
temp = []
for m in KNodes:
m.visit()
m.produce(MainNodes)
m.step()
m.iterate()
if m.isLive():
temp.append(m)
KNodes = temp
if TotalNumber != Index.NumberOfVisits:
raise NameError("Number of visited elements does not match {}/{}".format(Index.NumberOfVisits, TotalNumber))
if MinIndex != Index.MinIndex:
raise NameError("Minimal index is out of bounds {}/{}".format(Index.MinIndex, MinIndex))
if MaxIndex != Index.MaxIndex:
raise NameError("Maximal index is out of bounds {}/{}".format(Index.MaxIndex, MaxIndex))
Traverse(6)
Implementation Simplified
Helper class to store index:
class Index:
def __init__(self, i, j, k, lifetime):
self.i = i
self.j = j
self.k = k
self.lifetime = lifetime
def visit(self):
print("[{}, {}, {}]".format(self.i, self.j, self.k))
Set of functions to iterate Main nodes in proper direction:
def StepMainPlusI(mainPlusI, minusJ, lifetime):
result = []
for m in mainPlusI:
if lifetime > 0:
minusJ.append(Index(m.i, m.j-1, m.k, lifetime))
m.lifetime -= 1
m.i += 1
if m.lifetime > 0:
result.append(m)
return result
def StepMainMinusJ(mainMinusJ, minusI, lifetime):
result = []
for m in mainMinusJ:
if lifetime > 0:
minusI.append(Index(m.i-1, m.j, m.k, lifetime))
m.lifetime -= 1
m.j -= 1
if m.lifetime > 0:
result.append(m)
return result
def StepMainMinusI(mainMinusI, plusJ, lifetime):
result = []
for m in mainMinusI:
if lifetime > 0:
plusJ.append(Index(m.i, m.j+1, m.k, lifetime))
m.lifetime -= 1
m.i -= 1
if m.lifetime > 0:
result.append(m)
return result
def StepMainPlusJ(mainPlusJ, plusI, lifetime):
result = []
for m in mainPlusJ:
if lifetime > 0:
plusI.append(Index(m.i+1, m.j, m.k, lifetime))
m.lifetime -= 1
m.j += 1
if m.lifetime > 0:
result.append(m)
return result
Set of functions to iterate a third dimensional K nodes:
def StepMainPlusK(mainPlusK, mainPlusI, mainMinusI, mainPlusJ, mainMinusJ, lifetimeA, lifetimeB):
result = []
for m in mainPlusK:
if lifetimeA > 0:
mainPlusI.append(Index(+1, 0, m.k, lifetimeA))
mainPlusJ.append(Index(0, +1, m.k, lifetimeA))
if lifetimeB > 0:
mainMinusI.append(Index(-1, 0, m.k, lifetimeB))
mainMinusJ.append(Index(0, -1, m.k, lifetimeB))
m.lifetime -= 1
m.k += 1
if m.lifetime > 0:
result.append(m)
return result
def StepMainMinusK(mainMinusK, mainPlusI, mainMinusI, mainPlusJ, mainMinusJ, lifetimeA, lifetimeB):
result = []
for m in mainMinusK:
if lifetimeA > 0:
mainPlusI.append(Index(+1, 0, m.k, lifetimeA))
mainPlusJ.append(Index(0, +1, m.k, lifetimeA))
if lifetimeB > 0:
mainMinusI.append(Index(-1, 0, m.k, lifetimeB))
mainMinusJ.append(Index(0, -1, m.k, lifetimeB))
m.lifetime -= 1
m.k -= 1
if m.lifetime > 0:
result.append(m)
return result
These two functions have two different lifetime parameters for the case when n is odd and one half can be less than another. I've divided them by sign - negatively oriented will have lower half of indexes.
Set of functions to iterate Secondary nodes:
def StepPlusI(plusI):
result = []
for m in plusI:
m.i += 1
m.lifetime -= 1
if m.lifetime > 0:
result.append(m)
return result
def StepMinusI(minusI):
result = []
for m in minusI:
m.i -= 1
m.lifetime -= 1
if m.lifetime > 0:
result.append(m)
return result
def StepPlusJ(plusJ):
result = []
for m in plusJ:
m.j += 1
m.lifetime -= 1
if m.lifetime > 0:
result.append(m)
return result
def StepMinusJ(minusJ):
result = []
for m in minusJ:
m.j -= 1
m.lifetime -= 1
if m.lifetime > 0:
result.append(m)
return result
And the main function:
def Traverse(N):
halfA = halfB = (N-1)/2
if N % 2 == 0: # size is even
halfA = N/2
halfB = N/2-1
# visit center
Index(0,0,0,0).visit()
# Secondary nodes
PlusI = []
MinusI = []
PlusJ = []
MinusJ = []
# Main nodes
MainPlusI = []
MainMinusI = []
MainPlusJ = []
MainMinusJ = []
MainPlusK = []
MainMinusK = []
# Add Main nodes
if halfA > 0:
MainPlusI.append( Index(+1, 0, 0, halfA) )
MainPlusJ.append( Index(0, +1, 0, halfA) )
MainPlusK.append( Index(0, 0, +1, halfA) )
if halfB > 0:
MainMinusI.append( Index(-1, 0, 0, halfB) )
MainMinusJ.append( Index(0, -1, 0, halfB) )
MainMinusK.append( Index(0, 0, -1, halfB) )
# Finish condition flag
visited = True
while visited:
visited = False
# visit all Main nodes
for m in MainPlusI:
m.visit()
visited = True
for m in MainMinusI:
m.visit()
visited = True
for m in MainPlusJ:
m.visit()
visited = True
for m in MainMinusJ:
m.visit()
visited = True
for m in MainPlusK:
m.visit()
visited = True
for m in MainMinusK:
m.visit()
visited = True
# Visit all Secondary nodes
for m in PlusI:
m.visit()
visited = True
for m in MinusI:
m.visit()
visited = True
for m in PlusJ:
m.visit()
visited = True
for m in MinusJ:
m.visit()
visited = True
# Iterate Secondary nodes first
PlusI = StepPlusI(PlusI)
MinusI = StepMinusI(MinusI)
PlusJ = StepPlusJ(PlusJ)
MinusJ = StepMinusJ(MinusJ)
# Iterate all Main nodes as they might generate Secondary nodes
MainPlusI = StepMainPlusI(MainPlusI, MinusJ, halfB)
MainMinusJ = StepMainMinusJ(MainMinusJ, MinusI, halfB)
MainMinusI = StepMainMinusI(MainMinusI, PlusJ, halfA)
MainPlusJ = StepMainPlusJ(MainPlusJ, PlusI, halfA)
# Iterate K nodes last as they might produce Main nodes
MainPlusK = StepMainPlusK(MainPlusK, MainPlusI, MainMinusI, MainPlusJ, MainMinusJ, halfA, halfB)
MainMinusK = StepMainMinusK(MainMinusK, MainPlusI, MainMinusI, MainPlusJ, MainMinusJ, halfA, halfB)
And the live example Code
Octant symmetry
The cells in a cubic matrix which are at a certain Manhattan-distance from the center form an octahedron, which is symmetrical about the xy, xz and yz planes going through the center of the cube.
This means that you only need to find the cells which form one face of the octahedron, in the first octant of the cube, and mirror them to get the cells in the other 7 octants. So the problem is reduced to traversing the first octant of the cube (which itself is a cube) diagonally, from the center (distance 0) to the corner cell (maximum distance = 3 × n/2).
Algorithm to find coordinates
Finding the cells which are at a certain Manhattan-distance from the (0,0,0) cell in the first octant (i.e. the cells which form one face of the octahedron, perpendicular to the diagonal of the cube), means finding cells whose coordinates (x,y,z) sum to that distance. So, in the example of a 5x5x5 octant, the cells at distance 3 are the cells with coordinates:
(3,0,0) (2,1,0) (1,2,0) (0,3,0)
(2,0,1) (1,1,1) (0,2,1)
(1,0,2) (0,1,2)
(0,0,3)
You'll notice the similarity to a partitioning of the distance (actually, it's a so-called weak composition with a bounded length of 3).
Finding these combinations can be easily achieved with three nested loops; the only complication is that the distance in each dimension is restricted to n/2, so you have to skip values for x and/or y for which there exists no value for z so that x, y and z sum to the distance; that is what the min() and max() in the JavaScript code example, and the xmin, xmax, ymin and ymax variables in the C code example are for.
The mirroring of the cells in an even-sized cube is straightforward; in an odd-sized cube, the cells are not mirrored in the dimension for which their coordinate is zero (i.e. when the cell lies in the plane of symmetry). That is what the checks whether x, y or z equal zero are for in the code examples.
Parallel programming
I don't know much about GPU programming, but I assume you can completely parallellise the algorithm. For every iteration of the outer loop (i.e. for every distance), once the minimum and maximum value of x has been calculated, the iterations with different values of x can be run in parallel. Then for each value of x, once the minimum and maximum value of y has been calculated, the iterations with different values of y can be run in parallel. And finally, for each coordinate set of (x,y,z) the mirroring to the other octants can be run in parallel.
Code example 1 (JavaScript)
(Run the code snippet to see the inside-out traversal of a 9x9x9 matrix as shown in the diagrams.)
function insideOut(n) {
var half = Math.ceil(n / 2) - 1;
for (var d = 0; d <= 3 * half; d++) {
for (var x = Math.max(0, d - 2 * half); x <= Math.min(half, d); x++) {
for (var y = Math.max(0, d - x - half); y <= Math.min(half, d - x); y++) {
document.write("<br>distance " + d + " (±" + x + ",±" + y + ",±" + (d - x - y) + ") → ");
n % 2 ? mirrorOdd(x, y, d - x - y) : mirrorEven(x, y, d - x - y);
}
}
}
function mirrorEven(x, y, z) {
for (var i = 1; i >= 0; --i, x *= -1) {
for (var j = 1; j >= 0; --j, y *= -1) {
for (var k = 1; k >= 0; --k, z *= -1) {
visit(half + x + i, half + y + j, half + z + k);
}
}
}
}
function mirrorOdd(x, y, z) {
for (var i = 0; i < (x ? 2 : 1); ++i, x *= -1) {
for (var j = 0; j < (y ? 2 : 1); ++j, y *= -1) {
for (var k = 0; k < (z ? 2 : 1); ++k, z *= -1) {
visit(half + x, half + y, half + z);
}
}
}
}
function visit(x, y, z) {
document.write("(" + x + "," + y + "," + z + ") " );
}
}
insideOut(9);
Code example 2 (C)
The mirroring functions can be unrolled for simplicity. In fact, the whole algorithm consists of nothing more than 3 nested loops and simple integer calculations.
void mirrorEven(unsigned int x, unsigned int y, unsigned int z, unsigned int h) {
visit(h+x+1, h+y+1, h+z+1);
visit(h+x+1, h+y+1, h-z);
visit(h+x+1, h-y, h+z+1);
visit(h+x+1, h-y, h-z);
visit(h-x, h+y+1, h+z+1);
visit(h-x, h+y+1, h-z);
visit(h-x, h-y, h+z+1);
visit(h-x, h-y, h-z);
}
void mirrorOdd(unsigned int x, unsigned int y, unsigned int z, unsigned int h) {
visit(h+x, h+y, h+z);
if ( z) visit(h+x, h+y, h-z);
if ( y ) visit(h+x, h-y, h+z);
if ( y && z) visit(h+x, h-y, h-z);
if (x ) visit(h-x, h+y, h+z);
if (x && z) visit(h-x, h+y, h-z);
if (x && y ) visit(h-x, h-y, h+z);
if (x && y && z) visit(h-x, h-y, h-z);
}
void insideOut(unsigned int n) {
unsigned int d, x, xmin, xmax, y, ymin, ymax, half = (n-1)/2;
for (d = 0; d <= 3*half; d++) {
xmin = d < 2*half ? 0 : d-2*half;
xmax = d < half ? d : half;
for (x = xmin; x <= xmax; x++) {
ymin = d < x+half ? 0 : d-x-half;
ymax = d > x+half ? half : d-x;
for (y = ymin; y <= ymax; y++) {
if (n%2) mirrorOdd(x, y, d-x-y, half);
else mirrorEven(x, y, d-x-y, half);
}
}
}
}
[I'm using the Manhattan distance in the solution]
For simplicity, let start assuming 3D arrays of odd dimension ([2N+1, 2N+1, 2N+1])
Using manhattan distance the greatest distance between the center ([0,0,0]) and a point is 3N ([N,N,N], [N,N,-N], ...)
So, basically the idea is find a way to generate all coordinates that have a specific distance. Then starting from distance 0 to 3N generating those coordinates.
To generate coordinates [X,Y,Z] that distance to center in some value K, what we need is all numbers X, Y, Z between -N and N such that ABS(X) + ABS(Y) + ABS(Z) == K. This can be done with this:
FUNC COORDS_AT_DIST(K)
FOR X = -MIN(N, K) TO MIN(N, K)
FOR Y = -MIN(N, K - ABS(X)) TO MIN(N, K - ABS(X))
LET Z = K - ABS(X) - ABS(Y)
IF Z <= N
VISIT(X, Y, Z)
IF Z != 0
VISIT(X, Y, -Z)
Then, use this function like so:
FOR K = 0 TO 3N
COORDS_AT_DIST(K)
This code visit all coordinates with values between [-N,-N,-N] and [N,N,N] sorted according to the distance to [0,0,0].
Now, to handle even dimensions too, we need some extra checks since the values in coordinates for a dimension L goes between [-(L/2-1),-(L/2-1),-(L/2-1)] and [L/2,L/2,L/2].
Something like this:
FUNC VISIT_COORDS_FOR_DIM(L)
LET N = L/2 //Integer division
FOR K = 0 TO 3N
FOR X = -MIN(N - REM(L+1, 2), K) TO MIN(N, K)
FOR Y = -MIN(N - REM(L+1, 2), K - ABS(X)) TO MIN(N, K - ABS(X))
LET Z = K - ABS(X) - ABS(Y)
IF Z <= N
VISIT(X, Y, Z)
IF Z != 0 && (REM(L, 2) != 0 || Z < N)
VISIT(X, Y, -Z)
Just for clarity:
MIN(X, Y): Minimum value between X and Y
ABS(X): Absolute value of X
REM(X, Y): Remainder after division of X by Y
VISIT(X, Y, Z): Visit the generated coordinate (X, Y, Z)
Using VISIT_COORDS_FOR_DIM function with L=3 you get this:
1. [0, 0, 0] DISTANCE: 0
2. [-1, 0, 0] DISTANCE: 1
3. [0, -1, 0] DISTANCE: 1
4. [0, 0, -1] DISTANCE: 1
5. [0, 0, 1] DISTANCE: 1
6. [0, 1, 0] DISTANCE: 1
7. [1, 0, 0] DISTANCE: 1
8. [-1, -1, 0] DISTANCE: 2
9. [-1, 0, -1] DISTANCE: 2
10. [-1, 0, 1] DISTANCE: 2
11. [-1, 1, 0] DISTANCE: 2
12. [0, -1, -1] DISTANCE: 2
13. [0, -1, 1] DISTANCE: 2
14. [0, 1, -1] DISTANCE: 2
15. [0, 1, 1] DISTANCE: 2
16. [1, -1, 0] DISTANCE: 2
17. [1, 0, -1] DISTANCE: 2
18. [1, 0, 1] DISTANCE: 2
19. [1, 1, 0] DISTANCE: 2
20. [-1, -1, -1] DISTANCE: 3
21. [-1, -1, 1] DISTANCE: 3
22. [-1, 1, -1] DISTANCE: 3
23. [-1, 1, 1] DISTANCE: 3
24. [1, -1, -1] DISTANCE: 3
25. [1, -1, 1] DISTANCE: 3
26. [1, 1, -1] DISTANCE: 3
27. [1, 1, 1] DISTANCE: 3
And for L=4:
1. [0, 0, 0] DISTANCE: 0 33. [1, -1, -1] DISTANCE: 3
2. [-1, 0, 0] DISTANCE: 1 34. [1, -1, 1] DISTANCE: 3
3. [0, -1, 0] DISTANCE: 1 35. [1, 0, 2] DISTANCE: 3
4. [0, 0, -1] DISTANCE: 1 36. [1, 1, -1] DISTANCE: 3
5. [0, 0, 1] DISTANCE: 1 37. [1, 1, 1] DISTANCE: 3
6. [0, 1, 0] DISTANCE: 1 38. [1, 2, 0] DISTANCE: 3
7. [1, 0, 0] DISTANCE: 1 39. [2, -1, 0] DISTANCE: 3
8. [-1, -1, 0] DISTANCE: 2 40. [2, 0, -1] DISTANCE: 3
9. [-1, 0, -1] DISTANCE: 2 41. [2, 0, 1] DISTANCE: 3
10. [-1, 0, 1] DISTANCE: 2 42. [2, 1, 0] DISTANCE: 3
11. [-1, 1, 0] DISTANCE: 2 43. [-1, -1, 2] DISTANCE: 4
12. [0, -1, -1] DISTANCE: 2 44. [-1, 1, 2] DISTANCE: 4
13. [0, -1, 1] DISTANCE: 2 45. [-1, 2, -1] DISTANCE: 4
14. [0, 0, 2] DISTANCE: 2 46. [-1, 2, 1] DISTANCE: 4
15. [0, 1, -1] DISTANCE: 2 47. [0, 2, 2] DISTANCE: 4
16. [0, 1, 1] DISTANCE: 2 48. [1, -1, 2] DISTANCE: 4
17. [0, 2, 0] DISTANCE: 2 49. [1, 1, 2] DISTANCE: 4
18. [1, -1, 0] DISTANCE: 2 50. [1, 2, -1] DISTANCE: 4
19. [1, 0, -1] DISTANCE: 2 51. [1, 2, 1] DISTANCE: 4
20. [1, 0, 1] DISTANCE: 2 52. [2, -1, -1] DISTANCE: 4
21. [1, 1, 0] DISTANCE: 2 53. [2, -1, 1] DISTANCE: 4
23. [2, 0, 0] DISTANCE: 2 54. [2, 0, 2] DISTANCE: 4
23. [-1, -1, -1] DISTANCE: 3 55. [2, 1, -1] DISTANCE: 4
24. [-1, -1, 1] DISTANCE: 3 56. [2, 1, 1] DISTANCE: 4
25. [-1, 0, 2] DISTANCE: 3 57. [2, 2, 0] DISTANCE: 4
26. [-1, 1, -1] DISTANCE: 3 58. [-1, 2, 2] DISTANCE: 5
27. [-1, 1, 1] DISTANCE: 3 59. [1, 2, 2] DISTANCE: 5
28. [-1, 2, 0] DISTANCE: 3 60. [2, -1, 2] DISTANCE: 5
29. [0, -1, 2] DISTANCE: 3 61. [2, 1, 2] DISTANCE: 5
30. [0, 1, 2] DISTANCE: 3 62. [2, 2, -1] DISTANCE: 5
31. [0, 2, -1] DISTANCE: 3 63. [2, 2, 1] DISTANCE: 5
32. [0, 2, 1] DISTANCE: 3 64. [2, 2, 2] DISTANCE: 6
This solution has as benefits that no requires any special data-structure, not even an array.
Another solution could be if you can use a queue (it is not hard to implement with an array) and 3D boolean (or int) array is do like a BFS starting from the center.
Firstly, to define what is a neighbor, you can use movement arrays, for example:
Two cells are neighbors if share a common side (Manhattan distance):
DX = { 1, 0, 0, -1, 0, 0 }
DY = { 0, 1, 0, 0, -1, 0 }
DZ = { 0, 0, 1, 0, 0, -1 }
Two cells are neighbors if share a common edge:
DX = { 1, 0, 0, -1, 0, 0, 1, 1, 0, -1, -1, 0, 1, 1, 0, -1, -1, 0 }
DY = { 0, 1, 0, 0, -1, 0, 1, 0, 1, 1, 0, -1, -1, 0, 1, -1, 0, -1 }
DZ = { 0, 0, 1, 0, 0, -1, 0, 1, 1, 0, 1, 1, 0, -1, -1, 0, -1, -1 }
Two cells are neighbors if share a common corner (Chebyshev distance):
DX = { 1, 0, 0, -1, 0, 0, 1, 1, 0, -1, -1, 0, 1, 1, 0, -1, -1, 0, 1, -1, 1, 1, -1, -1, 1, -1 }
DY = { 0, 1, 0, 0, -1, 0, 1, 0, 1, 1, 0, -1, -1, 0, 1, -1, 0, -1, 1, 1, -1, 1, -1, 1, -1, -1 }
DZ = { 0, 0, 1, 0, 0, -1, 0, 1, 1, 0, 1, 1, 0, -1, -1, 0, -1, -1, 1, 1, 1, -1, 1, -1, -1, -1 }
Now, using a queue you can start from center position, then add the neighbors, then neighbors of the neighbors and so on. In each iteration you can visit each generated position.
Something like this:
DX = { 1, 0, 0, -1, 0, 0 }
DY = { 0, 1, 0, 0, -1, 0 }
DZ = { 0, 0, 1, 0, 0, -1 }
VISIT_COORDS_FOR_DIM(L):
LET N = L/2
IF (REM(L, 2) == 0)
N--
V: BOOLEAN[L, L, L]
Q: QUEUE<POINT3D>
V[N, N, N] = TRUE
ENQUEUE(Q, POINT3D(N, N, N))
WHILE COUNT(Q) > 0
P = DEQUEUE(Q)
VISIT(P.X - N, P.Y - N, P.Z - N) //To Transform coords to [-N, N] range.
FOR I = 0 TO LENGTH(DX) - 1
LET X = P.X + DX[I]
LET Y = P.Y + DY[I]
LET Z = P.Z + DZ[I]
IF IS_VALID_POS(L, X, Y, Z) && V[X, Y, Z] == FALSE
V[X, Y, Z] = TRUE
ENQUEUE(Q, POINT3D(X, Y, Z))
IS_VALID_POS(L, X, Y, Z)
RETURN X >= 0 && X < L &&
Y >= 0 && Y < L &&
Z >= 0 && Z < L
Used functions:
REM(X, Y): Remainder after division of X by Y
ENQUEUE(Q, X): Enqueue element X in queue Q
DEQUEUE(Q): Dequeue first element from queue Q
COUNT(Q): Number of elements in queue Q
VISIT(X, Y, Z): Visit the generated coordinate (X, Y, Z)
This solution has as benefits that you can define when two position are neighbors using movement arrays.
The key to getting an efficient algorithm to this question is to see the geometry underlying it. What you are asking for is to solve the Diophantine equation N = a^2 + b^2 + c^2 for each successive value of N, enumerating such solutions in any order. The solutions to this equation are integral points on a sphere of radius N. So in one sense your problem is enumerating spheres.
First, though, it should be clear that the hard problem here is enumerating nonnegative solutions for coordinates (a,b,c). For each such coordinate, there are eight other solutions from mirror symmetry around the coordinate planes, since a^2 = (-a)^2 etc. (In general. If one or more of a,b,c are zero, you get fewer mirror points.) There's a further symmetry by permuting the coordinates so that a <= b <= c. This is the easy part of the enumeration.
The essence of the sphere enumeration algorithm is to consider two sets of points that approximate a sphere of radius N: one which is consists of points with norm "slightly less" than N and one which consists of their lattice neighbors with norm "slightly greater" than N or equal to N. "Slightly less" means that for a point (a,b,c), a^2 + b^2 + c^2 < N, but one or more of the points (a+1,b,c), (a,b+1,c), or (a,b,c+1) has norm >= N. As far as code, you don't need to represent the "slightly less" set; it's already been processed. It's sufficient to create a heap of the "slightly greater" set, sorted by their norm.
Each step of the algorithm changes to the "slightly greater" set for N into one for N+1. Remove the least element of the heap, say, (a,b,c). Now add its nearest neighbor points with greater norm to the heap, the three points (a+1,b,c), (a,b+1,c), and (a,b,c+1). Some of them may already be there; I'll come back to that. When you add an incremental point onto the heap, you need its norm. You do not, however, need to calculate it from scratch. Rely on the identity (a+1)^2 - a^2 = 2a + 1. In other words, you don't need any multiplication operations to compute these norms. Depending on your GPU, you can compute expressions a << 1 + 1 or maybe a + a + 1.
You can also optimize checking for existing points on the heap. Each point has six immediate lattice neighbors. The lattice neighbor with the least norm will be the first one to add it. Suppose a < b < c for a point (a,b,c). Its neighbor with the least norm is (a,b,c-1). Thus when enumerating the point (a-1,b,c), the point (a,b,c) is already on the heap; you don't need even to check that it's there. Pay attention to special cases where the some of the coordinates are equal.
This algorithm enumerates spheres, not cubes. It's easy enough to restrict attention to a cube of with maximum index D. If one of the coordinates is equal to D, then don't add three points, but fewer. The enumeration ends on the point (D,D,D), when there are no more valid neighbor points to add.
The performance of this algorithm should be very fast. It needs a heap of size O(N^2). If you enumerate all the points beforehand you need storage O(N^3). Furthermore, it needs no multiplication, for a further constant speed up.
If it is just centers: There are a lot of different valid orders. Just compute a 3d map, with elements sorted in order. Offset by origin. Make the map:
for x,y,z -domain, domain
map.add ( x,y,z, distance(x,y,z) )
map.sort ( distance )
Then at point x,y,z to traverse
for ( i=0; i++ )
visit ( map[i].xyz + x,y,z )
If it's real distance and not voxel centers, it gets a lot harder.
generating indexes in Manhatan distance order is similar to subset sum problem so just compute the max distance (the sum) and then separate axises to reduce the problem. Here C++ example:
int x,y,z,d,dx,dy,dz,D;
// center idx
int cx=n>>1;
int cy=n>>1;
int cz=n>>1;
// min idx
int x0=-cx;
int y0=-cy;
int z0=-cz;
// max idx
int x1=n-1-cx;
int y1=n-1-cy;
int z1=n-1-cz;
// max distance
x=max(x0,x1);
y=max(y0,y1);
z=max(z0,z1);
D=x+y+z;
// here do your stuff
#define traverse(x,y,z) { /* do something with the array beware x,y,z are signed !!! */ }
// traversal
for (d=0;d<=D;d++) // distance
for (dx=d ,x=-dx;x<=dx;x++) if ((x>=x0)&&(x<=x1)) // x axis separation
for (dy=d-abs(x) ,y=-dy;y<=dy;y++) if ((y>=y0)&&(y<=y1)) // y axis separation
{
dz=d-abs(x)-abs(y); // z axis have only 1 or 2 options
z=-dz; if (z>=z0) traverse(x,y,z);
z=+dz; if ((z)&&(z<=z1)) traverse(x,y,z);
}
#undef traverse
You can replace the traverse(x,y,z) macro by any stuff or function you want. beware x,y,z are signed so can be negative to get C++ style indexes you need to use (x+cx,y+cy,z+cz).
This can handle booth even and odd indexes and also non cube resolutions (if you simply convert n to nx,ny,nz in the first few constants computations). Also the [0,0,0] can be everywhere (not in center) so it is easily applicable to any needs I can think of...
Here example output for n=5
[ 0, 0, 0] = 0
[-1, 0, 0] = 1
[ 0,-1, 0] = 1
[ 0, 0,-1] = 1
[ 0, 0, 1] = 1
[ 0, 1, 0] = 1
[ 1, 0, 0] = 1
[-2, 0, 0] = 2
[-1,-1, 0] = 2
[-1, 0,-1] = 2
[-1, 0, 1] = 2
[-1, 1, 0] = 2
[ 0,-2, 0] = 2
[ 0,-1,-1] = 2
[ 0,-1, 1] = 2
[ 0, 0,-2] = 2
[ 0, 0, 2] = 2
[ 0, 1,-1] = 2
[ 0, 1, 1] = 2
[ 0, 2, 0] = 2
[ 1,-1, 0] = 2
[ 1, 0,-1] = 2
[ 1, 0, 1] = 2
[ 1, 1, 0] = 2
[ 2, 0, 0] = 2
[-2,-1, 0] = 3
[-2, 0,-1] = 3
[-2, 0, 1] = 3
[-2, 1, 0] = 3
[-1,-2, 0] = 3
[-1,-1,-1] = 3
[-1,-1, 1] = 3
[-1, 0,-2] = 3
[-1, 0, 2] = 3
[-1, 1,-1] = 3
[-1, 1, 1] = 3
[-1, 2, 0] = 3
[ 0,-2,-1] = 3
[ 0,-2, 1] = 3
[ 0,-1,-2] = 3
[ 0,-1, 2] = 3
[ 0, 1,-2] = 3
[ 0, 1, 2] = 3
[ 0, 2,-1] = 3
[ 0, 2, 1] = 3
[ 1,-2, 0] = 3
[ 1,-1,-1] = 3
[ 1,-1, 1] = 3
[ 1, 0,-2] = 3
[ 1, 0, 2] = 3
[ 1, 1,-1] = 3
[ 1, 1, 1] = 3
[ 1, 2, 0] = 3
[ 2,-1, 0] = 3
[ 2, 0,-1] = 3
[ 2, 0, 1] = 3
[ 2, 1, 0] = 3
[-2,-2, 0] = 4
[-2,-1,-1] = 4
[-2,-1, 1] = 4
[-2, 0,-2] = 4
[-2, 0, 2] = 4
[-2, 1,-1] = 4
[-2, 1, 1] = 4
[-2, 2, 0] = 4
[-1,-2,-1] = 4
[-1,-2, 1] = 4
[-1,-1,-2] = 4
[-1,-1, 2] = 4
[-1, 1,-2] = 4
[-1, 1, 2] = 4
[-1, 2,-1] = 4
[-1, 2, 1] = 4
[ 0,-2,-2] = 4
[ 0,-2, 2] = 4
[ 0, 2,-2] = 4
[ 0, 2, 2] = 4
[ 1,-2,-1] = 4
[ 1,-2, 1] = 4
[ 1,-1,-2] = 4
[ 1,-1, 2] = 4
[ 1, 1,-2] = 4
[ 1, 1, 2] = 4
[ 1, 2,-1] = 4
[ 1, 2, 1] = 4
[ 2,-2, 0] = 4
[ 2,-1,-1] = 4
[ 2,-1, 1] = 4
[ 2, 0,-2] = 4
[ 2, 0, 2] = 4
[ 2, 1,-1] = 4
[ 2, 1, 1] = 4
[ 2, 2, 0] = 4
[-2,-2,-1] = 5
[-2,-2, 1] = 5
[-2,-1,-2] = 5
[-2,-1, 2] = 5
[-2, 1,-2] = 5
[-2, 1, 2] = 5
[-2, 2,-1] = 5
[-2, 2, 1] = 5
[-1,-2,-2] = 5
[-1,-2, 2] = 5
[-1, 2,-2] = 5
[-1, 2, 2] = 5
[ 1,-2,-2] = 5
[ 1,-2, 2] = 5
[ 1, 2,-2] = 5
[ 1, 2, 2] = 5
[ 2,-2,-1] = 5
[ 2,-2, 1] = 5
[ 2,-1,-2] = 5
[ 2,-1, 2] = 5
[ 2, 1,-2] = 5
[ 2, 1, 2] = 5
[ 2, 2,-1] = 5
[ 2, 2, 1] = 5
[-2,-2,-2] = 6
[-2,-2, 2] = 6
[-2, 2,-2] = 6
[-2, 2, 2] = 6
[ 2,-2,-2] = 6
[ 2,-2, 2] = 6
[ 2, 2,-2] = 6
[ 2, 2, 2] = 6
The distance from the origin is known for each point (a, b, c) to be sqrt(a*a + b*b + c*c). We can define this to be distance(a, b, c).†
For each point in your 3D array, you can insert it into a min heap using distance as your ordering criteria. To avoid recalculation, augment your point representation in the heap to include the cached calculation of distance for that point when it was inserted into the heap.
heap_element = (x, y, z, d)
heap_compare(heap_element a, heap_element b) = a.d < b.d
for each point (x,y,z) in 3D array
· heap.add(heap_element(x, y, z, distance(x, y, z)))
Now, you can just draw out each point from the top of the heap to get your ordering.
N = heap.size
for i in 0..N
· ordering[i] = heap.top
· heap.pop
† For the purposes of this algorithm, using the actual distance is not critical. For performance reasons, you can omit taking the sqrt, and just use a*a + b*b + c*c as the metric for the heap ordering criteria.
In ruby, I just get all points for each distance from the center in order.
def get_points(side_len)
side_len % 2 == 0 ? min_dist = 1 : min_dist = 0
if side_len % 2 == 0
min_dist = 1
max_1d_dist = side_len / 2
else
min_dist = 0
max_1d_dist = (side_len - 1) / 2
end
max_dist = 3 * max_1d_dist
min_dist.upto(max_dist) do |dist|
min_x_dist = [min_dist, dist - 2 * max_1d_dist].max
max_x_dist = [dist - 2 * min_dist, max_1d_dist].min
min_x_dist.upto(max_x_dist) do |x_dist|
min_y_dist = [min_dist, dist - x_dist - max_1d_dist].max
max_y_dist = [dist - x_dist - min_dist, max_1d_dist].min
min_y_dist.upto(max_y_dist) do |y_dist|
z_dist = dist - x_dist - y_dist
print_vals(x_dist, y_dist, z_dist)
end
end
end
end
def print_vals(x_dist, y_dist, z_dist)
x_signs = [1]
y_signs = [1]
z_signs = [1]
x_signs << -1 unless x_dist == 0
y_signs << -1 unless y_dist == 0
z_signs << -1 unless z_dist == 0
x_signs.each do |x_sign|
y_signs.each do |y_sign|
z_signs.each do |z_sign|
puts "[#{x_sign*x_dist}, #{y_sign*y_dist}, #{z_sign*z_dist}]"
end
end
end
end
Output is:
2.1.2 :277 > get_points(1)
[0, 0, 0]
2.1.2 :278 > get_points(2)
[1, 1, 1]
[1, 1, -1]
[1, -1, 1]
[1, -1, -1]
[-1, 1, 1]
[-1, 1, -1]
[-1, -1, 1]
[-1, -1, -1]
2.1.2 :279 > get_points(3)
[0, 0, 0]
[0, 0, 1]
[0, 0, -1]
[0, 1, 0]
[0, -1, 0]
[1, 0, 0]
[-1, 0, 0]
[0, 1, 1]
[0, 1, -1]
[0, -1, 1]
[0, -1, -1]
[1, 0, 1]
[1, 0, -1]
[-1, 0, 1]
[-1, 0, -1]
[1, 1, 0]
[1, -1, 0]
[-1, 1, 0]
[-1, -1, 0]
[1, 1, 1]
[1, 1, -1]
[1, -1, 1]
[1, -1, -1]
[-1, 1, 1]
[-1, 1, -1]
[-1, -1, 1]
[-1, -1, -1]
This is a simple and fast algorithm to transverse a 3D array using Manhattan distance.
The 3D array with size n in each dimension shall be represented by a coordinate system with the origin in the middle of your array. For the centre to be defined, we assume the size of the array in each dimension is a odd number. Every element has a triple of coordinates like this [x, y, z] and each coordinate can reach a maximum value of `(n/2)-1. (Info: The pictures added are in 2D for better understanding)
First we can simplify this by regarding just the positive octant (all coordinates are positive). All other elements can be generated by reflection.
In one octant all elements with the same distance to the centre are defined by a plane with the equation: x+y+z=distance. We achieve this by counting the distance from 0 to n-1 in single steps. For each distance we look for all element on the corresponding plane.
When reaching a distance>(n/2)-1 some of the points will be outside the array (one of the coord > (n/2)-1). So we have to exclude them from the results.
Each calculated element is representing up to 8 elements you get by reflection (see point 1). You can simply achieve this by alternately multiplying each coordinate with -1. [+/-x, +/-y, +/-z] (8 possible combinations if all coords != 0)
Here is a code schema for my algorithm:
//rise the distance by one each iteration
for(distance=0; distance<n-1; distance++) //loop distance from 0 to n-1
for(i=0; i<=distance; i++)
x=i; //x ∈ [0, distance]
for(j=0; j<=distance-x; j++)
y=j; //y ∈ [0, distance-x]
z=distance-(x+y); //because distance=x+y+z
//now we have to exclude all elements with one coord <= (n/2)-1
if(x<=(n/2)-1 && y<=(n/2)-1 && z<=(n/2)-1)
//[x,y,z] we found a valid element!
//let's generate the 7 corresponding elements (up to 7)
if(x!=0) //[-x,y,z]
if(y!=0) //[x,-y,z]
if(z!=0) //[x,y,-z]
if(x!=0 && y!=0) //[-x,-y,z]
if(x!=0 && z!=0) //[-x,y,-z]
if(y!=0 && z!=0) //[x,-y,-z]
if(y!=0 && y!=0 && z!=0) //[-x,-y,-z]
Here is the output for n=7:
Distance:0 [0,0,0]
Distance:1 [0,0,1] [0,0,-1] [0,1,0] [0,-1,0] [1,0,0] [-1,0,0]
Distance:2 [0,0,2] [0,0,-2] [0,1,1] [0,-1,1] [0,1,-1] [0,-1,-1] [0,2,0] [0,-2,0] [1,0,1] [-1,0,1] [1,0,-1] [-1,0,-1] [1,1,0] [-1,1,0] [1,-1,0] [-1,-1,0] [2,0,0] [-2,0,0]
Distance:3 [0,1,2] [0,-1,2] [0,1,-2] [0,-1,-2] [0,2,1] [0,-2,1] [0,2,-1] [0,-2,-1] [1,0,2] [-1,0,2] [1,0,-2] [-1,0,-2] [1,1,1] [-1,1,1] [1,-1,1] [1,1,-1] [-1,-1,1] [-1,1,-1] [1,-1,-1] [-1,-1,-1] [1,2,0] [-1,2,0] [1,-2,0] [-1,-2,0] [2,0,1] [-2,0,1] [2,0,-1] [-2,0,-1] [2,1,0] [-2,1,0] [2,-1,0] [-2,-1,0]
Distance:4 [0,2,2] [0,-2,2] [0,2,-2] [0,-2,-2] [1,1,2] [-1,1,2] [1,-1,2] [1,1,-2] [-1,-1,2] [-1,1,-2] [1,-1,-2] [-1,-1,-2] [1,2,1] [-1,2,1] [1,-2,1] [1,2,-1] [-1,-2,1] [-1,2,-1] [1,-2,-1] [-1,-2,-1] [2,0,2] [-2,0,2] [2,0,-2] [-2,0,-2] [2,1,1] [-2,1,1] [2,-1,1] [2,1,-1] [-2,-1,1] [-2,1,-1] [2,-1,-1] [-2,-1,-1] [2,2,0] [-2,2,0] [2,-2,0] [-2,-2,0]
Distance:5 [1,2,2] [-1,2,2] [1,-2,2] [1,2,-2] [-1,-2,2] [-1,2,-2] [1,-2,-2] [-1,-2,-2] [2,1,2] [-2,1,2] [2,-1,2] [2,1,-2] [-2,-1,2] [-2,1,-2] [2,-1,-2] [-2,-1,-2] [2,2,1] [-2,2,1] [2,-2,1] [2,2,-1] [-2,-2,1] [-2,2,-1] [2,-2,-1]
If you are using Euclidian Norm, you have to replace your distance with: sqrt(x*x+y*y+z*z) and you cannot rise the distance in steps of one. But beside that you could do it very similiar.
Waited until the bounty race was completed, as to not interfere. I would however like to point out that imho all answers that i can see atm are rather dubious. The question holds this statement:
"this algorithm is intended to perform on GPU"
It is indeed possible to do efficient numeric processing on the GPU, but all solutions here propose some kind of looping. This is not how the GPU works.
The GPU executes in parallel. Indeed one can loop in the GPU, but then it happens in an extremely isolated fashion. A loop requires a "global controller", for example a counter, but the point of the GPU execution is that it executes in no particular order, using multiple cores. It is impossible to "grab" the result of one cores' execution and use it as argument to another cores' calculation. This is simply not how the GPU works.
It is possible to do calculation of multidimensional arrays on the GPU, even on higher than 3-dimensional. That is just a matter of cell addressing, and it is trivial to address for example 4 dimensions using a 1-dimensional address space, and describe/access the corresponding data.
But it is not possible to step through cells (pixels, memory locations) one by one, in a specific order - there is no control for such. And it is not possible to increment, or have loops, or have inner loops. On a GPU, the last element may well be executed first. On a GPU, each element is totally autonomous. No calculation is aware of any other calculation, since there is no communication between the them. The environment for each calculation on the GPU is as if it was alone in the world.
The answers given in this thread all assume CPU logic. IF solving the problem on CPU, it is trivial. Taking a 2-dimensional example, here are the absolute x,y coordinate pairs for 5x5, adjusted for zero in the middle:
┌───┬───┬───┬───┬───┐
│2 2│2 1│2 0│2 1│2 2│
├───┼───┼───┼───┼───┤
│1 2│1 1│1 0│1 1│1 2│
├───┼───┼───┼───┼───┤
│0 2│0 1│0 0│0 1│0 2│
├───┼───┼───┼───┼───┤
│1 2│1 1│1 0│1 1│1 2│
├───┼───┼───┼───┼───┤
│2 2│2 1│2 0│2 1│2 2│
└───┴───┴───┴───┴───┘
Adding the pairs in each cell gives us the Manhattan distances:
4 3 2 3 4
3 2 1 2 3
2 1 0 1 2
3 2 1 2 3
4 3 2 3 4
At the same time, we can have an indexing system (this is however a weakness in the question, as it does no state adressing spaces):
1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
16 17 18 19 20
21 22 23 24 25
We can flatten both the distances:
dist = 4 3 2 3 4 3 2 1 2 3 2 1 0 1 2 3 2 1 2 3 4 3 2 3 4
and the indexes
index = 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
The unique Manhattan distances for an N (or N * N or N * N * N) array are all numbers between 1 and N-1 preceeded by 0 if N is odd. For a 5*5 matrix:
0 1 2 3 4
Walk through each distance and see where that distance equals "dist". For 5*5:
0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 // dist = 0 at locations 13
0 0 0 0 0 0 0 1 0 0 0 1 0 1 0 0 0 1 0 0 0 0 0 0 0 // dist = 1 at locations 8 12 14 18
0 0 1 0 0 0 1 0 1 0 1 0 0 0 1 0 1 0 1 0 0 0 1 0 0 // dist = 2 at locations 3 7 9 11 15 17 19 23
0 1 0 1 0 1 0 0 0 1 0 0 0 0 0 1 0 0 0 1 0 1 0 1 0 // dist = 3 at locations 2 4 6 10 16 20 22 24
1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 // dist = 4 at locations 1 5 21 25
and build up an array based on those locations. Ie. have an ampty array, append 13 to it as it's first element, then append 8 12 14 18, etc. The eventual result will be
13 8 12 14 18 3 7 9 11 15 17 19 23 2 4 6 10 16 20 22 24 1 5 21 25
That is the desired sord order. It is simple to re-arrange that into for example 2-dimensional address space, by using divide, min and residue.
However, this way to calculate is USELESS ON A GPU. It requires a specific execution order, and that we do not have on a GPU.
If solving the traversal order on a GPU, the solution should be
lookupcoordinates = fn(currentposition)
It may be possible to describe fn, but the question is way too incomplete - a whole lot more detail is required, than just saying "on GPU". How is the 3d array described? In what memory is it situated? What is the result's adressing space? Etc.
I'd be happy to hear more insight detail on this, as well as corrections to what i write above.
Addendum for discussion with user m69
One (minor) thought is doing the calculation in [number of dimensions] steps, where an accumulation from previous dimension would be used for next, up to for example 3. It may be that the numerical behaviour would be useful in such a case.
If looking closer at the basics, one would probably assume a linear target space, such that it is a simple indexing vector from 1 to [number of cells], where for example a 10*10*10 cube would have a linear 1D vector of 1000 indexes, from 1 to 1000. It is always possible to later on re-phrase these indexes into square or more-dimensional format.
In a 1-dimensional case, let's assume we have a 9-element data "cube" (same as 9*1*1, if expressed 3-dimensionally). Would be as below
x x x x x x x x x // Data
1 2 3 4 5 6 7 8 9 // Indexes
4 3 2 1 0 1 2 3 4 // Manhattan distances
5 4 6 3 7 2 8 1 9 // Pick order, starting from center
Hence we need a fn that maps as below
┌──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐
│1 to 5│2 to 4│3 to 6│4 to 3│5 to 7│6 to 2│7 to 8│8 to 1│9 to 9│
└──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┘
Now, if for example looking at index 4 of the result, fn must be able to, independently of any other index, resolve a result = 3, ie. fn(4) = 3. It should be possible to describe fn. It should be possible to first conclude that "4" resides in layer 2 (if layer 0 is the innermost). After that, it should be possible to conclude how meny cells layer 2 has (all layers have 2 cells), and finally whether this cell is the first or second occurance/element of layer 2. That would resolve into 3, ie. for result[4] we pick data[3].
Now, if assuming a 2-dimensional "cube" of size 11*11(*1), we have this situation:
0 1 2 3 4 5 6 7 8 9 10 // Unique Manhattan distances
1 4 8 12 16 20 20 16 12 8 4 // How many cells in each distance-layer?
We note that the "how many" is rather symmetrical, and even more symmetrical for 10*10:
1 2 3 4 5 6 7 8 9 // Unique Manhattan distances
4 8 12 16 20 16 12 8 4 // How many cells in each distance-layer?
4 12 24 40 60 76 88 96 100 // Cumulative sum of "How many..."
Notice the "cumulative sum"! Using that, if we are atm solving for example index=55 (can happen at any time, before or after 54, remember!), we can conclude that we are currently aiming at layer 5, which holds 20 elements, those with index = 40...60.
This layer starts with 40, and we are now at 55. Difference is 15. Maybe it is possible to describe the offset (x,y)-coordinates from "layer 5-origin x,y) using that "15"? My guess is that we are just entering the 4th quadrant.
Same for 3D?

Comparing arrays to find out of one is contained within another

I've got the following code:
while rounds<=5
fprintf('Rolling the dice...\n');
roll=randi(6,1,5);
roll=sort(roll);
fprintf('You rolled:');
disp(roll);
rollCount=rollCount+1;
for x=1:2:17
y=all(ismember(roll,rule{x}))
disp(ismember(roll,rule{x}));
z=all(ismember(rule{x},roll))
disp(ismember(rule{x},roll));
rounds=rounds+1;
end
end
What it SHOULD do is compare the roll array to the rule{x} array and tell me if it's a match. If it's not a match, it should tell me which indexes of the roll array aren't matching the rule array. It's not working correctly however. Say the example roll is [2 2 3 5 5] and the rule{x} is [1 2 3 4 5].
The output I'd like is an array that has [0 1 1 0 1] but the ones I get out of y is [1 1 1 1 1] and for z is [0 1 1 0 1]. That might seem like the right output, but if we change the rule to [5 5 5 5 5] I get [1 1 1 1 1] which is incorrect.
This is for a Yahtzee game I'm writing. The roll is the roll of the dice, and the rule is what I'm trying to match against so I can see what ones I need to re-roll to try and get it to match.
EDIT:
Using the code from dspyz, I wrote the function:
function[scoreCode]=ForwardChaining(rollFunc,ruleFunc)
temp=histc(rollFunc,1:6);
for x=1:2:11
if (ruleFunc{x}<=temp)
scoreCode=ruleFunc{x+1};
break;
else scoreCode=0;
end
end
The main function calls this as:
c= ForwardChaining(roll,rule);
if c == 12;
break;
end
But for some reason, even after 100,000 iterations it doesn't stop, which I take as it not working as intended.
Since the dice can only take on values from 1 to 6. Why not instead generate the histogram counts of each roll.
ie take your (row-)vector of rolls v and say
a = histc(roll, 1:6);
Now if you want to check if a rule is a subset of a (where the rule itself is also phrased in terms of counts of each number), you can just check if
rule <= a
The roll satisfies the rule if this is true in all 6 indices
To clarify:
I don't know about a first-grader, but given a set of (possibly-repeated) values for example [1, 1, 2, 4, 5] where everything is from 1 to 6, we can represent this by counting how many of each number from 1 to 6 is present. In this example:
1: 2
2: 1
3: 0
4: 1
5: 1
6: 0
Now we want to compare this against a rule of the same form but with possibly fewer elements. For example [1, 2, 3, 4]. The counts for this rule would be
1: 1
2: 1
3: 1
4: 1
5: 0
6: 0
To check if [1, 2, 3, 4] is a subset of [1, 1, 2, 4, 5], we only need to know if the counts for [1, 2, 3, 4] are all less than or equal to the counts of [1, 1, 2, 4, 5]. Ie we want to check if
1: 1 <= 2
2: 1 <= 1
3: 1 <= 0
4: 1 <= 1
5: 0 <= 1
6: 0 <= 0
All of these are true except for 3 so we know that [1 2 3 4] is not a subset of [1 1 2 4 6] because it contains no 3
But if all 6 inequalities are true, then it would be
ex. If you want to know if [1 3 3 3 5] contains [3 3 3], you can check all of
1: 0 <= 1
2: 0 <= 0
3: 3 <= 3
4: 0 <= 0
5: 0 <= 1
6: 0 <= 0
which is true
EDIT: Looking at MATLAB's documentation, it says the argument to histc must be sorted
EDIT 2: Oops, got that wrong, it says the second argument must be sorted. Changing it back.
I am not sure what your arrays are doing exactly, but the idea is you do if you have [2 2 3 5 5] and the rule{x} is [1 2 3 4 5], simply doing:
[2 2 3 5 5] == [1 2 3 4 5] will get you [0 1 1 0 1] or something like A=(roll==rule)

Resources