if a function can be created based on different input information? - python

I have the following question, I am wondering if there is a solution or not.
Because I just learned the function can be a variable in python, I am wondering if it is possible to design a function which create different function based on the input.
let's say, we have a long list of chars:
longlist = abcdefghijklmnopqrstuvwxyz
Given the an test list, test = [1,2,3]
An function (func1) can read the test list as input and return a function (func2) as output.
This function can be used to separate the long list of chars into different group and print out
a,bc,def,g,hi,jkl,o,pq.... which follow the test list 1,2,3 pattern and repeat again.
if the test list is test = [1,2,3,4]
then func1(test) --> func2
func2(longlist) prints out a,bc,def,ghij,k,lm,n
In this case, it follows the 1,2,3,4,1,2... pattern
The example I made looks not so interesting, but the fundamental question is if the function can be created based on different input information?

Yes. This is called a closure. The inner function (func2) keeps the scope that it was defined in. Try this:
def func1(x):
def func2(y):
ret = []
for f in x * len(y):
ret += [y[:f]]
y = y[f:]
if not y:
return ret
return func2
print(func1([1, 2, 3, 4])('This should do what you want'))

You can define a function within the first function and return it later. Function 1 can be used to set up params, etc. Below is an implementation of your particular question.
def make_func(params):
# Params must be a list of integers
def split_string_in_pattern(string):
res = []
pattern_index = 0
while True:
res.append(string[:params[pattern_index]])
print(res)
string = string[params[pattern_index]:]
print(string)
if not string:
return res
if pattern_index >= len(params) - 1:
pattern_index = 0
else:
pattern_index += 1
return split_string_in_pattern
""" Test """
long_string = 'asdqweasdasdacasdasdadas'
split_func = make_func([1,2,3,4])
split_func(long_string)

from itertools import permutations, combinations
# Here you can use which one is more suited for your situation: permutations or combinations
def func1(test):
def func2(longlist):
result = []
for t in test:
perms = permutations(longlist, t)
result += perms
result = [''.join(t) for t in result]
return result
return func2
f2 = func1([1, 2])
print(f2('abc'))
You get
['a', 'b', 'c', 'ab', 'ac', 'ba', 'bc', 'ca', 'cb'] if you used permutations
['a', 'b', 'c', 'ab', 'ac', 'bc'] if you used combinations

Related

Python list comprehension - generating multiple data

import random
M = 4
N = 3
def generisanjevol1(nekalista, m):
return random.choices(nekalista, k=m)
def generisanjevol2(nekalista, m,n):
#obj = [[random.choice(nekalista,k=m)] for i in range(N)]]
obj = [[random.choice(nekalista)] for i in range(n)]
return obj
#def poredjenje()
listaslova = ['A', 'B', 'C', 'D', 'E']
lista = generisanjevol1(listaslova, M)
lista2 = generisanjevol2(listaslova, M, N)
print(lista)
print(lista2)
So above is my try (generisanjevol2(nekalista, m,n)...
What I am trying to do is next:
I want to generate N of arrays and fill them with strings which are generated by random.choice function and they still must be strings from listaslova)
Perhaps let's say N=3 (N represents numbers of arrays) and M=4 (M represents length of array) I should get something like this (doesn't have to be same data in arrays, because of course they are randomly generated):
[A,C,D,E]
([A,C,E,D] [E,C,B,A] [E,D,D,A])
But the results which I get are following:
[A,D,E,C]
[[B],[D],[E]]
P.S If I try the one which is commented I get an error
The error in your commented line is because you have an extra ]. And random.choice should be random.choices.
But you also shouldn't put another list around the call to random.choice(). It already returns a list.
def generisanjevol2(nekalista, m,n):
obj = [random.choices(nekalista,k=m) for i in range(n)]
return obj
Like #Barmar said, you indeed have an extra [].
Your function should look:
def generisanjevol2(nekalista, m,n):
...: obj = [random.choices(nekalista, k=m) for i in range(n)]
...: return obj

How to build a list recursively given only the length?

I am trying to write a recursive function that takes a number n and some value, let's say 'a', and recursively builds a list made up of n 'a's.
So, func(4, 'a') returns ['a','a','a','a'].
My internet searches so far have been surprisingly futile. Most examples of list recursion I have found take additional parameters.
I have tried various versions of the following code:
def func(n, a):
if n == 1:
return [a]
else:
return func(n-1, a).append(a)
I keep getting cannot append to NoneType errors on that last return. So func(n-1,a) is returning None.
list.append only returns None as its purpose is to update the target list in-place. Instead of appending, simply add:
def func(n, a):
if n == 1:
return [a]
else:
return func(n-1, a) + [a]
print(func(4, 'a'))
Output:
['a', 'a', 'a', 'a']
Additionally, you may want to consider using generators to provide a cleaner (and shorter) result:
def func(n, a):
if n:
yield a
yield from func(n-1, a)
print(list(func(4, 'a')))
Output:
['a', 'a', 'a', 'a']
Because the list.append method returns None you have to do it this way if you want to use the append method:
def func(n, a):
if n == 1:
return [a]
else:
temp = func(n-1, a)
temp.append(a)
return temp
print(func(5, 'a'))
Prints:
['a', 'a', 'a', 'a', 'a']
Does it have to be recursive?
def func(n, a):
a_list = []
for i in range(n):
a_list.append(str(a))
return a_list

A basic filter is returning an empty list

def only_upper(t):
res = []
for s in t:
if s.isupper():
res.append(s)
return res
t = ['a', 'B', 'c', 'D']
print only_upper(t)
I'm trying to figure out why this is returning an empty list. The result looks like this in the console:
[]
You return immediately after the first element of the for loop, instead of after it complete. Unindent your return statement and you should be fine:
def only_upper(t):
res = []
for s in t:
if s.isupper():
res.append(s)
return res # Here!
Also worth mentioning that it's often easier to write filters like this in list-comprehensions instead of implementing the entire logic:
print([x for x in t if x.isupper()])
As alternative to the above list comprehension is built-in filter function (in case it is even further in your book):
list(filter(lambda x: x.isupper(), ['a', 'B', 'c', 'D']))

How to split a list based on whether the elements were next to each other in the list they came from?

I'm going through Problem 3 of the MIT lead python course, and I have an admittedly long drawn out script that feels like it's getting close. I need to print the longest substring of s in which the letters occur in alphabetical order. I'm able to pull out any characters that are in alphabetical order with regards to the character next to it. What I need to see is:
Input : 'aezcbobobegghakl'
needed output: 'beggh'
my output: ['a', 'e', 'b', 'b', 'b', 'e', 'g', 'g', 'a', 'k']
My code:
s = 'aezcbobobegghakl'
a = 'abcdefghijklmnopqrstuvwxyz'
len_a = len(a)
len_s = len(s)
number_list = []
letter_list = []
for i in range(len(s)):
n = 0
letter = s[i+n]
if letter in a:
number_list.append(a.index(letter))
n += 1
print(number_list)
for i in number_list:
letter_list.append(a[i])
print(letter_list)
index_list = []
for i in range(len(letter_list)):
index_list.append(i)
print(index_list)
first_check = []
for i in range(len(letter_list)-1):
while number_list[i] <= number_list[i+1]:
print(letter_list[i])
first_check.append(letter_list[i])
break
print(first_check)
I know after looking that there are much shorter and completely different ways to solve the problem, but for the sake of my understanding, is it even possible to finish this code to get the output I'm looking for? Or is this just a lost cause rabbit hole I've dug?
I would build a generator to output all the runs of characters such that l[i] >= l[i-1]. Then find the longest of those runs. Something like
def runs(l):
it = iter(l)
try:
run = [next(it)]
except StopIteration:
return
for i in it:
if i >= run[-1]:
run.append(i)
else:
yield run
run = [i]
yield run
def longest_increasing(l):
return ''.join(max(runs(l), key=len))
Edit: Notes on your code
for i in range(len(s)):
n = 0
letter = s[i+n]
if letter in a:
number_list.append(a.index(letter))
n += 1
is getting the "number value" for each letter. You can use the ord function to simplify this
number_list = [ord(c) - 97 for c in s if c.islower()]
You never use index_list, and you never should. Look into the enumerate function.
first_check = []
for i in range(len(letter_list)-1):
while number_list[i] <= number_list[i+1]:
print(letter_list[i])
first_check.append(letter_list[i])
break
this part doesn't make a ton of sense. You break out of the while loop every time, so it's basically an if. You have no way of keeping track of more than one run. You have no mechanism here for comparing runs of characters against one another. I think you might be trying to do something like
max_run = []
for i in range(len(letter_list)-1):
run = []
for j in range(i, len(letter_list)):
run.append(letter_list[j])
if letter_list[j] > letter_list[j+1]:
break
if len(run) > len(max_run):
max_run = run
(Disclaimer: I'm pretty sure the above is off by one but it should be illustrative). The above can be improved in a lot of ways. Note that it loops over the last character as many as len(s) times, making it a n**2 solution. Also, I'm not sure why you need number_list, as strings can be compared directly.
What about a simple recursive approach :
data = 'ezcbobobegghakl'
words=list(data)
string_s=list(map(chr,range(97,123)))
final_=[]
def ok(list_1,list_2):
if not list_1:
return 0
else:
first = list_1[0]
chunks = list_2[list_2.index(first):]
track = []
for j, i in enumerate(list_1):
if i in chunks:
track.append(i)
chunks=list_2[list_2.index(i):]
else:
final_.append(track)
return ok(list_1[j:],list_2)
final_.append(track)
print(ok(words,string_s))
print(max(final_,key=lambda x:len(x)))
output:
['b', 'e', 'g', 'g', 'h']
You can find a list of all substrings of the input string, and then find all the strings that are sorted alphabetically. To determine of a letter is sorted alphabetically, sorted the original string by position in the alphabet, and then see if the final string equals the original string:
from string import ascii_lowercase as l
s = 'aezcbobobegghakl'
substrings = set(filter(lambda x:x, [s[i:b] for i in range(len(s)) for b in range(len(s))]))
final_substring = max([i for i in substrings if i == ''.join(sorted(list(i), key=lambda x:l.index(x)))], key=len)
Output:
'beggh'
This is one way of getting the job done:
s = 'aezcbobobegghakl'
l = list(s)
run = []
allrun = []
element = 'a'
for e in l:
if e >= element:
run.append(e)
element = e
else:
allrun.append(run)
run = [e]
element = e
lengths = [len(e) for e in allrun]
result = ''.join(allrun[lengths.index(max(lengths))])
"run" is basically an uninterrupted run; it keeps growing as you add elements bigger than what is previously seen ("b" is bigger than "a", just string comparison), and resets else.
"allrun" contains all "run"s, which looks like this:
[['a', 'e', 'z'], ['c'], ['b', 'o'], ['b', 'o'], ['b', 'e', 'g', 'g', 'h']]
"result" finally picks the longest "run" in "allrun", and merges it into one string.
Regarding your code:
It is very very inefficient, I would not proceed with it. I would adopt one of the posted solutions.
Your number_list can be written as [a.index(_) for _ in s], one liner.
Your letter_list is actually just list(s), and you are using a loop for that!
Your index_list, what does it even do? It is equivalent to range(len(letter_list)), so what are you aiming with the append in the loop?
Finally, the way you write loops reminds me of matlab. You can just iterate on the elements of a list, no need to iterate on index and fetch the corresponding element in list.

slice a list based on some values

Hi I'm looking for a way to split a list based on some values, and assuming the list's length equals to sum of some values, e.g.:
list: l = ['a','b','c','d','e','f']
values: v = (1,1,2,2)
so len(l) = sum(v)
and I'd like to have a function to return a tuple or a list, like: (['a'], ['b'], ['c','d'], ['d','e'])
currently my code is like:
(list1,list2,list3,list4) = (
l[0:v[0]],
l[v[0]:v[0]+v[1]],
l[v[0]+v[1]:v[0]+v[1]+v[2]],
l[v[0]+v[1]+v[2]:v[0]+v[1]+v[2]+v[3]])`
I'm thinking about make this clearer, but closest one I have so far is (note the results are incorrect, not what I wanted)
s=0
[list1,list2,list3,list4] = [l[s:s+i] for i in v]
the problem is I couldn't increase s at the same time while iterating values in v, I'm hoping to get a better code to do so, any suggestion is appreciated, thanks!
If you weren't stuck on ancient Python, I'd point you to itertools.accumulate. Of course, even on ancient Python, you could use the (roughly) equivalent code provided in the docs I linked to do it. Using either the Py3 code or equivalent, you could do:
from itertools import accumulate # Or copy accumulate equivalent Python code
from itertools import chain
# Calls could be inlined in listcomp, but easier to read here
starts = accumulate(chain((0,), v)) # Extra value from starts ignored when ends exhausted
ends = accumulate(v)
list1,list2,list3,list4 = [l[s:e] for s, e in zip(starts, ends)]
Maybe make a generator of the values in l?
def make_list(l, v):
g = (x for x in l)
if len(l) == sum(v):
return [[next(g) for _ in range(val)] for val in v]
return None
You could just write a simple loop to iterate over v to generate a result:
l = ['a','b','c','d','e','f']
v = (1,1,2,2)
result = []
offset = 0
for size in v:
result.append(l[offset:offset+size])
offset += size
print result
Output:
[['a'], ['b'], ['c', 'd'], ['e', 'f']]
The idea here is using a nested loop. Assuming that your condition will always holds true, the logic then is to run through v and pick up i elements from l where i is an number from v.
index = 0 # this is the start index
for num in v:
temp = [] # this is a temp array, to hold individual elements in your result array.
for j in range(index, index+num): # this loop will pickup the next num elements from l
temp.append(l[j])
data.append(temp)
index += num
Output:
[['a'], ['b'], ['c', 'd'], ['e', 'f']]
The first answer https://stackoverflow.com/a/39715361/5759063 is the most pythonic way to do it. This is just the algorithmic backbone.
Best I could find is a two line solution:
breaks=[0]+[sum(v[:i+1]) for i in range(len(v))] #build a list of section indices
result=[l[breaks[i]:breaks[i+1]] for i in range(len(breaks)-1)] #split array according to indices
print result

Categories