How to handle list with nested lists and strings elements? - python-3.x

I want to flat a list that has nested and not nested elements. From this solution I've tried and it works if all elements within mylist were lists, but
in mylist I have simple text strings and nested lists.
My list is like this:
mylist = [
'tz',
'7',
['a', 'b', 'c'],
[['2'], ['4', 'r'], ['34']],
[['7'], ['3', ['2', ['1']]], ['9']],
[['11',['7','w']], 'U1', ['0']]
]
And my current code is this, getting the error below:
import collections#.abc
def flatten(l):
for el in l:
if isinstance(el, collections.Iterable) and not isinstance(el, (str, bytes)):
yield from flatten(el)
else:
yield el
mylist1=[list(flatten(sublist)) if type(sublist) is list else sublist for sublist in mylist]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 1, in <listcomp>
File "<stdin>", line 3, in flatten
TypeError: isinstance() arg 2 must be a type or tuple of types
>>>
My expected output would be like this
mylist1 = [
'tz',
'7',
['a', 'b', 'c'],
['2', '4', 'r','34'],
['7','3','2','1','9'],
['11','7','w','U1','0']
]
What is missing to fix this? thanks.
UPDATE
Now I'm getting this error with code suggested by #Alok
>>> for item in mylist:
... # if the item in mylist is a list, pass it to your flatten method else
... # add it to the final list
... if isinstance(item, list):
... final_list.append(list(flatten(item)))
... else:
... final_list.append(item)
...
Traceback (most recent call last):
File "<stdin>", line 5, in <module>
File "<stdin>", line 3, in flatten
TypeError: isinstance() arg 2 must be a type or tuple of types

The problem is with some instances which I will point out:
Python 3.x is not following collections.Iterable anymore, you need to import the items from collections.abc and always do try-catch for any import error
try:
from collections.abc import Iterable
except ImportError:
from collections import Iterable
Not utilizing your flatten method appropriately, the method, returns the data, but you have to store it in the form of a list and append it to the final answer list YOUR SUB ARRAYS ONLY BE PASSED INTO THIS METHOD
data.append(list(flatten(item)))
FINAL SOLUTION:
try:
from collections.abc import Iterable
except ImportError:
from collections import Iterable
mylist = [
'tz',
'7',
['a', 'b', 'c'],
[['2'], ['4', 'r'], ['34']],
[['7'], ['3', ['2', ['1']]], ['9']],
[['11',['7','w']], 'U1', ['0']]
]
def flatten(l):
for el in l:
if isinstance(el, Iterable) and not isinstance(el, (str, bytes)):
yield from flatten(el)
else:
yield el
final_list = []
for item in mylist:
# if the item in mylist is a list, pass it to your flatten method else
# add it to the final list
if isinstance(item, type([])): final_list.append(list(flatten(item)))
else: final_list.append(item)
print(final_list)
OUTPUT
['tz', '7', ['a', 'b', 'c'], ['2', '4', 'r', '34'], ['7', '3', '2', '1', '9'], ['11', '7', 'w', 'U1', '0']]
I hope you can achieve your desired output in this way.

Related

How to reorder keys in a dictionary?

I have a list of dictionaries in the form:
my_list = [{'a': 'Jane', 'b': 32}, {'a': 'Jack', 'b': 54}]
I want to re-order this to the form:
new_dt = [{'b': 32, 'a': 'Jane'}, {'b': 54, 'a': 'Jack'}]
I have used the following code:
order_dict = ['b', 'a']
for dt in my_list:
for k in order_dict:
new_dt = my_list[k]
Traceback
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-87-0d451ee34800> in <module>
3 for dt in my_list:
4 for k in order_dict:
----> 5 new_dt = my_list[k]
TypeError: list indices must be integers or slices, not str
You can simply do a:
my_list = [{'a': 'Jane', 'b': 32}, {'a': 'Jack', 'b': 54}]
print([dict(sorted(x.items(),key = lambda x:x[0],reverse=True)) for x in my_list])
The OUTPUT:
[{'b': 32, 'a': 'Jane'}, {'b': 54, 'a': 'Jack'}]
Explanation
sorted(x.items(),key = lambda x:x[0],reverse=True)
Here we are just sorting the items (keys and values) in the dictionary to get the order as required.
key = lambda x:x[0]
This component ensures that we are sorting based on the first element in items(essentially the key of dictionary)
We have reverse Set to true to get the desired sequence.

convert list of list to dictionary

I have a text file below:
A test B echo C delete
A test B echo C delete D modify
A test B echo C delete
I want to parse the text file above, translate to list of list, and then to a dictionary.
Expected list of list is:
[['A', 'test', 'B', 'echo', 'C', 'delete'], ['A', 'test', 'B', 'echo', 'C', 'delete', 'D', 'modify'], ['A', 'test', 'B', 'echo', 'C', 'delete']]
Final result for dictionary is:
[{'A':'test','B':'echo','C':'delete'},{'A':'test','B':'echo','C':'delete','D': 'modify'},{'A':'test', 'B':'echo', 'C':'delete'}]
This is my script:
#!/usr/bin/python3
def listToDict(list):
listDict = {list[i]: list[i + 1] for i in range (0, len(list), 2)}
return listDict
def parse_file(filepath):
string_to_listoflist = []
with open(filepath, 'r') as file_object:
lines = file_object.readlines()
for line in lines:
string_to_listoflist.append(line.rstrip().split())
dictionary = listToDict(string_to_listoflist)
print(dictionary)
if __name__ == '__main__':
filepath = 'log.txt'
parse_file(filepath)
with the above script will produce an error below:
Traceback (most recent call last):
File "parse.py", line 19, in <module>
parse_file(filepath)
File "parse.py", line 14, in parse_file
dictionary = listToDict(string_to_listoflist)
File "parse.py", line 4, in listToDict
listDict = {list[i]: list[i + 1] for i in range (0, len(list), 2)}
File "parse.py", line 4, in <dictcomp>
listDict = {list[i]: list[i + 1] for i in range (0, len(list), 2)}
TypeError: unhashable type: 'list'
Now I create another loop in the list of list below:
#!/usr/bin/python3
def listToDict(list):
listDict = {list[i]: list[i + 1] for i in range (0, len(list), 2)}
return listDict
def parse_file(filepath):
string_to_listoflist = []
dictionary = {}
with open(filepath, 'r') as file_object:
lines = file_object.readlines()
for line in lines:
string_to_listoflist.append(line.rstrip().split())
for e in string_to_listoflist:
dictionary = listToDict(e)
print(dictionary)
if __name__ == '__main__':
filepath = 'log.txt'
parse_file(filepath)
The script above will produce unexpected result even I define the dictionary variable before the loop:
{'A': 'test', 'B': 'echo', 'C': 'delete'}
Then change the position of print command as below:
#!/usr/bin/python3
def listToDict(list):
listDict = {list[i]: list[i + 1] for i in range (0, len(list), 2)}
return listDict
def parse_file(filepath):
string_to_listoflist = []
dictionary = {}
with open(filepath, 'r') as file_object:
lines = file_object.readlines()
for line in lines:
string_to_listoflist.append(line.rstrip().split())
for e in string_to_listoflist:
dictionary = listToDict(e)
print(dictionary)
if __name__ == '__main__':
filepath = 'log.txt'
parse_file(filepath)
Unexpected result for the script above is:
{'A': 'test', 'B': 'echo', 'C': 'delete'}
{'A': 'test', 'B': 'echo', 'C': 'delete', 'D': 'modify'}
{'A': 'test', 'B': 'echo', 'C': 'delete'}
Can anyone help how to resolve my issue?
Thanks
In your first attempt, your variable string_to_listoflist is a list of lists.
When you pass it to your function listToDict, the function iterates on the parent level of the list instead of iterating over each list within the parent list. Thus, the first entry attempted in the dictionary is
['A', 'test', 'B', 'echo', 'C', 'delete']:['A', 'test', 'B', 'echo', 'C', 'delete', 'D', 'modify']
rather than your intended
'A':'test'
This causes the error you observe TypeError: unhashable type: 'list' since a list (mutable) is attempted to be used as a key in a dictionary, which requires immutable keys.
Adding the extra loop surrounding each element of the parent list is the correct way to resolve this. However, if you want your final result to be inside a list, you simply need to append the result to a list.
In other words, perhaps the following
dictionaries=[]
for e in string_to_listoflist:
dictionary = listToDict(e)
dictionaries.append(dictionary)
print(dictionaries)
You can use re module to obtain your desired dict.
For example:
import re
with open('file.txt', 'r') as f_in:
out = [dict(re.findall(r'([A-Z]+) ([^\s]+)', line)) for line in f_in]
print(out)
Prints:
[{'A': 'test', 'B': 'echo', 'C': 'delete'}, {'A': 'test', 'B': 'echo', 'C': 'delete', 'D': 'modify'}, {'A': 'test', 'B': 'echo', 'C': 'delete'}]

Why does list.__iadd__ work with other iterable object types when __add__ does not?

I recently came across a python FIFO buffer example, which initialized the buffer as an empty list despite the intended input and output being of type bytes or bytearray. To my surprise this was not a mistake, and I have been left stumped as to why list.__iadd__ works in this case, but not list.__add__.
Below is code demonstrating this discrepancy (python version 3.7.4).
>>> buffer = []
>>> some_bytes = b'12345'
>>> some_bytearray = bytearray(some_bytes)
>>> buffer + some_bytes
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can only concatenate list (not "bytes") to list
>>> buffer += some_bytes
>>> buffer
[49, 50, 51, 52, 53]
>>> buffer + some_bytearray
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can only concatenate list (not "bytearray") to list
>>> buffer += some_bytearray
>>> buffer
[49, 50, 51, 52, 53, 49, 50, 51, 52, 53]
Edit: this same functionality also applies to other iterate types such as str, dict and tuple. Does __iadd__ attempt to mutate the argument if __add__ raises a TypeError?
List concatenation (adding) requires that both variables in the operation are lists - this (I guess) allows us to be sure of the type of object that will result from the operation. Otherwise we predict cannot the type of object that resulted from
some_list + some_set
On the other hand, extending the a list (somelist += ...) only requires that the object on the right hand side be an iterable, so that each of its elements can be added to the original list.
>>> L = []
>>> L += {1, 2, 3}
>>> L
[1, 2, 3]
>>> L += ('a', 'b', 'c')
>>> L
[1, 2, 3, 'a', 'b', 'c']
>>> L += ['x', 'y', 'z']
>>> L
[1, 2, 3, 'a', 'b', 'c', 'x', 'y', 'z']
In short, concatenation returns a new object, so the left and right and sides of the expression need to be of the same type. In-place addition mutates the original object, so the object on the left hand side need only be an iterable, its type is irrelevant.
The answer appears to be in:
a = bytearray()
help(a)
....
| __add__(self, value, /)
| Return self+value.
...
| __iadd__(self, value, /)
| Implement self+=value.
...
Thus, iadd changes the class while add does not.

Using a list comprehension, insert an incrementing integer into a list of lists that contain strings

How can I turn this for loop into a list comprehension?
in:
docs = [['a'], ['b']]
i=0
for each in docs:
print(each.insert(0, str(i)))
i+=1
print(docs)
out:
[['0', 'a'], ['1', 'b']]
It depends on if you want a new list or mutate the old list:
[l.insert(0,str(i)) for i,l in enumerate(docs)]
[l.insert(0,str(i)) or l for i,l in enumerate(docs)]
[[str(i)]+docs[i] for i in range(len(docs))]
This will mutate the old list, but only as a side effect, the 'returned' list is wrong:
>>> x = [['a'],['b']]
>>> [ l.insert(0,str(i)) for i,l in enumerate(x)]
[None, None]
>>> x
[['0', 'a'], ['1', 'b']]
This can be fixed:
>>> x = [['a'],['b']]
>>> [ l.insert(0,str(i)) or l for i,l in enumerate(x)]
[['0', 'a'], ['1', 'b']]
>>> x
[['0', 'a'], ['1', 'b']]
>>>
or one can generate a new list without mutating the old list:
>>> y = [['a'],['b']]
>>> [ [str(i)]+y[i] for i in range(len(y))]
[['0', 'a'], ['1', 'b']]
>>> y
[['a'], ['b']]

Input on dictionnary with a counter

I'm new at Python, and i need your help for this.
I have a user input like :
5 72 245 62
And i need to split this integers into a dictionary like this :
{1=5;2=72;3=245;4=62}
I tried something like :
sequence = dict(x ,input().split())
Where x is incrementing counter.
If your desired end result is a Python dictionary, then I think you're pretty close.
You can actually use a python builtin to achieve this called enumerate:
>>> values = input().split()
1 2 3 4
>>> values
['1', '2', '3', '4']
>>>
>>> sequence = dict(enumerate(values))
>>> sequence
{0: '1', 1: '2', 2: '3', 3: '4'}
enumerate just goes through any iterable (such as a list of strings) and outputs the index of each item and the item itself in a tuple:
>>> for x in enumerate(values):
... print(x)
...
(0, '1')
(1, '2')
(2, '3')
(3, '4')
You can then call dict on an iterable of tuples (which is what enumerate produces) in order to turn them into a dictionary.
Of course, enumerate, like most things is zero-indexed, so you can also pass in a starting number if you would like to start a 1:
>>> sequence = dict(enumerate(values, 1))
>>> sequence
{1: '1', 2: '2', 3: '3', 4: '4'}
The problem with what you have
Let's say, as above, we have a list of strings. In order to match up numbers with each string in the list, we need something like the following:
>>> dict([(1, '1'), (2, '2')...])
Notice that I am passing one argument to dict: a list of tuples where each item in the list looks like (1, '1') and I have one container holding all of them.
Your attempt was the following:
>>> sequence = dict(x ,input().split())
This is interpreted probably something like (guessing on the x):
>>> dict(1, ['1', '2', '3'])
Which produces the following Traceback:
>>> dict(1, [1, 2, 3])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: dict expected at most 1 arguments, got 2
You are passing two arguments to dict, not one, which is what it expects.
It expects some kind of container with a bunch of things in it where the first element of each thing is mapped to the second element, such as the following:
>>> [(1, '1'), (2, '2')]

Resources