Looping through list and nested lists in python - python

Im trying to add a prefix to a list of strings in Python. The list of strings may contain multiple levels of nested lists.
Is there a way to loop through this list (and its nested lists), while keeping the structure?
nested for-loops became unreadable very quick, and did not seem to be the right approach..
list = ['a', 'b', ['C', 'C'], 'd', ['E', ['Ee', 'Ee']]]
for i in list:
if isinstance(i, list):
for a in i:
a = prefix + a
#add more layers of for loops
else:
i = prefix + i
desired outcome:
prefix = "#"
newlist = ['#a', '#b', ['#C', '#C'], '#d', ['#E', ['#Ee', '#Ee']]]
Thanks in advance!

You could write a simple recursive function
def apply_prefix(l, prefix):
# Base Case
if isinstance(l, str):
return prefix + l
# Recursive Case
else:
return [apply_prefix(i, prefix) for i in l]
l = ['a', 'b', ['C', 'C'], 'd', ['E', ['Ee', 'Ee',]]]
print(apply_prefix(l, "#"))
# ['#a', '#b', ['#C', '#C'], '#d', ['#E', ['#Ee', '#Ee']]]

This will use recursion:
a = ['a', 'b', ['C', 'C'], 'd', ['E', ['Ee', 'Ee',]]]
def insert_symbol(structure, symbol='#'):
if isinstance(structure, list):
return [insert_symbol(sub_structure) for sub_structure in structure]
else:
return symbol + structure
print(insert_symbol(a))
>>> ['#a', '#b', ['#C', '#C'], '#d', ['#E', ['#Ee', '#Ee']]]

You can use a recursive code like this!, Try it, and if you have questions you can ask me
def add_prefix(input_list):
changed_list = []
for elem in input_list:
if isinstance(elem, list):
elem = add_prefix(elem)
changed_list.append(elem)
else:
elem = "#" + elem
changed_list.append(elem)
return changed_list

Maybe you can use a function to do it recursively.
list_example = ['a', 'b', ['C', 'C'], 'd', ['E', ['Ee', 'Ee']]]
def add_prefix(p_list, prefix):
for idx in range(len(p_list)):
if isinstance(p_list[idx], list):
p_list[idx] = add_prefix(p_list[idx], prefix)
else:
p_list[idx] = prefix + p_list[idx]
return p_list
add_prefix(list_example, '#')
edit: I see now someone has posted the almost same thing.
btw. it is considered bad practice to name a list list, since it is also a typename in python. Might result in unwanted behaviour

Related

Get keys of a deeply nested dictionary by the nesting level

I have a multilevel dictionary, imagine something like:
raw_dict = {'a':{'b':{'c':[1,2,3]}},
'd':{'e':{'f':{'g':[4,5,6]}}}}
Is it possible to access the keys of a specified nesting level?
That is, is there a way to do something like:
level = 1
keys_level_1 = list([a way to call the dictionary at specified nesting level])
print(keys_level_1)
which will return
['b', 'e']
this is similar, but not exactly what I want.
Use a recursive function
def get_keys(dct, level):
def get_(d, current=0):
# check if we're at the level we want, yield if so
if current==level:
yield d
# if not, move down one more level
else:
for v in d.values():
# only move down if the value is a dict
if isinstance(v, dict):
yield from get_(v, current+1)
return [x for s_l in get_(dct) for x in s_l]
raw_dict = {'a':{'b':{'c':[1,2,3]}},
'd':{'e':{'f':{'g':[4,5,6]}}}}
get_keys(raw_dict, 0)
['a', 'd']
get_keys(raw_dict, 1)
['b', 'e']
get_keys(raw_dict, 2)
['c', 'f']
get_keys(raw_dict, 3)
['g']
One possibility using a recursive function:
raw_dict = {'a':{'b':{'c':[1,2,3]}},
'd':{'e':{'f':{'g':[4,5,6]}}}}
def get_level(d, level):
from itertools import chain
if level == 0:
return list(d)
else:
return list(chain.from_iterable(get_level(d[item], level=level-1)
for item in d if isinstance(d[item], dict)))
get_level(raw_dict, level=1)
output: ['b', 'e']
other examples:
get_level(raw_dict, level=0)
['a', 'd']
get_level(raw_dict, level=1)
['b', 'e']
get_level(raw_dict, level=2)
['c', 'f']
get_level(raw_dict, level=3)
['g']
get_level(raw_dict, level=4)
[]

Delete a list from collection of lists

I have collection of lists like this:
example = [['a','b','c'],['d','e','f'],[ ],['z'],['g','h','i'],[ ],['z']]
I want to remove [] and ['z'] from the list.
The desired output is:
example = [['a','b','c'],['d','e','f'],['g','h','i']]
How can I do it? Can I remove both using an one liner?
I am familiar with .pop() and .remove() command but I have doubts if it will work for [ ] type of list.
You can use list comprehension for the filtering:
example = [['a','b','c'],['d','e','f'],[ ],['z'],['g','h','i'],[ ],['z']]
output = [sublst for sublst in example if sublst not in ([], ['z'])]
print(output) # [['a', 'b', 'c'], ['d', 'e', 'f'], ['g', 'h', 'i']]
you can remove them like this:
example = list(filter(lambda val: val != [] and val!=['z'], example))

How to number elements in a list of string and return a new list?

So I have a list of strings. I want to create a new list of string which turns the same string into a new string and name it "A". If there's a different string in the list, name it "B" and so on.
If the string is:
['F4','A3','F4','B5','A3','K2']
Then it should give me a result of:
['A','B','A','C','B','D']
I don't know how to start the code and can only think of something like a dictionary.
dict = {}
result = []
for line in list1:
if line not in dict:
dict.update({line:str(chr(65+len(dict)))})
result.append(dict.get(line))
Then I don't know how to continue. Any help will be appreciated.
You can make an iterator of ascii upper-case strings and pull them off one-at-a-time in a defaultdict constructor. One you have that, it's just a list comprehension. Something like:
import string
from collections import defaultdict
keys = iter(string.ascii_uppercase)
d = defaultdict(lambda: next(keys))
l = ['F4','A3','F4','B5','A3','K2']
[d[k] for k in l]
# ['A', 'B', 'A', 'C', 'B', 'D']
import string
mapping = {}
offset = 0
for item in l:
if item in mapping:
continue
mapping[item] = string.ascii_uppercase[offset]
offset += 1
[mapping.get(item) for item in l]
Output
['A', 'B', 'A', 'C', 'B', 'D']
You can create a simple class to store the running results:
import string
class L:
def __init__(self):
self.l = {}
def __getitem__(self, _v):
if (val:=self.l.get(_v)) is not None:
return val
self.l[_v]= (k:=string.ascii_uppercase[len(self.l)])
return k
l = L()
vals = ['F4','A3','F4','B5','A3','K2']
result = [l[i] for i in vals]
Output:
['A', 'B', 'A', 'C', 'B', 'D']

How to keep unique inner lists within a list of lists by ignoring one element of the inner list

I have a list of lists, and would like to keep the unique lists by ignoring one element of the list.
MWE:
my_list_of_lists = [['b','c','1','d'],['b','c','1','d'],['b','c','2','e']]
print(my_list_of_lists)
new_list_of_lists = []
for the_list in my_list_of_lists:
if the_list not in new_list_of_lists:
new_list_of_lists.append(the_list)
print(new_list_of_lists)
MWE Output:
[['b', 'c', '1', 'd'], ['b', 'c', '1', 'd'], ['b', 'c', '2', 'e']] # 1st print
[['b', 'c', '1', 'd'], ['b', 'c', '2', 'e']] # 2nd print
Question:
Is there a Pythonic way to remove duplicates as with the example above by ignoring a specific element within the inner list?
ie for my_list_of_lists = [['b','c','1','d'],['b','c','3','d'],['b','c','2','e']] should yield [['b','c','1','d'],['b','c','2','e']]
my_list_of_lists = [['b','c','1','d'],['b','c','3','d'],['b','c','2','e']]
# my_list_of_lists[0] and my_list_of_lists[1] are identical
# if my_list_of_lists[n][-2] is ignored
print(my_list_of_lists)
new_list_of_lists = []
for the_list in my_list_of_lists:
if the_list[ignore:-2] not in new_list_of_lists: #ignore the second last element when comparing
new_list_of_lists.append(the_list)
print(new_list_of_lists)
This is not "Pythonic" per se, but it is relatively short and gets the job done:
my_list_of_lists = [['b','c','1','d'],['b','c','3','d'],['b','c','2','e']]
print(my_list_of_lists)
new_list_of_lists = []
ignore = 2
for the_list in my_list_of_lists:
if all(
any(e != other_list[i]
for i, e in enumerate(the_list)
if i != ignore)
for other_list in new_list_of_lists
):
new_list_of_lists.append(the_list)
print(new_list_of_lists)
It outputs [['b', 'c', '1', 'd'], ['b', 'c', '2', 'e']] for the given input.
My question and your reply from the comments:
"ignoring a specific element" - Which one? The first? The largest? The one that's a digit? Some other rule? Your example input doesn't specify. – Heap Overflow
#HeapOverflow, I think a generic (non-specific) function would be best as other users in the future can integrate this generic function for their own use. – 3kstc
Doing that #GreenCloakGuy's style:
def unique(values, key):
return list({key(value): value for value in values}.values())
new_list_of_lists = unique(my_list_of_lists, lambda a: tuple(a[:2] + a[3:]))
A bit shorter:
def unique(values, key):
return list(dict(zip(map(key, values), values)).values())
Those take the last duplicate. If you want the first, you could use this:
def unique(values, key):
tmp = {}
for value in values:
tmp.setdefault(key(value), value)
return list(tmp.values())
The following approach
Creates a dict
where the values are the lists in the list-of-lists
and the corresponding keys are those lists without the indices you want to ignore, converted to tuples (since lists cannot be used as dict keys but tuples can)
Gets the dict's values, which should appear in order of insertion
Converts that to a list, and returns it
This preserves the later elements in the original list, as they overwrite earlier elements that are 'identical'.
def filter_unique(list_of_lists, indices_to_ignore):
return list({
tuple(elem for idx, elem in enumerate(lst) if idx not in indices_to_ignore) : lst
for lst in list_of_lists
}.values())
mlol = [['b','c','1','d'],['b','c','3','d'],['b','c','2','d']]
print(filter_unique(mlol, [2]))
# [['b', 'c', '3', 'd'], ['b', 'c', '2', 'e']]
print(filter_unique(mlol, [3]))
# [['b', 'c', '1', 'd'], ['b', 'c', '3', 'd'], ['b', 'c', '2', 'e']]
That's a one-liner, abusing a dict comprehension. A multi-line version might look like this:
def filter_unique(list_of_lists, indices_to_ignore):
dct = {}
for lst in list_of_lists:
key = []
for idx, elem in enumerate(lst):
if idx not in indices_to_ignore:
key.append(elem)
dct[tuple(key)] = lst
return list(dct.values())

A program to mutate Nested Lists

Could someone help me do two things:
Review the code and see if it could be written in a better way.
Finish this program. I got stuck in trying to put the list back the way it is. i.e. a nested list of lists.
Here we go:
t = ['a', 'b', ['c', 'd'], ['e'], 'f']
def capitalize_list(t):
L = []
N = []
for i in range(len(t)):
if type(t[i]) == str:
L.append(t[i].capitalize())
if type(t[i]) == list:
L.extend(t[i])
for s in L:
N.append(s.capitalize())
print N
capitalize_list(t)
This code prints:
['A', 'B', 'C', 'D', 'E', 'F']
I need it to print:
['A', 'B', ['C', 'D'], ['E'], 'F']
You can use recursion:
def capitalize_list(t):
N = []
for i in range(len(t)):
if type(t[i]) == str:
N.append(t[i].capitalize())
if type(t[i]) == list:
N.append(capitalize_list(t[i]))
return N
Output:
['A', 'B', ['C', 'D'], ['E'], 'F']
An alternative way of doing this recursively:
def capitalize(item):
if type(item) is str:
return item.capitalize()
if type(item) is list:
return map(capitalize, item)
You could even do
def capitalize(item):
try:
return item.capitalize()
except AttributeError:
return map(capitalize, item)
-- which would use the capitalize method for any object that has it (such as a normal or unicode string) and attempt to iterate through any object that does not.

Categories