Reverse a List by Swap Ends - python

I am trying to reverse a list's order by finding three bugs in this function. This function is supposed to reverse the first and last elements of a list, the second and second to last elements, and so on. I believe I found two, but am having trouble fixing the line of list[j] = y.
def reverse(list):
"""Reverses elements of a list."""
for i in range(len(list)):
j = len(list) - i
x = list[i]
y = list[j-1]
list[i] = x
list[j] = y
l = ['a', 'b', 'c', 'd', 'e']
reverse(l)
print(l)

Homework I suspect...
But - we all need a break from homework. By looping over the whole list you're reversing it twice.
def reverse(list):
"""Reverses elements of a list."""
for i in range(len(list)/2):
j = i + 1
x = list[i]
y = list[-j]
list[-j] = x
list[i] = y
l = ['a', 'b', 'c', 'd', 'e']
l=reverse(l)
print(l)
resulting in
['e', 'd', 'c', 'b', 'a']

You have a couple problems. Your first problem is that you use list[j] = y instead of list[j-1] = x. You defined y correctly with j-1, but you should be changing list[j-1] to the other one, x. Another problem is that you are going from the beginning of the list all the way to the end. Once you get to more than half way through the list, you are undoing your work. You also don't need to use len(list)-i because you can just use -i. Here is the updated code:
def reverse(seq):
"""Reverses elements of a list."""
for i in range(len(seq)//2):
x = seq[i]
y = seq[-i-1]
seq[i] = y
seq[-i-1] = x
l = ['a', 'b', 'c', 'd', 'e']
reverse(l)
print(l)
Output:
['e', 'd', 'c', 'b', 'a']
You don't even need to define x and y. Instead, do this:
def reverse(seq):
"""Reverses elements of a list."""
for i in range(len(list)//2):
seq[i], seq[-i-1] = seq[-i-1], seq[i]
I also changed your naming. There's probably a better name than seq, but list is unacceptable because it conflicts with the built-in type.

Use this code:
l = ['a', 'b', 'c', 'd', 'e']
l=l[::-1]
print(l)
Why you want to complicate this simple construction? Or if you don't wanna do this on that way, try to use:
l.reverse()
function. Python has a lot of functions ready to use.

Related

How to remove elements from a list that appear less than k = 2?

I am trying to keep elements of a list that appear at least twice, and remove the elements that appear less than twice.
For example, my list can look like:
letters = ['a', 'a', 'b', 'b', 'b', 'c']
I want to get a list with the numbers that appear at least twice, to get the following list:
letters_appear_twice = ['a', 'b'].
But since this is part of a bigger code, I don't know exactly what my lists looks like, only that I want to keep the letters that are repeated at least twice. But for the sake of understanding, we can assume I know what the list looks like!
I have tried the following:
'''
letters = ['a', 'a', 'b', 'b', 'b', 'c']
for x in set(letters):
if letters.count(x) > 2:
while x in letters:
letters.remove(x)
print(letters)
'''
But this doesn't quite work like I want it too...
Thank you in advance for any help!
letters = ['a', 'a', 'b', 'b', 'b', 'c']
res = []
for x in set(letters):
if letters.count(x) >= 2:
res.append(x)
print(res)
Prints:
['b', 'a']
Using your code above. You can make a new list, and append to it.
new_list = []
for x in set(letters):
if letters.count(x) >= 2:
new_list.append(x)
print(new_list)
Output
['b', 'a']
Easier to create a new list instead of manipulating the source list
def letters_more_or_equal_to_k(letters, k):
result = []
for x in set(letters):
if letters.count(x) >= k:
result.append(x)
result.sort()
return result
def main():
letters = ['a', 'a', 'b', 'b', 'b', 'c']
k = 2
result = letters_more_or_equal_to_k(letters, k)
print(result) # prints ['a', 'b']
if __name__ == "__main__":
main()
If you don't mind shuffling the values, here's one possible solution:
from collections import Counter
letters = ['a', 'a', 'b', 'b', 'b', 'c']
c = Counter(letters)
to_remove = {x for x, i in c.items() if i < 2}
result = list(set(letters) - to_remove)
print(result)
Output:
['a', 'b']
You can always sort later.
This solution is efficient for lists with more than ~10 unique elements.

Counting in one list based on another

I'd like to count the elements in y that exist in the same order than in x. So for:
x = [a,b,c,d,e,f,g,h]
y = [c,a,b,z,k,f,g,d,s,t]
I'd want a function that returns me a 4 as 'a','b','c','d' are in y but not "e" I'd like a function that returns 4. y is random but it never has any duplicates. x is constant and len(x) = 8.
x and y are both lists of strings.
That means for:
x = [a,b,c,d,e,f,g,h]
y = [c,a,k,z,k,f,g,d,s,t]
I'd like the function to return 1.
I've tried something with a nested loop:
i = 0
h = 0
for s in x:
for t in y:
if s == t:
i = i + 1 #i is what I'm looking for in the end.
h = 0
elif h = 9:
break
else:
h = h + 1
My idea was to count the delta from one 't' to the next 't' but I can't get it to work properly as I just can't wrap my head around the required math.
Thanks a lot for your suggestions already and please enjoy your day!
In my previous answer, the code would throw an error when all elements of x were in y - so, here is my revised code:
print(([value in y for value in x] + [False]).index(False))
It does the job, but it's really hard to read. Let's split it up (the comments explain what each line does):
# This is our new list. In the previous code, this was a tuple - I'll get into
# that later. Basically, for each element in x, it checks whether that value is in
# y, resulting in a new list of boolean values. (In the last code, I used the map
# function with a lambda, but this is definitely more readable).
# For example, in OP's example, this list would look like
# [True, True, True, True, False, True, True, False]
new_list = [value in y for value in x]
# This is the step lacking with the old code and why I changed to a list.
# This adds a last False value, which prevents the index function from throwing an
# error if it doesn't find a value in the list (it instead returns the index this
# last False value is at). I had to convert from a tuple because
# you cannot add to a tuple, but you can add to a list. I was using a tuple in the
# last code because it is supposed to be faster than a list.
new_list_with_overflow = (new_list + [False])
# This is the final result. The index function gets the first element that is equal
# to False - meaning, it gets the index of the first element where x is not in y.
result = new_list_with_overflow.index(False)
# Finally, print out the result.
print(result)
Hopefully this explains what that one line is doing!
Some more links for reading:
What's the difference between lists and tuples?
How do I concatenate two lists in Python?
Python Docs on List Comprehensions
Here is another (arguably less readable) code snippet:
print((*(value in y for value in x), False).index(False))
A benefit of this code is that it uses tuples, so it is faster than the previous code, with the drawback of being a bit harder to understand. It also is not supported by older versions of python. However, I can leave this as an exercise for you to figure out! You might want to check out what the * does.
EDIT: This is the new answer. The code below only works when all elements of x are not in y - otherwise, it throws an error. Also, these solutions are just more readable.
A "pythonic" one-liner:
print(tuple(map(lambda value: value in y, x)).index(False))
Here's your function needed:
def counter(x, y):
print("_" * 50)
print("x: " + str(x))
print("y: " + str(y))
for i, letter in enumerate(x):
if letter not in y:
break
print("i: " + str(i))
return i
counter(
["a","b","c","d","e","f","g","h"],
["c","a","b","z","k","f","g","d","s","t"]
)
counter(
["a","b","c","d","e","f","g","h"],
["a","b","z","k","f","g","d","s","t"]
)
counter(
["a","b","c","d","e","f","g","h"],
["c","a","b","z","k","f","g","d","s","t", "e"]
)
return:
__________________________________________________
x: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
y: ['c', 'a', 'b', 'z', 'k', 'f', 'g', 'd', 's', 't']
i: 4
__________________________________________________
x: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
y: ['a', 'b', 'z', 'k', 'f', 'g', 'd', 's', 't']
i: 2
__________________________________________________
x: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
y: ['c', 'a', 'b', 'z', 'k', 'f', 'g', 'd', 's', 't', 'e']
i: 7
Using itertools.takewhile:
from itertools import takewhile
result = len(list(takewhile(lambda item : item in y, x)))
It takes every item in x starting from the first item in x until the condition lambda item : item in y is no longer satisfied.

How to create a list which only adds letters that are unique to adjacent indices in python?

I have created a function which randomly generates a list of the letters "a", "b", "c", and "d". I would like to create a new list which is the same as the first list but with any letters/items which are the same as the previous letter/item removed. Where I am having problems is referring to the previous letter in the list.
For example, if :
letterlist = ['a','a','a','b','b','a,',b']
then the output should be,
nondupelist = ['a','b','a','b']
The problem is that nodupeletterlist is the same as letterlist - meaning it's not removing items which are the same as the last - because I am getting the function to refer to the previous item in letterlist wrong. I have tried using index and enumerate, but I am obviously using them wrong because I'm not getting the correct results. Below is my current attempt.
import random
def rdmlist(letterlist, nodupeletterlist):
for item in range(20):
rng = random.random()
if rng < 0.25:
letterlist.append("a")
elif 0.25 <= rng and rng < 0.5:
letterlist.append("b")
elif 0.5 <= rng and rng < 0.75:
letterlist.append("c")
else:
letterlist.append("d")
for letter in letterlist:
if letter != letterlist[letterlist.index(letter)-1]:
nodupeletterlist.append(letter)
else:
pass
return
letterlist1 = []
nodupeletterlist1 = []
rdmlist(letterlist1, nodupeletterlist1)
EDIT:
This is what I ended up using. I used this solution simply because I understand how it works. The answers below may provide more succinct or pythonic solutions.
for index, letter in enumerate(letterlist, start=0):
if 0 == index:
nodupeletterlist.append(letter)
else:
pass
for index, letter in enumerate(letterlist[1:], start = 1):
if letter != letterlist[index-1]:
nodupeletterlist.append(letter)
else:
pass
for i, letter in enumerate(([None]+letterlist)[1:], 1):
if letter != letterlist[i-1]:
nodupeletterlist.append(letter)
You can use itertools.groupby:
import itertools
nodupeletterlist = [k for k, _ in itertools.groupby(letterlist)]
Solution without using itertools, as requested in the comments:
def nodupe(letters):
if not letters:
return []
r = [letters[0]]
for ch in letters[1:]:
if ch != r[-1]:
r.append(ch)
return r
nodupeletterlist = nodupe(letterlist)
A fixed version of the proposed "working solution":
def nodupe(letters):
if not letters:
return []
r = [letters[0]]
r += [l for i, l in enumerate(letters[1:]) if l != letters[i]]
return r
nodupeletterlist = nodupe(letterlist)
You can also simplify your random generator a bit, by using random.choices:
import random
chars = 'abcd'
letterlist = random.choices(chars, k=20)
or by using random.randint:
import random
start, end = ord('a'), ord('d')
letterlist = [chr(random.randint(start, end)) for _ in range(20)]
Here's what I came up with. Using random.choices() would be better than what I have below, but same idea. doesn't involve itertools
>>> li_1 = [random.choice("abcdefg") for i in range(20)]
>>> li_1
['c', 'e', 'e', 'g', 'b', 'd', 'b', 'g', 'd', 'c', 'e', 'g', 'e', 'c', 'd',
'e', 'e', 'f', 'd', 'd']
>>>
>>> li_2 = [li_1[i] for i in range(len(li_1))
... if not i or i and li_1[i - 1] != li_1[i]]
>>> li_2
['c', 'e', 'g', 'b', 'd', 'b', 'g', 'd', 'c', 'e', 'g', 'e', 'c',
'd', 'e', 'f', 'd']
The problem with the way that you are using letterlist.index(letter)-1 is that list.index(arg) returns the the index of the first occurrence of arg in list, in this case the letter. This means that if you have list = ["a", "b", "a"] and you run list.index("a") it will always return 0.
A way to do what you intend to (removing consecutive repetitions of letters) would be:
nodupeletterlist.append(letterlist[0])
for idx in range(1, len(letterlist)):
if letterlist[idx] != letterlist[idx-1]:
nodupeletterlist.append(letterlist[idx])
Do This:
L1 = ['a','a','a','b','b','c','d']
L2 = []
L2.append(L1[0])
for i in range(1,len(L1)):
if L1[i] != L1[i-1]:
L2.append(L1[i])
set() will create a set with only unique values,then the list() will convert it back to a a list containing values without any repetition.
I hope this helps...

insert element into list, move pre-existing elements

I'm trying to create a defined function (without list/string methods):
so far i have code to update at the given location, but I'm struggling to create code that can insert the variable at list location, and move existing elements over. I want the output to be ['a', 'b', 'c', 'd'] instead of ['a', 'b', 'c', 'e'].
Please note: this is for study, I do want to learn I'd prefer to be pointed in the right direction, not just provided the answer.
def insert(list1, var, i):
counter = 0
for i in my_list:
if counter == i:
my_list[i] = var
counter += 1
return list1
list = ['a', 'b', 'd', 'e']
func = insert(list, 'c', 2)
i've been working on code overnight (sorry for such a late reply i was on a plane)
I've come up with this code and I think it works, even though it's a little crude. can anyone give some input as to whether they see this being an issue or not?
def insert(list1, var, i):
new_list = []
counter = -1
for index in (list1[:-1]):
counter += 1
new_list.append(index)
if counter == (i-1):
new_list.append(var)
return new_list
list2= ['a', 'b', 'c', 'd', 'e', 'f', 'h']
func = insert(list2, 'g', 6)
print(func)
Using slicing, it's fairly easy to do this without use of any list methods at all. If you're using Python 3.5+ with additional unpacking generalizations, it even looks pretty:
def insert(list1, var, i):
# Reassign all elements at and beyond the insertion point to be the new
# value, and all but the last value previously there (so size is unchanged)
list1[i:] = var, *list1[i:-1]
or if mutating the caller's argument in place is bad, and you want to return a new value:
def insert(list1, var, i):
# Unpacking the components sliced as before to a brand new list
return [*list1[:i], var, *list1[i:-1]]
Without the unpacking generalizations, it's not quite as pretty, but still doable, either the argument mutating:
def insert(list1, var, i):
# Uglier without unpacking generalizations
list1[i], list1[i+1:] = var, list1[i:-1]
or new list constructing:
def insert(list1, var, i):
# An extra temporary without unpacking generalizations, but not ugly
return list1[:i] + [var] + list1[i:-1]
If you can slice or index, this won't help, but I wanted to point out the power of slice assignment and the new unpacking generalizations.
If you want a solution using only range and/or .append with no slicing:
def insert(list1, var, i):
for idx in range(len(list1)-1, i, -1):
list1[idx] = list1[idx-1]
list1[i] = var
or non-mutating:
def insert(list1, var, i):
newlist = []
for idx in range(i):
newlist.append(list1[idx])
newlist.append(var)
for idx in range(i, len(list1)-1):
newlist.append(list1[idx])
return newlist
def insert(list1, var, i):
new_list = list1[:i]
new_list.append(var)
new_list.extend(list1[i:])
return new_list
list = ['a', 'b', 'd', 'e']
func = insert(list, 'c', 2)
take the part of list1 up to i. This is called slicing
tack var onto new_list with append
tack on the rest of the list with extend
['a', 'b', 'c', 'd', 'e']

Python List removal issue [duplicate]

This question already has answers here:
for loop ignores some elements of my list [duplicate]
(2 answers)
Closed 9 years ago.
I am removing elements from a list. But using for to iterate through the elements , I can see that alternate elements are being accessed, hence all elements are not getting deleted.
Code
l = ['A','B','C','D','E','F']
for x in l:
l.remove(x)
print "My List:"+str(l)
print "Final List:"+str(l)
Output
My List:['B', 'C', 'D', 'E', 'F']
My List:['B', 'D', 'E', 'F']
My List:['B', 'D', 'F']
Final List:['B', 'D', 'F']
Can you please suugest any modification in this logic which would sort the issue. Alternatively if there is a better approach to this.
You should not modify the list you are iterating upon, else you are bound to get wierd results.
Rather iterate over a copy of list:
for x in l[:]:
l.remove(x)
An alternative to copying the list (and would make more sense imo given the context):
In [1]: from collections import deque
In [2]: l = deque(['A','B','C','D','E','F'])
In [3]: while len(l) > 0:
...: l.popleft()
...: print l
...:
deque(['B', 'C', 'D', 'E', 'F'])
deque(['C', 'D', 'E', 'F'])
deque(['D', 'E', 'F'])
deque(['E', 'F'])
deque(['F'])
deque([])
How about this? This does not make up any new list.
l = ['A','B','C','D','E','F']
while l: del l[0]; print("My List:"+str(l))
print("Final List:"+str(l))
Instead of deleting elements from the list, you could just slice it:
l = ['A','B','C','D','E','F']
for i in range(len(l) + 1):
print "My List:"+str(l[i:])
It seems what you want is just removing all the elements from the list. I think you can just use:
l = []
Or if there is a condition, you can use list comprehension:
[x for x in l in if x ...]

Categories