Python logic error? - python

Basically, I'm trying to flatten a list in my function but ignore that (you can also ignore the print functions I put in).
take x = [[1,2,3],4,5] to be my variable.
I call prob7(x) but the issue is that when type([1,2,3]) gets checked == list, it returns false. Why is that? I explicitly check this on the interpreter command line and it returns true. But inside the function, I get a false.
Just a bug that I missed because I'm sleepy or am I misunderstanding some part of the Python language? I run version 2.6 if it matters.
def prob7(list): # flatten a list
tempList = []
if list: # meaning if there are elements in the list and it is not empty
for i in list:
if type(i) != list:
print tempList,'if',i,type(i)==list
tempList.append(i)
else:
print tempList,'else',i
tempList.extend(prob7(i))
return tempList

Just not use 'list' as a variable name and use isinstance(var, list) instead of type(var) == list.
Please find corrected sample below.
def prob7(mylist): # flatten a list
tempList = []
if mylist: # meaning if there are elements in the list and it is not empty
for i in mylist:
if not isinstance(i, list):
print tempList, 'if', i, isinstance(i, list)
tempList.append(i)
else:
print tempList, 'else', i
tempList.extend(prob7(i))
return tempList
Or if you don't really required to use recursion and you don't care about values order then you can use something like this:
lVals = [[1,2,3],4,5, [1,[4,7]]]
def make_flat(mylist): # flatten a list
while any(isinstance(x, list) for x in mylist):
for i, val in enumerate(mylist):
if isinstance(val, list):
mylist.extend(mylist.pop(i))
break
return mylist
make_flat(lVals)
>>> [4, 5, 1, 2, 3, 1, 4, 7]

Artisom has your answer. In addtion, type checks are not very Pythonic. Duck typing often is the way to go. In case your elements are numbers only, the following does the job too, without explicit type checks but behavior checks:
def prob7(inlist): # flatten a list
outlist = []
for x in inlist:
try:
outlist += x
except TypeError:
outlist.append(x)
return outlist
Note that string elements in this implementation would behave like nested lists. Anyway, just wanted to illustrate what it means to expect behavior, not types.

Some alternate approaches:
# Iterative, but more functional-style
def flatten(a_list):
while any(isinstance(x, list) for x in a_list):
a_list = sum((x if isinstance(x, list) else [x] for x in a_list), [])
return a_list
# Using a generator recursively,
# then evaluating the generator to produce the list
# instead of explicitly appending each element.
def flatten_gen(a_list):
for x in a_list:
if isinstance(x, list):
for y in flatten_gen(x): yield y
else: yield x
def flatten(a_list): return list(flatten_gen(a_list))

The problem here is you are using a local variable name (list) that is the same as the global list type. You should change your variable name. Also, when checking types like that you can use the is operator.
type(l) is list
But here's my version of flatten.
def flatten(alist):
rv = []
for val in alist:
if isinstance(val, list):
rv.extend(flatten(val))
else:
rv.append(val)
return rv
This does not alter the original list, but returns a new list. This is consistent with most other patterns.

Related

Why does this function to convert all items in a list to strings not work?

I have a two dimensional array and I try to convert all items within each array to strings.
First I tried to use a function to_str and this approach didn't work. I do not understand why it doesn't work (it returns the input unchanged):
lst = [['test1', 555], ['test2', 3333]]
def to_str(item):
for i in item:
if not isinstance(i, str):
i = str(i)
return item
output = list(map(lambda item:to_str(item), lst))
output: [['test1', 555], ['test2', 3333]]
Then I used a list comprehension instead, and it worked:
output = list(map(lambda item: [str(i) for i in item], lst))
output: [['test1', '555'], ['test2', '3333']]
Why does the first approach using to_str not work?
You're trying to modify the iteration variable named i. This has no effect at all, you're just rewriting the value of a local variable that points to a list element, but not changing the list itself. For this to work you have to modify the list elements at each index position, something like this:
def to_str(item):
# iterate over the indexes in the item
for i in range(len(item)):
# we can remove this check, and simply convert everything to str
if not isinstance(item[i], str):
item[i] = str(item[i])
return item
Or we can create a new list with the results, instead of overwriting the original (but this will be equivalent to using a list comprehension, better use a list comprehension):
def to_str(item):
result = []
for element in item:
# we can remove this check, and simply convert everything to str
if not isinstance(element, str):
result.append(str(element))
else:
result.append(element)
return result
Also, regarding your second approach: it'd be better if you avoid using list, map and lambda, if what you want is to create a new list as a result use a list comprehension directly. This is a more idiomatic way to solve the problem, also removing the unnecessary string check:
[[str(i) for i in item] for item in lst]
=> [['test1', '555'], ['test2', '3333']]
Converted value i is not used anywhere in approach #1 and function just returns input
def to_str(item):
result = []
for i in item:
if not isinstance(i, str):
i = str(i)
result.append(i)
return result

Deleting a value from a list using recursion

I have defined a recursive function delete() that takes in two parameters:
lst : of type list
to_delete : a value that needs to be removed from the list
However, I have used the function del(), one that we haven't learned yet. So I am wondering whether there is a simpler way to output the same solution without the use of the function del()
This is my code:
def delete(lst, to_delete):
"""
parameters : lst of type list
to_delete : represents a value one wants to delete from the list
returns : another list with the same elements minus the ones one asks to delete
"""
if not lst:
return []
else:
if lst[0] == to_delete:
del lst[0]
return delete(lst[1:], to_delete)
return [lst[0]] + delete(lst[1:], to_delete)
print(delete([1,2,3,4,5,5,6,5,7,5], 5))
Output:
> [1,2,3,4,6] #where is 7 ?
7 is missing, because you return delete(lst[1:], to_delete) even when you just deleted lst[0]: You should use delete(lst[0:], to_delete) here.
The alternative is not to del the 0th element and just return delete(lst[1:], to_delete).
Instead of doing this recursively, you can also just use a list comprehension:
.
def delete(lst, to_delete):
return [element for element in lst if element != to_delete]
Using Recursion
Even with recursion, you don't need to use del:
def delete(lst, to_delete):
"""
parameters : lst of type list
to_delete : represents a value one wants to delete from the list
returns : another list with the same elements minus the ones one asks to delete
"""
if not lst:
return []
if lst[0] == to_delete:
return delete(lst[1:], to_delete)
return [lst[0]] + delete(lst[1:], to_delete)
As you can see, you are repeating yourself a bit (delete(lst[1:], to_delete) is used twice), so you can shorten this to:
def delete(lst, to_delete):
"""
parameters : lst of type list
to_delete : represents a value one wants to delete from the list
returns : another list with the same elements minus the ones one asks to delete
"""
if not lst:
return []
start = [] if lst[0] == to_delete else [lst[0]]
return start + delete(lst[1:], to_delete)
I don't know about its performance though.
No Recursion
If you don't need to use recursion, you can use list comprehensions for much less code:
def delete(lst, to_delete):
return [x for x in lst if x != to_delete]
In case you don't know list comprehensions well, this is logically equivalent to the following:
def delete(lst, to_delete):
res = []
for x in lst:
if x != to_delete:
res.append(x)
return res
EDIT: I missed it, but the reason you don't see 7 in the output is, that del lst[0] already removes the first value from the list and so, you are missing the "new" first value of the list.
You seem to be interested in recursion. Recursion is a functional heritage and so I will give you a glimpse of a functional perspective on the problem. Below, delete is a specialization of filter, which is a specialization of reduce, a simple recursive form -
def reduce (f, state = None, xs = [], i = 0):
if i >= len (xs):
return state
else:
return reduce \
( f
, f (state, xs[i], i)
, xs
, i + 1
)
def filter (f, xs = []):
return reduce \
( lambda acc, x, i:
acc + [x] if f (x) else acc
, []
, xs
)
def delete (q, xs = []):
return filter \
( lambda x: q != x
, xs
)
print (delete (5, [ 1, 2, 5, 3, 5, 5, 2, 3, 1, 5, 1 ]))
# [1, 2, 3, 2, 3, 1, 1]
print (delete ('x', 'abxcdxefxghxi'))
# ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']
Functional style is not the idiomatic style of Python. Those wishing to explore functional style in Python are not completely ignored however. You can find reduce and filter (among many other useful functions) tucked away in Python's functools module.
The definitions of reduce and filter here are my own. If you use functools, you'll want to read up on specific behaviors more closely.
It would be much simpler to recreate your list without the unwanted items:
def delete(lst, to_delete):
return [x for x in lst if x!=to_delete]
print(delete([1,2,3,4,5,5,6,5,7,5], 5))
# [1,2,3,4,6,7]
Correcting your code (but leaving recursion out of it) would look like this:
def delete(lst, to_delete):
"""
parameters : lst of type list
to_delete : represents a value one wants to delete from the list
returns : another list with the same elements minus the ones one asks to delete
"""
if not lst:
return []
else:
res = []
for item in lst:
if item == to_delete:
continue
else:
res.append(item)
return res
which has the same result.
Finally, the recursive option which I highly discourage for this application is the following:
def delete(lst, to_delete, res=[]):
"""
parameters : lst of type list
to_delete : represents a value one wants to delete from the list
returns : another list with the same elements minus the ones one asks to delete
"""
if not lst:
return res
else:
item = lst[0]
if item != to_delete:
res.append(item)
return delete(lst[1:], to_delete, res=res)

matching list structure other than map() in python

I am trying to write a recursive function that will perform some action on every object within the list and then output a new list that matches the structure of the processed list exactly. I had some help from users here, but in my example i tried strings which happen to be iterable and work well with map(). However when I am applying it to other data types like int that are not iterable, i get an error:
def process_list(_list):
new_list = []
for x in _list:
if is_list(x):
new_list.append(process_list(x))
else:
new_list.append(map(lambda y: y + 1, x))
return new_list
def is_list(l):
return type(l) == types.ListType
_list = [[0,1,2],[0,1,2],[0,1,2]],[0,1,2]]
i am using Integers as an example here because they are not iterable and i am expecting that map() will fail on a list of integers. Imagine any other data type that is not iterable. Is there another way to define this that will let me perform some function(s) on objects within the list and then create an output list that matches the input list?
thank you,
You are trying to iterate (by using the map() function) over x, which is not iterable in most cases. The reason you can do it for strings is because they are iterable objects in themselves, which is why you didn't run into this right away. You really don't need to use map() at all in your for loop... you could just do
for x in _list:
if is_list(x):
new_list.append(process_list(x))
else:
new_list.append(x+1)
Really the best thing you could do is use map directly:
def process_list(_list):
return map( lambda x: process_list(x) if type(x)==list else x+1, _list )
Try modifying the else clause so it performs simple addition on the item. No map or lambda required.
import types
def process_list(_list):
new_list = []
for x in _list:
if is_list(x):
new_list.append(process_list(x))
else:
new_list.append(x+1)
return new_list
def is_list(l):
return type(l) == types.ListType
_list = [[[0,1,2],[0,1,2],[0,1,2]],[0,1,2]]
print process_list(_list)
Result:
[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [1, 2, 3]]
If you want calls to process_list to look more like map, you can require an additional parameter, which indicates what kind of processing should be done.
import types
def process_list(func, _list):
new_list = []
for x in _list:
if is_list(x):
new_list.append(process_list(func, x))
else:
new_list.append(func(x))
return new_list
def is_list(l):
return type(l) == types.ListType
_list = [[[0,1,2],[0,1,2],[0,1,2]],[0,1,2]]
print process_list(lambda x: x+1, _list)
check with isinstance to see what the type is and base your logic on that:
def process_list(_list):
new_list = []
for x in _list:
if isinstance(x, list):
new_list.append(process_list(x))
elif isinstance(x, (int, float)):
new_list.append(x+1)
else:
new_list.append(map(lambda y: y + 1, x))
return new_list
You can also use collections.Iterable to find iterable elements after you have checked for a list, if you wanted to only call map on strings you would use isinstance(x, basestring) what you check for all depends on what you are trying to achieve:
from collections import Iterable
def process_list(_list):
new_list = []
for x in _list:
if isinstance(x, list):
new_list.append(process_list(x))
elif isinstance(x, Iterable):
new_list.append(map(lambda y: y + 1, x))
else:
new_list.append(x+1)
return new_list

Python Simple program iteration through list

I'm new to programming and I'm making a simple program to test.
Here is the code:
list1 = [1,2,d,,t,h,7,8]
for x in list1:
if x ==
I'm trying to iterate in my list and check to see which item is a string and which is
a number(I know its basic, but im new). So what would be a correct way to write this line
of code. All suggestions would help
In Python, use the builtin isinstance(variable, type) to test whether a variable is of a given type.
The type variable can also be a tuple of several types to test against.
Your list is a little messed up. If those letters are supposed to be strings, it should look like this:
list1 = [1,2,'d','t','h',7,8]
Otherwise they are referring to variables.
To check if number
for x in list1:
if isinstance(x, int):
...
To check if string
for x in list1:
if isinstance(x, str):
...
Combination
for x in list1:
if x.isinstance(x, int):
....
elif isinstance(x, str)L
....
This should print the type of each element in your list
for item in list1:
print type(item)
Assuming your list contains purely numbers and strings:
for x in list1:
if type(x) == str:
# do something
else:
# do something for numbers
Since everyone is giving answers similar to my old one (see below), I'll try something else.You can easily make 2 lists that separate ints and strings with Python's list comprehensions:
strings = [x for x in list1 if isinstance(x, str)]
intlist = [x for x in list1 if isinstance(x, int)]
Using list comprehensions made the code more compact and easier to understand.
Old response:
list1 = [1, 4, 5, '5', '3', 3, 'a']
for x in list1:
if(isinstance(x, str)):
print(str(x) + ' is a string.')
else:
print(str(x) + ' is not a string.')
You could try this one,
list1=[1,2,'d','t','h',7,8]
for data in list1:
if type(data) is str:
#do something with it
elif type(data) is int:
#do something with it
you can try something like this or any of those methods mentioned from other answers in the python interpreter to see it works -
>> type(1) is str
and you would get
>> False

Do sum in a nested list of a given list

Than I want to write a function that achieves this purpose, if given:
t = [1,2,[2,2],[3,3]]
I want a function that makes t be
[1,2,4,6]. Here, is my code in Python:
t=[1,2,[2,2],[3,3]]
def nested_sum(t):
for x in t:
if type(t[x])=='int':
t[x]=t[x]
else:
t[x]=['sum(t[x])']
return t
nested_sum(t)
I got the error message that
Traceback (most recent call last):
File "nested_sum.py", line 11, in <module>
nested_sum(t)
File "nested_sum.py", line 5, in nested_sum
if type(t[x])=='int':
TypeError: list indices must be integers, not list
I am not quite sure about the mistake(s) I made. Since my logic is that:
type(t[0])=1 which is of "Int" type and type(t[2])=[2,2] which is of "List" type and I think these fulfills the "if...else..." statement.
Any help would be appreciated for pointing my mistakes. Thank you so much in advance.
You can put it in a single list comprehension using isinstance:
[sum(x) if isinstance(x,list) else x for x in t]
[1, 2, 4, 6]
You could use collection.Iterable which will work on any iterable like tuples etc..
t = [1,2,[2,2],[3,3],(4,5)]
from collections import Iterable
print [sum(x) if isinstance(x, Iterable) else x for x in t]
[1, 2, 4, 6, 9]
In the list comprehension, if x is an iterable/list we add the sum of the subelements or else we just take the element x
Using your own code, you would use enumerate to access the list elements using their index:
def nested_sum(t):
for ind, x in enumerate(t):
if type(t[ind])== int: # int not "int"
t[ind] = x
else:
t[ind] = sum(t[ind])
return t
In the code ind is the index of each subelement and x is the actual subelement
The problem with your code is that you are looping through the items of your list and not through the indexes. For making it work you should change your code like this:
def nested_sum(t):
for x in range(len(t)):
if type(t[x]) == int:
t[x] = t[x]
else:
t[x] = sum(t[x])
return t
Note also that int in type(t[x]) == int and sum(t[x]) in your else clause should not be strings.
Use sum() when x is a list, append it to res otherwise
t=[1,2,[2,2],[3,3]]
def nested_sum(l):
res = []
for x in l:
if type(x) == type([]):
res.append(sum(x))
elif type(x) == type(1):
res.append(x)
return res
nested_sum(t)
def nested_sum(t):
for index, item in enumerate(t):
if type(item) == list:
t[index] = sum(item)
return t
Explanation:
enumerate(t) returns (0, t[0]), (1, t[1]) etc.
Then, for each item in t, check if item is a list. If it is, replace it with the sum of all elements in that list. This is done in t[index] = sum(item).
An answer would be:
def nested_sum(mixed_list):
res = list()
for element in mixed_list:
try:
a = sum(element)
except TypeError:
a = element
res.append(a)
return res
Which works fine if the list contains numbers and lists of numbers.

Categories

Resources