nested lists as arrays

B

benjamin.cordes

Hi,

why can't I do this:

dummy = self.elements[toy][tox]

self.elements[toy][tox] = self.elements[fromy][fromx]
self.elements[fromy][fromx] = dummy

after initialising my nested list like this:

self.elements = [[0 for column in range(dim)] for row in
range(dim) ]
 
D

Diez B. Roggisch

Hi,

why can't I do this:

dummy = self.elements[toy][tox]

self.elements[toy][tox] = self.elements[fromy][fromx]
self.elements[fromy][fromx] = dummy

after initialising my nested list like this:

self.elements = [[0 for column in range(dim)] for row in
range(dim) ]

Works for me:

dim = 10
elements = [[0 for column in xrange(dim)] for row in
xrange(dim) ]

toy, tox = (2,5)
fromy, fromx = (7,5)

dummy =elements[toy][tox]
elements[toy][tox] = elements[fromy][fromx]
elements[fromy][fromx] = dummy


And use xrange instead of range.
 
B

bruno modulix

Hi,

why can't I do this:

dummy = self.elements[toy][tox]

self.elements[toy][tox] = self.elements[fromy][fromx]
self.elements[fromy][fromx] = dummy

after initialising my nested list like this:

self.elements = [[0 for column in range(dim)] for row in
range(dim) ]

Sorry, I'm not psychic enough to guess what is exactly your problem:
- what do you mean "can't do" ? You have a traceback ? please post it.
You have unexpected results ? please describe.
- what are self, dim, toy, tox, fromy, fromx ?
- is all that code in the same method ?
- etc etc

So please post more informations if you expect us to help you.


Note that the following code is correct:
>>> l = [[0 for i in range(3)] for y in range(3)]
>>> l [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
>>> l[0][0] = 1
>>> l [[1, 0, 0], [0, 0, 0], [0, 0, 0]]
>>> l[0][0], l[0][1] = l[0][1], l[0][0]
>>> l [[0, 1, 0], [0, 0, 0], [0, 0, 0]]
>>>

So I guess that your problem has nothing to do with nested lists.

(Also note BTW that, thanks to the magic of multiple assignement, you
don't need the dummy stuff. The pythonic idiom for swapping 2 bindings is
a, b = b, a)
 
B

benjamin.cordes

Diez said:
Hi,

why can't I do this:

dummy = self.elements[toy][tox]

self.elements[toy][tox] = self.elements[fromy][fromx]
self.elements[fromy][fromx] = dummy

after initialising my nested list like this:

self.elements = [[0 for column in range(dim)] for row in
range(dim) ]

Works for me:

dim = 10
elements = [[0 for column in xrange(dim)] for row in
xrange(dim) ]

toy, tox = (2,5)
fromy, fromx = (7,5)

dummy =elements[toy][tox]
elements[toy][tox] = elements[fromy][fromx]
elements[fromy][fromx] = dummy


And use xrange instead of range.

thanks so far, and sorry for posting the topic now 3 times.
 
B

benjamin.cordes

Thanks for the code.
What I want to do is this:
I want to solve the block puzzle problem. The problem is as follows:
You have a nxn Array of Numbers and one empty space. Now you there are
up to four possible moves: up, right, down and left, so that each
neighbour of the empty slot can be moved there.

The method getEmptySlot is for getting the empty slot. The method
getPossibleMoves returns the possible moves. Perfoming a move is
swaping one element with the empty slot marked as -1.

The full executable code is appended.
The expected behaviour is that when the move is performed the
appropiate elements should be swaped. But for some reason every now and
then it swaps the wrong elements.

# Puzzle.py
# class for a sliding block puzzle

# an starting state of a 8-puzzle could look like the following:
# ------------
# | 7 2 4 |
# | |
# | 5 X 6 |
# | |
# | 8 3 1 |
# ------------

# the goal is to reach this state:
# ------------
# | X 1 2 |
# | |
# | 3 4 5 |
# | |
# | 6 7 8 |
# ------------

import random
import copy

class Puzzle:

def __init__(self, dim):
self.dim = dim
# self.elements = {}
# for column in range(dim):
# for row in range(dim):
# elements[(column, row)] = 0
self.elements = [[0 for column in range(dim)] for row in
range(dim) ]

def __str__(self):
s = ""
i = 0
j = 0
while i <= self.dim-1:
while j <= self.dim-1:
s = s + str(self.elements[j])
j=j+1
s = s + "\t"
i=i+1
j = 0
s = s + "\n"
return s

def compare(self, compareTo):
if (self.dim != compareTo.dim):
return 0
i = 0
j = 0
equal = 1
while i <= self.dim-1:
while j <= self.dim-1:
if self.elements[j] != compareTo.elements[j]:
equal = 0
j = j+1
i = i+1
return equal


def setAsGoalState(self):
# create elements of puzzle
i = 0
j = 0
while i <= self.dim-1:
while j <= self.dim-1:
self.elements[j] = i*3+j
j=j+1
j=0
i=i+1

# create empty element seperately
self.elements[0][0] = -1


def setRandomState(self):
# container for the elements to pick from
container = [1,2,3,4,5,6,7,8,-1]

# create elements of puzzle randomly
i = 0
j = 0
while i <= self.dim-1:
while j <= self.dim-1:
if len(container) > 0:
randomindex = random.randint(0,len(container)-1)
self.elements[j] = container[randomindex]
del container[randomindex]
j=j+1
else:
break
j=0
i=i+1

def getEmptySlot(self):
i = 0
j = 0
while i <= self.dim-1:
while j <= self.dim-1:
if self.elements[j] == -1:
return [j, i]
j = j+1
j = 0
i = i + 1

def performMove(self, direction):
slot = self.getEmptySlot()

if (direction == "up"):
self.swapElements(slot[1], slot[0], slot[1]+1, slot[0])

elif (direction == "down"):
self.swapElements(slot[1], slot[0], slot[1]-1, slot[0])

elif direction == "left":
self.swapElements(slot[1], slot[0], slot[1], slot[0]-1)
elif (direction == "right"):
self.swapElements(slot[1], slot[0], slot[1], slot[0]+1)

def swapElements(self, fromx, fromy, tox, toy):
dummy = self.elements[toy][tox]

self.elements[toy][tox] = self.elements[fromy][fromx]
self.elements[fromy][fromx] = dummy


def getChildren(self):
moves = self.getPossibleMoves()
self.children = []
for eachMove in moves:
newchild = copy.copy(self.performMove(eachMove))
try:
self.children.append(newchild)
except:
print "Exception: " + str(len(self.children))

def getPossibleMoves(self):
emptySlot = self.getEmptySlot()
y = emptySlot[1]
x = emptySlot[0]
north = (y == 0)
south = (y == (self.dim-1))
west = (x == 0)
east = (x == (self.dim-1))

middle = not(north or south or west or east)

northwest = north and west
northeast = north and east
southwest = south and west
southeast = south and east
# orientation has to be distinct
# save original values
orignorth = north
origsouth = south
# original north or south
orignors = north or south

north = north and not (west or east)
south = south and not (west or east)
west = west and not (orignors)
east = east and not (orignors)

if middle:
return ["up", "down", "left", "right"]
elif north:
return ["up", "left", "right"]
elif south:
return ["down", "left", "right"]
elif west:
return ["up", "down", "left"]
elif east:
return ["up", "down", "right"]
elif northwest:
return ["up", "left"]
elif northeast:
return ["up", "right"]
elif southwest:
return ["down", "left"]
elif southeast:
return ["down", "right"]

# ~Puzzle.py

# SolvePuzzleAgent.py
# imports
from Puzzle import *
from Tree import *
import bintree

# set up puzzle
puzzle = Puzzle(3)
puzzle.setRandomState()
goalpuzzlestate = Puzzle(3)
goalpuzzlestate.setAsGoalState()
print "goal state of the puzzle: "
print str(goalpuzzlestate)
print "start state of the puzzle: "
print str(puzzle)
#start search
tree = bintree.BinaryTree()

done = 0
currentBranch = 0
while not done:
tree.insert(puzzle)
# get the fringe
moves = puzzle.getPossibleMoves()
children = puzzle.getChildren()
#print "possible moves: " + str(moves)
print "moving: " + str(moves[currentBranch])
puzzle.performMove(moves[currentBranch])

# test if we're have reached the goal state
done = puzzle.compare(goalpuzzlestate)
print "done ? : " + str(done)
print "state:\n" + str(puzzle)
done = 1


print "search tree: " + str(tree.inorder())

# bintree.py
# a binary tree
class Node:
def __init__(self, val, left=None, right=None):
self.val = val
self.left = left
self.right = right

class BinaryTree:
def __init__(self):
self.root = None

def insert(self, val):
root = self.root
lastdir = None
while root:
lastroot = root
if val <= root.val:
lastdir = 'left'
root = root.left
else:
lastdir = 'right'
root = root.right
new = Node(val)
if lastdir is None:
self.root = new
elif lastdir == 'left':
lastroot.left = new
else:
lastroot.right = new

def insertList(self, list):
for x in list:
self.insert(x)


def inorder(self):
result = []
self.__helper(self.root, result)
return result

def __helper(self, root, result):
if root is not None:
self.__helper(root.left, result)
result.append(root.val)
self.__helper(root.right, result)

# ~bintree.py
 
T

Terry Reedy

Diez B. Roggisch said:
And use xrange instead of range.

For small dimensions like 3,4,5, xrange is way overkill and perhaps takes
both more space and time. Since dim is a constant for a given puzzle, I
would set self._range = range(dim) in __init__() and use that to iterate.
And, as I said in another response, I would not iterate to make a move.

Terry J. Reedy
 
T

Terry Reedy

def setRandomState(self):
# container for the elements to pick from
container = [1,2,3,4,5,6,7,8,-1]

# create elements of puzzle randomly
i = 0
j = 0
while i <= self.dim-1:
while j <= self.dim-1:
if len(container) > 0:
randomindex = random.randint(0,len(container)-1)
self.elements[j] = container[randomindex]
del container[randomindex]
j=j+1
else:
break
j=0
i=i+1


Without reading closely, I believe that the above can generate any possible
position. Are you aware that half are unsolvable? If that matters, you
need to either find a book or site that explains the parity test for
solvability or generate the start position from the goal position by a
series of random moves.

Terry J. Reedy
 
M

Michael Spencer

Terry said:
def setRandomState(self):
# container for the elements to pick from
container = [1,2,3,4,5,6,7,8,-1]

# create elements of puzzle randomly
i = 0
j = 0
while i <= self.dim-1:
while j <= self.dim-1:
if len(container) > 0:
randomindex = random.randint(0,len(container)-1)
self.elements[j] = container[randomindex]
del container[randomindex]
j=j+1
else:
break
j=0
i=i+1



Without reading closely, I believe that the above can generate any possible
position. Are you aware that half are unsolvable? If that matters, you
need to either find a book or site that explains the parity test for
solvability or generate the start position from the goal position by a
series of random moves.

Terry J. Reedy

This covers the test for solvability - enjoy ;-):
http://www.cs.tcd.ie/publications/tech-reports/reports.01/TCD-CS-2001-24.pdf

BTW, just because your puzzle looks like a grid doesn't neceesarily mean that
representing the data as nested arrays is easiest. A flat list might be just as
good here. It simplifies some of the operations (creating a random ordering
becomes a one-liner), at the expense of a little more complexity in some others:

import random
class n2grid(object):
"""A grid for the n squared puzzle"""
def __init__(self,dim = 4):
self.cells = range(dim*dim)
self.dim = dim
self.pos = (0,0)
def shuffle(self):
random.shuffle(self.cells)
self.pos = divmod(self.cells.index(0),self.dim)
def show(self):
for row in self._asarray():
print "".join("[%2s]" % (cell or "") for cell in row)
def _move(self,dy,dx):
dim = self.dim
cells = self.cells
oldy, oldx = self.pos
newy, newx = oldy + dy, oldx + dx
if 0 <= newx < dim and 0 <= newy < dim:
ix = newy * dim + newx
ox = oldy * dim + oldx
cells[ix], cells[ox] = cells[ox], cells[ix]
self.pos = newy,newx
else:
raise Exception, "Illegal move to: (%s,%s)" % (newy, newx)
def move(self, dx, dy):
try:
self._move(dx,dy)
self.show()
except:
pass

def _asarray(self): #create the array representation when needed
cells = iter(self.cells)
dim = self.dim
return [[cells.next() for j in range(dim)] for i in range(dim)]

def __repr__(self):
return repr(self._asarray())



...
[ ][ 1][ 2][ 3]
[ 4][ 5][ 6][ 7]
[ 8][ 9][10][11]
[12][13][14][15][ 3][15][ 6][ 7]
[10][ ][12][ 5]
[ 4][ 1][14][ 8]
[ 2][11][13][ 9][ 3][15][ 6][ 7]
[10][14][12][ 5]
[ 4][ 1][ ][ 8]
[ 2][11][13][ 9][ 3][15][ 6][ 7]
[10][14][12][ 5]
[ 4][ 1][13][ 8]
[ 2][11][ ][ 9]
Cheers
Michael
 
B

bruno modulix

(e-mail address removed) a écrit :
Thanks for the code.
What I want to do is this:
I want to solve the block puzzle problem. The problem is as follows:
You have a nxn Array of Numbers and one empty space. Now you there are
up to four possible moves: up, right, down and left, so that each
neighbour of the empty slot can be moved there.

The method getEmptySlot is for getting the empty slot. The method
getPossibleMoves returns the possible moves. Perfoming a move is
swaping one element with the empty slot marked as -1.

The full executable code is appended.
s/executable/source/ !-)
The expected behaviour is that when the move is performed the
appropiate elements should be swaped. But for some reason every now and
then it swaps the wrong elements.

So the problem is not that you "can't" swap 2 elements, but that there
seems to be some logical bug(s) in your code.
# Puzzle.py
# class for a sliding block puzzle
(snip)

import random
import copy

class Puzzle:

def __init__(self, dim):
self.dim = dim
self.elements = [[0 for column in range(dim)] for row in
range(dim) ]

So at this stage you've got 3 rows of 3 colums:
[
['row 0 - col 0', 'row 0 - col 1', 'row 0 - col 2'],
['row 1 - col 0', 'row 1 - col 1', 'row 1 - col 2'],
['row 2 - col 0', 'row 2 - col 1', 'row 2 - col 2']
]

Is that right ?
def __str__(self):
s = ""
i = 0
j = 0
while i <= self.dim-1:
ok, looping on rows
while j <= self.dim-1:
and now looping on columns in current row...
s = s + str(self.elements[j])


oops ! Looks like you're addressing row[j] column instead of row
column[j] !-)
j=j+1
s = s + "\t"
i=i+1
j = 0
s = s + "\n"
return s

(snip the rest of the code)

Ok, let's try with a slightly more pythonic (and thus more readable)
version of this code (I used another method name so we can compare both
versions):

def to_string(self):
s = []
for row in self.elements:
for col in row:
s.append('%d\t' % col)
s.append('\n')
return ''.join(s)

For testing purpose, let's modify the __init__ a bit (this will break
the code but what, it's just to check something, we can get back to
original code later):

def __init__(self, dim):
self.dim = dim
self.range = range(self.dim)
#self.elements = [[0 for column in self.range] for row in self.range]
self.elements = [[int('%d%d' % (row, column)) for column in
self.range] for row in self.range]
# check we have a correct layout, remove me when fixed:
print "%s" % self.elements

and now let's try:[[0, 1, 2], [10, 11, 12], [20, 21, 22]]

so far, so good, we have 3 rows of 3 columns each.
let's continue:0 1 2
10 11 12
20 21 22

Ok, so our new to_string() method is ok. Now the real test:0 10 20
1 11 21
2 12 22

Remember ?
"""
oops ! Looks like you're addressing row[j] column instead of row
column[j]
"""

Looks like we found one bug...

Let's continue and rewrite some more code:

def compare(self, other):
if (self.dim != other.dim):
return False

for self_row, other_row in zip(self.elements, other.elements):
for self_col, other_col in zip(self_row, other_row):
if self_col != other_col:
return False

return True

Ok, that one was easy. Let's try with another one

def getEmptySlot(self):
for row in self.elements:
for cell in row:
if cell == -1:
return ???

Mmmm... won't work. Seems we need indices here, let's try again:

def getEmptySlot(self):
for row in self.range:
for col in self.range:
if self.elements[row][col] == -1:
return (row, col)

Ok, not so clean but still readable (notice how using explicit names
like 'row' and 'col' helps understanding the code...).

Now let's do the swap:

def swapElements(self, from_cell, to_cell):
from_row, from_col = from_cell
to_row, to_col = to_cell
elts = self.elements
elts[to_row][to_col], elts[from_row][from_col] =
elements[from_row][from_col], elts[to_row][to_col]

Mmmm... this begins to smell. I don't like having to split the coords
like this. I'd prefer something like:

def swapElements(self, from_cell, to_cell):
self.elements[from_cell], self.elements[to_cell] \
= self.elements[to_cell], self.elements[from_cell]

This could be possible if we used another data structure. Something like
a dict of coords:value pairs. We could build it like this:

def __init__(self, dim):
self.dim = dim
self.range = range(self.dim)
self.elements = {}
for row in self.range:
for col in self.range:
self.elements[(row,col)] = 0

and then the search for the empty slot:

def getEmptySlot(self):
for coords, value in self.elements.items():
if value == -1:
return coords
assert(False) # shouldn't be here anyway !

Hey, that looks nicer, isn't it ? But what with the __str__() method ?
We can't use the dict.items() method since dicts are not ordered. But we
can recompute the keys... What about :

def __str__(self):
s = []
for row in self.range:
for col in self.range:
s.append("%d\t" % self.elements[(row, col)])
s.append('\n')
return ''.join(s)

Was not so difficult, finally. Now the compare() has to be rewritten too:

def compare(self, other):
if (self.dim != other.dim):
return 0

for row in self.range:
for col in self.range:
if self.elements[(row,col)] != other.elements[(row,col)]:
return False
# no difference
return True

Err... Wait a minute... What are we doing here ? Comparing 2 dicts ?
Wait, wait, let's check something...
True

Haha ! Our good'ole dict already has an 'equality' special method... Now
is this a 'value' or 'identity' equality ?
True

Bingo ! Play it again, sam :

def compare(self, other):
return self.elements == other.elements

Hehehe. Less code, better code, as grand'ma used to say !-)

Now the fine part : let's make it move. What was it like ?

def performMove(self, direction):
slot = self.getEmptySlot()
if (direction == "up"):
self.swapElements(slot[1], slot[0], slot[1]+1, slot[0])
elif (direction == "down"):
self.swapElements(slot[1], slot[0], slot[1]-1, slot[0])
elif direction == "left":
self.swapElements(slot[1], slot[0], slot[1], slot[0]-1)
elif (direction == "right"):
self.swapElements(slot[1], slot[0], slot[1], slot[0]+1)

This won't work since our swapElements() method now expects 2 tuples.
Let's look closer. The first pair is always the same, it's the actually
the empty slot. Quite sensible. We just have to create the second tuple
from the first. And BTW, I don't like the if/elif neither. What about
using a dict again ? :

def performMove(self, direction):
slot = self.getEmptySlot()
to_slot = {'up' : lambda (row, col): (row-1, col),
'down' : lambda (row, col): (row+1, col),
'left' : lambda (row, col): (row, col-1),
'right': lambda (row, col): (row, col+1)}
self.swapElements(slot, to_slot[direction](slot))

Better, but not quite ok: it's silly to recreate the to_slot dict each
time. Let's put it in the __init__ :

def __init__(self, dim):
self.dim = dim
self.range = range(self.dim)
self.elements = {}

for row in self.range:
for col in self.range:
self.elements[(row,col)] = 0

self.to_slot = {'up' : lambda (row, col): (row-1, col),
'down' : lambda (row, col): (row+1, col),
'left' : lambda (row, col): (row, col-1),
'right': lambda (row, col): (row, col+1)}


and now our performMove() method become:

def performMove(self, direction):
slot = self.getEmptySlot()
self.swapElements(slot, self.to_slot[direction](slot))


Begins to look better, isn't it ? Ok, your turn to play !-)
Just two more things :

1/
getPossibleMoves(self):
emptySlot = self.getEmptySlot()
y = emptySlot[1]
x = emptySlot[0]
(...)

could benefit from being rewritten as:

getPossibleMoves(self):
row, col = self.getEmptySlot()
(...)

(Yes, Python will automagically 'unpack' the tuple !-)

2/
def getChildren(self):
moves = self.getPossibleMoves()
self.children = []
for eachMove in moves:
newchild = copy.copy(self.performMove(eachMove))

performMove() doesn't explicitly return anything, so it implicitly
returns None. This may not give you what you want !-)

If the idea is to 'capture' the state of the puzzle object, you may want
to have performMove() returning self. *But* remember that the state of
the puzzle object is affected by performMove(), so this may not behave
as expected either, since after each move, the possible moves change.

So the solution would be to 'clone' self *before* each performMove(),
call the performMove() on each clone and store the clone.

(BTW, the name of the method may be somewhat misleading, and I'm not
sure this method really belongs to the Puzzle class. )

and finally (pun intend !-) :

try:
self.children.append(newchild)
except:
print "Exception: " + str(len(self.children))

This is the only place where you handle exceptions. But there is no
particular reason to expect an exception here. So your try/except block
is useless. Worse, it's badly coded, since you don't specify which
exception you want to catch. This may put you into troubles (probably
not here, but... I recently had hard time to debug some code that had
many 'catch-all' except clauses.)

Well... You seem to like puzzles, so I leave it up to you to finish that
one.

*warning* : this 100% untested code. You should write some unit tests to
make sure every method woks as expected.

HTH
Bruno
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,020
Latest member
GenesisGai

Latest Threads

Top