Logical or every entry in two arrays - arrays

Say I have these
a = [0, 1, 0, 0, 0]
b = [0, 0, 0, 1, 0]
I want something like c = a | b
and get the answer as c = [0, 1, 0, 1, 0].

I would do something like this:
a = [0, 1, 0, 0, 0]
b = [0, 0, 0, 1, 0]
a.zip(b).map { |a, b| a | b }
#=> [0, 1, 0, 1, 0]

You are doing bit-twiddling with an integer's bits the elements of an array, which is a rather roundabout way of doing that. I suggest you simply deal with the integers directly:
x = 8
y = 2
Note:
x.to_s(2) #=> "1000"
y.to_s(2) #=> "10"
or, say,
x.to_s(2).rjust(8,"0") #=> "00001000"
y.to_s(2).rjust(8,"0") #=> "00000010"
Now you can obtain the result you want very simply with Fixnum#|:
z = x | y #=> 10
Let's confirm:
z.to_s(2) #=> "1010"
To retrieve bit i (i=1), use Fixnum#[]:
y[0] #=> 0
y[1] #=> 1
y[2] #=> 0
y[99] #=> 0
To set bit i, you will need to use Fixnum#<< to obtain an integer that has a 1 in bit position i and 0 for all other bit positions:
1 << i
For example:
1 << O #=> 1
1 << 1 #=> 2
1 << 2 #=> 4
Alternatively, you could of course write:
2**i
To set bit i to 0 use Fixnum#^ (pronounced "XOR"). For i=1,
y = y ^ (1<<1) #=> 0
which we can more compactly as:
y ^= (1<<1)
To set bit i to 1, for i=1, (recall y is now 0):
y |= (1<<1) #=> 2
Similarly (y now equals 2),
y |= (1<<9) #=> 514
y.to_s(2) #=> "1000000010"

You can also convert you arrays to numbers and then just use the binary operators, it should be faster if you need to perform lots of such operations.
If needed you can convert the result back to array.
a = [0, 1, 0, 0, 0]
b = [0, 0, 0, 1, 0]
a_number = a.join.to_i(2)
b_number = b.join.to_i(2)
c_number = a_number | b_number
c_array = c_number.to_s(2).split('').map(&:to_i)
c_array = [0] * (a.size - c_array.size) + c_array if c_array.size < a.size
p c_number.to_s(2)
p c_array

Related

Finding subsets from array

Scenario : I want to count no. of subsets having negative numbers between two zeros. Here no. of subsets with negative numbers can be consider as travelling down the hill from sea level
Consider a array
a=[0,1,0,-1,-2,-l,0]
0 here represent sea level. Numbers between zeros are steps going up/down and is represent by positive/negative consecutive steps.
You can move any steps up/down but it should come to sea level before moving up/down
If you consider
0,1,0 then it means that you moved from sea level to uphill and then again to sea level
0, 1, 0
similarly
From sea level to moving down one step, second step down, one step up and then again to sea level
0, -1, -2, -1, 0
Now considering an example.
a=[0,1,0,-1,-2,-1,0]
subset #1 = [0,1]
subset #2 = [0,-1,-2,-1,0]
Output: 1 (One time travelling below sea level)
Consider another example
b = [0,1,2,1,0,-1,-2,-1,0,1,2,1,0,-1,0]
subset #1 = [0,1,2,1,0] (moving up)
subset #2 = [0,-1,-2,-1,0] (moving down)
subset #3 = [0,1,2,1,0] (moving up)
subset #4 = [0,-1,0] (moving down)
Output: 2 (Two times travelling below sea level)
I'm wondering if there's a reason that you couldn't just count the times that you have a zero followed by a negative number; something like:
def count_below(arr)
count = 0
arr.each_index do |i|
count += 1 if arr[i] == 0 && (arr[i + 1] || 0) < 0
end
count
end
irb(main):039:0> count_below([0,1,0,-1,-2,-1,0])
=> 1
irb(main):040:0> count_below([0,1,2,1,0,-1,-2,-1,0,1,2,1,0,-1,0])
=> 2
Option 1: Produce Desired "Output" (Number of times below "Sea Level")
Assumptions:
We start at or above Sea Level.
Proposed Solution:
b = [0,1,2,1,0,-1,-2,-1,0,1,2,1,0,-1,0]
b.each_cons(2).count {|a,b| !a.negative? && b.negative? }
Steps:
Create an Enumerator of consecutive 2 elements (b.each_cons(2))
Count each time the first element is not negative and the second is negative (count {|a,b| !a.negative? && b.negative? })
Option 2: To produce the slices in your question the following should work
Assumptions:
We always start at Sea Level (0)
One must touch Sea Level when ascending or descending (e.g. [0,1,-1] is considered invalid input)
The first example is incorrect and should be [[0,1,0],[0,-1,-2,-1,0]]
Proposed Solution:
b.each_cons(2).with_object([]) do |(a,b),obj|
obj << [a] if a.zero?
obj.last << b
end
#=> [[0, 1, 2, 1, 0], [0, -1, -2, -1, 0], [0, 1, 2, 1, 0], [0, -1, 0]]
Steps:
Create an Enumerator of consecutive 2 elements (b.each_cons(2))
Iterate with a Accumulator Array (with_object([]))
Every time the first element is 0 insert a new sub Array (obj << [a] if a.zero?)
Insert the second element into the current (last) sub Array (obj.last << b)
with_object will return the accumulator Array as the result
You could chain methods to get the same result as Option 1 (such as count {|a| a.any?(&:negative)})
My solution assumes that you wish to count the number of sub-arrays that begin and end with zero and from the initial zero monotonically decrease to a minimum (negative) value, then monotonically increase to the final zero.
def count_em(arr)
arr.flat_map.with_index { |e,i| (e.zero? && i > 0 && i < arr.size-1) ? [0,0] : [e] }
.slice_when { |*pair| pair == [0,0] }
.count { |a| downhill?(a) }
end
def downhill?(arr)
return false if arr[1] > 0
imin = arr.each_index.min_by { |i| arr[i] }
return false unless decreasing?(arr[0..imin])
decreasing?(arr[imin..-1].reverse)
end
def decreasing?(arr)
arr.each_cons(2).all? { |e,f| f < e }
end
count_em [0, 1, 0, -1, -2, -1, 0]
#=> 1
count_em [0, 1, 2, 1, 0, -1, -2, -1, 0, 1, 2, 1, 0, -1, 0]
#=> 2
count_em [0, -2, -1, -2, 0]
#=> 0
count_em [0, -1, -2, 1, 0]
#=> 0
The last two examples reflect my understanding of the question.
The calculations are as follows.
arr = [0, 1, 2, 1, 0, -1, -2, -1, 0, 1, 2, 1, 0, -1, 0]
Use Enumerable#flat_map and Enumerator#with_index to insert a zero into a copy of arr after every zero but the first and last.
a = arr.flat_map.with_index { |e,i| (e.zero? && i > 0 && i < arr.size-1) ? [0,0] : [e] }
#=> [0, 1, 2, 1, 0, 0, -1, -2, -1, 0, 0, 1, 2, 1, 0, 0, -1, 0]
We can now use Enumerable#slice_when to obtain each sub-array that begins and end with a zero.
enum = a.slice_when { |*pair| pair == [0,0] }
#=> #<Enumerator: #<Enumerator::Generator:0x00007fa9fd970f28>:each>
We can see the elements that will be generated by this enumerator by converting it to an array.
enum.to_a
#=> [[0, 1, 2, 1, 0], [0, -1, -2, -1, 0], [0, 1, 2, 1, 0], [0, -1, 0]]
Lastly, use Enumerable#count to count the number of the four arrays generated by enum that are "downhill".
enum.count { |a| downhill?(a) }
#=> 2
This is because
downhill? [0, 1, 2, 1, 0] #=> false
downhill? [0, -1, -2, -1, 0] #=> true
downhill? [0, 1, 2, 1, 0] #=> false
downhill? [0, -1, 0] #=> true
To see how downhill? works, let
arr = [0, -1, -2, -1, 0]
Then
arr[1] > 0
#=> -1 > 0 => false, so do not return
imin = arr.each_index.min_by { |i| arr[i] }
#=> 2
decreasing?(arr[0..imin])
#=> decreasing? [0, -1, -2] => false, so do not return
decreasing?(arr[imin..-1].reverse)
#=> decreasing? [-2, -1, 0].reverse => [0, -1, -2]
# decreasing? [0, -1, -2] => true
Let's now look at the calculations performed by decreasing?. Suppose
arr = [0, -1, -2]
We see that
enum = arr.each_cons(2)
#=> #<Enumerator: [0, -1, -2]:each_cons(2)>
enum generates the following arrays:
enum.to_a
#=> [[0, -1], [-1, -2]]
Hence
arr.each_cons(2).all? { |e,f| f < e }
#=> true
because -1 < 0 #=> true and -2 < -1 #=> true.

Ruby - Pick one element from array by possibility

I have an array with 3 elements and I want to pick one and add that into another array base on possibility.
For example, num 1 has 5% chance to be picked, num 2 has 60% chance to be picked and num 3 has 35% chance to be picked.
arr = [{:num=>1, :diff=>-29}, {:num=>2, :diff=>5}, {:num=>3, :diff=>25}]
I found below methods from stackoverflow, just wondering if this would work? Or there is another way to do it?
def get_num(arr)
case rand(100) + 1
when 1..5
p arr[0]
when 6..65
p arr[1]
when 66..100
p arr[2]
end
end
get_num(arr)
Thanks!
Your code is fine but here are two other approaches.
Use a cumulative distribution function ("CDF")
CDF = [[0.05,0], [0.05+0.60,1], [0.5+0.60+0.35,2]]
#=> [[0.05,0], [0.65,1], [1.0,2]]
def get_num(arr)
n = rand
arr[CDF.find { |mx,_idx| n <= mx }.last]
end
arr = [{:num=>1, :diff=>-29}, {:num=>2, :diff=>5}, {:num=>3, :diff=>25}]
get_num(arr)
#=> {:num=>2, :diff=>5}
get_num(arr)
#=> {:num=>2, :diff=>5}
get_num(arr)
#=> {:num=>3, :diff=>25}
get_num(arr)
#=> {:num=>1, :diff=>-29}
get_num(arr)
#=> {:num=>2, :diff=>5}
Suppose:
n = rand
#=> 0.5385005480168696
then
a = CDF.find { |mx,_idx| n <= mx }
#=> [0.65,1]
i = a.last
#=> 1
arr[i]
#=> {:num=>2, :diff=>5}
Note that I've followed the convention of beginning the name of find's second block variable (_idx) with an underscore to signal to the reader that that block variable is not used in the block calculation. Often just an underscore (_) is used.
Now consider the fraction of times each element of arr will be randomly-drawn if n draws are made:
def outcome_fractions(arr, n)
n.times
.with_object(Hash.new(0)) { |_,h| h[get_num(arr)] += 1 }
.transform_values { |v| v.fdiv(n) }
end
Randomly select from an array of indices
outcome_fractions(arr, 1_000)
#=> {{:num=>2, :diff=>5} =>0.612,
# {:num=>3, :diff=>25} =>0.328,
# {:num=>1, :diff=>-29}=>0.06}
outcome_fractions(arr, 100_000)
#=> {{:num=>3, :diff=>25} =>0.34818,
# {:num=>1, :diff=>-29}=>0.04958,
# {:num=>2, :diff=>5} =>0.60224}
Notice that the fraction of each hash that is randomly drawn approaches its specified population probability as the sample size is increased (though the "pseudo-random" draws are not truly random).
Do not be concerned with how outcome_fractions works.
Here is another way that is more efficient (because it does not use find, which performs a linear search) but uses more memory.
CHOICE = [*[0]*5, *[1]*60, *[2]*35]
#=> [0, 0, 0, 0, 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, 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, 1, 1, 1,
# 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
# 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
# 2, 2, 2, 2, 2]
def get_num(arr)
arr[CHOICE[rand(100)]]
end
#=> {{:num=>2, :diff=>5} =>0.60029,
# {:num=>3, :diff=>25}=>0.35022,
# {:num=>1, :diff=>-29}=>0.04949}
Note that:
[*[0]*5, *[1]*60, *[2]*35]
produces the same array as
[[0]*5, [1]*60, [2]*35].flatten
The first * in *[0]*5 is the splat operator; the second is the method Array#*. [0]*5 #=> [0,0,0,0,0] is evaluated first.
CHOICE has 100 elements. If the three probabilities were, say, 0.048, 0.604 and 0.348, CHOICE would have 10**3 #=> 1_000 elements (48 zeros, 604 ones and 348 twos).
Here's a small variation / addition to Cary's great answer.
Instead of calculating the cumulative sums yourself, you can let Ruby build it for you out of the initial probabilities:
probs = [5, 60, 35]
sum = 0
sums = probs.map { |x| sum += x }
#=> [5, 65, 100]
we can now calculate a random number between 0 and the total sum and find the corresponding index:
r = rand(sum) #=> 37
sums.find_index { |i| r < i } #=> 1
Note that the initial probabilities don't have to sum to 100. instead of [5, 60, 35] you could also use:
probs = [1, 12, 7]
You can wrap the above code into a method:
def random_index(*probs)
sum = 0
sums = probs.map { |x| sum += x }
r = rand(sum)
sums.find_index { |i| r < i }
end
random_index(5, 60, 35) #=> 1
random_index(5, 60, 35) #=> 1
random_index(5, 60, 35) #=> 2
You could also make the method return a proc / lambda that can be reused:
def random_index_proc(*probs)
sum = 0
sums = probs.map { |x| sum += x }
-> {
r = rand(sum)
sums.find_index { |i| r < i }
}
end
prc = random_index_proc(5, 60, 35)
prc.call #=> 1
prc.call #=> 1
prc.call #=> 0
Last not least, you can also pre-populate an array this way: (using Cary's naming convention)
CHOICE = [5, 60, 35].flat_map.with_index { |v, i| [i] * v }
and get a random element via:
def get_num(arr)
arr[CHOICE.sample]
end
To keep the array small, you should prefer [1, 12, 7] (20 elements) over [5, 60, 35] (100 elements). With a little help from gcd you don't even have to calculate it yourself:
probs = [5, 60, 35]
gcd = probs.reduce { |a, b| a.gcd(b) }
#=> 5
probs.map { |i| i / gcd }
#=> [1, 12, 7]

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?

Changing surrounding elements of an array element

I am a beginner working through some exercises. I'm trying to manipulate a 2-dimensional array such that if an element is a 1, then the surrounding non-diagonal elements should be changed to 1:
[[0,0,0,0],
[0,0,1,0],
[0,0,0,0],
[0,0,0,0]]
should return
[[0,0,1,0],
[0,1,1,1],
[0,0,1,0],
[0,0,0,0]]
I am running into problems using nested each_with_index: After I adjust initial changes for the surrounding left and right, as the method iterates it picks up by earlier adjustment and makes an unwanted change. Moreover, the line which should change the "bottom" element is throwing an error:
a = [[0,0,0,0],
[0,0,1,0],
[0,0,0,0],
[0,0,0,0]
]
a.each_with_index do |m, n| # n == index of main array
m.each_with_index do |x, y| # y == index of subarray
if x == 1
a[n][y+1] = 1 unless (a[n][y+1]).nil? #right
a[n][y-1] = 1 unless (a[n][y-1]).nil? #left
a[n-1][y] = 1 unless (a[n-1][y]).nil? #top
a[n+1][y] = 1 unless (a[n+1][y]).nil? #bottom--currently giving an error
end
end
end
Any suggestions as to how I can go about solving these two aspects will be well received.
In order to avoid interference of a previous step, you can either (deep) duplicate the array and separate the reference array from the modifying one, or extract all the relevant indices before modifying the array. The latter is better. Also, using a flat array is much easier than handling a nested array, so I will convert a to and from a flattened array b, and work within b.
b = a.flatten
b
.each_index.select{|i| b[i] == 1}
.each do
|i|
b[i - 1] = 1 if b[i - 1] and i - 1 >= 0
b[i + 1] = 1 if b[i + 1]
b[i - 4] = 1 if b[i - 4] and i - 4 >= 0
b[i + 4] = 1 if b[i + 4]
end
a = b.each_slice(4).to_a
# => [[0, 0, 1, 0], [0, 1, 1, 1], [0, 0, 1, 0], [0, 0, 0, 0]]
I suggest you use the Matrix class.
require 'matrix'
m = Matrix[*a]
#=> Matrix[[0, 0, 0, 0],
# [0, 0, 1, 0],
# [0, 0, 0, 0],
# [0, 0, 0, 0]]
row, col = m.index(1)
#=> [1, 2]
Matrix.build(m.row_size, m.column_size) { |r,c|
(c-col).abs + (r-row).abs <= 1 ? 1 : 0 }.to_a
#=> [[0, 0, 1, 0],
# [0, 1, 1, 1],
# [0, 0, 1, 0],
# [0, 0, 0, 0]]
The non-matrix version of this (which uses the methods Array#index, Fixnum#divmod, Array::new, Enumerable#each_slice, and a few others) is as follows.
nrows, ncols = a.size, a.first.size
#=> [4, 4]
row, col = a.flatten.index(1).divmod(ncols)
#=> [1, 2]
Array.new(nrows*ncols) do |i|
r, c = i.divmod(ncols)
(c-col).abs + (r-row).abs <= 1 ? 1 : 0
end.each_slice(ncols).to_a
#=> [[0, 0, 1, 0],
# [0, 1, 1, 1],
# [0, 0, 1, 0],
# [0, 0, 0, 0]]
I find the method using the Matrix class to be easier to understand, though it may not be as efficient.

Numpy: constructing a 3D array from a 1D array

Assume a 1D array A is given. Is there an easy way to construct a 3D array B, such that B[i,j,k] = A[k] for all i,j,k? You can assume that the shape of B is prescribed, and that B.shape[2] = A.shape[0].
>>> k = 4
>>> a = np.arange(k)
>>> j = 3
>>> i = 2
>>> np.tile(a,j*i).reshape((i,j,k))
array([[[0, 1, 2, 3],
[0, 1, 2, 3],
[0, 1, 2, 3]],
[[0, 1, 2, 3],
[0, 1, 2, 3],
[0, 1, 2, 3]]]
Another easy way to do this is simple assignment -- broadcasting will automatically do the right thing:
i = 2
j = 3
k = 4
a = numpy.arange(k)
b = numpy.empty((i, j, k))
b[:] = a
print b
prints
[[[ 0. 1. 2. 3.]
[ 0. 1. 2. 3.]
[ 0. 1. 2. 3.]]
[[ 0. 1. 2. 3.]
[ 0. 1. 2. 3.]
[ 0. 1. 2. 3.]]]
for k,v in enumerate(A): B[:,:,k] = v

Resources