Python 2-dimensional array is behaving strangely - python

I've been attempting to port a program I've written in C++ to Python. Specifically, it is a program for generating heightmaps which can be exported to 3D models, and which ideally, when viewed, would look like realistic terrain.
The HeightMap class wraps a two-dimensional array of float values. For now I've kept it as integers because it prints nicer, and I haven't had a chance to implement any other features. The glitch I'm having is that when I call set(self, x, y, value), which simply sets the value at x, y to value, with the code "self.rows[y][x] = value", it seems to change the entire column, visiting each member of self.rows and setting the xth member of that array to value.
Here is my code. I am about 90% sure the mistake is somewhere in the 2-array's initialization.
def filledArray(length, value) :
result = []
for i in range(1, length) :
result.append(value)
return result
def resizeArray(array, newLength, nullValue) :
if newLength == len(array) :
return array
result = []
for i in range(0, newLength) :
if i < len(array) :
result.append(array[i])
else :
result.append(nullValue)
return result
class HeightMap:
"""A class that wraps a 2D array for generating height maps"""
def __init__(self) :
self.width = 0
self.height = 0
self.rows = []
def __init__(self, initWidth, initHeight) :
self.clear(initWidth, initHeight);
def clear(self, initWidth, initHeight) :
self.width = initWidth
self.height = initHeight
self.rows = filledArray(initHeight, filledArray(initWidth, 0))
def setHeight(self, newHeight) :
if self.height == newHeight :
return
self.rows = resizeArray(self.rows, newHeight, filledArray(self.width, 0))
def setWidth(self, newWidth) :
if self.width == newWidth :
return
for i in range(0, len(self.rows)) :
self.rows[i] = resizeArray(self.rows[i], newWidth, 0);
self.width = newWidth
def get(self, x, y) :
return self.rows[y][x]
def set(self, x, y, value) :
self.rows[y][x] = value
def add(self, x, y, value) :
self.rows[y][x] += value
def multiply(self, x, y, value) :
self.rows[y][x] *= value

Your problem is that when you call filledArray(initHeight, filledArray(initWidth, 0)) the second argument is passed by reference (this is always the case with python objects). Inside filledArray you need to make a copy of value for each. This will require adding a little bit of additional logic to your function, something along the lines of:
if type(value) == list:
value = list(value)
Or you could do the slightly less pythonic:
type(value)(value)
to create a copy.

Related

Add function not working - what I want is to be able to sum two matrices

I want to sum two matrices, so I'm doing the __add__ method, but it doesn’t seem to work. Can somebody help me please? I don’t know what I'm doing wrong. This is my code:
class Matrix():
def __init__(self, width = int, height = int, fill_value=0):
self.height = height #columna
self. width = width #fila
self.rows = [[fill_value] * width for _ in range (height)] #A for matrix A
def __str__(self):
return "\n" .join(" ".join(map(str, self.rows))for self.rows in self.rows)
def set(self, row_index = int, column_index = int, value = int):
self.rows[row_index][column_index] = value
def get(self, row_index, column_index):
return self.rows[row_index][column_index]
def __add__(self, other):
matrix_addition = []
for i in range(self.height):
new_row = []
for j in range(self.width):
addition = self.rows[i][j] + other.rows[i][j]
new_row.append(addition)
matrix_addition.append(new_row)
return Matrix(matrix_addition)
I'm having an error that says:
addition = self.rows[i][j] + other.rows[i][j],
TypeError : ‘int’ object has no attribute ‘__getitem__’
Your code is pretty good, you almost got it.
The first issue is your constructor and the set() method: by using
the default value=int in the parameters, you're essentially assigning type(int), which is not what you want. You should either assign a default value, say 2 or use type hinting to specify data type, say int. Here's an example with both type hinting and defaults:
def __init__(self, width: int = 2, height: int = 2, fill_value=0):
pass
def set(self, row_index: int, column_index: int, value: int):
pass
Here is the fatal error in your __add__ method: return Matrix(matrix_addition). This creates a new Matrix object and passes matrix_addition as an argument. However, the first argument in Matrix.__init__ is width, which expects an int, not List[List[int]]. Instead, call return matrix_addition and it will work.

Strange behavior with generator\iterator

I got a problem with an iterator which I created for some OOP exercises.
Here is the problematic generator:
def shapeIterator(listOfShapes):
print("Generator...")
print(listOfShapes)
listOfShapessoretedbyArea = shape.sortedByArea(listOfShapes)
for shapes in listOfShapessoretedbyArea:
yield str(shapes)
shape.sortedByArea(listOfShapes) is a static method, which need one argument, a list, which is sorted by the calculate area, and returned to the caller.
This method works perfectly in this main function:
if __name__ == '__main__':
rect = rectangle(20, 5)
squa = square(2)
tri = equiTria(2, 5)
circ = circle(2)
pent = pentagon(5)
hexa = hexagon(3)
listOfShapes = [rect, squa, hexa, tri, circ, pent]
listOfShapessoretedbyArea = sorted(listOfShapes, key=lambda x: x.calculate_area())
listOfShapessoretedbyPeri = sorted(listOfShapes, key=lambda x: x.calculate_perimeter())
listOfShapessoretedbyArea2 = shape.sortedByArea(listOfShapes)
listOfShapessoretedbyPeri2 = shape.sortedByPerim(listOfShapes)
iterator = shapeIterator(listOfShapes)
for i in range(6):
sleep(1)
value = next(iterator)
print(value)
print("NOT SORTED")
for shape in listOfShapes:
print(str(shape))
print("\nSORTED BY AREA")
for shape in listOfShapessoretedbyArea:
print(str(shape))
print("\nSORTED BY PERIMETER")
for shape in listOfShapessoretedbyPeri:
print(str(shape))
print("\nSORTED BY AREA v2")
for shape in listOfShapessoretedbyArea2:
print(str(shape))
print("\nSORTED BY PERIMETER v2")
for shape in listOfShapessoretedbyPeri2:
print(str(shape))
but when I move this part:
iterator = shapeIterator(listOfShapes)
for i in range(6):
sleep(1)
value = next(iterator)
print(value)
at the end of the main, like this:
if __name__ == '__main__':
rect = rectangle(20, 5)
squa = square(2)
tri = equiTria(2, 5)
circ = circle(2)
pent = pentagon(5)
hexa = hexagon(3)
listOfShapes = [rect, squa, hexa, tri, circ, pent]
listOfShapessoretedbyArea = sorted(listOfShapes, key=lambda x: x.calculate_area())
listOfShapessoretedbyPeri = sorted(listOfShapes, key=lambda x: x.calculate_perimeter())
listOfShapessoretedbyArea2 = shape.sortedByArea(listOfShapes)
listOfShapessoretedbyPeri2 = shape.sortedByPerim(listOfShapes)
print("NOT SORTED")
for shape in listOfShapes:
print(str(shape))
print("\nSORTED BY AREA")
for shape in listOfShapessoretedbyArea:
print(str(shape))
print("\nSORTED BY PERIMETER")
for shape in listOfShapessoretedbyPeri:
print(str(shape))
print("\nSORTED BY AREA v2")
for shape in listOfShapessoretedbyArea2:
print(str(shape))
print("\nSORTED BY PERIMETER v2")
for shape in listOfShapessoretedbyPeri2:
print(str(shape))
iterator = shapeIterator(listOfShapes)
for i in range(6):
sleep(1)
value = next(iterator)
print(value)
I got this error:
TypeError: sortedByArea() takes 1 positional argument but 2 were given
That's very strange. Trying to do some naive debug, I printed the argument passed at the function sortedByArea() in the second case, and I got effectively two arguments. One is the to string value printed in the last for each statement, and the second is the list itself.
The last to string value is referred to this for each statement:
for shape in listOfShapessoretedbyPeri2:
print(str(shape))
I also tried to change the value of the list, and effectively the value "concatenated" to the argument passed to the shapeIterator function is the last string printed.
If needed here are the classes and import used in the main .py:
from math import pi
from math import sqrt
from time import sleep
class shape():
def calculate_area():
pass
def calculate_perimeter():
pass
def ltarea(self, other):
return self.calculate_area() < other.calculate_area()
def ltperim(self, other):
return self.calculate_perimeter() < other.calculate_perimeter()
def sortedByArea(shapes):
return sorted(shapes, key=lambda x: x.calculate_area())
def sortedByPerim(shapes):
return sorted(shapes, key=lambda x: x.calculate_perimeter())
def nametype(self):
return "shape"
def __str__(self):
return "{0}, area: {1}, perim: {2}".format(self.nametype(),
self.calculate_area(),
self.calculate_perimeter())
class rectangle(shape):
def __init__(self, side1, side2):
self.__side1 = side1
self.__side2 = side2
def calculate_area(self):
return self.__side1 * self.__side2
def calculate_perimeter(self):
return (self.__side1 * 2) + (self.__side2 * 2)
def nametype(self):
return "rectangle"
class square(rectangle):
def __init__(self, side):
self._rectangle__side1 = side
self._rectangle__side2 = side
def nametype(self):
return "square"
class equiTria(shape):
def __init__(self, side, height):
self.__side = side
def calculate_area(self):
self.__height = self.calculate_perimeter() / (2 * sqrt(3))
return (self.__side * self.__height)/2
def calculate_perimeter(self):
return self.__side * 3
def nametype(self):
return "equiTria"
class circle(shape):
def __init__(self, radius):
self.__radius = radius
def calculate_area(self):
return pi * pow(self.__radius, 2)
def calculate_perimeter(self):
return 2 * pi * self.__radius
def nametype(self):
return "circle"
class pentagon(shape):
def __init__(self, side):
self.__side = side
self.__apothem = side * 0.688
def calculate_perimeter(self):
return self.__side * 5
def calculate_area(self):
return (self.calculate_perimeter() * self.__apothem) / 2
def nametype(self):
return "pentagon"
class hexagon(shape):
def __init__(self, side):
self.__side = side
def calculate_area(self):
self.__apothem = self.__side * 0.866
return (self.calculate_perimeter() * self.__apothem) / 2
def calculate_perimeter(self):
return self.__side * 6
def nametype(self):
return "hexagon"
def shapeIterator(listOfShapes):
print("Generator...")
print(listOfShapes)
listOfShapessoretedbyArea = shape.sortedByArea(listOfShapes)
for shapes in listOfShapessoretedbyArea:
yield str(shapes)
You rebind shape in your loops, so it is no longer the class, but one instance.
For example, just above your use of the generator:
for shape in listOfShapessoretedbyPeri2:
print(str(shape))
The variables in the __main__ section are still globals, so that replaced the class used by the generator.
Your options are:
Use a different name for the loop variable; ashape for example.
Use a different name for the class. The Python style guide recommends using CamelCase for class names, so renaming it to Shape would do nicely here.
Put all the code under the if __name__ == '__main__': block in a function, so that variable names like the loop target become locals.
Personally, I'd implement both 2 and 3; avoiding polluting your global namespace is always a good idea, and so is following the almost universally adopted Python style guide; this helps avoid such mistakes in the future.
In addition, if sortedByArea is meant to be a static method, do at least use the #staticmethod decorator. That way it is still useable as a static method even on instances:
class Shape:
# ...
#staticmethod
def sortedByArea(shapes):
return sorted(shapes, key=lambda x: x.calculate_area())
#staticmethod
def sortedByPerim(shapes):
return sorted(shapes, key=lambda x: x.calculate_perimeter())
You reused the shape variable, once for the shape class and once for the loop variable in all your for shape in loops.

python class rectangle new type

I am new to python and I am trying to write class RectangleCollection. class Rectangle is given, and I need to write class RectangleCollection part.
class RectangleCollection has one list instance variable, rectangles, that should initially refer to an empty list.
get_same_area_rects takes a number as a parameter and returns a list of all Rectangles from the rectangles list that have that area.
class Rectangle:
""" A rectangle with a width and height. """
def __init__(self, w, h):
""" (Rectangle, number, number)
Create a new rectangle of width w and height h.
>>> r = Rectangle(1, 2)
>>> r.width
1
>>> r.height
2
"""
self.width = w
self.height = h
def area(self):
""" (Rectangle) -> number
Return the area of this rectangle.
>>> r = Rectangle(10, 20)
>>> r.area()
200
"""
return self.width * self.height
These are what I have done :
class RectangleCollection:
def __init__(self):
""" (RectangleCollection) -> NoneType
>>> rc = RectangleCollection()
>>> rc.rectangles
[]
"""
self.rectangles = []
def get_same_area_rects(self, number):
"""
>>>rc = RectangleCollection()
>>>r1 = Rectangle(10, 20)
>>>r2 = Rectangle(15, 20)
>>> r3 = Rectangle(20, 10)
>>>rc.rectangles.extend([r1, r2, r3])
>>>res = rc.get_same_area_rects(200)
>>>res == [r1, r3]
True
"""
self.number = number
a = self.rectangles.expend(self.area())
if number == self.rectangles.area():
return True
return False
but for get_same_area_rects part, I always get False..
I have no idea what I did wrong. Please help
i think it is because rc.rectangles gives me r1,r2,r3 addresses, not the areas. I should get [200,300,200] but I get the addresses. I think this is why I always get False.. How can I fix this problem?
How about use filter function to only take rectangles whose area is number
def get_same_area_rects(self, number):
return filter(lambda rect: rect.area() == number, self.rectangles)
CSC108 right? This function within class is not asking you to return True of False, it is asking you to call this function to get a list of rectangles that their area is 200
You have a typographic error in your code. It should be:
extend not expend as follows:
a = self.rectangles.extend(self.area())
if number == self.rectangles.extend(self.area()):
return True
Or simply:
a = self.rectangles.extend(self.area())
if number == a:
return True
You have to create a temporary list and then loop over the rectangles. This is because since we have to return a list which has the same area, we would need to use the rectangle.area() to compare if they are true or not and then add into the list.
def get_same_area_rects(self, number):
temp_list = []
for rectangle in self.rectangles:
if number == rectangle.area():
temp_list.append(rectangle)
return temp_list
hope it helps :)

What do I do if a method in one class uses a method from another?

I have this class (in a file named "occ_grid.py"):
class Grid:
def __init__(self, width, height, occupancy_value):
self.width = width
self.height = height
self.cells = []
# initialize grid to all specified occupancy value
for row in range(0, self.height):
self.cells.append([])
for col in range(0, self.width):
self.cells[row].append(occupancy_value)
def set_cell(self, point, value):
self.cells[point.y][point.x] = value
def get_cell(self, point):
return self.cells[point.y][point.x]
And I have another class/method(s) (in the file "worldmodel.py"):
class WorldModel:
def __init__(self, num_rows, num_cols, background):
self.background = occ_grid.Grid(num_cols, num_rows, background)
self.num_rows = num_rows
self.num_cols = num_cols
self.occupancy = occ_grid.Grid(num_cols, num_rows, None)
self.entities = []
self.action_queue = ordered_list.OrderedList()
def is_occupied(self, pt):
return (self.within_bounds(pt) and
occ_grid.get_cell(self.occupancy, pt) != None)
Notice how "def is_occupied" uses the method "get_cell". The thing is, "get_cell" is in a different file and it's a method in a different class. I thought about creating a new "grid" object, but I'm confused on where in my code I should create this object.
You're pretty close. It's just:
self.occupancy.get_cell(pt)
You could write:
occ_grid.Grid.get_cell(self.occupancy, pt)
and that would be equivalent -- But, that'd be pretty unidiomatic.
In order to use Grid at all from the file worldmodel.py, you need to import the Grid class.
You can do this in two possible ways.
import occ_grid. If you do this, you will have to access Grid with occ_grid.Grid
from occ_grid import Grid. Then you can use it with just Grid.
mgilson's answer is very clear about how to use it after you import it.

python cast object to int

I am using the numpy module to retrieve the position of the maximum value in a 2d array. But this 2d array consists of MyObjects. Now I get the error:
TypeError: unorderable types: int() > MyObject()
I tried to override the int function with this code:
def int(self):
return self.score
But this does not solve my problem.
Do I have to convert my 2d array of MyObjects into a 2d array of integers, do I have to extend the Integer object (if this is possible in python) or can I override this int() function in another way?
[EDIT]
The full object:
class MyObject:
def __init__(self, x, y, score, direction, match):
self.x = x
self.y = y
self.score = score
self.direction = direction
self.match = match
def __str__(self):
return str(self.score)
def int(self):
return self.score
The way I call this object:
def traceBack(self):
self.matrix = np.array(self.matrix)
maxIndex = self.matrix.argmax()
print(self.matrix.unravel_index(maxIndex))
Try to use
...
def __int__(self):
return self.score
...
test = MyObject(0, 0, 10, 0, 0)
print 10+int(test)
# Will output: 20
in your MyObject class definition.
The max function takes a key that is applied on the elements. that's where you put score
Typically :
a = max(my_list, key=score)

Categories

Resources