Convert for loop into list comprehension with assignment? - python

I am trying to convert a for loop with an assignment into a list comprehension.
More precisely I am trying to only replace one element from a list with three indexes.
Can it be done?
for i in range(len(data)):
data[i][0] = data[i][0].replace('+00:00','Z').replace(' ','T')
Best

If you really, really want to convert it to a list comprehension, you could try something like this, assuming the sub-lists have three elements, as you stated in the questions:
new_data = [[a.replace('+00:00','Z').replace(' ','T'), b, c] for (a, b, c) in data]
Note that this does not modify the existing list, but creates a new list, though. However, in this case I'd just stick with a regular for loop, which much better conveys what you are actually doing. Instead of iterating the indices, you could iterate the elements directly, though:
for x in data:
x[0] = x[0].replace('+00:00','Z').replace(' ','T')

I believe it could be done, but that's not the best way to do that.
First you would create a big Jones Complexity for a foreign reader of your code.
Second you would exceed preferred amount of chars on a line, which is 80. Which again will bring complexity problems for a reader.
Third is that list comprehension made to return things from comprehensing of lists, here you change your original list. Not the best practice as well.

List comprehension is useful when making lists. So, it is not recommended here. But still, you can try this simple solution -
print([ele[0].replace('+00:00','Z').replace(' ','T') for ele in data])

Although I don't recommend you use list-comprehension in this case, but if you really want to use it, here is a example.
It can handle different length of data, if you need it.
code:
data = [["1 +00:00",""],["2 +00:00","",""],["3 +00:00"]]
print([[i[0].replace('+00:00','Z').replace(' ','T'),*i[1:]] for i in data])
result:
[['1TZ', ''], ['2TZ', '', ''], ['3TZ']]

Related

How to add up values of the "sublists" within a list of lists

I have a list of lists in my script:
list = [[1,2]
[4,3]
[6,2]
[1,6]
[9,2]
[6,5]]
I am looking for a solution to sum up the contents of each "sublist" within the list of lists. The desired output would be:
new_list = [3,7,8,7,11,11]
I know about combining ALL of these lists into one which would be:
new_list = [27,20]
But that's not what i'm looking to accomplish.
I need to combine the two values within these "sublists" and have them remain as their own entry in the main list.
I would also greatly appreciate it if it was also explained how you solved the problem rather than just handing me the solution. I'm trying to learn python so even a minor explanation would be greatly appreciated.
Using Python 3.7.4
Thanks, Riftie.
The "manual" solution will be using a for loop.
new_list = []
for sub_list in list:
new_list.append(sum(sub_list))
or as list compherension:
new_list = [sum(sub_list) for sub_list in list]
The for loop iterates through the elements of list. In your case, list is a list of lists. So every element is a list byitself. That means that while iterating, sub_list is a simple list. To get a sum of list I used sum() build-in function. You can of course iterate manually and sum every element:
new_list = []
for sub_list in list:
sum_val = 0
for element in sub_list:
sum_val = sum_val + element
new_list.append(sum_val)
but no need for that.
A better approach will be to use numpy, which allows you to sum by axis, as it looks on list of lists like an array. Since you are learning basic python, it's too soon to learn about numpy. Just keep in mind that there is a package for handling multi-dimensions arrays and it allows it perform some actions like sum on an axis by your choice.
Edit: I've seen the other solution suggest. As both will work, I believe this solution is more "accessible" for someone who learn to program for first time. Using list comprehension is great and correct, but may be a bit confusing while first learning. Also as suggested, calling your variables list is a bad idea because it's keyword. Better names will be "my_list", "tmp_list" or something else.
Use list comprehension. Also avoid using keywords as variable names, in your case you overrode the builtin list.
# a, b -> sequence unpacking
summed = [a + b for a, b in lst] # where lst is your list of lists
# if the inner lists contain variable number of elements, a more
# concise solution would be
summed2 = [sum(seq) for seq in lst]
Read more about the powerful list comprehension here.

Making a complicated list comprehension conditional

Here is my current one-liner:
leader = [server.get_member(x) for x in self.rosters[server.id][clan]['members'] if discord.utils.get(server.get_member(x).roles, id="463226598351699968")]
I want to only run this if server.get_member(x) is not False. How can I add this extra logic into this list comprehension? I understand how to do a basic for in statement, but nesting it deeper than that becomes a bit confusing for me.
In general, do not sacrifice readability for the sake of writing a one-liner. If it not immediately obvious how to do it with a list-comprehension, then use a for-loop.
leader = []
for x in self.rosters[server.id][clan]['members']:
member = server.get_member(x)
if member and discord.utils.get(member.roles, id="463226598351699968"):
leader.append(member)
Although, in this specific case, since you do not need x, you can use map to apply server.get_member while iterating.
leader = [m for m in map(server.get_member, self.rosters[server.id][clan]['members'])
if m and discord.utils.get(m.roles, id="463226598351699968")]
You can't. The item in list comprehension can't be saved, so you'll have to evaluate it twice. Even if you could, don't. List comprehensions are for filtering, not for running code as side effect. It's unreadable and prone to mistakes.
In general, you can achieve the effect of a temporary variable assignment with a nested list comprehension that iterates through a 1-tuple:
leader = [m for x in self.rosters[server.id][clan]['members'] for m in (server.get_member(x),) if m and discord.utils.get(m.roles, id="463226598351699968")]
But in this particular case, as #OlivierMelançon pointed out in the comment, since the additional assignment is simply mapping a value to a function call, you can achieve the desired result with the map function instead:
leader = [m for m in map(server.get_member, self.rosters[server.id][clan]['members']) if m and discord.utils.get(m.roles, id="463226598351699968")]
While I agree with the comments suggesting you should not write this as a comprehension for readability you could try:
leader = [server.get_member(x) for x in self.rosters[server.id][clan]['members'] if discord.utils.get(server.get_member(x).roles, id="463226598351699968") if server.get_member(x)]
Similar to this answer.

Creating a Python list comprehension with an if and break with nested for loops

I noticed from this answer that the code
for i in userInput:
if i in wordsTask:
a = i
break
can be written as a list comprehension in the following way:
next([i for i in userInput if i in wordsTask])
I have a similar problem which is that I would like to write the following (simplified from original problem) code in terms of a list comprehension:
for i in xrange(N):
point = Point(long_list[i],lat_list[i])
for feature in feature_list:
polygon = shape(feature['geometry'])
if polygon.contains(point):
new_list.append(feature['properties'])
break
I expect each point to be associated with a single polygon from the feature list. Hence, once a polygon that contains the point is found, break is used to move on to the next point. Therefore, new_list will have exactly N elements.
I wrote it as a list comprehension as follows:
new_list = [feature['properties'] for i in xrange(1000) for feature in feature_list if shape(feature['geometry']).contains(Point(long_list[i],lat_list[i])]
Of course, this doesn't take into account the break in the if statement, and therefore takes significantly longer than using nested for loops. Using the advice from the above-linked post (which I probably don't fully understand), I did
new_list2 = next(feature['properties'] for i in xrange(1000) for feature in feature_list if shape(feature['geometry']).contains(Point(long_list[i],lat_list[i]))
However, new_list2 has much fewer than N elements (in my case, N=1000 and new_list2 had only 5 elements)
Question 1: Is it even worth doing this as a list comprehension? The only reason is that I read that list comprehensions are usually a bit faster than nested for loops. With 2 million data points, every second counts.
Question 2: If so, how would I go about incorporating the break statement in a list comprehension?
Question 3: What was the error going on with using next in the way I was doing?
Thank you so much for your time and kind help.
List comprehensions are not necessarily faster than a for loop. If you have a pattern like:
some_var = []
for ...:
if ...:
some_var.append(some_other_var)
then yes, the list comprehension is faster than the bunch of .append()s. You have extenuating circumstances, however. For one thing, it is actually a generator expression in the case of next(...) because it doesn't have the [ and ] around it.
You aren't actually creating a list (and therefore not using .append()). You are merely getting one value.
Your generator calls Point(long_list[i], lat_list[i]) once for each feature for each i in xrange(N), whereas the loop calls it only once for each i.
and, of course, your generator expression doesn't work.
Why doesn't your generator expression work? Because it finds only the first value overall. The loop, on the other hand, finds the first value for each i. You see the difference? The generator expression breaks out of both loops, but the for loop breaks out of only the inner one.
If you want a slight improvement in performance, use itertools.izip() (or just zip() in Python 3):
from itertools import izip
for long, lat in izip(long_list, lat_list):
point = Point(long, lat)
...
I don't know that complex list comprehensions or generator expressions are that much faster than nested loops if they're running the same algorithm (e.g. visiting the same number of values). To get a definitive answer you should probably try to implement a solution both ways and test to see which is faster for your real data.
As for how to short-circuit the inner loop but not the outer one, you'll need to put the next call inside the main list comprehension, with a separate generator expression inside of it:
new_list = [next(feature['properties'] for feature in feature_list
if shape(feature['shape']).contains(Point(long, lat)))
for long, lat in zip(long_list, lat_list)]
I've changed up one other thing: Rather than indexing long_list and lat_list with indexes from a range I'm using zip to iterate over them in parallel.
Note that if creating the Point objects over and over ends up taking too much time, you can streamline that part of the code by adding in another nested generator expression that creates the points and lets you bind them to a (reusable) name:
new_list = [next(feature['properties'] for feature in feature_list
if shape(feature['shape']).contains(point))
for point in (Point(long, lat) for long, lat in zip(long_list, lat_list))]

List comprehension

I have some trouble with list comprehension, I think I already know how to use it well but certainly I don't.
So here is my code:
vector1=[x for x in range(0,351,10)]
first=list(range(0,91))
second=list(range(100,181))
third=list(range(190,271))
fourth=list(range(280,351))
Quadrants=first+second+third+fourth
string=['First']*91+['Second']*81+['Third']*81+['Fourth']*71
vector2=dict(zip(Quadrants,string))
Quadrant=[]
for n in range (len(vector1)):
Quadrant+=[vector2[vector1[n])]]
So i want to do the for_loop with list comprehension, but i can't... I tried this:
Quadrant=[y3 for y3 in [vector2[vector1[i]]] for i in range (len(vector1))]
Here's the code you're trying to convert to a listcomp:
Quadrant=[]
for n in range (len(vector1)):
Quadrant+=[y[vector1[n]]]
First, you have to convert that into a form using append. There's really no reason to build a 1-element list out of y[vector1[n]] in the first place, so just scrap that and we have something we can appenddirectly:
Quadrant=[]
for n in range(len(vector1)):
Quadrant.append(y[vector1[n]])
And now, we have something we can convert directly into a list comprehension:
Quadrant = [y[vector1[n]] for n in range(len(vector1))]
That's all there is to it.
However, I'm not sure why you're doing for n in range(len(vector1)) in the first place if the only thing you need n for is vector1[n]. Just loop over vector1 directly:
Quadrant=[]
for value in vector1:
Quadrant.append(y[value])
Which, again, can be converted directly:
Quadrant = [y[value] for value in vector1]
However, all of this assumes that your original explicit loop is correct in the first place, which obviously it isn't. Your vector1 is a dict, not a list. Looping over it the keys from 0 to len(vector1) is just going to raise KeyErrors all over the place. Changing it to loop directly over vector1 is going to solve that problem, but it means you're looping over the keys. So… I have no idea what your code was actually trying to do, but get the simple but verbose version right first, and you can probably convert it to a comprehension just as easily as the above.

Replace whitespaces with dashes for each item in a list -python

Is there a way of simplifying this loop where i replaces whitespace with dashes for each item in a list?
for item in a_list:
alist[alist.index(item)] = '-'.join(item.split(" "))
or is this better?
for item in a_list:
alist[alist.index(item)] = item.replace(" ", "-")
NOTE: The above solution only updates the 1st occurrence in this list, as David suggested, use list comprehension to do the above task.
I have a list of words and some have dashes while some doesn't. The items in a_list looks like this:
this-item has a-dash
this has dashes
this should-have-more dashes
this foo
doesnt bar
foo
bar
The output should look like this, where all items in list should have dashes instead of whitespace:
this-item-has-a-dash
this-has-dashes
this-should-have-more-dashes
this-foo
doesnt-bar
foo
bar
Use a list comprehension:
a_list = [e.replace(" ", "-") for e in a_list]
When you find yourself using the index method, you've probably done something wrong. (Not always, but often enough that you should think about it.)
In this case, you're iterating a list in order, and you want to know the index of the current element. Looking it up repeatedly is slow (it makes an O(N) algorithm O(N^3))—but, more importantly, it's fragile. For example, if you have two identical items, index will never find the second one.
This is exactly what enumerate was created for. So, do this:
for i, item in enumerate(a_list):
alist[i] = '-'.join(item.split(" "))
Meanwhile, you could replace the loop with a list comprehension:
a_list = ['-'.join(item.split(" ")) for item in a_list]
This could be slower or use more memory (because you're copying the list rather than modifying it in-place), but that almost certainly doesn't matter (it certainly won't be as slow as your original code), and immutable algorithms are simpler and easier to reason about—and more flexible; you can call this version with a tuple, or an arbitrary iterable, not just a list.
As another improvement, do you really need to split and then join, or can you just use replace?
a_list = [item.replace(" ", "-") for item in a_list]
You could use regular expressions instead, which might be better for performance or readability in some similar cases—but I think in this case it would actually be worse. So, once you get here, you're done.

Categories

Resources