Python function template for time-series data - python

I want to process some array/list/timeseries data, and want to use many different filters for this.
This led to two problems: I don't want to copy-paste the function every time, especially if I change something. Also with different dependencies (there might be a dependency on the previous, or n-th previous element, or n-th following element), the array that is looped over can go out of bounds, if I don't adjust the ranges.
The conditions for the filters could be arbitrarily complex, but always involve relative position in the data.
Here is a minimal example:
import random as r
data = [r.random() for _ in range(100)]
def example_filter(data):
counter = 0
for i in range(1, len(data)):
if((data[i-1]>0.8) and (data[i]<0.5)):
counter +=1
#might want to change something here
#right now I would need to do this in all filters separately
return counter
def example_filter_2(data):
counter = 0
for i in range(2, len(data)):
if((data[i-2]>0.8) or ((data[i-1]>0.9) and (data[i]<0.2))):
counter +=1
return counter
My idea was to somehow compress the conditions (they are more complicated in the real example), use a converter function to make the real condition out of them, pass it as a string to a template function, and then use the condition, like this:
def filter_template(condition):
def instance_of_filter(data):
counter = 0
#problem: the range isn't adjusted to account for out of bounds here
for i in range(len(data)):
#problem: condition will be passed as a string, so how can I evaluate it
#also, I can't evaluate condition before I know what 'data' is, so I need to keep the dependency
if condition:
counter += 1
return counter
return instance_of_filter
Any ideas?

You can use your last code idea, just change the condition from variable to a predicate function based on data and index.
Example:
def filter_template(condition_func, start_at=0):
def instance_of_filter(data):
counter = 0
for i in range(start_at, len(data)):
if condition_func(data, i):
counter += 1
return counter
return instance_of_filter
def condition1(data, i):
return (data[i-1]>0.8) and (data[i]<0.5)
def condition2(data, i):
return ((data[i-2]>0.8) or ((data[i-1]>0.9) and (data[i]<0.2)))
# usage
filter_template(condition1, 1)
filter_template(condition2, 2)

Related

Error: variable not storing modified value on recursion

I am finding count of all the ways a target is reached. In base case, i am updating the value but when returning, it is taking the initial value only. How to change it to updated value, Kindly help me making changes in this code only and let me know how can i store so that it can return the modified value.
Input list:[1,2,3]
target:3
Output: 2 as [1,2] and [3] will make it 3
def counter(ind,grid,target,count):
if target==0: #if target becomes 0(achieved)
count+=1
return count
if ind==0: #if ind=0 is reached and target=value at that index(achieved)
if target==grid[ind]:
count+=1
return count
else:
return
nottake=counter(ind-1,grid,target,count) #not taking the index's value
take=0
if target-grid[ind]>=0: #only if value at index is smaller that target
take=counter(ind-1,grid,target-grid[ind],count) #taking the index's value
return count
grid=[1,2,3]
target=3
ind=len(grid)-1
print(counter(ind,grid,target,0)) #output should be 2 but i am getting 0
For starters, please format your code with Black. It's difficult to understand code that's scrunched together. Also, use default values to avoid burdening the caller with fussy indices and extra parameters.
This approach exhibits a common mistake with recursion: trying to pass the result downward through the recursive calls as well as upward. Just pass the result upward only, and pass parameters/state downward.
Doing so gives:
from functools import cache
#cache
def count_ways(available_numbers, target, i=0):
if target == 0:
return 1
elif target < 0:
return 0
elif i >= len(available_numbers):
return 0
take = count_ways(available_numbers, target - available_numbers[i], i + 1)
dont_take = count_ways(available_numbers, target, i + 1)
return take + dont_take
if __name__ == "__main__":
print(count_ways(available_numbers=(1, 2, 2, 1, 3, 4) * 70, target=7))
This is clearly exponential since each recursive call spawns 2 child calls. But adding a cache (formerly lru_cache(maxsize=None) prior to CPython 3.9) avoids repeated calls, giving a linear time complexity as long as the list fits within the stack size. Use a bottom-up dynamic programming approach if it doesn't

Previous in yield operations - python

Recently i have been using the 'yield' in python. And I find generator functions very useful. My query is that, is there something which could decrement the imaginative cursor in the generator object. Just how next(genfun) moves and outputs +i'th item in the container, i would like to know if there exists any function that may call upon something like previous(genfun) and moves to -1th item in the conatiner.
Actual Working
def wordbyword():
words = ["a","b","c","d","e"]
for word in words:
yield word
getword = wordbyword()
next(getword)
next(getword)
Output's
a
b
What I would like to see and achieve is
def wordbyword():
words = ["a","b","c","d","e"]
for word in words:
yield word
getword = wordbyword()
next(getword)
next(getword)
previous(getword)
Expected Output
a
b
a
This may sound silly, but is there someway there is this previous in generator, if not why is it so?. Why not we could decrement the iterator, or am I ignorant of an existing method, pls shower some light. What can be the closest way to implement what I have here in hand.
No there is no such function to sort of go back in a generator function. The reason is that Python does not store up the previous value in a generator function natively, and as it does not store it, it also cannot perform a recalculation.
For example, if your generator is a time-sensitive function, such as
def time_sensitive_generator():
yield datetime.now()
You will have no way to recalculate the previous value in this generator function.
Of course, this is only one of the many possible cases that a previous value cannot be calculated, but that is the idea.
If you do not store the value yourself, it will be lost forever.
As already said, there is no such function since the entire point of a generator is to have a small memory footprint. You would need to store the result.
You could automate the storing of previous results. One use-case of generators is when you have a conceptually infinite list (e.g. that of prime numbers) for which you only need an initial segment. You could write a generator that builds up these initial segments as a side effect. Have an optional history parameter that the generator appends to while it is yielding. For example:
def wordbyword(history = None):
words = ["a","b","c","d","e"]
for word in words:
if isinstance(history,list): history.append(word)
yield word
If you use the generator without an argument, getword = wordbyword(), it will work like an ordinary generator, but if you pass it a list, that list will store the growing history:
hist = []
getword = wordbyword(hist)
print(next(getword)) #a
print(next(getword)) #b
print(hist) #['a','b']
Iterating over a generator object consumes its elements, so there is nothing to go back to after using next. You could convert the generator to a list and implement your own next and previous
index = 0
def next(lst):
global index
index += 1
if index > len(lst):
raise StopIteration
return lst[index - 1]
def previous(lst):
global index
index -= 1
if index == 0:
raise StopIteration
return lst[index - 1]
getword = list(wordbyword())
print(next(getword)) # a
print(next(getword)) # b
print(previous(getword)) # a
One option is to wrap wordbyword with a class that has a custom __next__ method. In this way, you can still use the built-in next function to consume the generator on-demand, but the class will store all the past results from the next calls and make them accessible via a previous attribute:
class save_last:
def __init__(self, f_gen):
self.f_gen = f_gen
self._previous = []
def __next__(self):
self._previous.append(n:=next(self.i_gen))
return n
def __call__(self, *args, **kwargs):
self.i_gen = self.f_gen(*args, **kwargs)
return self
#property
def previous(self):
if len(self._previous) < 2:
raise Exception
return self._previous[-2]
#save_last
def wordbyword():
words = ["a","b","c","d","e"]
for word in words:
yield word
getword = wordbyword()
print(next(getword))
print(next(getword))
print(getword.previous)
Output:
a
b
a

Changing from lists to arguments in Python 3.0

I've recently constructed a piece of python code which finds the least commonly repeated number in a list! Here is my code...
from collections import Counter
def least_common():
from collections import Counter
List = [1,1,1,0,0,3,3,2]
CountList = Counter(List)
Mincount = min(CountList.values())
least_common = next(n for n in reversed(List) if CountList[n] == Mincount)
print (least_common)
least_common()
However as you can clearly see, this uses a list to call the numbers which will be compared.
I'm now trying to get it to do the same task, but instead of using a built in list, I want it to use an argument of integers.
For example
def the_least_common(integers)
--------code with argument which will find lowest repeated number---------
print the_least_common([1,1,1,0,0,3,3,2])
LEAST COMMON BEING 2
Is any of the code which I've already created reusable for what I now need to create? Apologies if this is a stupid question or comes across as really simple as I'm a little stuck
Any advice is much appreciated!
Since you're using Counter, there's a builtin method - most_common - that returns a sorted list of elements and their counts, starting with the most common first. You can query the last element of this list.
In [418]: Counter([1,1,1,0,0,3,3,2]).most_common()[-1]
Out[418]: (2, 1)
Your function would look something like this:
def least_common(data):
return Counter(data).most_common()[-1][0]
If your data can have multiple integers with the same least count, and your function needs to return every one of them, you can iterate over most_common:
def least_common(data):
c = Counter(data).most_common()[::-1]
yield c[0][0]
for x, y in c[1:]:
if x != c[0][1]:
break
yield y

Python Coin Change Dynamic Programming

I am currently trying to implement dynamic programming in Python, but I don't know how to setup the backtracking portion so that it does not repeat permutations.
For example, an input would be (6, [1,5]) and the expected output should be 2 because there are 2 possible ways to arrange 1 and 5 so that their sum is equivalent to 6. Those combinations are {1,1,1,1,1,1} and {1,5} but the way my program currently works, it accounts for the combinations displayed above and the combination {5,1}. This causes the output to be 3 which is not what I wanted. So my question is "How do I prevent from repeating permutations?". My current code is shown below.
import collections as c
class DynamicProgram(object):
def __init__(self):
self.fib_memo = {}
# nested dictionary, collections.defaultdict works better than a regular nested dictionary
self.coin_change_memo = c.defaultdict(dict)
self.__dict__.update({x:k for x, k in locals().items() if x != 'self'})
def coin_change(self, n, coin_array):
# check cache
if n in self.coin_change_memo:
if len(coin_array) in self.coin_change_memo[n]:
return [n][len(coin_array)]
# base cases
if n < 0: return 0
elif n == 1 or n == 0: return 1
result = 0
i = 0
# backtracking (the backbone of how this function works)
while i <= n and i < len(coin_array):
result += self.coin_change(n-coin_array[i], coin_array)
i += 1
# append to cache
self.coin_change_memo[n][len(coin_array)] = result
# return result
return result
One of the way of avoiding permutation is to use the numbers in "non-decreasing" order. By doing so you will never add answer for [5 1] because it is not in "non-decreasing" order.And [1 5] will be added as it is in "non-decreasing" order.
So the change in your code will be if you fix to use the ith number in sorted order than you will never ever use the number which is strictly lower than this.
The code change will be as described in Suparshva's answer with initial list of numbers sorted.
Quick fix would be:
result += self.coin_change(n-coin_array[i], coin_array[i:]) # notice coin_array[i:] instead of coin_array
But you want to avoid this as each time you will be creating a new list.
Better fix would be:
Simply add a parameter lastUsedCoinIndex in the function. Then always use coins with index >= lastUsedCoinIndex from coin array. This will ensure that the solutions are distinct.
Also you will have to make changes in your memo state. You are presently storing sum n and size of array(size of array is not changing in your provided implementation unlike the quick fix I provided, so its of no use there!!) together as a state for memo. Now you will have n and lastUsedCoinIndex, together determining a memo state.
EDIT:
Your function would look like:
def coin_change(self,coin_array,n,lastUsedCoinIndex):
Here, the only variables changing will be n and lastUsedCoinIndex. So you can also modify your constructor such that it takes coin_array as input and then you will access the coin_array initialized by constructor through self.coin_array. Then the function would become simply:
def coin_change(self,n,lastUsedCoinIndex):

recursive function in class in python

I don't know, why isn't working that code in python. The problem is, that I can get numbers, but sometimes some numbers are uniform. And I want to do 5 different numbers between 1 and 90.
class lottery:
def __init__(self):
self.price = 50
def list(self):
numbers = []
for i in range(0,5):
numbers.append(random.randint(0,90))
for i in range(1,5):
for j in range(0,i-1):
if (numbers[i]==numbers[j]):
game.list()
return numbers
game = lottery()
game.list()
Or is there any better way to solve my problem?
Thanks!
Use random.sample:
def list(self):
return random.sample(xrange(90), 5)
This is (especially for large values of 5) much more efficient than starting over every time your randomization creates a repeat, and also avoids the possibility of overflowing the stack.
First of all you should import the random module:
import random
The problem is that you are not returning the result that you get from the recursive call, therefore you are still returning the list with repeated numbers. It should be:
def list(self):
numbers = []
for i in range(0, 5):
numbers.append(random.randint(0, 90))
for i in range(1, 5):
for j in range(0, i - 1):
if (numbers[i] == numbers[j]):
return self.list() # return
return numbers
note that self is used to access to the instance of the class that calls the method. Also, don't forget to print the results:
game = lottery()
print game.list()
Note:
Don't use list as the name of a variable or method because it will hide the built-in definition of list.

Categories

Resources