Skip to content

Instantly share code, notes, and snippets.

@DagnyTagg2013
Created December 16, 2017 17:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save DagnyTagg2013/cd4411932aa7f9e940974458b4a1a336 to your computer and use it in GitHub Desktop.
Save DagnyTagg2013/cd4411932aa7f9e940974458b4a1a336 to your computer and use it in GitHub Desktop.
FunctionalMatrixOps
# 2D Matrix Ops
import sys
from functools import partial
# SLICING
"""
=> operates on SHALLOW REFERENCE PTR to COMPLEX types (like list, array);
OR actual VALUES of PRIMITIVE types (like char, int)
- https://www.python-course.eu/deep_copy.php
a[start:end] # items start through end-1 (ie NOT inclusive of END)
a[start:] # items start through the rest of the array
a[:end] # items from the beginning through end-1
a[:] # a copy of the whole array
a[start:end:step] #
a[-1] # last item
x[::-1] # reverse
"""
# FUNCTION CURRYING -- i.e. breakdown COMPOUND-NESTED function into functions-parameterized-by-level-of-nesting
"""
https://stackoverflow.com/questions/24881604/when-should-i-use-function-currying-in-python
https://stackoverflow.com/questions/218025/what-is-the-difference-between-currying-and-partial-application
- eg f takes input of three function arguments and two constants from L to R
- function arguments within f are applied in nested fashion in ORDER from most-nested function LEFTMOST
on orig arglist to
outer function from RIGHTMOST
- then PARTIAL parameters may be passed into arglists to generate a PARTIAL function which essentially applies
DEFAULT functional or non args
from LEFT to RIGHT
then allows flexible input args
to be supplied on the RIGHT
- if argument referenced is NOT in current function scope or arglist;
its value will get searched for in the NEXT-ENCLOSING function closure
i.e. function WITHIN a function will search for next-enclosing function's 'closure' for variable declaration
- function argument DEFAULTING applies ORDER from LEFT to RIGHT;
where LEFT is OUTERMOST nested closure,
but RIGHT is INNERMOST
- constant argument DEFAULTING applies ORDER from RIGHT to LEFT
function f(x,y,z,a,b) { x(y(z(a,b)); } where x,y,z are lambda function types;
with nested function/closure scope;
where LEFTMOST functional arg x has OUTERMOST enclosing scope;
and RIGHTMOST functional arg z has INNERMOST nested enclosing scope
but where a, b are non-function types
and DEFAULT applies from LEFT to RIGHT
=>
function q(a,b) { lambda(x)(y,z,a,b) { lambda(y)(z,a,b) { lambda(z)(a,b); } } }
=>
equivalently:
function q(a,b) = f(x)(y)(z)(a,b);
=>
If you only call 'curried-form' f(x)(y) passing in actual x, y params, then you get a
PARTIAL-applied-function
the return value is a closure of lambda(z){(x(y))}
with passed-in the values of x and y to f(x,y).
e.g.
function fold(combineFunction, accumalator, list) {/* ... */}
function sum = curry(fold)(lambda(accumulated-value,element-of-list-e){a+e}))(0);
- where 0 initializes accumulated-value
- where lambda parameterizes a customizable combineFunction
another example with ANONYMOUS on-the-fly function declarations
http://www.secnetix.de/olli/Python/lambda_functions.hawk
nums = [ for i in range(0,(50+1) ]
# where 2-7 are prime numbers
for i in range(2,8):
# where op returning 0 (divisibility by prime factor) will filter OUT value from result
nums = filter(lambda x: x == i or x % i, nums)
How does it work? First, we put all numbers from 2 to 49 into a list called "nums". Then we have a "for" loop that iterates over all possible divisors, i.e. the value of "i" goes from 2 to 7. Naturally, all numbers that are multiples of those divisors cannot be prime numbers, so we use a filter function to remove them from the list. (This algorithm is called "the sieve of Eratosthenes".)
*** Curry vs Partial function application
*** dynamic anonymous lambda declaration
vs static reusable def declaration of object type function
https://docs.python.org/2/library/functools.html
https://mtomassoli.wordpress.com/2012/03/18/currying-in-python/
"""
# ATTN: opChar is on LHS as defaultable PARTIAL function parameter
def binaryOp(opChar, leftNum, rightNum):
result = None
if (opChar == '+'):
result = leftNum + rightNum
elif (opChar == '-'):
result = leftNum - rightNum
elif (opChar == '*'):
result = leftNum * rightNum
elif (opChar == '/'):
result = leftNum / rightNum
else:
raise ValueError("Unrecognized Binary Operator Char")
return result
A = [ [1,2,3],[4,5,6] ]
B = [ [10,20,30],[40,50,60] ]
# DIMENSIONS of matrix
# inspect rows and columns of matrix
numRows = len(A)
numCols = len(A[0])
print ('NumRows is: {}, and NumCols is: {}'.format(numRows, numCols))
"""
ATTENTION:
- one REASON WHY SCALA is BETTER:
- TYPE of FUNCTION with SPECIFIC PARAM-LIST is VALIDATED
"""
# pointWise operation A op B,
# - where binaryOp is a function or lambda type
# ATTN: defaultable NESTED function args are put to LEFTMOST of arglist
# but non-function args are put to RIGHT of arglist
# where OUTERMOST function in nesting is the LEFTMOST
def pointWiseMatrixOp(binaryOp, leftM, rightM):
# check dimensions are equal
numRowsLeft = len(leftM)
numColsLeft = len(leftM[0])
numRowsRight = len(rightM)
numColsRight = len(rightM[0])
if ( (numRowsLeft != numRowsRight) or (numColsLeft != numColsRight)):
raise ValueError("Matrix Dimensions must match for Pointwise Op!")
# pre-allocate 2D matrix
# allocate rows
result = [ [ None for i in range(numCols) ] for i in range(numRows) ]
for row in range(numRows):
for col in range(numCols):
result[row][col] = binaryOp(leftM[row][col], rightM[row][col])
return result
# ********** TEST DRIVER SCRIPT *************
try:
# AWESOMENESS: curry on lambda-parameterized function which internally
# implements a NESTED operation,
# can use 'CURRY' syntactic sugar of CHAINING-EACH-LEVEL-OF-NESTING
# on function params list
# - i.e. any function args NOT DEFAULTED on the LHS,
# are the RHS REMAINDER (function or primitive variable) arglist
# of the resulting partially-defined complex function!
numBinaryAddOp = partial(binaryOp, '+')
matrixPointWiseAddOp = partial(pointWiseMatrixOp, numBinaryAddOp)
result = matrixPointWiseAddOp(A, B)
print ('\nLEFT:\n{}\nRIGHT:\n{}'.format(A, B))
print ('POINTWISE ADD RESULT:\n{}'.format(result))
except ValueError as err:
print(err)
except:
print("Unexpected error:", sys.exc_info()[0])
raise
# 2D Matrix transpose (rows to columns)
# ATTN indexing:
# point = A[row][col]
# row = A[row]
# col => take column SLICE through matrix ACROSS all rows
transposeA = [ [row[j] for row in A] for j in range(numCols) ]
print ('\nA: ', A)
print ('TRANSPOSE A: ', transposeA)
# TODOs:
# 2D Matrix inversion (only on SQUARE)
# 2D Matrix multiply (N x K) x (K x M) => (N x M)
# slice column by accessing list comprehension as in transpose op, for specific column j
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment