Tuple() vs () for Tuple Comprehension & Tuple Creation - python

I understand I can create a tuple by these two methods:
tuple([1, 2, 3])
(1, 2, 3)
I'm trying to understand what exactly is happening here:
tuple( (x, x*x) for x in [1, 2, 3] )
the tuple constructor seems to want a list, but it doesn't seem like the following would produce a list
(x, x*x) for x in [1, 2, 3]
Also I'm wondering why this doesn't work:
( (x, x*x) for x in [1, 2, 3] )
With dictionaries it seems I can use either:
my_dict = dict()
my_dict = {}
but now I'm wondering if, like tuples, there's a difference. Is ():tuple() a different :: relationship than {}:dict()?

Let's break down what's happening here. You're not wrong that the tuple constructor seems to want a list, but it would be more precise to say that the tuple constructor seems to want a list-like object. (to be specific, any iterable works)
This is a philosophy called as Duck Typing.
The saying goes as follows:
If it walks like a duck, swims like a duck, and quacks like a duck,
then it probably is a duck.
So then, a list works
a = [1, 2, 3, 2]
tuple(a) #Output: (1, 2, 3, 2)
but so does a different iterable, such as a set
tuple(set(a)) #Output: (1, 2, 3)
So, tuple does not care whether the object it receives is a list, just that it should be able to iterate and get values from the object.
Now, The second part of the magic comes from something called list comprehension/generator expressions. They are iterables you can create which make it easy to write 1 liners that create a list or a generator expression respectively. More on the generator later, for now, it is sufficient to see how a list comprehension works.
A simple example of list comprehension
[a for a in range(4)] #Output: [0, 1, 2, 3]
[a*a for a in range(4)] #output: [0, 1, 4, 9]
We see that they produce lists. So, can we feed them to a tuple constructor? Why not!
tuple([a for a in range(4)]) #Output: (0, 1, 2, 3)
tuple([a*a for a in range(4)]) #output: (0, 1, 4, 9)
Now, what about using the same expression but wrapping it in curved brackets instead?
(a for a in range(4)) #Output: <generator object <genexpr> at 0x000000FA4FDBE728>
You just created a generator expression
They are essentially memory efficient on-demand iterables. (to be jargon specific, they have a yield and next , and only yield values as needed). Let's see it in action.
my_generator = (a for a in range(4)) #generator created.
next(my_generator) #Outputs 0
next(my_generator) #Outputs 1
next(my_generator) #outputs 2
next(my_generator) #outputs 3
next(my_generator) #Raises StopIteration Error. The generator is exhausted.
We can see that we receive the same values as with our list comprehension.
So, does a tuple accept something like a generator? Well, duck typing to the rescue! Absolutely!
tuple((a for a in range(4))) #Output: (0, 1, 2, 3)
Do i need the redundant parenthesis? Nope!
tuple(a for a in range(4)) #Output: (0, 1, 2, 3)
tuple(a*a for a in range(4)) #Output: (0, 1, 4, 9)
Now, what does this produce? (x, x*x) for x in [1, 2, 3]
Well, it is an expression, but let's get an idea of how it would look in a list comprehension instead
[(x, x*x) for x in [1, 2, 3]] #Output: [(1, 1), (2, 4), (3, 9)]
Ah, its a list of tuples? Can a generator do the same?
my_generator = ((x, x*x) for x in [1, 2, 3]) #<generator object <genexpr> at 0x000000FA4FD2DCA8>
next(my_generator) #Output: (1, 1)
next(my_generator) #Output: (2, 4)
next(my_generator) #Output: (3, 9)
next(my_generator) #Raises StopIteration
Yep, looks good. So its a generator, but its an iterable. Behaves like a duck anyways, doesn't it? So, the tuple constructor should work just fine!
tuple((x, x*x) for x in [1, 2, 3]) #Output: ((1, 1), (2, 4), (3, 9))
So, that wraps up everything. The parenthesis do not imply a tuple all the time, () are not reserved for tuples. We see here that they can be used for generator expressions as well! Similarly, the {} do not have to be tied to dictionaries always, something similar to list comprehension actually exists for dictionaries too! (Known as dict comprehension)
I highly recommend going through the links for a more thorough explanation of the individual pieces that are working together here. Hope this helps!

Related

on the 'immutability' of tuples in python [duplicate]

This question already has answers here:
python tuple is immutable - so why can I add elements to it
(4 answers)
Closed 1 year ago.
If tuples are supposed to be immutable (which are in most cases), why can we change their data in the following way :
tuple = (1, 2, 3)
tuple += (4, 5)
print (tuple) #outputs (1, 2, 3, 4, 5)
You didn't mutate a tuple, you made a new one.
>>> tup = (1, 2, 3)
>>> tup2 = tup
>>> tup
(1, 2, 3)
>>> tup2
(1, 2, 3)
>>> tup += (4, 5)
>>> tup
(1, 2, 3, 4, 5)
>>> tup2
(1, 2, 3)
Initially, tup and tup2 refer to the same tuple. After the tup += (4, 5), tup refers to a new tuple and tup2 still refers to the old one. No mutation occurred.
Tuples are immutable which means you cannot update or change the values of tuple elements. You are able to take portions of existing tuples to create new tuples.
In your example, += sneakily stops being an in-place operator. tuple is a different tuple from when you started, because adding two tuples necessarily produces a new one, because tuples are immutable.

Behaviour of set that stores tuples, Python

I'd like to create a set of tuples, but have been encountering some behaviour that I don't understand. For example:
>>> b=set()
>>> b.add((1,2))
>>> b.add((4,5))
>>> b
{(1, 2), (4, 5)}
# all fine
>>> f = set((1,2,3))
>>> f
{1, 2, 3}
# ?
>>> b=set().add((1,2))
>>> b
# b is empty?
Can someone help me understand this behaviour? My understanding is that tuples are hashable, so I should be able to store them in a set. I'm using Python 3.
You are confusing different things.
set() takes an iterable and creates a set from the contents.
If you want to create a set with a single tuple, pass in an iterable with that single tuple:
>>> set([(1, 2, 3)])
{(1, 2, 3)}
It's better to use the {...} set literal syntax:
>>> {(1, 2, 3)}
{(1, 2, 3)}
set().add() updates a set in-place and returns None.
If you wanted to create an empty set and add to that, do so in two separate expressions:
>>> b = set()
>>> b.add((1, 2))
>>> b
{(1, 2)}
Neither of these two issues have anything to do with tuples being hashable.
set((1, 2, 3)) means a set of (1, 2, 3)'s elements. A set whose only element is (1, 2, 3) is {(1, 2, 3)}, not set((1, 2, 3)).
set.add modifies a set in place. If you want to add to a set, you need to store the set itself, then add to it, not store add's return value.

Python List indexing multiple ranges

Sorry if this has already been asked, I couldn't find it anywhere. Basically how do I get 2 separate ranges within a list in Python.
If I want the 1st, 2nd, 5th and 6th elements of a list I know I can do this,
l = range(0,15)
l[1:3]+l[5:7]
but this assumes that l is easy to write. However I am scrapping something from a webpage using BeautifulSoup4, so I'm using soup.find_all (which gives me a list), so I can't simply write out 2 lists, l and concatenate them.
I want an answer that is something like
l = range(0,15)
l[1:3,5:7]
(but of course without the error) :)
This might be what you want. itemgetter creates a function that retrieves the listed indices:
>>> import operator
>>> snip = operator.itemgetter(1,2,5,6)
>>> snip(range(15))
(1, 2, 5, 6)
>>> snip('abcdefg')
('b', 'c', 'f', 'g')
>>> snip([1,2,3,4,5,6,7,8])
(2, 3, 6, 7)
I would do this with a function:
def multi_range(l, *args):
output = []
for indices in args:
output += l[indices[0]:indices[1]]
return output
So the first argument would be the list, and the rest of the parameters are tuples with the indices you're looking to pull. It would work fine with a long list name:
long_list_name = range(0, 15)
print multi_range(long_list_name, (1, 3), (5, 7))
>>> [1, 2, 5, 6]
l = range(0, 15)
print([l[i] for i in [1,2, 5,6]])
Not sure why you think l[1:3]+l[5:7] is hard, find_all returns a normal python list like any other.
Or using map:
l = range(0, 15)
print(list(map(l.__getitem__,(1,2,5,6))))
Is this OK?
indices = [1, 2, 5, 6]
selected = [l[i] for i in indices]

List comprehension with tuple assignment

I want to ask if something like this is possible in python:
a,b = [i,i+1 for i in range(5)]
I know this isn't possible because I have got an error, but I think you understand what I am trying to achieve. Let me clear it up, I can do :
a,b = 3+2,3
Edit ---> Or even better:
a,b = [0,1,2,3,4],[1,2,3,4,5]
I wan't a similar thing in my first code example. I am trying to assign variables 'a' and 'b' as list, with list comprehension, but using tuple as assignment, the point is I don't want to use this:
a = [i for in range(5)]
b = [i+1 for in range(5)]
I am aware that I can use this: t = [(i,i+1) for i in range(5)], but that's not the point.
By the way this is only a simple example => "i,i+1"
Edit ---> I would like to clarify my question. How to assign several variables (type list) in one line, using list comprehension?
When you run this:
a,b = [(i,i+1) for i in range(5)] # wrapped i, i+1 in parentheses (syntax error)
It makes a list of five two-item tuples, like this:
[(0, 1), (1, 2), (2, 3), (3, 4), (4, 5)]
But you're trying to assign those five tuples to only two objects (a and b)
Using argument unpacking (*) in zip, you can "unzip" the output to the first and second elements of each tuple:
a,b = zip(*[(i,i+1) for i in range(5)])
Which is this:
[(0, 1, 2, 3, 4), (1, 2, 3, 4, 5)]
And can be assigned to a and b as you've written
Don't try to be clever. This is perfectly acceptable code:
>>> a = range(5)
>>> b = range(1,6)
>>> a, b
([0, 1, 2, 3, 4], [1, 2, 3, 4, 5])

How to call a pre-define function in python using map()? [duplicate]

The Python 2 documentation says:
Built-in Functions: map(function, iterable, ...)
Apply function to every item of iterable and return a list of the
results. If additional iterable arguments are passed, function must
take that many arguments and is applied to the items from all
iterables in parallel.
If one iterable is shorter than another it is assumed to be extended
with None items.
If function is None, the identity function is assumed; if there are
multiple arguments, map() returns a list consisting of tuples
containing the corresponding items from all iterables (a kind of
transpose operation).
The iterable arguments may be a sequence or any iterable object; the
result is always a list.
What role does this play in making a Cartesian product?
content = map(tuple, array)
What effect does putting a tuple anywhere in there have? I also noticed that without the map function the output is abc and with it, it's a, b, c.
I want to fully understand this function. The reference definitions is also hard to understand. Too much fancy fluff.
map isn't particularly pythonic. I would recommend using list comprehensions instead:
map(f, iterable)
is basically equivalent to:
[f(x) for x in iterable]
map on its own can't do a Cartesian product, because the length of its output list is always the same as its input list. You can trivially do a Cartesian product with a list comprehension though:
[(a, b) for a in iterable_a for b in iterable_b]
The syntax is a little confusing -- that's basically equivalent to:
result = []
for a in iterable_a:
for b in iterable_b:
result.append((a, b))
map doesn't relate to a Cartesian product at all, although I imagine someone well versed in functional programming could come up with some impossible to understand way of generating a one using map.
map in Python 3 is equivalent to this:
def map(func, iterable):
for i in iterable:
yield func(i)
and the only difference in Python 2 is that it will build up a full list of results to return all at once instead of yielding.
Although Python convention usually prefers list comprehensions (or generator expressions) to achieve the same result as a call to map, particularly if you're using a lambda expression as the first argument:
[func(i) for i in iterable]
As an example of what you asked for in the comments on the question - "turn a string into an array", by 'array' you probably want either a tuple or a list (both of them behave a little like arrays from other languages) -
>>> a = "hello, world"
>>> list(a)
['h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd']
>>> tuple(a)
('h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd')
A use of map here would be if you start with a list of strings instead of a single string - map can listify all of them individually:
>>> a = ["foo", "bar", "baz"]
>>> list(map(list, a))
[['f', 'o', 'o'], ['b', 'a', 'r'], ['b', 'a', 'z']]
Note that map(list, a) is equivalent in Python 2, but in Python 3 you need the list call if you want to do anything other than feed it into a for loop (or a processing function such as sum that only needs an iterable, and not a sequence). But also note again that a list comprehension is usually preferred:
>>> [list(b) for b in a]
[['f', 'o', 'o'], ['b', 'a', 'r'], ['b', 'a', 'z']]
map creates a new list by applying a function to every element of the source:
xs = [1, 2, 3]
# all of those are equivalent — the output is [2, 4, 6]
# 1. map
ys = map(lambda x: x * 2, xs)
# 2. list comprehension
ys = [x * 2 for x in xs]
# 3. explicit loop
ys = []
for x in xs:
ys.append(x * 2)
n-ary map is equivalent to zipping input iterables together and then applying the transformation function on every element of that intermediate zipped list. It's not a Cartesian product:
xs = [1, 2, 3]
ys = [2, 4, 6]
def f(x, y):
return (x * 2, y // 2)
# output: [(2, 1), (4, 2), (6, 3)]
# 1. map
zs = map(f, xs, ys)
# 2. list comp
zs = [f(x, y) for x, y in zip(xs, ys)]
# 3. explicit loop
zs = []
for x, y in zip(xs, ys):
zs.append(f(x, y))
I've used zip here, but map behaviour actually differs slightly when iterables aren't the same size — as noted in its documentation, it extends iterables to contain None.
Simplifying a bit, you can imagine map() doing something like this:
def mymap(func, lst):
result = []
for e in lst:
result.append(func(e))
return result
As you can see, it takes a function and a list, and returns a new list with the result of applying the function to each of the elements in the input list. I said "simplifying a bit" because in reality map() can process more than one iterable:
If additional iterable arguments are passed, function must take that many arguments and is applied to the items from all iterables in parallel. If one iterable is shorter than another it is assumed to be extended with None items.
For the second part in the question: What role does this play in making a Cartesian product? well, map() could be used for generating the cartesian product of a list like this:
lst = [1, 2, 3, 4, 5]
from operator import add
reduce(add, map(lambda i: map(lambda j: (i, j), lst), lst))
... But to tell the truth, using product() is a much simpler and natural way to solve the problem:
from itertools import product
list(product(lst, lst))
Either way, the result is the cartesian product of lst as defined above:
[(1, 1), (1, 2), (1, 3), (1, 4), (1, 5),
(2, 1), (2, 2), (2, 3), (2, 4), (2, 5),
(3, 1), (3, 2), (3, 3), (3, 4), (3, 5),
(4, 1), (4, 2), (4, 3), (4, 4), (4, 5),
(5, 1), (5, 2), (5, 3), (5, 4), (5, 5)]
The map() function is there to apply the same procedure to every item in an iterable data structure, like lists, generators, strings, and other stuff.
Let's look at an example:
map() can iterate over every item in a list and apply a function to each item, than it will return (give you back) the new list.
Imagine you have a function that takes a number, adds 1 to that number and returns it:
def add_one(num):
new_num = num + 1
return new_num
You also have a list of numbers:
my_list = [1, 3, 6, 7, 8, 10]
if you want to increment every number in the list, you can do the following:
>>> map(add_one, my_list)
[2, 4, 7, 8, 9, 11]
Note: At minimum map() needs two arguments. First a function name and second something like a list.
Let's see some other cool things map() can do.
map() can take multiple iterables (lists, strings, etc.) and pass an element from each iterable to a function as an argument.
We have three lists:
list_one = [1, 2, 3, 4, 5]
list_two = [11, 12, 13, 14, 15]
list_three = [21, 22, 23, 24, 25]
map() can make you a new list that holds the addition of elements at a specific index.
Now remember map(), needs a function. This time we'll use the builtin sum() function. Running map() gives the following result:
>>> map(sum, list_one, list_two, list_three)
[33, 36, 39, 42, 45]
REMEMBER:
In Python 2 map(), will iterate (go through the elements of the lists) according to the longest list, and pass None to the function for the shorter lists, so your function should look for None and handle them, otherwise you will get errors. In Python 3 map() will stop after finishing with the shortest list. Also, in Python 3, map() returns an iterator, not a list.
Python3 - map(func, iterable)
One thing that wasn't mentioned completely (although #BlooB kinda mentioned it) is that map returns a map object NOT a list. This is a big difference when it comes to time performance on initialization and iteration. Consider these two tests.
import time
def test1(iterable):
a = time.clock()
map(str, iterable)
a = time.clock() - a
b = time.clock()
[ str(x) for x in iterable ]
b = time.clock() - b
print(a,b)
def test2(iterable):
a = time.clock()
[ x for x in map(str, iterable)]
a = time.clock() - a
b = time.clock()
[ str(x) for x in iterable ]
b = time.clock() - b
print(a,b)
test1(range(2000000)) # Prints ~1.7e-5s ~8s
test2(range(2000000)) # Prints ~9s ~8s
As you can see initializing the map function takes almost no time at all. However iterating through the map object takes longer than simply iterating through the iterable. This means that the function passed to map() is not applied to each element until the element is reached in the iteration. If you want a list use list comprehension. If you plan to iterate through in a for loop and will break at some point, then use map.

Categories