Related
I'm writing recursive code to reverse a list in-place. This is what I have, but the code fails at assigning the slice to the input list, giving the error 'NoneType' object is not iterable:
def reverse(a):
if len(a) == 1:
return
temp = a[0]
a[0] = a[-1]
a[-1] = temp
a[1:-1] = reverse(a[1:-1])
return a
I have read that slices are not objects, so I attempted to use list() and range() to convert the returned slice, but by doing that, I still get the same error. Is there a way of assigning a slice to another slice of an array?
All your problem is return which sends None and gives 'NoneType' object is not iterable
You need return a. And rest of code start working.
BTW: You could use <= 1 instead of == 1 to work with empty list too.
def reverse(a):
if len(a) <= 1: # <-- `<=` to work with empty list
return a # <-- has to return `a`
temp = a[0]
a[0] = a[-1]
a[-1] = temp
#a[0], a[-1] = a[-1], a[0]
a[1:-1] = reverse(a[1:-1])
return a
# --- tests ---
print(reverse([1,2,3,4,5]))
print(reverse([1]))
print(reverse([]))
EDIT: To replace elements you can also use
a[0], a[-1] = a[-1], a[0]
EDIT: Using slice to create list with single element and list + list to join lists you can do:
def reverse(a):
if len(a) <= 1:
return a
return a[-1:] + reverse(a[1:-1]) + a[:1]
EDIT: You can even write it as lambda function (if you like functional programming which often uses recursion)
reverse = lambda a: a if len(a) <= 1 else a[-1:] + reverse(a[1:-1]) + a[:1]
# --- tests ---
print(reverse([1,2,3,4,5]))
print(reverse([1,2,3,4]))
print(reverse([1]))
print(reverse([]))
Reverse in-place
We will just keep on swapping start and end of list till both of them meet
def reverse(a,start=0,end=len(a)-1):
if start==end:return
a[start],a[end] = a[end],a[start]
reverse(a,start+1,end-1)
# return a # if it is inplace no need to return
a = [1,2,3,4,5]
reverse(a)
print(a)
[5, 4, 3, 2, 1]
I guess maybe this algorithm would be a bit closer to what you have in mind:
Here we are passing an index to the function you've already designed.
We would then increment that index using (-~index or simply index + 1).
def reverse(a, index=0):
if len(a) == 0:
return []
if len(a) == 1:
return a
if index == len(a) - 1:
return a
temp = a[0]
a[0] = a[-1]
a[-1] = temp
return reverse(a, index + 1)
print(reverse([1, 2, 3, 5, 6, 7, 8, 9]))
print(reverse([100, 90, -10, 1200, 1, 2, 3, 5, 6, 7, 8, 9]))
The three statements that you already have in your code are much more "algorithmic" (in all languages) than using Python's swap (a, b = b, a):
temp = a[0]
a[0] = a[-1]
a[-1] = temp
Output
[9, 2, 3, 5, 6, 7, 8, 1]
[9, 90, -10, 1200, 1, 2, 3, 5, 6, 7, 8, 100]
I have a list, let us call it l = [1,2,3,7,8,9,10,7]. Given this list l, I am trying to remove the first occurrence of the number 7 without using the .pop() or .remove() built-in functions.
I have tried
def remove_item(l, item_to_remove):
newlst = []
for item in l:
if item != item_to_remove:
newlst.append(item)
return newlst
However, this removes all instances of the item I am trying to remove when in fact I only want to remove the very first instance of that said specific item. Does anyone have some tips on how to accomplish this??
You only need to take care that the removing part of your code doesn't run twice.
lst = [1,2,3,7,8,9,10,7] # [1, 2, 3, 7, 8, 9, 10, 7]
print(lst)
for i in range(len(lst)):
if lst[i] == 7:
del lst[i] # [1, 2, 3, 8, 9, 10, 7]
break
print(lst)
It does exactly the same as the following:
lst = [1,2,3,7,8,9,10,7]
print(lst) # [1, 2, 3, 7, 8, 9, 10, 7]
for i in range(len(lst)):
if lst[i] == 7:
lst.pop(i)
break
print(lst) # [1, 2, 3, 8, 9, 10, 7]
as well as this
lst = [1,2,3,7,8,9,10,7]
print(lst) # [1, 2, 3, 7, 8, 9, 10, 7]
for i in range(len(lst)):
if lst[i] == 7:
lst.remove(lst[i])
break
print(lst) # [1, 2, 3, 8, 9, 10, 7]
Overview of the used methods:
del list[i] - The del statement can also be used to remove slices from a list
list.pop - remove and return item at index (default last). Raises IndexError if list is empty or index is out of range.
list.remove - remove first occurrence of value.Raises ValueError if the value is not present.
You just need to add a little logic to it. I add a looking variable which signifies that we havent found the entry were looking for. Heres the code
def remove_item(l, item_to_remove):
newlst = []
looking = True
for item in l:
if item != item_to_remove or not looking:
newlst.append(item)
else:
looking = False
return newlst
list = [1,3,4,5,6,7,3,10]
print(remove_item(list, 3))
which returns [1, 4, 5, 6, 7, 3, 10]
Very wasteful, but here you go, a solution:
def remove_first(sequence, element):
return sequence[:sequence.index(element)] + sequence[sequence.index(element)+1:]
Then you can:
>>> remove_first(["a", "b", "a", "c"], "a"):
['b', 'a', 'c']
index returns the index of the first found occurrence of an element.
The rest is sequence splicing and catenation.
Of course, you could generalize this to remove(sequence, element, n) to remove the n-th found element.
EDIT: I just stated falsely that index also supports that. Statement removed.
Or you could choose to mutate the input, but for one, returning the output is cleaner, and you could not have a general "sequence" argument, as not all sequences are mutable. See the tuple type.
.index(x) returns the first index location of x within the list, so just delete it. If x is not found, it returns ValueError.
my_list = [1, 2, 3, 7, 8, 9, 10, 7]
val = 7
if val in my_list:
del my_list[my_list.index(val)]
>>> my_list
[1, 2, 3, 8, 9, 10, 7]
Signature: my_list.index(value, start=0, stop=9223372036854775807, /)
Docstring:
Return first index of value.
Raises ValueError if the value is not present.
Welcome to StackOverflow!
Minor modification to your code,.
I would prefer remove but here is your modified code to do the required job
def remove_item(l, item_to_remove):
newlst = []
for item in l:
if item != item_to_remove:
newlst.append(item)
else:
return newlst + l[len(newlst) + 1 :]
return newlst
In Python, you can add the lists. Using list comprehensions, you select sub-lists(l[len(newlst) + 1 :]).
Testing
>>> list = [1,3,4,5,6,7,3,10]
>>> print(remove_item(list, 3))
[1, 4, 5, 6, 7, 3, 10]
lst = [1,2,3,7,8,9,10,7]
new_lst = lst[:lst.index(7)] + lst[lst.index(7) + 1:]
new_lst
[1, 2, 3, 8, 9, 10, 7]
Similar idea to CEWeinhauer's solution, but one which takes advantage of Python features to minimize overhead once we've found the item to remove:
def remove_item(l, item_to_remove):
newlst = []
liter = iter(l) # Make single pass iterator, producing each item once
for item in liter:
if item == item_to_remove: # Found single item to remove, we're done
break
newlst.append(item) # Not found yet
newlst += liter # Quickly consume all elements after removed item without tests
return newlst
The above works with any input iterable in a single pass, so it's better if the input might not be a list and/or might be huge. But it's admittedly more complex code. The much simpler solution is to just find the element with index and remove it. It might be slightly slower in some cases, since it's two O(n) steps instead of just one, but it uses C built-ins more, so it's likely to be faster in practice:
def remove_item(l, item_to_remove):
newlst = list(l)
del newlst[newlst.index(item_to_remove)]
return newlst
I understand this question has been asked before but I haven't seen any that answer it in a way without splitting the list.
say I have a list:
num = [1,2,3,4,5,6]
I want to create a function:
rotate(lst, x):
So that if I call rotate(num, 3) it will globally edit the list num. That way when I later call print(num) it will result in [4,5,6,1,2,3].
I understand that I could write the function something like:
rotate(lst, x):
return [lst[-x:] + lst[:-x]
But I need to do this function without a return statement, and without splitting the list. What I'm thinking would work would be to put the last value of the list into a variable: q = lst[-1] and then from there create a loop that runs x amount of times that continues to move the values towards the end of the list and replacing the 0th position with whatever is stored in q.
One more thing. If I call rotate(lst, -3) then instead of rotating to the "right" it would have to rotate to the "left".
I'm new to python and having trouble wrapping my mind around this concept of manipulating lists. Thank you everyone for your time and effort. I hope this problem was clear enough.
You can use slicing assignment to modify your current strategy to do what you want. You're already generating the rotated list correctly, just modify the list in place with lst[:] = ...
def rotate(lst, x):
lst[:] = lst[-x:] + lst[:-x]
Example in the interactive interpreter:
>>> l = [1, 2, 3, 4, 5, 6]
>>> def rotate(lst, x):
... lst[:] = lst[-x:] + lst[:-x]
...
>>> rotate(l, 2)
>>> l
[5, 6, 1, 2, 3, 4]
Now rotate it backwards:
>>> rotate(l, -2)
>>> l
[1, 2, 3, 4, 5, 6]
>>> rotate(l, -2)
>>> l
[3, 4, 5, 6, 1, 2]
See this answer on a different question: https://stackoverflow.com/a/10623383/3022310
Here is a solution using a double-ended queue.
As required, it modifies the list in place, neither uses return nor uses chunks of the list.
from collections import deque
def rotate(lst, x):
d = deque(lst)
d.rotate(x)
lst[:] = d
num = [1,2,3,4,5,6]
rotate(num,3)
print(num)
rotate(num,-3)
print(num)
produces
[4, 5, 6, 1, 2, 3]
[1, 2, 3, 4, 5, 6]
Please have a look at PMOTW's tutorial on deque
def rotate(lst, num):
copy = list(lst)
for (i, val) in enumerate(lst):
lst[i] = copy[i - num]
Try:
num = [1,2,3,4,5,6]
def rotate(lst,x):
copy = list(lst)
for i in range(len(lst)):
if x<0:
lst[i+x] = copy[i]
else:
lst[i] = copy[i-x]
rotate(num, 2)
print num
Here is a simple method using pop and insert on the list.
num = [1,2,3,4,5,6]
def rotate(lst, x):
if x >= 0:
for i in range(x):
lastNum = lst.pop(-1)
lst.insert(0, lastNum)
else:
for i in range(abs(x)):
firstNum = lst.pop(0)
lst.append(firstNum)
return
print num #[1, 2, 3, 4, 5, 6]
rotate(num, 2)
print num #[5, 6, 1, 2, 3, 4]
rotate(num, -2)
print num #[1, 2, 3, 4, 5, 6]
I believe this satisfies all requirements. The idea is from the Programming Pearls book(http://goo.gl/48yJPw). To rotate a list we can reverse it and then reverse sublists with the rotating index as pivot.
def rotate(num, rot):
if rot < 0:
rot = len(num) + rot
rot = rot - 1
num.reverse()
for i in range(rot/2 + 1):
num[i], num[rot-i] = num[rot-i], num[i]
for i in range(1, (len(num) - rot)/2):
num[rot+ i], num[len(num) - i] = num[len(num) - i], num[rot+ i]
#Testing...
num = range(1, 10)
rot = -1
print num
rotate(num, rot)
print num
I am learning python and have a working code (shown below). However, I would like to know if there's a better way to rewrite it below.
What I am trying to do is to match list A that is passed to a method against a predefined list B. If an item in list A contains an item in list B, I'd like to move it to the end of list A. Here's an example:
# example 1
a = [1, 2, 3, 4, 5]
sanitized_list = sanitize(a) # [3, 4, 5, 1, 2]
# example 2
a = [3, 6, 1, 7, 4]
sanitized_list = sanitize(a) # [3, 6, 7, 4, 1]
def sanitize(arg):
# predefined list
predefined_list = [1, 2]
for item in predefined_list:
try:
# check to see if 'arg' contain any item
# in the predefined list
i = arg.index(item)
# save the value of arg[i]
j = arg[i]
# remove "j" from "arg"
arg.pop(i)
# append item to end of "arg"
arg.append(j)
except ValueError:
pass
return arg
You can use sorting; simply sort on the result of a containment test; False is sorted before True, but your order otherwise remains stable. You can make b a set to make the containment test faster:
def sanitize(lst):
# predefined list
b = {1, 2}
return sorted(lst, key=lambda v: v in b)
I made b a set here, but that is optional (but faster if you do).
So for each element in lst, if that element is in b sort it after anything that is not in b, but keep their relative order otherwise.
Note that rather than list I used a different name for the argument here; you don't want to shadow the built-in type.
Demo:
>>> a = [1, 2, 3, 4, 5]
>>> def sanitize(lst):
... # predefined list
... b = {1, 2}
... return sorted(lst, key=lambda v: v in b)
...
>>> sanitize(a)
[3, 4, 5, 1, 2]
Better to create always a new list:
def sanitize(l):
b = set([1, 2])
result = ([], [])
for item in l:
result[item in b].append(item)
result[0].extend(result[1])
return result[0]
Operations between lists are easier if transform them into sets.
a = [1, 2, 3, 4, 5]
def sanitize(l):
b = [1, 2]
try:
sanitized = list(set(l) - set(b)) + b
return sanitized
except ValueError:
return None
I hope to write the join_lists function to take an arbitrary number of lists and concatenate them. For example, if the inputs are
m = [1, 2, 3]
n = [4, 5, 6]
o = [7, 8, 9]
then we I call print join_lists(m, n, o), it will return [1, 2, 3, 4, 5, 6, 7, 8, 9]. I realize I should use *args as the argument in join_lists, but not sure how to concatenate an arbitrary number of lists. Thanks.
Although you can use something which invokes __add__ sequentially, that is very much the wrong thing (for starters you end up creating as many new lists as there are lists in your input, which ends up having quadratic complexity).
The standard tool is itertools.chain:
def concatenate(*lists):
return itertools.chain(*lists)
or
def concatenate(*lists):
return itertools.chain.from_iterable(lists)
This will return a generator which yields each element of the lists in sequence. If you need it as a list, use list: list(itertools.chain.from_iterable(lists))
If you insist on doing this "by hand", then use extend:
def concatenate(*lists):
newlist = []
for l in lists: newlist.extend(l)
return newlist
Actually, don't use extend like that - it's still inefficient, because it has to keep extending the original list. The "right" way (it's still really the wrong way):
def concatenate(*lists):
lengths = map(len,lists)
newlen = sum(lengths)
newlist = [None]*newlen
start = 0
end = 0
for l,n in zip(lists,lengths):
end+=n
newlist[start:end] = list
start+=n
return newlist
http://ideone.com/Mi3UyL
You'll note that this still ends up doing as many copy operations as there are total slots in the lists. So, this isn't any better than using list(chain.from_iterable(lists)), and is probably worse, because list can make use of optimisations at the C level.
Finally, here's a version using extend (suboptimal) in one line, using reduce:
concatenate = lambda *lists: reduce((lambda a,b: a.extend(b) or a),lists,[])
One way would be this (using reduce) because I currently feel functional:
import operator
from functools import reduce
def concatenate(*lists):
return reduce(operator.add, lists)
However, a better functional method is given in Marcin's answer:
from itertools import chain
def concatenate(*lists):
return chain(*lists)
although you might as well use itertools.chain(*iterable_of_lists) directly.
A procedural way:
def concatenate(*lists):
new_list = []
for i in lists:
new_list.extend(i)
return new_list
A golfed version: j=lambda*x:sum(x,[]) (do not actually use this).
You can use sum() with an empty list as the start argument:
def join_lists(*lists):
return sum(lists, [])
For example:
>>> join_lists([1, 2, 3], [4, 5, 6])
[1, 2, 3, 4, 5, 6]
Another way:
>>> m = [1, 2, 3]
>>> n = [4, 5, 6]
>>> o = [7, 8, 9]
>>> p = []
>>> for (i, j, k) in (m, n, o):
... p.append(i)
... p.append(j)
... p.append(k)
...
>>> p
[1, 2, 3, 4, 5, 6, 7, 8, 9]
>>>
This seems to work just fine:
def join_lists(*args):
output = []
for lst in args:
output += lst
return output
It returns a new list with all the items of the previous lists. Is using + not appropriate for this kind of list processing?
Or you could be logical instead, making a variable (here 'z') equal to the first list passed to the 'join_lists' function
then assigning the items in the list (not the list itself) to a new list to which you'll then be able add the elements of the other lists:
m = [1, 2, 3]
n = [4, 5, 6]
o = [7, 8, 9]
def join_lists(*x):
z = [x[0]]
for i in range(len(z)):
new_list = z[i]
for item in x:
if item != z:
new_list += (item)
return new_list
then
print (join_lists(m, n ,o)
would output:
[1, 2, 3, 4, 5, 6, 7, 8, 9]