- 
	
	Creating
2d Lists
 
		- Static Allocation
 
 # create a 2d list with fixed values (static allocation)
 a = [ [ 2, 3, 4 ] , [ 5, 6, 7 ] ]
 print a
 
 
- Dynamic (Variable-Length) Allocation
 
			- 
			Wrong:  Cannot use * (Shallow Copy)
 
 # Try, and FAIL, to create a variable-sized 2d list
 rows = 3
 cols = 2
 
 a = [ [0] * cols ] * 
			rows # Error: creates 
			shallow copy
 # Creates one unique row, the rest are aliases!
 
 print "This SEEMS ok.  At first:"
 print "   a =", a
 
 a[0][0] = 42
 print "But see what happens after a[0][0]=42"
 print "   a =", a
 
 
- 
			Right:  Append Each Row
 
 # Create a variable-sized 2d list
 rows = 3
 cols = 2
 
 a=[]
 for row in xrange(rows): a += [[0]*cols]
 
 print "This IS ok.  At first:"
 print "   a =", a
 
 a[0][0] = 42
 print "And now see what happens after a[0][0]=42"
 print "   a =", a
 
 
- 
			Even Better:  make2dList() 
 
 def make2dList(rows, cols):
 a=[]
 for row in 
			xrange(rows): a += [[0]*cols]
 return a
 
 rows = 3
 cols = 2
 a = make2dList(rows, cols)
 print "This IS ok.  At first:"
 print "   a =", a
 
 a[0][0] = 42
 print "And now see what happens after a[0][0]=42"
 print "   a =", a
 
- Another 
			option: use a list comprehension
 
 rows = 3
 cols = 2
 a = [ ([0] * cols) for row in xrange(rows) ]
 print "This IS ok.  At first:"
 print "   a =", a
 
 a[0][0] = 42
 print "And now see what happens after a[0][0]=42"
 print "   a =", a
 
- 
	
	Getting
2d List Dimensions
 
 # Create an "arbitrary" 2d List
 a = [ [ 2, 3, 5] , [ 1, 4, 7 ] ]
 print "a = ", a
 
 # Now find its dimensions
 rows = len(a)
 cols = len(a[0])
 print "rows =", rows
 print "cols =", cols
 
 
- 
	
	Nested Looping
over 2d Lists
 
 # Create an "arbitrary" 2d List
 a = [ [ 2, 3, 5] , [ 1, 4, 7 ] ]
 print "Before: a =", a
 
 # Now find its dimensions
 rows = len(a)
 cols = len(a[0])
 
 # And now loop over every element
 # Here, we'll add one to each element,
 # just to make a change we can easily see
 for row in xrange(rows):
 for col in xrange(cols):
 # This code will be run rows*cols times, once for each
 # element in the 2d list
 a[row][col] += 1
 
 # Finally, print the results
 print "After:  a =", a
 
 
- 
	
	Copying
2d Lists
	- 
	
	copy.deepcopy example
 
 import copy
 
 # Create a 2d list
 a = [ [ 1, 2, 3 ] , [ 4, 5, 6 ] ]
 
 # Try to copy it
 b = 
	copy.copy(a)      # Error:  creates shallow copy
 c = 
	copy.deepcopy(a)  # Ok
 
 # At first, things seem ok
 print "At first..."
 print "   a =", a
 print "   b =", b
 print "   c =", c
 
 # Now modify a[0][0]
 a[0][0] = 9
 print "But after a[0][0] = 9"
 print "   a =", a
 print "   b =", b
 print "   c =", c
 
- limitations of 
	copy.deepcopy of aliases
 
 a = [[0]*2]*3 # makes 3 shallow copies of (aliases of) the same row
 a[0][0] = 42  # appears to modify all 3 rows
 print a       # prints [[42, 0], [42, 0], [42, 
	0]]
 
 # now do it again with a deepcopy
 
 import copy
 a = [[0]*2]*3        # makes 3 shallow 
	copies of the same row
 a = copy.deepcopy(a) # meant to make each row distinct
 a[0][0] = 42         # so we hope 
	this only modifies first row
 print a              
	# STILL prints [[42, 0], [42, 0], [42, 0]]
 
 # now one more time with a simple deepcopy alternative that does
 # what we thought deepcopy did...
 
 def myDeepCopy(a):
 if (isinstance(a, list) or isinstance(a, tuple)):
 return [myDeepCopy(element) for 
	element in a]
 else:
 return copy.copy(a)
 
 a = [[0]*2]*3     # makes 3 shallow copies of the same 
	row
 a = myDeepCopy(a) # once again, meant to make each row distinct
 a[0][0] = 42      # so we hope this only modifies 
	first row
 print a           # 
	finally, prints [[42, 0], [0, 0], [0, 0]]
 
 # what's going on with deepcopy? Answer: if the original list has aliases,
 # the deepcopied list will have aliases (of a single copy, not the 
	original).
 # So copy.deepcopy preserves alias structure!
 
 
- Printing 2d Lists
 
 # Helper function for print2dList.
 # This finds the maximum length of the string
 # representation of any item in the 2d list
 def maxItemLength(a):
 maxLen = 0
 rows = len(a)
 cols = len(a[0])
 for row in 
	xrange(rows):
 for col in 
	xrange(cols):
 maxLen = max(maxLen, len(str(a[row][col])))
 return maxLen
 
 # Because Python prints 2d lists on one row,
 # we might want to write our own function
 # that prints 2d lists a bit nicer.
 def print2dList(a):
 if (a == []):
 # So we don't crash accessing a[0]
 print []
 return
 rows = len(a)
 cols = len(a[0])
 fieldWidth = maxItemLength(a)
 print "[ ",
 for row in 
	xrange(rows):
 if (row > 0): print "\n  ",
 print "[ ",
 for col in 
	xrange(cols):
 if (col > 0): print ",",
 # The next 2 lines print a[row][col] with the given fieldWidth
 format = "%" + str(fieldWidth) + "s"
 print format % str(a[row][col]),
 print "]",
 print "]"
 
 # Let's give the new function a try!
 a = [ [ 1, 2, 3 ] , [ 4, 5, 6 ] ]
 print2dList(a)
 
 
- Accessing 2d Lists by Row 
	or Column
 
		- Accessing a whole row
 
 # alias (not a copy!); cheap (no new list created)
 a = [ [ 1, 2, 3 ] , [ 4, 5, 6 ] ]
 row = 1
 rowList = a[row]
 print rowList
 
- Accessing a whole column
 
 # copy (not an alias!); expensive (new list created)
 a = [ [ 1, 2, 3 ] , [ 4, 5, 6 ] ]
 col = 1
 colList = [ ]
 for i in xrange(len(a)):
 colList += [ a[i][col] ]
 print colList
 
- Accessing a whole column with a list comprehension
 
 # still a copy, still expensive, but cheaper and cleaner with a list 
		comprehension!
 a = [ [ 1, 2, 3 ] , [ 4, 5, 6 ] ]
 col = 1
 colList = [ a[i][col] for i in 
		xrange(len(a)) ]
 print colList
 
 
- Non-Rectangular ("Ragged") 
	2d Lists# 2d lists do not have to be rectangular
a = [ [ 1, 2, 3 ] ,
      [ 4, 5 ],
      [ 6 ],
      [ 7, 8, 9, 10 ] ]
rows = len(a)
for row in xrange(rows):
    cols = len(a[row]) # now cols depends on each row
    print "Row", row, "has", cols, "columns: ",
    for col in xrange(cols):
        print a[row][col],
    print
- 3d Lists# 2d lists do not really exist in Python.
# They are just lists that happen to contain other lists as elements.
# And so this can be done for "3d lists", or even "4d" or higher-dimensional lists.
# And these can also be non-rectangular, of course!
a = [ [ [ 1, 2 ],
        [ 3, 4 ] ],
      [ [ 5, 6, 7 ],
        [ 8, 9 ] ],
      [ [ 10 ] ] ]
for i in xrange(len(a)):
    for j in xrange(len(a[i])):
        for k in xrange(len(a[i][j])):
            print "a[%d][%d][%d] = %d" % (i, j, k, a[i][j][k])
- Examples Using 2d Lists
	- WordSearch
- Connect4
- 
	
	Optional:  Matrices