Conditionally join with previous list entry using comprehension - python

I am trying to fix some broken linux paths in a list I am working with.
List:
mylist = ['/root/path/path', '/cat', '/dog', '/root/path/path', '/blue', '/red']
Requirements:
If element does not begin with '/root', join to the element to the left of it.
Code so far:
mylist2 = [''.join(x) for x in mylist]
print(mylist2)
Expected output:
['/root/path/path/cat/dog', '/root/path/path/blue/red']
Actual output:
['/root/path/path', '/cat', '/dog', '/root/path/path', '/blue', '/red']
I've also tried:
mylist2 = [''.join(x) if myroot not x for mylist]
...which produces a syntax error...
Any ideas on what I am doing wrong?

This is simpler if you just use a regular loop. The problem with the list comprehension is that you don't have a uniform operation on each element of the first list that creates an element for the new list. (Think of a list comphension as a combination of map and filter. You can map one old value to one new value, or drop an old value, but you can't combine multiple old values into a single new value.)
mylist2 = []
for path in mylist:
if path.startswith('/root'):
mylist2.append(path)
else:
mylist2[-1] += path
(This is only partially correct; it assumes the first element of mylist will actually start with /root, so that mylist2[-1] will never be used if mylist2 is empty.)

This is one method using list comprehension:
mylist2 = ['/root' + x for x in ''.join(mylist).split('/root') if x] # if x eliminates the empty split elements
# ['/root/path/path/cat/dog', '/root/path/path/blue/red']
Since your goal is basically to join everything and then split them by /root, this line of list comprehension does exactly that and adds /root back to each element.
But as you can see, given just the code, #chepner's answer is much more understandable and clearer. Just because list comprehension exist doesn't mean it should be your go-to.
Also I should note, if there's /root within any of your elements (not necessarily at the beginning), this code will also separate it because of the split, so it's not as exact as explicitly going through the loop. If you wanted to handle that scenario it becomes very ugly...:
['/root' + y for y in ''.join("_" + x if x.startswith("/root") else x for x in lst).split("_/root") if y]
# eww

Related

check a list of strings with list comprehension

I want to filter a list of strings that includes any specific string with using list comprehension. I tried following the code, but it didn't work.
ignore_paths = ['.ipynb_checkpoints', 'New', '_calibration', 'images']
A = [x for x in lof1 if ignore_paths not in x]
but when I try with one string, it will work:
A = [x for x in lof1 if 'images' not in x]
in fact, I want to define a list of a forbidden path (or string) and check if there is a certain path that exists, then ignore that.
I can do that with normal for and if loop and check one by one, but it is not fast. I am looking for a fast way because I need to check around 150k paths.
Thanks
EDIT:
To make it clear, if I had a list of files as below:
lof1 = ['User/images/20210701_151111_G1100_E53100_r121_g64_b154_WBA0_GA0_EA0_6aa87af_crop.png', 'User/images/16f48a97-7111-4f66-92cc-dc7329e7ec92.png', 'User/images/image_2022_06_21-11_41_04_AM.png']
I need to return an empty list since all of the elements contain 'images'
Do you have duplicates? if not, you can use sets which will be very fast
allowed = set(lof1) - set(ignore_paths)
Try:
A = [x for x in lof1 if x not in ignore_paths]

Python gives ValueError: list.remove(x): x not in list when removing a tuple from list

Im filtering a tuple like;
newtuple = filter(lambda x: x[2].startswith('902'), csvlist)
Then try to remove that from my original csvlist
csvlist.remove(newtuple) //<--Gives error
but getting;
ValueError: list.remove(x): x not in list
What Im doing wrong here?
Adapting my confirmed comment: filter returns all matches as a sequence, not just the first match, so "newtuple" is a misnomer here, it's really "newtuples" plural (a list of tuples on Py2, a generator of tuples on Py3).
The most straightforward fix is to change your code to:
newtuples = filter(lambda x: x[2].startswith('902'), csvlist)
for newtuple in newtuples: # in list(newtuples) on Py3 to avoid mutating csvlist while iterating
csvlist.remove(newtuple)
but that has some problems; as noted, you'd need to listify the result of filter on Py3, and performance-wise, it's O(n**2); each remove call is O(n), and you could conceivably perform one for every element in csvlist.
A much more efficient, portable, and Pythonic solution is to use a list comprehension to filter the input in a single pass, then replace csvlist's contents with the result of the list comprehension. It's only O(n) work total, and listcomps can avoid the function call overhead of filter+lambda. The improved code is:
csvlist[:] = [x for x in csvlist if x[2].startswith('902')]
That generates the new list, removing all undesired elements as it goes, then replaces the contents of csvlist in place. If you don't have any other references to csvlist that should be updated, you can drop the slice assignment for plain assignment (csvlist = ... rather than csvlist[:] = ...) for a small performance boost.

Sorting out unique elements from a list to a set

I was writing a function to save unique values returned by a list "list_accepted_car" to a set "unique_accepted_ant". list_car_id is list with the value ['12','18','3','7']. When i run the code i am getting error , "unhashable list ". Can anyone suggest me what is the error?
list_accepted_car = [] #set to store the value list_accepted_car
unique_accepted_car = set() #set to store the value unique_accepted_car
num_accepted = 2 #predifined value for the number of cars allowed to enter
def DoIOpenTheDoor(list_car_id): #list_ant_id is a list of cars allowed to enter
if len(list_accepted_car) < num_accepted:
if len(list_car_id) > 0:
list_accepted_car.append(list_car_id[0:min(len(list_car_id),num_accepted-len(list_accepted_car))])
unique_accepted_list = set(list_accepted_car)
print unique_accepted_list
return list_accepted_car
Under the assumption that list_car_id looks like: [1,2,3,4,5].
You add in list_accepted_car a sublist of list_car_id, so list_accepted_car will look like [[1,2]] i.e. a list of a list.
Then you should change
unique_accepted_list = set(list_accepted_car)
to
unique_accepted_list = set([x for y in list_accepted_car for x in y])
which will extract each element of the sublist and provide a flatten list. (There exists other options to flatten a list of list)
You are saving a list of lists, which can't be converted to a set. You have to flatten it first. There are many examples of how to do it (I'll supply one using itertools.chain which I prefer to python's nested comprehension).
Also, as a side note, I'd make this line more readable by separating to several lines:
list_accepted_car.append(list_car_id[0:min(len(list_car_id),num_accepted-len(list_accepted_car))])
You can do:
from itertools import chain
# code ...
unique_accepted_list = set(chain.from_iterable(list_accepted_car))
The best option would be to not use a list at all here, and use a set from the start.
Lists are not hashable objects, and only hashable objects can be members of sets. So, you can't have a set of lists. This instruction:
list_accepted_car.append(list_car_id[0:min(len(list_car_id),num_accepted-len(list_accepted_car))])
appends a slice of list_car_id to list_accepted_car, and a slice of a list is a list. So in effect list_accepted_car becomes a list of lists, and that's why converting it to a set:
unique_accepted_list = set(list_accepted_car)
fails. Maybe what you wanted is extend rather than append? I can't say, because I don't know what you wanted to achieve.

Python: Using a nested for loop to produce the index for a sublist

I've got the list student:
student = ["test_name","1","6"]
And I've got the read data sublist:
read_data = [["test_name","1","2","5","9"],["test_name_2","1","5","2","10"]]
I've written the following nested loop to return the index of a sublist (referred to as x) in read_data if student[0] == x[0]:
lst = [read_data.index(x) for x in read_data if x[0] == student [0]]
So I'm looping through each item in read data and (if x[0] == student[0]) the index of the sublist should be stored in lst.
When I try to use this in the index parameter of a list to insert data like so:
read_data[read_data.index(x) for x in read_data if x[0] == student[0]].insert(2,student[0])
I get an error saying generators can't be used for indexes - why not?
I can obviously just use the integer stored in lst but this seems inefficient - surely there's a way to do this on one line.
Is there some better alternative method to checking if a string is in any of a read_data's sublists and getting the index of this sublist?
In the assignment to lst you are creating a list. But in the snippet below that, you have merely taken the interior of that list expression and are trying to use it as an index. To fix your specific example, you would need to change:
read_data[iter_expr]
to:
read_data[[iter_expr][0]]
That will use the first value in the list as the index.
There are better ways to do this, but this is probably the smallest change that will fix the specific problem you asked about.
I dont know why you would want to do this but then you need to put brackets around the list comprehension and get index the first match of it like this:
read_data[[read_data.index(x) for x in read_data if x[0] == student[0]][0]].insert(2, student[0])
Remember if the list comprehension produces an empty list, you will encounter an IndexError.

Altering a list using append during a list comprehension

Caveat: this is a straight up question for code-golfing, so I know what I'm asking is bad practise in production
I'm trying to alter an array during a list comprehension, but for some reason it is hanging and I don't know why or how to fix this.
I'm dealing with a list of lists of indeterminite depth and need to condense them to a flat list - for those curious its this question. But at this stage, lets just say I need a flat list of all the elements in the list, and 0 if it is a list.
The normal method is to iterate through the list and if its a list add it to the end, like so:
for o in x:
if type(o)==type([]):x+=o
else:i+=o
print i
I'm trying to shorten this using list comprehension, like so.
print sum([
[o,x.append(o) or 0][type(o)==type([])]
for o in x
]))
Now, I know List.append returns None, so to ensure that I get a numeric value, lazy evaluation says I can do x.append(o) or 0, and since None is "falsy" it will evaulate the second part and the value is 0.
But it doesn't. If I put x.append() into the list comprehension over x, it doesn't break or error, or return an iteration error, it just freezes. Why does append freeze during the list comprehension, but the for loop above works fine?
edit: To keep this question from being deleted, I'm not looking for golfing tips (they are very educational though), I was looking for an answer as to why the code wasn't working as I had written it.
or may be lazy, but list definitions aren't. For each o in x, when the [o,x.append(o) or 0][type(o)==type([])] monstrosity is evaluated, Python has to evaluate [o,x.append(o) or 0], which means evaluating x.append(o) or 0, which means that o will be appended to x regardless of whether it's a list. Thus, you end up with every element of x appended to x, and then they get appended again and again and again and OutOfMemoryError
What about:
y = [element for element in x if type(element) != list or x.extend(element)]
(note that extend will flatten, while append will only add the nested list back to the end, unflattened).

Categories