How to represent multiple level combinations? - python

The book Introduction to Probability by Blitzstein and Hwang provides an example of combinations using ice cream.
The first level is cone: Waffle or Cake
The second level is flavour: chocolate, vanilla or strawberry
The basic example is 2 * 3 = 6 separate choices.
I can represent each level of choice separately:
from sympy.functions.combinatorial.numbers import nC
from sympy.utilities.iterables import combinations, combinations_with_replacement
cones = combinations('CW', 1)
list(cones)
>>> [('C',), ('W',)]
flavours = combinations('cvs', 1)
list(flavours)
>>> [('c',), ('v',), ('s',)]
# how to get a list representing all choices? (Cc, Cv, Cs, Wc, Wv, Ws)
# how to return a count of the choices, e.g. with nC()?
I was wondering if it is possible to combine the levels with sympy and return a list of each combination and a count of the available combinations?

What you need is cartes
from sympy.utilities.iterables import cartes
print list(cartes('CW', 'csv'))
# >>> [('C', 'c'), ('C', 's'), ('C', 'v'), ('W', 'c'), ('W', 's'), ('W', 'v')]
print [''.join(x) for x in list(cartes('CW', 'csv'))]
# >>> ['Cc', 'Cs', 'Cv', 'Wc', 'Ws', 'Wv']
print len(list(cartes('CW', 'csv')))
# >>> 6

What you are doing is a set multiplication. eg., {A,B}*{1,2} -> {{A,1}, {A,2}, {B,1}, {B,2}). In python you do this with itertools.product:
from itertools import product
allChoices = set(product(set('CW'), set('csv')))
allChoicesPretty = set(a+b for a, b in allChoices)
numberOfChoices = len(allChoices)
print(allChoices)
print(allChoicesPretty)
print(numberOfChoices)
output:
{('C', 'v'), ('W', 's'), ('W', 'c'), ('C', 'c'), ('C', 's'), ('W', 'v')}
{'Wv', 'Ws', 'Cs', 'Wc', 'Cc', 'Cv'}
6
Actually you don't need sympyat all for this, furthermore cartes is actually an alias to iterable.product [1]
Comments
In a set each element happens only once, and there is no order in set. If you need one of these or both, replace set by list and {} by []. This might matter for instance in probability when you pick an object from a bag and put it back. But operations on sets are faster. A way to have multiple times the "same event" while using sets, is to add a tag, such as a number eg., A,A,A -> A1,A2,A3. This is very practical to think this way, because it's usually simpler to think about events (or compute probabilities) with tags and then remove the tags (equivalent to say "the order doesn't matter") when we do probabilities.
It's also related to the fact that in mathematics we can express (interpret, build) everything in set theory [2], which is actually needs few deductive elements of proof theory [4]; which is a way to build the foundations of mathematics (ZFC := Zermelo Fraenkel Choice). All mathematical proof happens inside ZFC, but it's almost never mentionned in the proof.
There are other ways to interpret all mathematics, such as category theory [3], which is closely related to computing langages. The third possible foundation I know is homotopy type theory [5,6], with which to date we can do less many things because the field is very new, but what we can do is nore natural and is incredibly interesting conceptually.
[1] https://github.com/sympy/sympy/blob/da9fdef5e00f40dfd500bfa356c61ce6bad1b559/sympy/utilities/iterables.py#L6
[2] https://en.wikipedia.org/wiki/Set_theory
[3] https://en.wikipedia.org/wiki/Category_theory
[4] https://en.wikipedia.org/wiki/Proof_theory
[5] https://en.wikipedia.org/wiki/Homotopy_type_theory
[6] https://homotopytypetheory.org/book/

Related

Python how do i find second connections?

A's connections are B, C, D, and E. A's second connections (the people who are connected to A's connections but not directly to A) are F and G. H is the third connection of A and should not be returned. A's get_second_connections(self) method should return a set containing F and G. Can you give me some hint for this question?
I have all connection in a set, and init method gets the name and connection. The add connection method store name in set. How can I find get_second_connection?
connections = sets
def add_connection(self, other):
self.connections.add(other)
I'll keep this at a low level of technology; you can stuff this into a comprehension if you like.
Iterate over the list of first-order connections. For each connection, get their first-order connections and add them to the current second-order set -- which I've called friend_of_friend.
def second_connections(self):
for other in self.connections:
self.friend_of_friend.union(other.connections)
Since I can't see your class I'm just going to implement the solution without integrating it into a class. I'm also only going to describe the connections as below, since you didn't share your data structure.
sets = set([("A", "B"),("A", "D"),("A", "C"),("A", "E"),("B", "F"),("D", "G"), ("F","H")])
def up_to_second(name, sets):
connectables = [name] + [x[1] for x in sets if x[0] == name]
return [x for x in sets if x[0] in connectables]
up_to_second("A", sets)
This gives your expected outcome:
[('A', 'B'), ('A', 'D'), ('A', 'C'), ('A', 'E'), ('D', 'G'), ('B', 'F')]
Assuming your data structure is at least a bit different from this, you'll need to tweak this solution to conform to your patterns. But as best I can tell this is a solution.

Python: from list to enumerated list to pass to lambda reduce function

I am trying to pass a list of hex char, into a lambda function, reduce to calculate a total decimal value. I am not sure what I am doing wrong but the python interpreter wouldn't recognize list(enumerate(reversed(numList)) as a list of tuples.
numList = ['3', '0', 'e', 'f', 'e', '1']
reduce(lambda sum,(up,x):sum+ int(x,16)*16**up,
enumerate(reversed(numList)))
when I print out
list(enumerate(reversed(numList))
It is a list of tuples.
[(0, '1'), (1, 'e'), (2, 'f'), (3, 'e'), (4, '0'), (5, '3')]
But it spit our error: can only concatenate tuple (not "int") to tuple
UPDATE:
The code is now working with a minor addition ",0" added to the lambda
reduce(lambda sum,(up,x):sum+ int(x,16)*16**up,
list(enumerate(reversed(numList))),0)
I don't understand what that means. Also I am not sure what is the best way to approach this.
that means you make sure, that it starts with 0 instead of the first Argument - in this case (0,'1') - because otherwise the types dont match? – am2 1 min ago
.
the third argument you add is initializer. without it, the sum in first iteration will be (0,'1'). so you were trying to evaluate (0,'1')+int(x,16)*16**up which is invalid. – ymonad 14 mins ago
UPDATE 2:
reduce(lambda sum,(up,x):sum+ int(x,16)*16**up,enumerate(reversed(numList)),0)
is just as good and enumerate() returns iter and list(enumerate...) is redundant.
Marked it as solved.
You don't need to use the generic reduce function when all you really need is to calculate the sum.
This works and is vastly simpler:
sum( int(x,16)*16**up for up,x in enumerate(reversed(numList)) )
Also, I'm going to guess you already know you can do the exact same thing like this:
int(''.join(numList), 16)

Make a copy of a set and exclude one item

Im trying to make a set based in another set, and exclude only one item...
(do a for loop inside another for loop with an object that is inside a set, but not iterate with itself on the second loop)
Code:
for animal in self.animals:
self.exclude_animal = set((animal,))
self.new_animals = set(self.animals)
self.new_animals.discard(self.exclude_animal) # parameters for a set needs to be a set?
for other_animal in (self.new_animals):
if animal.pos[0] == other_animal.pos[0]:
if animal.pos[1] == other_animal.pos[1]:
self.animals_to_die.add(animal)
print other_animal
print animal
self.animals_to_die.add(other_animal)
Point is, my print statement returns the object id(x), so I know that they are the same object, but they should not be, I discard it on that set new_animals set.
Any insight in why this doesn't exclude the item?
set.discard() removes one item from the set, but you pass in a whole set object.
You need to remove the element itself, not another set with the element inside:
self.new_animals.discard(animal)
Demo:
>>> someset = {1, 2, 3}
>>> someset.discard({1})
>>> someset.discard(2)
>>> someset
set([1, 3])
Note how 2 was removed, but 1 remained in the set.
It would be easier to just loop over the set difference here:
for animal in self.animals:
for other_animal in set(self.animals).difference([animal]):
if animal.pos == other_animal.pos:
self.animals_to_die.add(animal)
print other_animal
print animal
self.animals_to_die.add(other_animal)
(where I assume that .pos is a tuple of two values, you can just test for direct equality here).
You don't need to store new_animals on self all the time; using local names suffices and is not even needed here.
As you mark both animals to die, you don't need to compare A to B and also B to A (which your current code does). You can ensure you get only unique pairs of animals by using itertools.combinations():
for animal, other_animal in itertools.combinations(self.animals, 2):
if animal.pos==other_animal.pos:
self.animals_to_die.update([animal, other_animal])
Just for fun, I'll point out you can even do it as a single expression (though I think it reads better as an explicit loop):
self.animals_to_die.update(sum(((animal,other_animal)
for animal,other_animal in itertools.combinations(self.animals, 2)
if animal.pos == other_animal.pos), ()))
For clarity, itertools.combinations() gives you all the unique combinations of its input. The second argument controls how many elements are selected each time:
>>> list(itertools.combinations(["A", "B", "C", "D"], 2))
[('A', 'B'), ('A', 'C'), ('A', 'D'), ('B', 'C'), ('B', 'D'), ('C', 'D')]
>>> list(itertools.combinations(["A", "B", "C", "D"], 3))
[('A', 'B', 'C'), ('A', 'B', 'D'), ('A', 'C', 'D'), ('B', 'C', 'D')]
That works well in this case as the code given appears to be symmetric (two animals on the same location mutually annihilate each other). If it had been asymmetric (one kills the other) then you would have to remember to test both ways round on each iteration.

Python - input from list of tuples

I've declared a list of tuples that I would like to manipulate. I have a function that returns an option from the user. I would like to see if the user has entered any one of the keys 'A', 'W', 'K'. With a dictionary, I would say this: while option not in author.items() option = get_option(). How can I accomplish this with a list of tuples?
authors = [('A', "Aho"), ('W', "Weinberger"), ('K', "Kernighan")]
authors = [('A', "Aho"), ('W', "Weinberger"), ('K', "Kernighan")]
option = get_option()
while option not in (x[0] for x in authors):
option = get_option()
How this works :
(x[0] for x in authors) is an generator expression, this yield the [0]th element of each item one by one from authors list, and that element is then matched against the option. As soon as match is found it short-circuits and exits.
Generator expressions yield one item at a time, so are memory efficient.
How about something like
option in zip(*authors)[0]
We are using zip to essentially separate the letters from the words. Nevertheless, since we are dealing with a list of tuples, we must unpack it using *:
>>> zip(*authors)
[('A', 'W', 'K'), ('Aho', 'Weinberger', 'Kernighan')]
>>> zip(*authors)[0]
('A', 'W', 'K')
Then we simply use option in to test if option is contained in zip(*authors)[0].
There are good answers here that cover doing this operation with zip, but you don't have to do it like that - you can use an OrderedDict instead.
from collections import OrderedDict
authors = OrderedDict([('A', "Aho"), ('W', "Weinberger"), ('K', "Kernighan")])
Since it remembers its entry order, you can iterate over it without fear of getting odd or unusual orderings of your keys.

Is there a 'multimap' implementation in Python?

I am new to Python, and I am familiar with implementations of Multimaps in other languages. Does Python have such a data structure built-in, or available in a commonly-used library?
To illustrate what I mean by "multimap":
a = multidict()
a[1] = 'a'
a[1] = 'b'
a[2] = 'c'
print(a[1]) # prints: ['a', 'b']
print(a[2]) # prints: ['c']
Such a thing is not present in the standard library. You can use a defaultdict though:
>>> from collections import defaultdict
>>> md = defaultdict(list)
>>> md[1].append('a')
>>> md[1].append('b')
>>> md[2].append('c')
>>> md[1]
['a', 'b']
>>> md[2]
['c']
(Instead of list you may want to use set, in which case you'd call .add instead of .append.)
As an aside: look at these two lines you wrote:
a[1] = 'a'
a[1] = 'b'
This seems to indicate that you want the expression a[1] to be equal to two distinct values. This is not possible with dictionaries because their keys are unique and each of them is associated with a single value. What you can do, however, is extract all values inside the list associated with a given key, one by one. You can use iter followed by successive calls to next for that. Or you can just use two loops:
>>> for k, v in md.items():
... for w in v:
... print("md[%d] = '%s'" % (k, w))
...
md[1] = 'a'
md[1] = 'b'
md[2] = 'c'
Just for future visitors. Currently there is a python implementation of Multimap. It's available via pypi
Stephan202 has the right answer, use defaultdict. But if you want something with the interface of C++ STL multimap and much worse performance, you can do this:
multimap = []
multimap.append( (3,'a') )
multimap.append( (2,'x') )
multimap.append( (3,'b') )
multimap.sort()
Now when you iterate through multimap, you'll get pairs like you would in a std::multimap. Unfortunately, that means your loop code will start to look as ugly as C++.
def multimap_iter(multimap,minkey,maxkey=None):
maxkey = minkey if (maxkey is None) else maxkey
for k,v in multimap:
if k<minkey: continue
if k>maxkey: break
yield k,v
# this will print 'a','b'
for k,v in multimap_iter(multimap,3,3):
print v
In summary, defaultdict is really cool and leverages the power of python and you should use it.
You can take list of tuples and than can sort them as if it was a multimap.
listAsMultimap=[]
Let's append some elements (tuples):
listAsMultimap.append((1,'a'))
listAsMultimap.append((2,'c'))
listAsMultimap.append((3,'d'))
listAsMultimap.append((2,'b'))
listAsMultimap.append((5,'e'))
listAsMultimap.append((4,'d'))
Now sort it.
listAsMultimap=sorted(listAsMultimap)
After printing it you will get:
[(1, 'a'), (2, 'b'), (2, 'c'), (3, 'd'), (4, 'd'), (5, 'e')]
That means it is working as a Multimap!
Please note that like multimap here values are also sorted in ascending order if the keys are the same (for key=2, 'b' comes before 'c' although we didn't append them in this order.)
If you want to get them in descending order just change the sorted() function like this:
listAsMultimap=sorted(listAsMultimap,reverse=True)
And after you will get output like this:
[(5, 'e'), (4, 'd'), (3, 'd'), (2, 'c'), (2, 'b'), (1, 'a')]
Similarly here values are in descending order if the keys are the same.
The standard way to write this in Python is with a dict whose elements are each a list or set. As stephan202 says, you can somewhat automate this with a defaultdict, but you don't have to.
In other words I would translate your code to
a = dict()
a[1] = ['a', 'b']
a[2] = ['c']
print(a[1]) # prints: ['a', 'b']
print(a[2]) # prints: ['c']
Or subclass dict:
class Multimap(dict):
def __setitem__(self, key, value):
if key not in self:
dict.__setitem__(self, key, [value]) # call super method to avoid recursion
else
self[key].append(value)
There is no multi-map in the Python standard libs currently.
WebOb has a MultiDict class used to represent HTML form values, and it is used by a few Python Web frameworks, so the implementation is battle tested.
Werkzeug also has a MultiDict class, and for the same reason.

Categories

Resources