array_tools

</> ursina.array_tools

Functions

flatten_list()

flatten_list(target_list=None)


flatten_completely()

flatten_completely(target_list=None)


enumerate_2d()

enumerate_2d(lst=None) usage: for (x, y), value in enumerate_2d(my_2d_list)



enumerate_3d()

enumerate_3d(target_3d_list=None) usage: for (x, y, z), value in enumerate_3d(my_3d_list)



chunk_list()

chunk_list(target_list=None, chunk_size=None)


rotate_2d_list()

rotate_2d_list(target_2d_list=None)


rotate_3d_list()

rotate_3d_list(target_3d_list=None, clockwise=True) rotates around the y (up) axis where 1 is 90 degrees clockwise



string_to_2d_list()

string_to_2d_list(string=None, char_value_map={'.': 0, '#': 1})


list_2d_to_string()

list_2d_to_string(target_2d_list=None, characters='.#')


sample_bilinear()

sample_bilinear(target_2d_list: Array2D=None, x=None, y=None, clamp_if_outside=True)


Examples


    from ursina.ursinastuff import _test def flatten_list(target_list):     import itertools     return list(itertools.chain(*target_list)) def flatten_completely(target_list):     for i in target_list:         if isinstance(i, (tuple, list)):             for j in flatten_list(i):                 yield j         else:             yield i def enumerate_2d(lst):    # usage: for (x, y), value in enumerate_2d(my_2d_list)     if isinstance(lst, Array2D):         width = lst.width         height = lst.height     else:         width = len(lst)         height = len(lst[0])     for y in range(height):         for x in range(width):             yield (x, y), lst[x][y] def enumerate_3d(target_3d_list):   # usage: for (x, y, z), value in enumerate_3d(my_3d_list)     for x, vertical_slice in enumerate(target_3d_list):         for y, log in enumerate(vertical_slice):             for z, value in enumerate(log):                 yield (x, y, z), value from typing import List from ursina.scripts.property_generator import generate_properties_for_class @generate_properties_for_class() class Array2D(list):     __slots__ = ('width', 'height', 'default_value')     def __init__(self, width:int=None, height:int=None, default_value=0, data:List[List]=None):         self.default_value = default_value         if data is not None:                # initialize with provided data             self.width = len(data)             self.height = len(data[0])             if self.width == 0 or self.height == 0:                 raise ValueError("width and height must be >= 1.")             if any(len(row) != self.height for row in data):                 raise ValueError("all rows in the data must have the same length.")             super().__init__(data)         else:   # Initialize with default values             if width is None or height is None:                 raise ValueError("width and height must be provided if no data is given.")             self.width = int(width)             self.height = int(height)             super().__init__([[self.default_value for _ in range(self.height)] for _ in range(self.width)])     @staticmethod     def from_string(string, convert_to_type=str, starts_lower_left=True):             string = string.strip()             if starts_lower_left:                 data = [[convert_to_type(word.strip()) for word in line.split(',') if word] for line in string.split('\n') if line]                 data = [list(row) for row in zip(*data[::-1])]  # rotate                 return Array2D(data=data)             else:                 return Array2D(data=[[word.strip() for word in line.split(',') if word] for line in string.split('\n') if line])     def to_string(self, separator=', ', always_separate=False):         lines = []         longest_width = max(len(str(value)) for (_,_), value in enumerate_2d(self))         if always_separate or longest_width > 1:    # separate each element with ', '             for y in range(self.height-1, -1, -1):                 line = separator.join([f'{str(self[x][y]):>{longest_width}}' for x in range(self.width)])                 lines.append(line)         else:   # make string without spaces since all the values are the same width             for y in range(self.height-1, -1, -1):                 line = ''.join([str(self[x][y]) for x in range(self.width)])                 lines.append(line)         return '\n'.join(lines)     @property     def size(self):         from ursina.vec2 import Vec2         return Vec2(self.width, self.height)     def rows_getter(self):         return [[self[x][y] for x in range(self.width)] for y in range(self.height)]     def columns_getter(self):         return [[self[x][y] for y in range(self.height)] for x in range(self.width)]     def __str__(self):         lines = self.to_string().strip().split('\n')         longest_number = int(len(str(self.height)))         for y in range(self.height-1, -1, -1):             lines[y] = f'{self.height-1-y:<{longest_number}}| {lines[y]}'         padding = longest_number + 2         lines.append(f'{"o":<{padding}}{"-"*(self.width)}w:{self.width}')         result = '\n'+'\n'.join(lines)         return result     def map(self, func):         for (x, y), value in enumerate_2d(self):             self[x][y] = func(x, y, value)         return self     def reset(self):         for x in range(self.width):             for y in range(self.height):                 self[x][y] = self.default_value     def paste(self, data, x, y, ignore=-1):         for true_x in range(x, min(self.width, x+data.width)):             for true_y in range(y, min(self.height, y+data.height)):                 if data[true_x-x][true_y-y] == ignore:                     continue                 self[true_x][true_y] = data[true_x-x][true_y-y]     def add_margin(self, top=0, right=0, bottom=0, left=0, value=None):         if top < 0 or right < 0 or right < 0 or left < 0:             raise ValueError('input must be >= 0')         if value is None:             value = self.default_value         new_width = self.width + right + left         new_height = self.height + top + bottom         new_array = Array2D(new_width, new_height, default_value=value)         new_array.paste(self, left, bottom)         return new_array     @property     def bounds(self):         from ursina.ursinamath import Bounds         from ursina.vec3 import Vec3         min_x = self.width         min_y = self.height         max_x = 0         max_y = 0         for (x, y), value in enumerate_2d(self):             if value:                 min_x = min(min_x, x)                 min_y = min(min_y, y)                 max_x = max(max_x, x)                 max_y = max(max_y, y)         return Bounds(start=Vec3(min_x, min_y, 1), end=Vec3(max_x, max_y, 1))     def get(self, x, y, default=0):         x, y = int(x), int(y)         if x < 0 or x >= self.width or y < 0 or y >= self.height:             return default         return self[x][y]     def set(self, x, y, value):         x, y = int(x), int(y)         if x < 0 or x >= self.width or y < 0 or y >= self.height:             return         self[x][y] = value     def get_area(self, start, end, allow_out_of_bounds=False):         cropped_array = Array2D(width=end[0]-start[0], height=end[1]-start[1], default_value=self.default_value)         if not allow_out_of_bounds:             for (x, y), _ in enumerate_2d(cropped_array):                 cropped_array[x][y] = self[int(x+start[0])][int(y+start[1])]         else:             for (x, y), _ in enumerate_2d(cropped_array):                 cropped_array[x][y] = self.get(x+start[0], y+start[1], default=self.default_value)         return cropped_array if __name__ == '__main__':     from textwrap import dedent, indent     grid = Array2D(width=16, height=8)     padded_grid = grid.add_margin(top=4, right=7, bottom=3, left=2, value=7)     _test(         Array2D(data=[[1,6], [2,7], [3,8], [4,9], [5,10]]).to_string()         ==         indent(dedent('''         6,  7,  8,  9, 10         1,  2,  3,  4,  5         ''').strip(), ' ')     )     _test(Array2D(data=[[1,6], [2,7], [3,8], [4,9], [5,10]]).rows == [[1,2,3,4,5], [6,7,8,9,10]])     _test(Array2D.from_string('''         0,1,0         2,3,4         0,1,1         0,0,0         ''', int) ==         Array2D(data=[         [0,0,2,0],         [0,1,3,1],         [0,1,4,0]         ])     ) class Array3D(list):     __slots__ = ('width', 'height', 'depth', 'default_value')     def __init__(self, width:int=None, height:int=None, depth:int=None, default_value=0, data:List[List]=None):         self.default_value = default_value         if data is not None:                # initialize with provided data             self.width = len(data)             self.height = len(data[0])             self.depth = len(data[0][0])             if self.width == 0 or self.height == 0 or self.depth == 0:                 raise ValueError("width, height, depth must be >= 1.")             if any(len(row) != self.height for row in data):                 raise ValueError("all rows in the data must have the same length.")             super().__init__(data)         else:   # Initialize with default values             if width is None or height is None or depth is None:                 raise ValueError("width and height and depth must be provided if no data is given.")             self.width = int(width)             self.height = int(height)             self.depth = int(depth)             super().__init__([[[self.default_value for _ in range(self.depth)] for _ in range(self.height)] for _ in range(self.width)])     @property     def size(self):         from ursina.vec3 import Vec3         return Vec3(self.width, self.height, self.depth)     @property     def bounds(self):         from ursina.ursinamath import Bounds         from ursina.vec3 import Vec3         min_x = self.width         min_y = self.height         min_z = self.depth         max_x = 0         max_y = 0         max_z = 0         for (x, y, z), value in enumerate_3d(self):             if value:                 min_x = min(min_x, x)                 min_y = min(min_y, y)                 min_z = min(min_z, z)                 max_x = max(max_x, x)                 max_y = max(max_y, y)                 max_z = max(max_z, z)         return Bounds(start=Vec3(min_x, min_y, min_z), end=Vec3(max_x, max_y, max_z))     def get(self, x, y, z, default=0):         x, y, z = int(x), int(y), int(z)         try:             return self[x][y][z]         except:             return default     def reset(self):         for x,y,z, _ in enumerate_3d(self):             self[x][y][z] = self.default_value     def paste(self, data, x, y, z, ignore=-1):         for true_x in range(x, min(self.width, x+data.width)):             for true_y in range(y, min(self.height, y+data.height)):                 for true_z in range(z, min(self.depth, y+data.depth)):                     if data[true_x-x][true_y-y][true_z-z] == ignore:                         continue                     self[true_x][true_y][true_z] = data[true_x-x][true_y-y][true_z-z] def chunk_list(target_list, chunk_size):     for i in range(0, len(target_list), chunk_size):         yield target_list[i:i + chunk_size] def rotate_2d_list(target_2d_list):     return [list(row) for row in zip(*target_2d_list[::-1])]  # rotate def rotate_3d_list(target_3d_list, clockwise=True): # rotates around the y (up) axis where 1 is 90 degrees clockwise     new_data = Array3D(width=target_3d_list.depth, height=target_3d_list.height, depth=target_3d_list.width)     print('rotate array3d, clockwise:', clockwise)     if clockwise:         for (x, y, z), value in enumerate_3d(target_3d_list):             new_x = z             new_z = target_3d_list.width - 1 - x             new_data[new_x][y][new_z] = value     else:         for (x, y, z), value in enumerate_3d(target_3d_list):             new_x = target_3d_list.depth - 1 - z             new_z = x             new_data[new_x][y][new_z] = value     return new_data def string_to_2d_list(string, char_value_map={'.':0, '#':1}):     from textwrap import dedent     grid = dedent(string).strip()     grid = grid.split('\n')     grid = [[char_value_map.get(e, 0) for e in line] for line in grid]     grid = [list(row) for row in zip(*grid[::-1])]  # rotate     return grid if __name__ == '__main__':     from ursina.ursinastuff import _test     _test(string_to_2d_list('''\         .##..#..#.####..#..         ''')         ==         rotate_2d_list([         [1,0,0,1, 0, 1,1,1,0, 0, 1,1,1,1, 0, 0,1,0,0],         [1,0,0,1, 0, 1,0,0,1, 0, 1,1,1,0, 0, 0,1,0,0],         [1,0,0,1, 0, 1,1,1,0, 0, 0,0,0,1, 0, 0,1,0,0],         [0,1,1,0, 0, 1,0,0,1, 0, 1,1,1,1, 0, 0,1,0,0],     ])     ) def list_2d_to_string(target_2d_list, characters='.#'):     return '\n'.join([''.join([characters[e] for e in line]) for line in target_2d_list]) if __name__ == '__main__':     list_2d = [         [1,0,0,1, 0, 1,1,1,0, 0, 1,1,1,1, 0, 0,1,0,0],         [1,0,0,1, 0, 1,0,0,1, 0, 1,1,1,0, 0, 0,1,0,0],         [1,0,0,1, 0, 1,1,1,0, 0, 0,0,0,1, 0, 0,1,0,0],         [0,1,1,0, 0, 1,0,0,1, 0, 1,1,1,1, 0, 0,1,0,0],     ]     from textwrap import dedent     expected_result = dedent('''         .##..#..#.####..#..         ''').strip()     _test(list_2d_to_string(list_2d) == expected_result) def sample_bilinear(target_2d_list:Array2D, x, y, clamp_if_outside=True):     X = clamp(int(x), 0, target_2d_list.width-1)     Y = clamp(int(y), 0, target_2d_list.height-1)     next_x = clamp(X+1, 0, target_2d_list.width-1)     next_y = clamp(Y+1, 0, target_2d_list.height-1)     height_bottom_left = target_2d_list[X][Y]     height_bottom_right = target_2d_list[next_x][Y]     height_top_left = target_2d_list[X][next_y]     height_top_right = target_2d_list[next_x][next_y]     x_fraction = x - X     y_fraction = y - Y     bottom_row_interpolation = (1 - x_fraction) * height_bottom_left + x_fraction * height_bottom_right     top_row_interpolation = (1 - x_fraction) * height_top_left + x_fraction * height_top_right     interpolated_height = (1 - y_fraction) * bottom_row_interpolation + y_fraction * top_row_interpolation     return interpolated_height if __name__ == '__main__':     list_2d = Array2D(data=[         [1,1,0],         [1,0,0],         [1,0,0],     ])     _test(sample_bilinear(list_2d, 0, 0) == 1)     _test(sample_bilinear(list_2d, 1, 1) == 0)     _test(sample_bilinear(list_2d, .5, .5) == .75)     _test(sample_bilinear(list_2d, .5, .75) == 0.625) class LoopingList(list):     def __getitem__(self, i):         return super().__getitem__(i % len(self))