Skip to main content
edited tags
Link
NotAName
  • 251
  • 2
  • 9
nits
Source Link
greybeard
  • 6.5k
  • 3
  • 20
  • 52

I'm working on a fun project where I'm trying to implement Random Forest algorithm in pure Python, i.e. without NumpyNumPy. But then I'll still be dealing with arrays all the time and so I'm writing my own Array class to use in place of Numpy'sNumPy's ndarray. Although I'm only focusing on 2d arrays as I don't need n-dimensional stuff for this project.

In the normal andditionaddition or multiplication of arrays in NumpyNumPy the result is element-wise addition or multiplication and my solution is O(n * m) which for large arrays will be a problem.

from typing import Iterable
from pathlib import Path
from functools import reduce


class Array:    
    def __init__(self, value: Iterable[Iterable[int | float]]) -> None:
        self.value = value
        
    @property
    def value(self):
        return self.__value
    
    @value.setter
    def value(self, val: Iterable[Iterable[int | float]]):
        if not isinstance(val, Iterable):
            raise ValueError('The array must be an Iterable.')
        if not all(isinstance(a, Iterable) for a in val):
            raise ValueError("All elements of array must be Iterables. "
                             "If creating a flat array, all elements must be length 1 iterables.")
        if not reduce(lambda x, y: len(x)==len(y), val):
            raise ValueError("Can not create array from a ragged sequence. "
                             "Please ensure all elements have the same length.")
        self.__value = val
    
    @property 
    def shape(self) -> tuple[int, int]:
        return (len(self.value), len(self.value[0]))
    
    def __repr__(self):
        return f"Array({self.value}, shape={self.shape})"
    
    def __eq__(self, other: 'Array') -> bool:
        if self.shape != other.shape:
            return False
        return all(x == y for x, y in zip(self.value, other.value))
    
    def __neq__(self):
        return not self.__eq__
    
    def __getitem__(self, idx: tuple[int, int]) -> int | float:
        if idx[0] > self.shape[0] or idx[1] > self.shape[1]:
            raise IndexError(f"Index out of bounds for the array of shape {self.shape}")
        return self.value[idx[0]][idx[1]]
    
    def __add__(self, other: 'Array') -> 'Array':
        v = []
        if self.shape != other.shape:
            raise ValueError("Can only add arrays of the same shape.")
        for i in range(self.shape[0]):
            v.append([])
            for j in range(self.shape[1]):
                v[i].append(self.value[i][j] + other.value[i][j])    
        return Array(v)
    
    def __mul__(self, other: 'Array') -> 'Array':
        v = []
        if self.shape != other.shape:
            raise ValueError("Can only add arrays of the same shape.")
        for i in range(self.shape[0]):
            v.append([])
            for j in range(self.shape[1]):
                v[i].append(self.value[i][j] * other.value[i][j])    
        return Array(v)        
    
    @classmethod
    def from_text(cls, input_file: str | Path, skip_rows: int | None = None, delimeter: str = ',') -> 'Array':
        inp = Path(input_file)
        if not input_file or not inp.exists():
            raise IOError("No input file provided or file does not exist.")
        with open(inp, 'r') as file:
            arr = []
            for idx, i in enumerate(file.readlines()):
                if skip_rows is None or skip_rows <= idx:
                    try:
                        arr.append([float(k) for k in i.strip().split(delimeter)])
                    except (ValueError, TypeError) as e:
                        raise TypeError("Input data must be castable to float. "
                                        "Found incompatible data type. Check your inputs.") from e
        return cls(arr)
    
    def matmul(self, other: 'Array') -> 'Array':
        pass    

```

I'm working on a fun project where I'm trying to implement Random Forest algorithm in pure Python, i.e. without Numpy. But then I'll still be dealing with arrays all the time and so I'm writing my own Array class to use in place of Numpy's ndarray. Although I'm only focusing on 2d arrays as I don't need n-dimensional stuff for this project.

In the normal anddition or multiplication of arrays in Numpy the result is element-wise addition or multiplication and my solution is O(n * m) which for large arrays will be a problem.

from typing import Iterable
from pathlib import Path
from functools import reduce


class Array:    
    def __init__(self, value: Iterable[Iterable[int | float]]) -> None:
        self.value = value
        
    @property
    def value(self):
        return self.__value
    
    @value.setter
    def value(self, val: Iterable[Iterable[int | float]]):
        if not isinstance(val, Iterable):
            raise ValueError('The array must be an Iterable.')
        if not all(isinstance(a, Iterable) for a in val):
            raise ValueError("All elements of array must be Iterables. "
                             "If creating a flat array, all elements must be length 1 iterables.")
        if not reduce(lambda x, y: len(x)==len(y), val):
            raise ValueError("Can not create array from a ragged sequence. "
                             "Please ensure all elements have the same length.")
        self.__value = val
    
    @property 
    def shape(self) -> tuple[int, int]:
        return (len(self.value), len(self.value[0]))
    
    def __repr__(self):
        return f"Array({self.value}, shape={self.shape})"
    
    def __eq__(self, other: 'Array') -> bool:
        if self.shape != other.shape:
            return False
        return all(x == y for x, y in zip(self.value, other.value))
    
    def __neq__(self):
        return not self.__eq__
    
    def __getitem__(self, idx: tuple[int, int]) -> int | float:
        if idx[0] > self.shape[0] or idx[1] > self.shape[1]:
            raise IndexError(f"Index out of bounds for the array of shape {self.shape}")
        return self.value[idx[0]][idx[1]]
    
    def __add__(self, other: 'Array') -> 'Array':
        v = []
        if self.shape != other.shape:
            raise ValueError("Can only add arrays of the same shape.")
        for i in range(self.shape[0]):
            v.append([])
            for j in range(self.shape[1]):
                v[i].append(self.value[i][j] + other.value[i][j])    
        return Array(v)
    
    def __mul__(self, other: 'Array') -> 'Array':
        v = []
        if self.shape != other.shape:
            raise ValueError("Can only add arrays of the same shape.")
        for i in range(self.shape[0]):
            v.append([])
            for j in range(self.shape[1]):
                v[i].append(self.value[i][j] * other.value[i][j])    
        return Array(v)        
    
    @classmethod
    def from_text(cls, input_file: str | Path, skip_rows: int | None = None, delimeter: str = ',') -> 'Array':
        inp = Path(input_file)
        if not input_file or not inp.exists():
            raise IOError("No input file provided or file does not exist.")
        with open(inp, 'r') as file:
            arr = []
            for idx, i in enumerate(file.readlines()):
                if skip_rows is None or skip_rows <= idx:
                    try:
                        arr.append([float(k) for k in i.strip().split(delimeter)])
                    except (ValueError, TypeError) as e:
                        raise TypeError("Input data must be castable to float. "
                                        "Found incompatible data type. Check your inputs.") from e
        return cls(arr)
    
    def matmul(self, other: 'Array') -> 'Array':
        pass    

```

I'm working on a fun project where I'm trying to implement Random Forest algorithm in pure Python, i.e. without NumPy. But then I'll still be dealing with arrays all the time and so I'm writing my own Array class to use in place of NumPy's ndarray. Although I'm only focusing on 2d arrays as I don't need n-dimensional stuff for this project.

In the normal addition or multiplication of arrays in NumPy the result is element-wise addition or multiplication and my solution is O(n * m) which for large arrays will be a problem.

from typing import Iterable
from pathlib import Path
from functools import reduce


class Array:    
    def __init__(self, value: Iterable[Iterable[int | float]]) -> None:
        self.value = value
        
    @property
    def value(self):
        return self.__value
    
    @value.setter
    def value(self, val: Iterable[Iterable[int | float]]):
        if not isinstance(val, Iterable):
            raise ValueError('The array must be an Iterable.')
        if not all(isinstance(a, Iterable) for a in val):
            raise ValueError("All elements of array must be Iterables. "
                             "If creating a flat array, all elements must be length 1 iterables.")
        if not reduce(lambda x, y: len(x)==len(y), val):
            raise ValueError("Can not create array from a ragged sequence. "
                             "Please ensure all elements have the same length.")
        self.__value = val
    
    @property 
    def shape(self) -> tuple[int, int]:
        return (len(self.value), len(self.value[0]))
    
    def __repr__(self):
        return f"Array({self.value}, shape={self.shape})"
    
    def __eq__(self, other: 'Array') -> bool:
        if self.shape != other.shape:
            return False
        return all(x == y for x, y in zip(self.value, other.value))
    
    def __neq__(self):
        return not self.__eq__
    
    def __getitem__(self, idx: tuple[int, int]) -> int | float:
        if idx[0] > self.shape[0] or idx[1] > self.shape[1]:
            raise IndexError(f"Index out of bounds for the array of shape {self.shape}")
        return self.value[idx[0]][idx[1]]
    
    def __add__(self, other: 'Array') -> 'Array':
        v = []
        if self.shape != other.shape:
            raise ValueError("Can only add arrays of the same shape.")
        for i in range(self.shape[0]):
            v.append([])
            for j in range(self.shape[1]):
                v[i].append(self.value[i][j] + other.value[i][j])    
        return Array(v)
    
    def __mul__(self, other: 'Array') -> 'Array':
        v = []
        if self.shape != other.shape:
            raise ValueError("Can only add arrays of the same shape.")
        for i in range(self.shape[0]):
            v.append([])
            for j in range(self.shape[1]):
                v[i].append(self.value[i][j] * other.value[i][j])    
        return Array(v)        
    
    @classmethod
    def from_text(cls, input_file: str | Path, skip_rows: int | None = None, delimeter: str = ',') -> 'Array':
        inp = Path(input_file)
        if not input_file or not inp.exists():
            raise IOError("No input file provided or file does not exist.")
        with open(inp, 'r') as file:
            arr = []
            for idx, i in enumerate(file.readlines()):
                if skip_rows is None or skip_rows <= idx:
                    try:
                        arr.append([float(k) for k in i.strip().split(delimeter)])
                    except (ValueError, TypeError) as e:
                        raise TypeError("Input data must be castable to float. "
                                        "Found incompatible data type. Check your inputs.") from e
        return cls(arr)
    
    def matmul(self, other: 'Array') -> 'Array':
        pass
Source Link
NotAName
  • 251
  • 2
  • 9

Looking for a more efficient algorithm for the sum of list of lists

I'm working on a fun project where I'm trying to implement Random Forest algorithm in pure Python, i.e. without Numpy. But then I'll still be dealing with arrays all the time and so I'm writing my own Array class to use in place of Numpy's ndarray. Although I'm only focusing on 2d arrays as I don't need n-dimensional stuff for this project.

It's all working correctly, although it's not fully covered by tests yet. And for most part I think I have reasonably decent performance, but I'm struggling with methods for addition and multiplication of arrays.

In the normal anddition or multiplication of arrays in Numpy the result is element-wise addition or multiplication and my solution is O(n * m) which for large arrays will be a problem.

Is there a faster algorithm to do this (the __add__ and __mul__ dunders)? Any other things I could improve?

from typing import Iterable
from pathlib import Path
from functools import reduce


class Array:    
    def __init__(self, value: Iterable[Iterable[int | float]]) -> None:
        self.value = value
        
    @property
    def value(self):
        return self.__value
    
    @value.setter
    def value(self, val: Iterable[Iterable[int | float]]):
        if not isinstance(val, Iterable):
            raise ValueError('The array must be an Iterable.')
        if not all(isinstance(a, Iterable) for a in val):
            raise ValueError("All elements of array must be Iterables. "
                             "If creating a flat array, all elements must be length 1 iterables.")
        if not reduce(lambda x, y: len(x)==len(y), val):
            raise ValueError("Can not create array from a ragged sequence. "
                             "Please ensure all elements have the same length.")
        self.__value = val
    
    @property 
    def shape(self) -> tuple[int, int]:
        return (len(self.value), len(self.value[0]))
    
    def __repr__(self):
        return f"Array({self.value}, shape={self.shape})"
    
    def __eq__(self, other: 'Array') -> bool:
        if self.shape != other.shape:
            return False
        return all(x == y for x, y in zip(self.value, other.value))
    
    def __neq__(self):
        return not self.__eq__
    
    def __getitem__(self, idx: tuple[int, int]) -> int | float:
        if idx[0] > self.shape[0] or idx[1] > self.shape[1]:
            raise IndexError(f"Index out of bounds for the array of shape {self.shape}")
        return self.value[idx[0]][idx[1]]
    
    def __add__(self, other: 'Array') -> 'Array':
        v = []
        if self.shape != other.shape:
            raise ValueError("Can only add arrays of the same shape.")
        for i in range(self.shape[0]):
            v.append([])
            for j in range(self.shape[1]):
                v[i].append(self.value[i][j] + other.value[i][j])    
        return Array(v)
    
    def __mul__(self, other: 'Array') -> 'Array':
        v = []
        if self.shape != other.shape:
            raise ValueError("Can only add arrays of the same shape.")
        for i in range(self.shape[0]):
            v.append([])
            for j in range(self.shape[1]):
                v[i].append(self.value[i][j] * other.value[i][j])    
        return Array(v)        
    
    @classmethod
    def from_text(cls, input_file: str | Path, skip_rows: int | None = None, delimeter: str = ',') -> 'Array':
        inp = Path(input_file)
        if not input_file or not inp.exists():
            raise IOError("No input file provided or file does not exist.")
        with open(inp, 'r') as file:
            arr = []
            for idx, i in enumerate(file.readlines()):
                if skip_rows is None or skip_rows <= idx:
                    try:
                        arr.append([float(k) for k in i.strip().split(delimeter)])
                    except (ValueError, TypeError) as e:
                        raise TypeError("Input data must be castable to float. "
                                        "Found incompatible data type. Check your inputs.") from e
        return cls(arr)
    
    def matmul(self, other: 'Array') -> 'Array':
        pass    

```