Given a group of boxes. I want arrange the boxes on top of each other to reach the maximum height. box cannot be placed on top of another box unless the area of 2D base is <= the 2D base of the lower box. allowed to rotated any box to use any two sides as its base.
For example, consider below 4 boxes where each box has the following dimensions
Input: Box 1: (4,5,2), Box 2:(3,1,6), Box 3:(3,2,1), Box 4:(6,8,3)
Output: From bottom to top as follows:
Box 4 on the base (6,3) and height 8,
then Box 1 on the base (4,2) and height 5,
then Box 2 on the base (1,3) and height 6,
finally, on top Box 3 on the base (2,1) and height 3.
The total height is 22
this solution is work but use all instances of all boxes but i need use only one instance for each box.
this code get the same height but use the box 2 twice and ignore another box
the problem is in if statement in this 2 for loop
> for i in range(1, n):
for j in range(0, i):
if (rot[i].w <= rot[j].w and rot[i].l <= rot[j].l) and rot[i].boxNo != rot[j].boxNo:
if msh[i] < msh[j] + rot[i].h:
msh[i] = msh[j] + rot[i].h
how to prevent using the same box twice?
class Box:
def __init__(self,l, w, h):
self.h = h
self.w = w
self.l = l
self.boxNo = 0
def __lt__(self,other):
return self.l * self.w < other.l * other.w
def maxStackHeight(arr, n):
# Create an array of all rotations of given boxes.
rot = [Box(0, 0, 0) for _ in range(3 * n)]
index = 0
no=1
for i in range(n):
# original box
rot[index].h = arr[i].h
rot[index].l = max(arr[i].l, arr[i].w)
rot[index].w = min(arr[i].l, arr[i].w)
rot[index].boxNo=no
index = index+ 1
# First rotation
rot[index].h = arr[i].w
rot[index].l = max(arr[i].h, arr[i].l)
rot[index].w = min(arr[i].h, arr[i].l)
rot[index].boxNo = no
index = index + 1
# Second rotation
rot[index].h = arr[i].l
rot[index].l = max(arr[i].h, arr[i].w)
rot[index].w = min(arr[i].h, arr[i].w)
rot[index].boxNo = no
index = index + 1
no=no+1
n=n*3 # new number of boxes
rot.sort(reverse=True) #Sort array in descending order of base area
msh = [0] * n
for i in range(n):
msh[i] = rot[i].h
# Compute optimized msh values in bottom up manner
for i in range(1, n):
for j in range(0, i):
if (rot[i].w <= rot[j].w and rot[i].l <= rot[j].l) and rot[i].boxNo != rot[j].boxNo:
if msh[i] < msh[j] + rot[i].h:
msh[i] = msh[j] + rot[i].h
maxm = -1
for i in range(n):
maxm = max(maxm, msh[i])
return maxm
arr = [Box(4,5,2),Box(3,1,6),Box(3,2,1),Box(6,8,3)]
n = len(arr)
print("The maximum possible height of stack is",maxStackHeight(arr, n))
This ought to do the trick, it uses itertools module to create every possible combination of the boxes using cartesian product, and only checks ones that meet the criteria for your problem.
from itertools import product
class Box:
def __init__(self,l, w, h):
self.h = h
self.w = w
self.l = l
self.boxNo = 0
def __lt__(self,other):
return self.l * self.w < other.l * other.w
def maxStackHeight(arr, n):
# Create an array of all rotations of given boxes.
rot = [Box(0, 0, 0) for _ in range(3 * n)]
numBoxes = n
index = 0
no=1
for i in range(n):
# original box
rot[index].h = arr[i].h
rot[index].l = max(arr[i].l, arr[i].w)
rot[index].w = min(arr[i].l, arr[i].w)
rot[index].boxNo=no
index = index+ 1
# First rotation
rot[index].h = arr[i].w
rot[index].l = max(arr[i].h, arr[i].l)
rot[index].w = min(arr[i].h, arr[i].l)
rot[index].boxNo = no
index = index + 1
# Second rotation
rot[index].h = arr[i].l
rot[index].l = max(arr[i].h, arr[i].w)
rot[index].w = min(arr[i].h, arr[i].w)
rot[index].boxNo = no
index = index + 1
no=no+1
rot.sort(reverse=True) #Sort array in descending order of base area
# Compute optimized msh values in bottom up manner
workable = []
for combo in product(rot, repeat = numBoxes):
if len(set(b.boxNo for b in combo)) != numBoxes:
continue
canwork = True
for a, b in zip(combo[:-1], combo[1:]):
if a < b :
canwork = False
if canwork:
workable.append(combo)
return max(sum(box.h for box in combo) for combo in workable)
arr = [Box(4,5,2),Box(3,1,6),Box(3,2,1),Box(6,8,3)]
n = len(arr)
print("The maximum possible height of stack is",maxStackHeight(arr, n))
Related
I have a code that takes set of points of a point cloud and detects multiple planes. the code gives me the equation of planes which have points more than a certain number (here 4000) belonging to it. All I need is to write the coordinates of points (not their index) belonging to these planes in a text file.
The code:
def ReadPlyPoint(fname):
pcd = o3d.io.read_point_cloud(fname)
return PCDToNumpy(pcd)
def NumpyToPCD(xyz):
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(xyz)
return pcd
def PCDToNumpy(pcd):
return np.asarray(pcd.points)
def PlaneRegression(points, threshold=0.01, init_n=3, iter=1000):
pcd = NumpyToPCD(points)
w, index = pcd.segment_plane(threshold, init_n, iter)
[a, b, c, d] = w
if len(index) > 4000 :
print (f'plane equation: {a:.2f}x + {b:.2f}y + {c:.2f}z + {d:.2f} = 0')
return w, index
def DetectMultiPlanes(points, min_ratio=0.05, threshold=0.01, iterations=1000):
plane_list = []
N = len(points)
target = points.copy()
count = 0
while count < (1 - min_ratio) * N:
w, index = PlaneRegression(target, threshold=threshold, init_n=3, iter=iterations)
count += len(index)
plane_list.append((w, target[index]))
target = np.delete(target, index, axis=0)
return plane_list
if __name__ == "__main__":
import random
points = ReadPlyPoint('segdown.ply')
results = DetectMultiPlanes(points, min_ratio=0.05, threshold=0.01, iterations=3000)
planes = []
colors = []
for _, plane in results:
r = random.random()
g = random.random()
b = random.random()
color = np.zeros((plane.shape[0], plane.shape[1]))
color[:, 0] = r
color[:, 1] = g
color[:, 2] = b
planes.append(plane)
colors.append(color)
planes = np.concatenate(planes, axis=0)
colors = np.concatenate(colors, axis=0)
DrawResult(planes, colors)
I have tried this but the points stored don't belong to the detected planes :
def PlaneRegression(points, threshold=0.01, init_n=3, iter=1000):
f = open('primitives_only1.txt', 'a')
pcd = NumpyToPCD(points)
w, index = pcd.segment_plane(threshold, init_n, iter)
[a, b, c, d] = w
if len(index) > 4000 :
for i in range(len(index)):
f.write("{} \n".format(str(points[i])))
f.close()
return w, index
detected planes
I am trying to develop a random tetrahedron generator that can take a list of four coordinates and produce a tetrahedron. Currently, I am only able to plot a tetrahedron using four pre-determined points. Here is the code I have as of now:
def rand_tetrahedron_generator(bounds, min_len, max_len):
"""
bounds: List - max length in each dimension
min_len: int - minimum length of tetrahedron
max_len: int - maximum length of tetrahedron
"""
assert len(bounds) == 3
assert min_len <= max_len
max_len = min(max_len, bounds[0], bounds[1], bounds[2])
bounds = np.array(bounds)
p1 = np.random.randint(low=0, high=bounds, size=3)
p2 = np.random.randint(low=0, high=bounds - min_len, size=3)
p3 = np.random.randint(low=0, high=p1+p2, size=3)
p4 = np.random.randint(low=0, high=p1+p2, size=3)
points = np.array([p1,p2,p3,p4])
center = np.mean(points, axis=0)
x, y, z = (np.indices((60, 60, 60))-np.array([20,25,25]).reshape(-1,1,1,1))/8
mx = midpoints(x)
my = midpoints(y)
mz = midpoints(z)
conditions = []
for p1,p2,p3 in itertools.combinations(points, 3):
a, n = surface_normal_form(p1,p2,p3)
conditions.append((mx-a[0])*n[0]+(my-a[1])*n[1]+(mz-a[2])*n[2] <= 0)
simplex = conditions[0] & conditions[1] & conditions[2] & conditions[3]
return simplex
def surface_normal_form(a,b,c):
v = b-a
w = c-b
n = np.cross(v,w)
#normal needs to point out
if (center-a)#n > 0:
n *= -1
return a, n
def midpoints(x):
sl = ()
for i in range(x.ndim):
x = (x[sl + np.index_exp[:-1]] + x[sl + np.index_exp[1:]]) / 2.0
sl += np.index_exp[:]
return x
I believe that the way I generate p1, p2, p3, and p4 is incorrect because the function sometimes generates points that are unable to form a tetrahedron. I would greatly appreciate any advice on how to solve this issue. I have also attached an image of the final result I am looking for.
I am currently stumped by an artefact in my code. It appears to produce very sharp points in a grid pattern that have a noticeable difference in value to their neighbours.
I am following the blog post at http://www.bluh.org/code-the-diamond-square-algorithm/ and converting from whichever language they are using (assuming either C# or Java), and have double-checked that what I am doing should match.
Is there any chance that someone could have a browse over this, and see what I'm doing wrong? I've stepped through it at smaller levels, and stopped it on specific iterations of the algorithm (by unrolling the top loop, and explicitly calling the algorithm a set number of times) and everything seems to work until we get to the very last set of points/pixels.
I use a class (called Matrix) to access the list, and wrap any out of bounds values.
The code for the algorithm is as follows:
class World :
def genWorld (self, numcells, cellsize, seed):
random.seed(seed)
self.dims = numcells*cellsize
self.seed = seed
self.cells = Matrix(self.dims, self.dims)
# set the cells at cellsize intervals
half = cellsize/2
for y in range(0, self.dims, cellsize):
for x in range(0, self.dims, cellsize):
self.cells[x,y] = random.random()
scale = 1.0
samplesize = cellsize
while samplesize > 1:
self._diamondSquare(samplesize, scale)
scale *= 0.8
samplesize = int(samplesize/2)
# I need to sort out the problem with the diamond-square algo that causes it to make the weird gridding pattern
def _sampleSquare(self, x, y, size, value):
half = size/2
a = self.cells[x-half, y-half]
b = self.cells[x+half, y-half]
c = self.cells[x-half, y+half]
d = self.cells[x+half, y+half]
res = min(((a+b+c+d+value)/5.0), 1.0)
self.cells[x, y] = res
def _sampleDiamond(self, x, y, size, value):
half = size/2
a = self.cells[x+half, y]
b = self.cells[x-half, y]
c = self.cells[x, y+half]
d = self.cells[x, y-half]
res = min(((a+b+c+d+value)/5.0), 1.0)
self.cells[x, y] = res
def _diamondSquare(self, stepsize, scale):
half = int(stepsize/2)
for y in range(half, self.dims+half, stepsize):
for x in range(half, self.dims+half, stepsize):
self._sampleSquare(x, y, stepsize, random.random()*scale)
for y in range(0, self.dims, stepsize):
for x in range(0, self.dims, stepsize):
self._sampleDiamond(x+half, y, stepsize, random.random()*scale)
self._sampleDiamond(x, y+half, stepsize, random.random()*scale)
and is called with:
w = World()
w.genWorld(16, 16, 1) # a 256x256 square world, since the numcells is multiplied by the cellsize to give us the length of ONE side of the resulting grid
then I save to file to check the result:
file = io.open("sample.raw",'wb')
arr = [int(i * 255) for i in w.cells.cells] # w.cells.cells should not have a value >= 1.0, so what's going on?
ind = 0
for a in arr:
if a > 255:
print ("arr["+str(ind)+"] ::= "+str(a))
ind += 1
file.write(bytearray(arr))
file.close()
which gives the result:
EDIT: Okay, so it appears that I managed to get it working. I swapped from using functions for working out the diamond and square steps to doing it all in the _diamondSquare() function, but this wasn't the only thing. I also found out that random.random() provides values in the range [0.0 ->1.0), when I was expecting values in the range [-1.0 -> 1.0). After I corrected this, everything started working properly, which was a relief.
Thanks for the advice everyone, here's the working code in case anyone else is struggling with something similar:
Random Function
# since random.random() gives a value in the range [0.0 -> 1.0), I need to change it to [-1.0 -> 1.0)
def rand():
mag = random.random()
sign = random.random()
if sign >=0.5:
return mag
return mag * -1.0
Matrix class
class Matrix:
def __init__(self, width, height):
self.cells = [0 for i in range(width*height)]
self.width = width
self.height = height
self.max_elems = width*height
def _getsingleindex(self, ind):
if ind < 0:
ind *= -1
while ind >= self.max_elems:
ind -= self.max_elems
return ind
def _getmultiindex(self, xind, yind):
if xind < 0:
xind *= -1
if yind < 0:
yind *= -1
while xind >= self.width:
xind -= self.width
while yind >= self.height:
yind -= self.height
return xind + (yind*self.height)
def __getitem__(self, inds):
# test that index is an integer, or two integers, and throw an indexException if not
if hasattr(inds, "__len__"):
if len(inds) > 1:
return self.cells[self._getmultiindex(int(inds[0]), int(inds[1]))]
return self.cells[self._getsingleindex(int(inds))]
def __setitem__(self, inds, object):
# test that index is an integer, or two integers, and throw an indexException if not
if hasattr(inds, "__len__"):
if len(inds) > 1:
self.cells[self._getmultiindex(int(inds[0]),int(inds[1]))] = object
return self.cells[self._getmultiindex(int(inds[0]),int(inds[1]))]
self.cells[self._getsingleindex(int(inds))] = object
return self.cells[self._getsingleindex(int(inds))]
def __len__(self):
return len(self.cells)
The Actual Diamond-Square Generation
# performs the actual 2D generation
class World:
def genWorld (self, numcells, cellsize, seed, scale = 1.0):
random.seed(seed)
self.dims = numcells*cellsize
self.seed = seed
self.cells = Matrix(self.dims, self.dims)
mountains = Matrix(self.dims, self.dims)
# set the cells at cellsize intervals
for y in range(0, self.dims, cellsize):
for x in range(0, self.dims, cellsize):
# this is the default, sets the heights randomly
self.cells[x,y] = random.random()
while cellsize > 1:
self._diamondSquare(cellsize, scale)
scale *= 0.5
cellsize = int(cellsize/2)
for i in range(len(mountains)):
self.cells[i] = self.cells[i]*0.4 + (mountains[i]*mountains[i])*0.6
def _diamondSquare(self, stepsize, scale):
half = int(stepsize/2)
# diamond part
for y in range(half, self.dims+half, stepsize):
for x in range(half, self.dims+half, stepsize):
self.cells[x, y] = ((self.cells[x-half, y-half] + self.cells[x+half, y-half] + self.cells[x-half, y+half] + self.cells[x+half, y+half])/4.0) + (rand()*scale)
# square part
for y in range(0, self.dims, stepsize):
for x in range(0, self.dims, stepsize):
self.cells[x+half,y] = ((self.cells[x+half+half, y] + self.cells[x+half-half, y] + self.cells[x+half, y+half] + self.cells[x+half, y-half])/4.0)+(rand()*scale)
self.cells[x,y+half] = ((self.cells[x+half, y+half] + self.cells[x-half, y+half] + self.cells[x, y+half+half] + self.cells[x, y+half-half])/4.0)+(rand()*scale)
Main Function (added for completeness)
# a simple main function that uses World to create a 2D array of diamond-square values, then writes it to a file
def main():
w = World()
w.genWorld(20, 16, 1)
mi = min(w.cells.cells)
ma = max(w.cells.cells) - mi
# save the resulting matrix to an image file
file = io.open("sample.raw",'wb')
maxed = [(i-mi)/ma for i in w.cells.cells]
arr = [int(i * 255) for i in maxed]
file.write(bytearray(arr))
file.close()
I'm using four inputs for my function: a picture object, x coordinate, y coordinate, and the height/width of the square. I want to separately average all the red, green, and blue values of the picture. I'm having trouble with the accumulator variables of the RGB pixels and checking the bounds of the square. Can anyone help me out?
from imageTools import *
p1 = makePicture("flower.jpg")
def averageColor(pic, xCord, yCord, width):
rAcc = 0
gAcc = 0
bAcc = 0
for x in range(xCord, xCord + width):
for y in range(yCord, yCord + width):
picWidth = getWidth(pic)
picHeight = getHeight(pic)
if x <= picWidth and y <= picHeight:
pixel = getPixel(pic, xCord, yCord)
r = getRed(pixel)
g = getGreen(pixel)
b = getBlue(pixel)
rAcc = rAcc + 1
gAcc = gAcc + 1
bAcc = bAcc + 1
avgRed = r / rAcc
avgGreen = g / gAcc
avgBlue = b / bAcc
newColor = makeColor(avgRed, avgGreen, avgBlue)
return newColor
col1 = averageColor(p1, 0, 150, 100)
print(col1)
You want to use < and not <= when comparing indexes because indexes are zero based. For instance if the picture is 10 pixels wide 9 is the index to the last pixel
Also you are overwriting your accumulation variables r, g and b. You need to do
r = r + getRed(...)
oh. Also it looks like you are trying to use xCoord and yCoord to get the pixel rather than your x and y variables
I want to generate all directions from a point in a 3D grid, but I can't quite get my head around the next bit. For the record it's all stored in a single list, so I need some maths to calculate where the next point will be.
I only really need 3 calculations to calculate any of the 26 or so different directions (up, up left, up left forwards, up left backwards, up right, up right forwards, etc), so I decided to work with X, Y, Z, then split them into up/down left/right etc, to then get the correct number to add or subtract. Generating this list to get the maths working however, seems to be the hard bit.
direction_combinations = 'X Y Z XY XZ YZ XYZ'.split()
direction_group = {}
direction_group['X'] = 'LR'
direction_group['Y'] = 'UD'
direction_group['Z'] = 'FB'
So basically, using the below code, this is the kind of stuff I'd like it to do, but obviously not have it hard coded. I could do it in a hacky way, but I imagine there's something really simple I'm missing here.
#Earlier part of the code to get this bit working
#I've also calculated the edges but it's not needed until after I've got this bit working
grid_size = 4
direction_maths = {}
direction_maths['U'] = pow(grid_size, 2)
direction_maths['R'] = 1
direction_maths['F'] = grid_size
direction_maths['D'] = -direction_maths['U']
direction_maths['L'] = -direction_maths['R']
direction_maths['B'] = -direction_maths['F']
#Bit to get working
starting_point = 25
current_direction = 'Y'
possible_directions = [direction_group[i] for i in list(current_direction)]
for y in list(possible_directions[0]):
print starting_point + direction_maths[y]
# 41 and 9 are adjacent on the Y axis
current_direction = 'XYZ'
possible_directions = [direction_group[i] for i in list(current_direction)]
for x in list(possible_directions[0]):
for y in list(possible_directions[1]):
for z in list(possible_directions[2]):
print starting_point + direction_maths[x] + direction_maths[y] + direction_maths[z]
# 44, 36, 12, 4, 46, 38, 14 and 6 are all adjacent on the corner diagonals
Here's a general idea of how the grid looks with the list indexes (using 4x4x4 as an example):
________________
/ 0 / 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 /26 /27 /
/___/___/___/___/
/28 /29 /30 /31 /
/___/___/___/___/
________________
/32 /33 /34 /35 /
/___/___/___/___/
/36 /37 /38 /39 /
/___/___/___/___/
/40 /41 /42 /43 /
/___/___/___/___/
/44 /45 /46 /47 /
/___/___/___/___/
________________
/48 /49 /50 /51 /
/___/___/___/___/
/52 /53 /54 /55 /
/___/___/___/___/
/56 /57 /58 /59 /
/___/___/___/___/
/60 /61 /62 /63 /
/___/___/___/___/
Edit: Using the answers mixed with what I posted originally (wanted to avoid converting to and from 3D points if possible), this is what I ended up with to count the number of complete rows :)
def build_directions():
direction_group = {}
direction_group['X'] = 'LR'
direction_group['Y'] = 'UD'
direction_group['Z'] = 'FB'
direction_group[' '] = ' '
#Come up with all possible directions
all_directions = set()
for x in [' ', 'X']:
for y in [' ', 'Y']:
for z in [' ', 'Z']:
x_directions = list(direction_group[x])
y_directions = list(direction_group[y])
z_directions = list(direction_group[z])
for i in x_directions:
for j in y_directions:
for k in z_directions:
all_directions.add((i+j+k).replace(' ', ''))
#Narrow list down to remove any opposite directions
some_directions = all_directions
opposite_direction = all_directions.copy()
for i in all_directions:
if i in opposite_direction:
new_direction = ''
for j in list(i):
for k in direction_group.values():
if j in k:
new_direction += k.replace(j, '')
opposite_direction.remove(new_direction)
return opposite_direction
class CheckGrid(object):
def __init__(self, grid_data):
self.grid_data = grid_data
self.grid_size = calculate_grid_size(self.grid_data)
self.grid_size_squared = pow(grid_size, 2)
self.grid_size_cubed = len(grid_data)
self.direction_edges = {}
self.direction_edges['U'] = range(self.grid_size_squared)
self.direction_edges['D'] = range(self.grid_size_squared*(self.grid_size-1), self.grid_size_squared*self.grid_size)
self.direction_edges['R'] = [i*self.grid_size+self.grid_size-1 for i in range(self.grid_size_squared)]
self.direction_edges['L'] = [i*self.grid_size for i in range(self.grid_size_squared)]
self.direction_edges['F'] = [i*self.grid_size_squared+j+self.grid_size_squared-self.grid_size for i in range(self.grid_size) for j in range(self.grid_size)]
self.direction_edges['B'] = [i*self.grid_size_squared+j for i in range(self.grid_size) for j in range(self.grid_size)]
self.direction_edges[' '] = []
self.direction_maths = {}
self.direction_maths['D'] = pow(self.grid_size, 2)
self.direction_maths['R'] = 1
self.direction_maths['F'] = self.grid_size
self.direction_maths['U'] = -self.direction_maths['D']
self.direction_maths['L'] = -self.direction_maths['R']
self.direction_maths['B'] = -self.direction_maths['F']
self.direction_maths[' '] = 0
def points(self):
total_points = defaultdict(int)
opposite_directions = build_directions()
all_matches = set()
#Loop through each point
for starting_point in range(len(self.grid_data)):
current_player = self.grid_data[starting_point]
if current_player:
for i in opposite_directions:
#Get a list of directions and calculate movement amount
possible_directions = [list(i)]
possible_directions += [[j.replace(i, '') for i in possible_directions[0] for j in direction_group.values() if i in j]]
direction_movement = sum(self.direction_maths[j] for j in possible_directions[0])
#Build list of invalid directions
invalid_directions = [[self.direction_edges[j] for j in possible_directions[k]] for k in (0, 1)]
invalid_directions = [[item for sublist in j for item in sublist] for j in invalid_directions]
num_matches = 1
list_match = [starting_point]
#Use two loops for the opposite directions
for j in (0, 1):
current_point = starting_point
while current_point not in invalid_directions[j]:
current_point += direction_movement*int('-'[:j]+'1')
if self.grid_data[current_point] == current_player:
num_matches += 1
list_match.append(current_point)
else:
break
#Add a point if enough matches
if num_matches == self.grid_size:
list_match = tuple(sorted(list_match))
if list_match not in all_matches:
all_matches.add(list_match)
total_points[current_player] += 1
return total_points
Here's basically the same thing that #AnnoSielder did, but makes use of itertools to reduce the amount of code.
from itertools import product
# Get a list of all 26 possible ways to move from a given coordinate in a 3 coordinate system.
base_deltas = filter(lambda point: not all(axis ==0 for axis in point), list(product([-1, 0, 1], repeat=3)))
# Define your max axis length or your grid size
grid_size = 4
# Simple function that applys the deltas to the given coordinate and returns you the list.
def apply_deltas(deltas, coordinate):
return [
(coordinate[0]+x, coordinate[1]+y, coordinate[2]+z)
for x, y, z in deltas
]
# This will determine whether the point is out of bounds for the given grid
is_out_of_bounds = lambda point: all(0 <= axis < grid_size for axis in point)
# Define your point, in this case it's block #27 in your example
coordinate = [3, 2, 1]
# Apply the deltas, then filter using the is_out_of_bounds lambda
directions = filter(is_out_of_bounds, apply_deltas(base_deltas, coordinate))
# directions is now the list of 17 coordinates that you could move to.
Don't make thinks unnecessary complicated. Do not describe a point in 3 dimensions with 1 number - 3 coordinates means 3 numbers.
Should be something like this:
numb = 37
cube_size = 4
# convert to x - y - z
start = [0, 0, 0]
start[2] = numb / cube_size ** 2
numb = numb % cube_size ** 2
start[1] = numb / cube_size
start[0] = numb % cube_size
for x in [-1, 0, 1]:
current_x = start[0] + x
for y in [-1, 0, 1]:
current_y = start[1] + y
for z in [-1, 0, 1]:
current_z = start[2] + z
#reconvert
convert = current_x + current_y * cube_size + current_z * cube_size ** 2
print("x: " + str(current_x) + " y: " + str(current_y) + " z: " + str(current_z) + " => " + str(convert))
Simply generate your x/y/z-coordinate, then run all possibilities of add -1/0/1 to these coordinates and re-convert to your number in the grid.