cycle through previous class instances results - python

This is a continuation from here.
I have a class B which holds some data (a and b fields).
I am loading these data to the Criteria class in order to pass through the criteria and change accordingly calling the functions avg, func, calcs.
Then , I call the function P to start running the program with the data.
The code:
import numpy as np
class B():
def __init__(self, a,b):
self.a = a
self.b = b
def __repr__(self):
return 'B(%s, %s)'%(self.a, self.b)
class Criteria():
def __init__(self, method, minimum, maximum, measures=None):
self.method = method
self.minimum = minimum
self.maximum = maximum
self.measures = measures
def __repr__(self):
if self.measures is None:
measures = 'measures: None'
else:
measures = [' measures:[']
for m in self.measures:
measures.append(' {}'.format(m))
measures.append(' ]')
measures = '\n'+ '\n'.join(measures)
return 'C({0.method},{0.minimum},{0.maximum}, {1})'.format(self, measures)
def calcs(self):
""" Update the `a` attribute from B class object according to
conditions
"""
if self.measures is not None:
for x in self.measures:
if (x.a > self.minimum and x.a < self.maximum):
x.a = 999
return self.measures
def avg(self, calcs=None):
"""Return the average of values"""
if calcs is None:
calcs = self.measures
if calcs is None:
return 'none'
elif len(calcs)==0:
return '[]'
else:
return np.average([x.a for x in calcs])
def func(self,calcs=None):
"""Return the minimum is input is array or multiple by 1000
if input is a number"""
if calcs is None:
calcs = self.measures
if calcs is None:
return 'none'
#elif len(calcs) == 0:
# return '[]'
else:
if isinstance(calcs, np.ndarray):
return np.amin([x.a for x in calcs])
else:
return calcs*1000
def P(alist):
# use these variables to hold the result of the corresponding execution
# use them in the list loop in order to be able to obtain a result from a `Criteria` evaluation
# and use it as input to the next
last_calcs_values = None
last_calcs_avg = None
last_calcs_m = None
for c in alist:
if c.method=='V':
last_calcs_values = c.calcs()
#print('calcs', last_calcs_values)
yield last_calcs_values
if c.method=='AVG':
if c.measures is None:
last_calcs_avg = c.avg(last_calcs_values)
else:
last_calcs_avg = c.avg()
#print('AVG', last_calcs_avg)
yield last_calcs_avg
if c.method == 'M':
if c.measures is None:
last_calcs_m = c.func(last_calcs_avg)
else:
last_calcs_m = c.func()
#print('M',last_calcs_m)
yield last_calcs_m
If I use the data:
b1 = np.array([B(10, 0.1), B(200,.5)])
c1 = Criteria('V', 1, 100, b1)
c2 = Criteria('AVG', 22, 220, None)
c3 = Criteria('M', 22, 220, None)
alist = [c1,c2,c3]
for i in P(alist):
print(i)
I receive:
[B(999, 0.1) B(200, 0.5)]
599.5
599500.0
which is correct.But it works because the code in P function is hardcoded..
So,
1) My data , c1,c2,c3 uses the methods V,AVG,M in that order.
So, in the P function , I used :
yield last_calcs_values
last_calcs_avg = c.avg(last_calcs_values)
yield last_calcs_avg
last_calcs_m = c.func(last_calcs_avg)
yield last_calcs_m
the same order (hardcoded).
My question is how can I use this code for any order.I must somehow check what the previous method value is and put that in the argument (instead of putting c.func(last_calcs_avg)
2) Inside the func I have commented out the lines:
#elif len(calcs) == 0:
# return '[]'
because if I run the code , it gives : object of type 'numpy.float64' has no len() .
I tried to check similar with the check I have later in func :
if isinstance(calcs, np.ndarray):
but with no success.
3) Is there a way to obtain only the last result?
So, instead of :
[B(999, 0.1) B(200, 0.5)]
599.5
599500.0
obtain :
599500.0

If I read P correctly it maintains 3 'state' variables. The 3 if clauses can called in any order (and I think could have been written with a if,ifthen,ifthen,else syntax since c.method only matches one (for each c.
But the order of the objects in the list will determine values. An AVG object will use what ever values were set by the last V object. Similarly the M will use the last AVG. If pass an AVG first it will use the initial None values in its calc.
So sequence of V1, AVG, M will use B values that were set by V1.
In this sequence V1, AVG, V2, M, M uses values from the last AVG which was dependent on V1;
avg should not be returning strings if its values are being used by func, or at least the tests must match. [] is an empty list with len() zero, but '[]' is a 2 character string, with len 2.
Similarly None is a unique value that you test with is None, while 'none' is a 4 character string. I used strings like that in earlier questions simply because we were printing the results of avg. At the time we weren't using them for further calculations.
If you want to make sure that AVG and M use values from the last V, you need to add some logic:
lastV, lastA, lastM = None,None,None
if c.method=='V':
lastV = <newV>
lastA, lastM = None,None
elif c.method=='A':
if lastV is None:
error
else:
lastA = <new A based on lastV>
elif c.method=='M':
if lastA is None:
error
<or update lastA>
else:
lastM = <new M based on lastA>
else:
error unknown c.method
So I am using None to indicate that the values are not valid. In which case it should either raise an error, or it should calculate new values. Done right it should ensure that both AVG and M will produce values based on the latest V.
From your pastebin:
def avg(self, calcs=None):
"""Return the average of values"""
if calcs is None: # fun called without argument
calcs = self.measures # get value stored in self
if calcs is None: # in case that too was None
return '[]' # I would return None or []
# '[]' is a useless string
else:
if hasattr(calcs,'__len__'):
return np.average([x.a for x in calcs])
else:
return np.average(calcs)
What works in np.average() that doesn't have a len? len(np.arange(10)) runs, but doesn't have the a attribute
In [603]: avg(None,calcs=np.arange(10))
....
<ipython-input-602-48c9f6b255e1> in <listcomp>(.0)
8 else:
9 if hasattr(calcs,'__len__'):
---> 10 return np.average([x.a for x in calcs])
11 else:
12 return np.average(calcs)
AttributeError: 'numpy.int32' object has no attribute 'a'
That __len__ between an array or list with B objects and other lists or arrays. Maybe refine this to test dtype? Or a try/except?
def avg(self, calcs=None):
"""Return the average of values"""
....
else:
try:
return np.average([x.a for x in calcs])
except AttributeError:
return np.average(calcs)
In [606]: avg(None,calcs=np.arange(10))
Out[606]: 4.5
And list or array of B objects works:
In [609]: alist = [B(1,2),B(2,4),B(3,3)]
In [610]: avg(None, alist)
Out[610]: 2.0
In [611]: avg(None, np.array(alist))
Out[611]: 2.0

I don't understand what you mean by not hardcoding P
Is there a way to obtain only the last result?
Yes. Obtain all the results, but only print the last one:
for i in P(alist): pass
print(i)

Related

Numba: how to parse arbitrary logic string into sequence of jitclassed instances in a loop

Tl Dr. If I were to explain the problem in short:
I have signals:
np.random.seed(42)
x = np.random.randn(1000)
y = np.random.randn(1000)
z = np.random.randn(1000)
and human readable string tuple logic like :
entry_sig_ = ((x,y,'crossup',False),)
exit_sig_ = ((x,z,'crossup',False), 'or_',(x,y,'crossdown',False))
where:
'entry_sig_' means the output will be 1 when the time series unfolds from left to right and 'entry_sig_' is hit. (x,y,'crossup',False) means: x crossed y up at a particular time i, and False means signal doesn't have "memory". Otherwise number of hits accumulates.
'exit_sig_' means the output will again become '0' when the 'exit_sig_' is hit.
The output is generated through:
#njit
def run(x, entry_sig, exit_sig):
'''
x: np.array
entry_sig, exit_sig: homogeneous tuples of tuple signals
Returns: sequence of 0 and 1 satisfying entry and exit sigs
'''
L = x.shape[0]
out = np.empty(L)
out[0] = 0.0
out[-1] = 0.0
i = 1
trade = True
while i < L-1:
out[i] = 0.0
if reduce_sig(entry_sig,i) and i<L-1:
out[i] = 1.0
trade = True
while trade and i<L-2:
i += 1
out[i] = 1.0
if reduce_sig(exit_sig,i):
trade = False
i+= 1
return out
reduce_sig(sig,i) is a function (see definition below) that parses the tuple and returns resulting output for a given point in time.
Question:
As of now, an object of SingleSig class is instantiated in the for loop from scratch for any given point in time; thus, not having "memory", which totally cancels the merits of having a class, a bare function will do. Does there exist a workaround (a different class template, a different approach, etc) so that:
combined tuple signal can be queried for its value at a particular point in time i.
"memory" can be reset; i.e. e.g. MultiSig(sig_tuple).memory_field can be set to 0 at a constituent signals levels.
Following code adds a memory to the signals which can be wiped using MultiSig.reset() to reset the count of all signals to 0. The memory can be queried using MultiSig.query_memory(key) to return the number of hits for that signal at that time.
For the memory function to work, I had to add unique keys to the signals to identify them.
from numba import njit, int64, float64, types
from numba.types import Array, string, boolean
from numba import jitclass
import numpy as np
np.random.seed(42)
x = np.random.randn(1000000)
y = np.random.randn(1000000)
z = np.random.randn(1000000)
# Example of "human-readable" signals
entry_sig_ = ((x,y,'crossup',False),)
exit_sig_ = ((x,z,'crossup',False), 'or_',(x,y,'crossdown',False))
# Turn signals into homogeneous tuple
#entry_sig_
entry_sig = (((x,y,'crossup',False),'NOP','1'),)
#exit_sig_
exit_sig = (((x,z,'crossup',False),'or_','2'),((x,y,'crossdown',False),'NOP','3'))
#njit
def cross(x, y, i):
'''
x,y: np.array
i: int - point in time
Returns: 1 or 0 when condition is met
'''
if (x[i - 1] - y[i - 1])*(x[i] - y[i]) < 0:
out = 1
else:
out = 0
return out
kv_ty = (types.string,types.int64)
spec = [
('memory', types.DictType(*kv_ty)),
]
#njit
def single_signal(x, y, how, acc, i):
'''
i: int - point in time
Returns either signal or accumulator
'''
if cross(x, y, i):
if x[i] < y[i] and how == 'crossdown':
out = 1
elif x[i] > y[i] and how == "crossup":
out = 1
else:
out = 0
else:
out = 0
return out
#jitclass(spec)
class MultiSig:
def __init__(self,entry,exit):
'''
initialize memory at single signal level
'''
memory_dict = {}
for i in entry:
memory_dict[str(i[2])] = 0
for i in exit:
memory_dict[str(i[2])] = 0
self.memory = memory_dict
def reduce_sig(self, sig, i):
'''
Parses multisignal
sig: homogeneous tuple of tuples ("human-readable" signal definition)
i: int - point in time
Returns: resulting value of multisignal
'''
L = len(sig)
out = single_signal(*sig[0][0],i)
logic = sig[0][1]
if out:
self.update_memory(sig[0][2])
for cnt in range(1, L):
s = single_signal(*sig[cnt][0],i)
if s:
self.update_memory(sig[cnt][2])
out = out | s if logic == 'or_' else out & s
logic = sig[cnt][1]
return out
def update_memory(self, key):
'''
update memory
'''
self.memory[str(key)] += 1
def reset(self):
'''
reset memory
'''
dicti = {}
for i in self.memory:
dicti[i] = 0
self.memory = dicti
def query_memory(self, key):
'''
return number of hits on signal
'''
return self.memory[str(key)]
#njit
def run(x, entry_sig, exit_sig):
'''
x: np.array
entry_sig, exit_sig: homogeneous tuples of tuples
Returns: sequence of 0 and 1 satisfying entry and exit sigs
'''
L = x.shape[0]
out = np.empty(L)
out[0] = 0.0
out[-1] = 0.0
i = 1
multi = MultiSig(entry_sig,exit_sig)
while i < L-1:
out[i] = 0.0
if multi.reduce_sig(entry_sig,i) and i<L-1:
out[i] = 1.0
trade = True
while trade and i<L-2:
i += 1
out[i] = 1.0
if multi.reduce_sig(exit_sig,i):
trade = False
i+= 1
return out
run(x, entry_sig, exit_sig)
To reiterate what I said in the comments, | and & are bitwise operators, not logical operators. 1 & 2 outputs 0/False which is not what I believe you want this to evaluate to so I made sure the out and s can only be 0/1 in order for this to produce the expected output.
You are aware that the because of:
out = out | s if logic == 'or_' else out & s
the order of the time-series inside entry_sig and exit_sig matters?
Let (output, logic) be tuples where output is 0 or 1 according to how crossup and crossdown would evalute the passed information of the tuple and logic is or_ or and_.
tuples = ((0,'or_'),(1,'or_'),(0,'and_'))
out = tuples[0][0]
logic = tuples[0][1]
for i in range(1,len(tuples)):
s = tuples[i][0]
out = out | s if logic == 'or_' else out & s
out = s
logic = tuples[i][1]
print(out)
0
changing the order of the tuple yields the other signal:
tuples = ((0,'or_'),(0,'and_'),(1,'or_'))
out = tuples[0][0]
logic = tuples[0][1]
for i in range(1,len(tuples)):
s = tuples[i][0]
out = out | s if logic == 'or_' else out & s
out = s
logic = tuples[i][1]
print(out)
1
The performance hinges on how many times the count needs to be updated. Using n=1,000,000 for all three time series, your code had a mean run-time of 0.6s on my machine, my code had 0.63s.
I then changed the crossing logic up a bit to save the number of if/else so that the nested if/else is only triggered if the time-series crossed which can be checked by one comparison only. This further halved the difference in run-time so above code now sits at 2.5% longer run-time your original code.

Cost function equation parsing

I'm trying to solve this assignment:
For the cost function​ below, where C is the cost of producing x units of a​ product, find the​ marginal-cost function. What is the marginal cost at the given value of​ x? C(x)=0.05x^3+0.8x^2+40x+100; x=500
How could I parse the bolded values, the formula, from this string? Something where this could be repeated with similar strings.
Here is my solution, it breaks down the equation into objects that need to be summed up and then evaluates each of them on its own:
def clean(string):
# Removes unnecesarry parts of input
return string.rstrip(';').split('=')[1]
def parse(string):
parts = string.split('+')
objs = []
for part in parts:
mult_pow = part.split('x')
if len(mult_pow) == 2:
# Both multiplier and power present
obj = (
float(mult_pow[0]),
float(mult_pow[1].lstrip('^')) if mult_pow[1] else 1.0
)
else:
if '^' in mult_pow[0]:
# Only power present
obj = (
1.0,
float(mult_pow[0].lstrip('^'))
)
else:
# Only multiplier present
obj = (
float(mult_pow[0]),
0
)
objs.append(obj)
return objs
def evaluate(parsed_objects, x):
result = 0
for obj in parsed_objects:
result += obj[0] * x**obj[1]
return result
def solve(equation, x):
cleaned_str = clean(equation)
parsed_objects = parse(cleaned_str)
result = evaluate(parsed_objects, x)
return result
x = 500
input_str = 'C(x)=0.05x^3+0.8x^2+40x+100;'
result = solve(input_str, x)
print(result)
Output:
6470100.0

Count number of function calls for distinct input argument values

With the code
def myfunction():
myfunction.counter += 1
myfunction.counter = 0
from https://stackoverflow.com/a/21717084/2729627 you can keep track of the number of times the function is called.
But how do I keep track of the number of times a function is called when (one of) its input arguments takes on a certain value?
So for instance
def myfunction(a):
# Do some calculations...
b = a**2
# Increase counter for specific value of 'a'.
myfunction.counter[a] += 1
# Return output argument.
return b
myfunction.counter[5] = 0
myfunction.counter[79648763] = 0
print(myfunction.counter[5])
print(myfunction.counter[79648763])
myfunction(5)
myfunction(79648763)
myfunction(79648763)
print(myfunction.counter[5]) # Should return 1.
print(myfunction.counter[79648763]) # Should return 2.
How should I modify this code to get it to work?
You can use a dictionary to keep this information:
counter_dict={} #new line
def myfunction(a):
b = a**2
if a in counter_dict.keys():
counter_dict[a] = counter_dict[a]+1 #increment the previous value
else:
counter_dict[a] = 1 #if the value is not present then initialize it with 1
return b
myfunction(5)
myfunction(79648763)
myfunction(79648763)
print(counter_dict[5]) # Should return 1.
print(counter_dict[79648763]) # Should return 2.
If you don't want to use global dict then you can write this:
def myfunction(a):
b = a**2
if a in myfunction.my_dict.keys():
myfunction.my_dict[a] = myfunction.my_dict[a]+1
else:
myfunction.my_dict[a] = 1
return b
myfunction.my_dict={}
myfunction(5)
myfunction(79648763)
myfunction(79648763)
print(myfunction.my_dict[5])
print(myfunction.my_dict[79648763])

python why data type changed by def function?

Why num_r1(x) and num_r2(x) type numpy.ndarray, but num_r(t) is type float? How can I keep num_r(t) type as array?
def num_r(t):
for x in t:
if x>tx:
return num_r2(x)
else:
return num_r1(x)
Thank you!
The complete example is below
# -*- coding: utf-8 -*
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
import math
from pylab import *
#### physical parameters
c = 2.998*10**10
hp = 6.626*10**-27
hb = 1.055*10**-27
kb = 1.381*10**-16
g = 6.673*10**-8
me = 9.109*10**-28
mp = 1.673*10**-24
q = 4.803*10**-10 #gausi
sigT = 6.652*10**-25
# The evolution of the characteristic frequencies
p = 2.5
E52 = 1
epsB_r = 1
epse_r = 1
D28 = 1
n1 = 1.0
nu15 = 1*10**(-5)
r014 = 1
g42 = 1
delt12 =1
g4 = g42*10**2.5
E0 = E52*10**52
eta = g4
N0 = E0/(g4*mp*c**2)
p_tx = 3**(1./3)*2**(4./3)*mp**(-1./3)*c**(-5./3)
tx = p_tx*n1**(-1./3)*eta**(-8./3)
p_num_r1 = 2**(11./2)*7**(-2)*mp**(5./2)*me**(-3)*pi**(-1./2)*q*p_tx**(-6)*2**30*3**18*10**12
p_nuc_r1 = 2**(-33./2)*3**(-4)*10**(-4)*me*mp**(-3./2)*c**(-2)*sigT**(-2)*pi**(-1./2)*q
p_Fmax_r1 = 2**(15./2)*3**(9./2)*10**30*p_tx**(-3./2)*10**(-56)*me*mp**(1./2)*c**3*sigT*q**(-1)*2**(1./2)*3**(-1)
p_num_r2 = 2**(11./2)*7**(-2)*mp**(5./2)*me**(-3)*pi**(-1./2)*q*p_tx**(54./35)*(2**5*3**3*10**2)**(-54./35)
p_nuc_r2 = 2**(-13./2)*3**2*pi**(-1./2)*me*mp**(-3./2)*c**(-2)*sigT**(-2)*q*p_tx**(-74./35)*(2**5*3**3*10**2)**(4./35)
p_Fmax_r2 = 2**(1./2)*3**(-1)*pi**(-1./2)*me*mp**(1./2)*c**3*sigT*q**(-1)*10**(-56)
num_r1 = lambda t : p_num_r1*eta**18*((p-2)/(p-1))**2*epse_r**2*epsB_r**(1./2)*n1**(5./2)*t**6*E52**(-2)
nuc_r1 = lambda t : p_nuc_r1*eta**(-4)*epsB**(-3./2)*n1**(-3./2)*t**(-2)
Fmax_r1 = lambda t : p_Fmax_r1*N0**t**(3./2)*n1*eta**6*E52**(-1./2)*D28**(-2)*epsB_r**(1./2)
num_r2 = lambda t : p_num_r2*((p-2)/(p-1))**2*n1**(-74./35)*n1**(74./105)*eta**(592./105)*E52**(-74./105)
nuc_r2 = lambda t : p_nuc_r2*eta**(172./105)*t**(4./35)*n1**(-167./210)*epsB_r**(-3./2)
Fmax_r2 = lambda t : N0*eta**(62./105)*n1**(37./210)*epsB_r**(1./2)*t**(-34./35)*D28**(-2)
def fspe(t,u):
if num_r(t)<nuc_r(t):
return np.where(u<num_r(t),(u/num_r(t))**(1./3)*Fmax_r(t),np.where(u<nuc_r(t),(u/num_r(t))**(-(p-1.)/2)*Fmax_r(t),(u/nuc_r(t))**(-p/2)*(nuc_r(t)/num_r(t))**(-(p-1.)/2)*Fmax_r(t)))
else:
return np.where(u<nuc_r(t),(u/nuc_r(t))**(1./3)*Fmax_r(t),np.where(u<num_r(t),(u/nuc_r(t))**(-1./2)*Fmax_r(t),(u/num_r(t))**(-p/2)*(num_r(t)/nuc_r(t))**(-1.2)*Fmax_r(t)))
def num_r(t):
for x in t:
if x>tx:
return num_r2(x)
else:
return num_r1(x)
def nuc_r(t):
for x in t:
if t>tx:
return nuc_r2(x)
else:
return nuc_r1(x)
def Fmax_r(t):
for x in t:
if t>tx:
return Fmax_r2(x)
else:
return Fmax_r1(x)
i= np.arange(-4,6,0.1)
t = 10**i
dnum = [math.log10(mmm) for mmm in num_r(t)]
dnuc = [math.log10(j) for j in nuc_r(t)]
nu_obs = [math.log(2.4*10**17,10) for a in i]
plt.figure('God Bless: Observable Limit')
plt.title(r'$\nu_{obs}$ and $\nu_c$ and $\nu_m$''\nComparation')
plt.xlabel('Time: log t')
plt.ylabel(r'log $\nu$')
plt.axvline(math.log10(tx))
plt.plot(i,nu_obs,'.',label=r'$\nu_{obs}$')
plt.plot(i,dnum,'D',label=r'$\nu_m$')
plt.plot(i,dnuc,'s',label=r'$\nu_c$')
plt.legend()
plt.grid(True)
plt.savefig("nu_obs.eps", dpi=120,bbox_inches='tight')
plt.show()
But thereś a Error
TypeError Traceback (most recent call last)
<ipython-input-250-c008d4ed7571> in <module>()
95 i= np.arange(-4,6,0.1)
96 t = 10**i
---> 97 dnum = [math.log10(mmm) for mmm in num_r(t)]
TypeError: 'float' object is not iterable
You should write your function as:
def num_r_(x):
if x > tx:
return num_r2(x)
else:
return num_r1(x)
And then pass it through np.vectorize to lift it from float to float to np.array to np.array
num_r = np.vectorize(num_r_)
From Efficient evaluation of a function at every cell of a NumPy array
And then when you use it in:
dnum = [math.log10(mmm) for mmm in num_r(t)]
You should rather do:
dnum = np.log10(num_r(t))
That is to say don't use the functions from the math module. Use those from the np module as they can take np.array as well as float.
As:
i = np.arange(-4,6,0.1)
t = 10**i
results in t being a np.array
So i is an array (arange); so is t (a math expression of i).
def num_r(t):
for x in t:
if x>tx:
return num_r2(x)
else:
return num_r1(x)
You iterate on t. x is an element of t. You test it and pass it through num_r2 or num_r1, and return immediately. So only the 1st element t is being processed. Thus the error - num_r returns one value, not an array.
You need to write num_r in a way that processes all the values of t, not just the first. A simple, crude way is
def num_r(t):
result = []
for x in t:
if x>tx:
value = num_r2(x)
else:
value = num_r1(x)
result.append(value)
# result = np.array(result)
return result
Now num_r should return a list the same length as t, and can be use in the list comprehension
[math.log10(mmm) for mmm in num_r(t)]
num_r could be written as a list comprehension:
[(num_r2(x) if x>tx else num_r1(x)) for x in t]
You could have it return an array instead of a list, but as long as you are using it in the list comprehension, there's no need. A list is just fine.
If it did return an array, then you could replace the list comprehension with a numpy log operation, e.g.
np.log10(num_r(t))
If num_r1 and num_r2 are written so they can take an array (looks off hand like they are, but I haven't tested), you could write
def num_r(t):
ind = t>tx
result = np.zeros_like(t)
result[ind] = num_r2(t[ind])
result[~ind] = num_r1(t[~ind])
return result
The idea is to find a mask of a values in t that are >tx, and pass all those through num_r2 at once; similarly for num_r1; and collect the values in the correct slots of result. The result is an array that can be passed to np.log10. This should be quite a bit faster than iterating on t, or using np.vectorize.
There may be some errors in my suggestions, since I did not test them in an script or interpreter. But the underlying ideas should be correct and set you on the right path.

Infinite loop and recursion in Python

I am working on implementing an iterative deepening depth first search to find solutions for the 8 puzzle problem. I am not interested in finding the actual search paths themselves, but rather just to time how long it takes for the program to run. (I have not yet implemented the timing function).
However, I am having some issues trying to implement the actual search function (scroll down to see). I pasted all the code I have so far, so if you copy and paste this, you can run it as well. That may be the best way to describe the problems I'm having...I'm just not understanding why I'm getting infinite loops during the recursion, e.g. in the test for puzzle 2 (p2), where the first expansion should yield a solution. I thought it may have something to do with not adding a "Return" in front of one of the lines of code (it's commented below). When I add the return, I can pass the test for puzzle 2, but something more complex like puzzle 3 fails, since it appears that the now the code is only expanding the left most branch...
Been at this for hours, and giving up hope. I would really appreciate another set of eyes on this, and if you could point out my error(s). Thank you!
#Classic 8 puzzle game
#Data Structure: [0,1,2,3,4,5,6,7,8], which is the goal state. 0 represents the blank
#We also want to ignore "backward" moves (reversing the previous action)
p1 = [0,1,2,3,4,5,6,7,8]
p2 = [3,1,2,0,4,5,6,7,8]
p3 = [3,1,2,4,5,8,6,0,7]
def z(p): #returns the location of the blank cell, which is represented by 0
return p.index(0)
def left(p):
zeroLoc = z(p)
p[zeroLoc] = p[zeroLoc-1]
p[zeroLoc-1] = 0
return p
def up(p):
zeroLoc = z(p)
p[zeroLoc] = p[zeroLoc-3]
p[zeroLoc-3] = 0
return p
def right(p):
zeroLoc = z(p)
p[zeroLoc] = p[zeroLoc+1]
p[zeroLoc+1] = 0
return p
def down(p):
zeroLoc = z(p)
p[zeroLoc] = p[zeroLoc+3]
p[zeroLoc+3] = 0
return p
def expand1(p): #version 1, which generates all successors at once by copying parent
x = z(p)
#p[:] will make a copy of parent puzzle
s = [] #set s of successors
if x == 0:
s.append(right(p[:]))
s.append(down(p[:]))
elif x == 1:
s.append(left(p[:]))
s.append(right(p[:]))
s.append(down(p[:]))
elif x == 2:
s.append(left(p[:]))
s.append(down(p[:]))
elif x == 3:
s.append(up(p[:]))
s.append(right(p[:]))
s.append(down(p[:]))
elif x == 4:
s.append(left(p[:]))
s.append(up(p[:]))
s.append(right(p[:]))
s.append(down(p[:]))
elif x == 5:
s.append(left(p[:]))
s.append(up(p[:]))
s.append(down(p[:]))
elif x == 6:
s.append(up(p[:]))
s.append(right(p[:]))
elif x == 7:
s.append(left(p[:]))
s.append(up(p[:]))
s.append(right(p[:]))
else: #x == 8
s.append(left(p[:]))
s.append(up(p[:]))
#returns set of all possible successors
return s
goal = [0,1,2,3,4,5,6,7,8]
def DFS(root, goal): #iterative deepening DFS
limit = 0
while True:
result = DLS(root, goal, limit)
if result == goal:
return result
limit = limit + 1
visited = []
def DLS(node, goal, limit): #limited DFS
if limit == 0 and node == goal:
print "hi"
return node
elif limit > 0:
visited.append(node)
children = [x for x in expand1(node) if x not in visited]
print "\n limit =", limit, "---",children #for testing purposes only
for child in children:
DLS(child, goal, limit - 1) #if I add "return" in front of this line, p2 passes the test below, but p3 will fail (only the leftmost branch of the tree is getting expanded...)
else:
return "No Solution"
#Below are tests
print "\ninput: ",p1
print "output: ",DFS(p1, goal)
print "\ninput: ",p2
print "output: ",DLS(p2, goal, 1)
#print "output: ",DFS(p2, goal)
print "\ninput: ",p3
print "output: ",DLS(p3, goal, 2)
#print "output: ",DFS(p2, goal)
The immediate issue you're having with your recursion is that you're not returning anything when you hit your recursive step. However, unconditionally returning the value from the first recursive call won't work either, since the first child isn't guaranteed to be the one that finds the solution. Instead, you need to test to see which (if any) of the recursive searches you're doing on your child states is successful. Here's how I'd change the end of your DLS function:
for child in children:
child_result = DLS(child, goal, limit - 1)
if child_result != "No Solution":
return child_result
# note, "else" removed here, so you can fall through to the return from above
return "No Solution"
A slightly more "pythonic" (and faster) way of doing this would be to use None as the sentinel value rather than the "No Solution" string. Then your test would simply be if child_result: return child_result and you could optionally leave off the return statement for the failed searches (since None is the default return value of a function).
There are some other issues going on with your code that you'll run into once this recursion issue is fixed. For instance, using a global visited variable is problematic, unless you reset it each time you restart another recursive search. But I'll leave those to you!
Use classes for your states! This should make things much easier. To get you started. Don't want to post the whole solution right now, but this makes things much easier.
#example usage
cur = initialPuzzle
for k in range(0,5): # for 5 iterations. this will cycle through, so there is some coding to do
allsucc = cur.succ() # get all successors as puzzle instances
cur = allsucc[0] # expand first
print 'expand ',cur
import copy
class puzzle:
'''
orientation
[0, 1, 2
3, 4, 5
6, 7, 8]
'''
def __init__(self,p):
self.p = p
def z(self):
''' returns the location of the blank cell, which is represented by 0 '''
return self.p.index(0)
def swap(self,a,b):
self.p[a] = self.p[b]
self.p[b] = 0
def left(self):
self.swap(self.z(),self.z()+1) #FIXME: raise exception if not allowed
def up(self):
self.swap(self.z(),self.z()+3)
def right(self):
self.swap(self.z(),self.z()-1)
def down(self):
self.swap(self.z(),self.z()-3)
def __str__(self):
return str(self.p)
def copyApply(self,func):
cpy = self.copy()
func(cpy)
return cpy
def makeCopies(self,s):
''' some bookkeeping '''
flist = list()
if 'U' in s:
flist.append(self.copyApply(puzzle.up))
if 'L' in s:
flist.append(self.copyApply(puzzle.left))
if 'R' in s:
flist.append(self.copyApply(puzzle.right))
if 'D' in s:
flist.append(self.copyApply(puzzle.down))
return flist
def succ(self):
# return all successor states for this puzzle state
# short hand of allowed success states
m = ['UL','ULR','UR','UDR','ULRD','UDL','DL','LRD','DR']
ss= self.makeCopies(m[self.z()]) # map them to copies of puzzles
return ss
def copy(self):
return copy.deepcopy(self)
# some initial state
p1 = [0,1,2,3,4,5,6,7,8]
print '*'*20
pz = puzzle(p1)
print pz
a,b = pz.succ()
print a,b

Categories