Python, sympy calculating multivariable function - python

Let us assume that we have two lists:
variables = ['a','b','c'...]
values = [1,2,3...]
and a simple product function:
function = 'a*b*c*...*z'
I tried to do this way (I do realize it is 100% incorrect, but I have no idea how to substitute multiple variables into a sympified expression):
import sympy
y = sympy.sympify(function).evalf(subs={variable:values})

Here's a complete example. You just need zip to mix the two lists. From the documentation :
To perform multiple substitutions at once, pass a list of (old, new)
pairs to subs.
from sympy import sympify
variables = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
values = range(1, len(variables) + 1)
expression = '*'.join(variables)
sub_table = zip(variables, values)
print values
# [1, 2, 3, 4, 5, 6, 7]
print expression
# a*b*c*d*e*f*g
print sub_table
# [('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5), ('f', 6), ('g', 7)]
print sympify(expression).subs(sub_table)
# 5040
As a bonus :
you don't need to build an extra dict
you get an exact integer as output from subs(), and not just a float from evalf().

Related

Combine lists of tuples by pairs into specific length of new list of tuples

I am newish to Python. I'm trying to combine two lists of tuples pairwise into a single list of tuples, where the tuples are of defined length (let's say 8):
For example,
input:
x = [(0,1,2,3),(4,5,6,7),(8,9,10,11)]
y = [('a','b','c','d'),('e','f','g','h'),('i','j','k','l')]
output:
[('a', 0, 'b', 1, 'c', 2, 'd', 3),
('e', 4, 'f', 5,'g', 6, 'h', 7),
('i', 8, 'j', 9, 'k', 10, 'l', 11)]
I've tried a few different loops that attempt to concatenate the pairwise combination tuples and then add them for a given length, but no luck. See below.
new = []
for n in range(len(x)):
for p in range(len(x[n])):
if p == len(x[n])-1:
new += [(x[n][p],y[n][p])]
for v in range(len(x[n])):
newer += new[v]
else:
new += [(x[n][p],y[n][p])]
The above 'newer' list is not useful, but the 'new' list provides the pairwise combination of tuples that I'm looking for, like I believe merge() would do, at least.
[('a', 0),('b', 1),('c', 2),('d', 3),('e', 4),('f', 5),('g', 6),('h', 7),('i', 8) ('j', 9),('k', 10), ('l', 11)]
I was thinking I could make a sort of window that read across the desired length (in this case four) and concatenated the selection, but have having trouble getting that to work.
Any other solutions are welcome.
Using a buffer:
b = [None] * 8
[tuple(b) for b[::2], b[1::2] in zip(y, x)]
Start by zipping your sub-lists into lists of matched pairs:
pair_list = [list(zip(a, b)) for a, b in zip(x, y) ]
Result:
[[(0, 'a'), (1, 'b'), (2, 'c'), (4, 'd')],
[(5, 'e'), (6, 'f'), (7, 'g'), (8, 'h')],
[(9, 'i'), (10, 'j'), (11, 'k'), (12, 'l')]]
Now, simply flatten the inner lists. You can look up simple flattening as an "exercise for the student", okay?
(Posted solution on behalf of the question author to move it to the answer space).
I ended up getting something to work:
final_list = []
for outer in range(len(a)):
for ele in range(len(a[outer])):
if ele == 0:
slice_start = 0
else:
slice_start += len(b[ele-1])
slice_end = len(b[ele])+slice_start
capture = [target for sublist in a[slice_start:slice_end] for target in sublist]
final_list.append(capture)
final_list = final_list[:len(a)]
Definitely not as beautiful as heap_overflow's answer.

Count number of pairs in list disregarding order

In example, if I have the following script:
import collections
lst = [['a','b'],['b','a'],['c','d'],['c','d'],['d','c']]
print([(a, b, v) for (a, b),v in collections.Counter(map(tuple,lst)).items()])
I get as output:
[('a', 'b', 1), ('b', 'a', 1), ('c', 'd', 2), ('d', 'c', 1)]
Can I adapt my code to yield the following output:
[('a', 'b', 2), ('c', 'd', 3)]
So a function that doesn't include the order of the pairs?
Use a data structure that doesn't care about order. In this case you'll need frozenset instead of a regular set because Counter requires it to be hashable. But basically it's a simple substitution of tuple in your original code for frozenset:
print([(a, b, v) for (a, b),v in collections.Counter(map(frozenset,lst)).items()])
Output:
[('a', 'b', 2), ('d', 'c', 3)]
You could just sort each element in the list before counting, like so:
import collections
lst = [['a','b'],['b','a'],['c','d'],['c','d'],['d','c']]
sorted_lst = [sorted(x) for x in lst]
print([(a, b, v) for (a, b),v in collections.Counter(map(tuple,sorted_lst)).items()])
Output:
[('a', 'b', 2), ('c', 'd', 3)]
Sorting the list before you get collections of it solves the problem.
import collections
lst = [['a','b'],['b','a'],['c','d'],['c','d'],['d','c']]
sort_list = sorted(x) for x in lst
print([(a, b, v) for (a, b),v in collections.Counter(map(tuple,sort_list)).items()])
You could sort the values of the key a,b and use groupby in itertools and then sum all the elements in the group.
import itertools as it
lst = [['a','b'],['b','a'],['c','d'],['c','d'],['d','c']]
output = [(*group,sum(1 for i in elements)) for group,elements in it.groupby(lst,key=lambda x:sorted(x))]
print(output)
OUTPUT
[('a', 'b', 2), ('c', 'd', 3)]

Generate iterator object with tuples of varying size

I am trying to create a branch and bound algorithm, to do this I would like to create an iterator object which stores all possible combinations of a list of items of size 0 to n.
Take the following example to demonstrate:
import itertools as it
list_tmp = ['a', 'b', 'c', 'd']
tmp_it = sum([list(map(list, it.combinations(list_tmp, i))) for i in range(2 + 1)], [])
tmp_it is a list of all possible combinations of size 0 to 2. This code works perfectly for small list sizes, but I need to act on a larger list and so would like to preserve
the iterator characteristics of the it.combinations object (generate the combinations on the fly). e.g.
for iteration in it.combinations(list_tmp, 2):
print(iteration)
Is there any method of doing this for combinations of multiple sizes? Rather than converting to a list and losing the characteristics of the iterator object.
You can do this using itertools.chain.from_iterable, which lazily evaluates its argument. Something like this:
tmp_it = it.chain.from_iterable(it.combinations(list_tmp, i) for i in range(2+1)))
You can chain iterators:
>>> sizes = it.chain.from_iterable(it.combinations(list_tmp, i) for i in range(len(list_tmp)))
>>> for i in sizes:
... print(i)
...
()
('a',)
('b',)
('c',)
('d',)
('a', 'b')
('a', 'c')
('a', 'd')
('b', 'c')
('b', 'd')
('c', 'd')
('a', 'b', 'c')
('a', 'b', 'd')
('a', 'c', 'd')
('b', 'c', 'd')

Combine two elements from different lists into a coordinate tuple

So I have these tuples in two lists in another tuple which look like
data_tuple = ([(1,2,3),(4,5,6)(7,8,9)],[(a,b,c),(d,e,f)(g,h,i)])
I want to build a set of coordinates using the corresponding indexed elements from each list, so that it looks like
final = [(2,b),(5,e),(8,h)]
Here's what I got:
for a in data_tuple[0]:
x = a[1]
for b in data_tuple[1]:
y = b[1]
print(x,y)
For now I just wanna check if my iteration/indentation is right so I don't need to put them in a list just yet. Right now the outcome for this particular code is
(2,b)
(2,e)
(2,h)
(5,b)
(5,e)
and so on until it reaches (8,h).
If I bring my print line to the left, under the second for loop, I get
(2,h)
(5,h)
(8,h)
How do I fix this? Sorry if I don't make any sense =/. As you can tell I'm extremely new to Python so I'm not familiar with a lot of import modules. Any help please?
>>> print [(i[0][1],i[1][1]) for i in zip(*data_tuple)]
[(2, 'b'), (5, 'e'), (8, 'h')]
but often clarity is more important than brevity
I dont really understand your original question since it looks like its working as intended that implies your indentation and iteration is fine..
[edit] this is what you want i think
for i in range(len(data_tuple[0]):
x,y = data_tuple[0][i][1],data_tuple[1][i][1],
print (x,y)
or
>>> for numbr,letr in zip(*data_tuple):
... x,y = numbr[1],letr[1]
... print(x,y)
...
Assuming you always want the second element:
[ (a, b) for ((_,a,_), (_,b,_)) in zip(*data_tuple) ]
Note that this produces the result you indicated, but isn't the same as your algorithm, which would produce the cartesian product. This would be achieved thus:
[ (a, b)
for (_,a,_) in data_tuple[0]
for (_,b,_) in data_tuple[1] ]
Assuming you always want the second element:
[ (a, b) for ((_,a,_), (_,b,_)) in zip(*data_tuple) ]
Note that this produces the result you indicated, but isn't the same as your algorithm, which would produce the cartesian product. This would be achieved thus:
[ (a, b)
for (_,a,_) in data_tuple[0]
for (_,b,_) in data_tuple[1] ]
EDIT: In response to your comment, here's the session I ran:
>>> [ (a, b)
... for (_,a,_) in data_tuple[0]
... for (_,b,_) in data_tuple[1] ]
[(2, 'b'), (2, 'e'), (2, 'h'), (5, 'b'), (5, 'e'), (5, 'h'), (8, 'b'), (8, 'e'), (8, 'h')]
This is the cartesian product, as per your original code.
Based on your amended question, however, I'm pretty sure you just want the first code I presented:
>>> [ (a, b) for ((_,a,_), (_,b,_)) in zip(*data_tuple) ]
[(2, 'b'), (5, 'e'), (8, 'h')]
Something like this?
>>> data_tuple = ([(1,2,3),(4,5,6),(7,8,9)],[("a","b","c"),("d","e","f"),("g","h","i")])
>>> full_numbers_list, full_letters_list = data_tuple
>>> desired_output_idx = (1,)
>>>
>>> results = []
>>> for numbers, letters in zip(full_numbers_list, full_letters_list):
... assert len(numbers) == len(letters) # make sure assumption is met
... for i in range(len(numbers)):
... if i in desired_output_idx:
... results.append((numbers[i], letters[i]))
...
>>> results
[(2, 'b'), (5, 'e'), (8, 'h')]
In place of using the steps of unpacking data_tuple you can use *data_tuple which will unpack your tuple for input to the zip() function.
>>> zip(*data_tuple)
[((1, 2, 3), ('a', 'b', 'c')), ((4, 5, 6), ('d', 'e', 'f')), ((7, 8, 9), ('g', 'h', 'i'))]
>>>
>>> # *data_tuple is the same as:
>>> numbers, letters = data_tuple
>>> zip(numbers, letters)
[((1, 2, 3), ('a', 'b', 'c')), ((4, 5, 6), ('d', 'e', 'f')), ((7, 8, 9), ('g', 'h', 'i'))]

Multiple Tuple to Two-Pair Tuple in Python?

What is the nicest way of splitting this:
tuple = ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h')
into this:
tuples = [('a', 'b'), ('c', 'd'), ('e', 'f'), ('g', 'h')]
Assuming that the input always has an even number of values.
zip() is your friend:
t = ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h')
zip(t[::2], t[1::2])
[(tuple[a], tuple[a+1]) for a in range(0,len(tuple),2)]
Or, using itertools (see the recipe for grouper):
from itertools import izip
def group2(iterable):
args = [iter(iterable)] * 2
return izip(*args)
tuples = [ab for ab in group2(tuple)]
I present this code based on Peter Hoffmann's answer as a response to dfa's comment.
It is guaranteed to work whether or not your tuple has an even number of elements.
[(tup[i], tup[i+1]) for i in range(0, (len(tup)/2)*2, 2)]
The (len(tup)/2)*2 range parameter calculates the highest even number less or equal to the length of the tuple so it is guaranteed to work whether or not the tuple has an even number of elements.
The result of the method is going to be a list. This can be converted to tuples using the tuple() function.
Sample:
def inPairs(tup):
return [(tup[i], tup[i+1]) for i in range(0, (len(tup)/2)*2, 2)]
# odd number of elements
print("Odd Set")
odd = range(5)
print(odd)
po = inPairs(odd)
print(po)
# even number of elements
print("Even Set")
even = range(4)
print(even)
pe = inPairs(even)
print(pe)
Output
Odd Set
[0, 1, 2, 3, 4]
[(0, 1), (2, 3)]
Even Set
[0, 1, 2, 3]
[(0, 1), (2, 3)]
Here's a general recipe for any-size chunk, if it might not always be 2:
def chunk(seq, n):
return [seq[i:i+n] for i in range(0, len(seq), n)]
chunks= chunk(tuples, 2)
Or, if you enjoy iterators:
def iterchunk(iterable, n):
it= iter(iterable)
while True:
chunk= []
try:
for i in range(n):
chunk.append(it.next())
except StopIteration:
break
finally:
if len(chunk)!=0:
yield tuple(chunk)

Categories

Resources