Traversing a Python list and making in-place changes - python

My task is to remove all instances of one particular element ('6' in this example) and move those to the end of the list. The requirement is to traverse a list making in-line changes (creating no supplemental lists).
Input example: [6,4,6,2,3,6,9,6,1,6,5]
Output example: [4,2,3,9,1,5,6,6,6,6,6]
So far, I have been able to do this only by making supplemental lists (breaking the task's requirements), so this working code is not allowed:
def shift_sixes(nums):
b = []
c = 0
d = []
for i in nums:
if i == 6:
b.insert(len(nums),i)
elif i != 6:
c = c +1
d.insert(c,i)
ans = d + b
return ans
I've also tried list.remove() and list.insert() but have gotten into trouble with the indexing (which moves when I insert() then move the element to the end): For example -
a = [6,4,6,2,3,6,9,6,1,6,5]
def shift_sixes(nums):
for i in nums:
if i == 6:
nums.remove(i)
nums.insert(nums[len(nums)-1], 0)
elif i != 0:
i
shift_sixes(a)
Additionally, I have tried to use the enumerate() function as follows, but run into problems on the right hand side of the b[idx] assigment line:
for idx, b in enumerate(a):
a[idx] = ???
Have read other stackoverflow entries here, here and here, but they do not tackle the movment of the element to one end.
Would appreciate any help on this list traversal / inplace switching issue. Many thanks.
EDIT
#eph - thank you. this is indeed an elegant response. I am sure it will pass my 'no new list' requirement? I surely intend to learn more about lambda and its uses
#falsetru - thank you for the reminder of the append/pop combination (which I tried to do in my original query via list.remove() and list.insert()
#tdelaney - thank you as well. somehow your response is closest to what I was attempting, but it seems not to pass the test for [0, 0, 5].

It is a bad idea to modify list while traverse. You can either make a copy to traverse, or generate a new list during traverse.
In fact, the question can be done in many ways, such as:
>>> a.sort(key = lambda i: i == 6)
>>> a
[4, 2, 3, 9, 1, 5, 6, 6, 6, 6, 6]

Iterating the list reverse way, pop the element if it's 6, then append it.
xs = [6,4,6,2,3,6,9,6,1,6,5]
for i in range(len(xs)-1, -1, -1): # 10 to 0
if xs[i] == 6:
xs.append(xs.pop(i))

Why not try something like this?
Basically, the approach is to first count the number of values.
If 0, then returns (since Python produces a ValueError if the list.index method is called for an element not in the list).
We can then set the first acceptable index for the value to be the length of the list minus the number of occurrences it exists in the list.
We can then combine list.pop/list.append to then traverse the list until all the values desired occur at the end of the list.
def shift_value(lst, value):
counts = lst.count(value) # 5
if not counts:
return lst
value_index = len(lst) - counts
index = lst.index(value)
while index != value_index:
lst.append(lst.pop(index))
index = lst.index(value)
return lst
lst = [6,4,6,2,3,6,9,6,1,6,5]
print(shift_value(lst, 6))
EDIT: This is horribly inefficient, better answer suggested above.
This requires O(n^2) time, rather than O(n) time.

The key term here is "In Line". The way you do that is move num[i] = num[i+1] for each i to the end of the list.
def shift_sixes(num):
for i, val in enumerate(num):
if val == 6:
# shift remaining items down
for j in range(i,len(num)-1):
num[j] = num[j+1]
# add 6 at the end
num[-1] = 6
return num
print(shift_sixes([1,9,4,6,2,7,8,6,2,2,6]))
print(shift_sixes([1,2,3]))
print(shift_sixes([6]))
print(shift_sixes([3]))

Use two runners. First from front to end checking for 6s, second from end to front pointing to last item that's not a 6. Keep swapping (a[i+1], a[i] = a[i], a[i+1]) until they meet.
Catch: this is not stable like in a stable sort. But I don't see that as a requirement.
Will try to write working code when in front of a python interpreter with a keyboard.

In case you need a stable sort (i.e. order of elements that are not 6 should remain the same), then the solution is:
def move_to_end(data, value):
current = 0 # Instead of iterating with for, we iterate with index
processed = 0 # How many elements we already found and moved to end of list
length = len(data) # How many elements we must process
while current + processed < length: # While there's still data to process
if data[current] == value: # If current element matches condition
data.append(data.pop(current)) # We remove it from list and append to end
processed += 1 # Our index remains the same since list shifted, but we increase number of processed elements
else: # If current element is not a match
current += 1 # We increase our index and proceed to next element
if __name__ == '__main__':
print
print 'Some testing:'
print
for test_case in (
[1, 9, 4, 6, 2, 7, 8, 6, 2, 2, 6], # Generic case
[6, 6, 6, 6], # All items are 6
[1, 7], # No items are 6
[], # No items at all
):
print 'Raw:', test_case
move_to_end(test_case, 6)
print 'Becomes:', test_case
print
Note that this solution retains the order of not only non-matching elements, but of matching elements as well. So for example, if you change the check condition from "equal to 6" to "is an even number", all elements matching the condition will be moved to the end of list while retaining their order among themselves.

Why not keep it simple?
a = [6,4,6,2,3,6,9,6,1,6,5]
def shift_sixes(nums):
for i in range(0,len(nums)):
if nums[i] == 6:
nums.append(nums.pop(i))
>>> shift_sixes(a)
>>> a
[3, 9, 1, 5, 2, 4, 6, 6, 6, 6]

Related

How to append to a list two numbers from within the list that add up to a number in the list?

First, I want to find the highest number in the list which is the second number in the list, then split it in two parts. The first part contains the 2nd highest number, while the second part contains the number from the list that sums to the highest number. Then, return the list
eg: input: [4,9,6,3,2], expected output:[4,6,3,6,3,2] 6+3 sums to 9 which is the highest number in the list
Please code it without itertools.
python
def length(s):
val=max(s)
s.remove(val)
for j in s:
if j + j == val:
s.append(j)
s.append(j)
return s
Here's what I have but it doesn't return what the description states.
Any help would be appreciated as I spent DAYS on this.
Thanks,
The main issue in your code seems to be that you are editing the list s whilst iterating through it, which can cause issues with the compiler and is generally just something you want to avoid doing in programming. A solution to this could be iterating through a copy of the original list.
The second problem is that your program doesn't actually find the second biggest value in the list, just a value which doubles to give you the biggest value.
The final problem (which I unfortunately only noticed after uploading what I thought was a solution) is that the split values are appended to the end of the list rather than to the position where originally the largest value was.
Hopefully this helps:
def length(array):
val = max(array)
idx = array.index(val) # gets the position of the highest value in the array (val)
array.remove(val)
for i in array.copy(): # creates a copy of the original list which we can iterate through without causing buggy behaviour
if max(array) + i == val:
array = array[:idx] + [max(array), i] + array[idx:]
# Redefines the list by placing inside of it: all values in the list upto the previous highest values, the 2 values we got from splitting the highest value, and all values which previously went after the highest value.
return array
This will return None if there is no value which can be added to the second highest value to get the highest value in the given array.
Input:
print(length([1,2,3,4,5]))
print(length([4,8,4,3,2]))
print(length([11,17,3,2,20]))
print(length([11,17,3,2,21]))
Output:
[1, 2, 3, 4, 4, 1]
[4, 4, 4, 4, 3, 2]
[11, 17, 3, 2, 17, 3]
None
Here are the docs on list slicing (which are impossible to understand) and a handy tutorial.
when you say "The first part contains the 2nd highest number" does that mean second highest number from the list or the larger of the two numbers that add up the largest number from list?
Here I assume you just wanted the larger of the two numbers that add up to the largest number to come first.
def length(s:list):
#start by finding the largest value and it's position in the list:
largest_pos = 0
for i in range(len(s)):
if s[i] > s[largest_pos]:
largest_pos = i
# find two numbers that add up to the largest number in the s
for trail in range(len(s)):
for lead in range(trail, len(s)):
if (s[trail] + s[lead]) == s[largest_pos]:
if s[trail] > s[lead]:
s[largest_pos] = s[trail]
s.insert(largest_pos +1, s[lead])
else:
s[largest_pos] = s[lead]
s.insert(largest_pos + 1, s[trail])
return s
# if no two numbers add up to the largest number. return s
return s
Since you are limited to 2 numbers, a simple nested loop works.
def length(s):
val = max(s)
idx = s.index(val)
s.remove(val)
for i in range(len(s) - 1):
for j in range(i + 1, len(s)):
if s[i] + s[j] == val:
s = s[:idx] + [s[i], s[j]] + s[idx:]
return s
print(length([4,9,6,3,2]))
Output:
[4, 6, 3, 6, 3, 2]
I used deque library
first to find the highest element or elements then remove all of them and replace them with second high value and rest like : 9 replace with 6 and 3 in example:
from collections import deque
l = [4, 9, 6, 3, 2]
a = deque(l)
e = a.copy()
s = max(a)
while s in a:
a.remove(s) # remove all highest elements
s2 = max(a) # find second high value
c = s - s2
for i in l:
if i == s:
w = e.index(i) # find index of high values
e.remove(max(e))
e.insert(w, s2)
e.insert(w+1, c)
print(list(e))

How to find the unique sets of each element where difference is 1 in Python?

I have an array of integers. I want to find how all unique sets where the difference is 1 and separate them into unique sets.
example input: [3,4,5,8,9,11]
example output: [{3,4,5}, {8,9}, {11}]
What's the simplest way to do this with Python?
Catch the begin of the chain and add all the elements of the chain in a set.
Here is the super simple code for this idea:
def f(arr):
res = []
st = set(arr)
for num in st:
if num - 1 not in st: #begin of chain
temp = []
while num in st:
temp.append(num)
num += 1
res.append(temp)
return res
print(f([3,4,5,8,9,11]))
Output: [[3, 4, 5], [8, 9], [11]]
Time complexity: O(n)
Space complexity: O(n)
I guess this is the best complexity we can achieve. (Don't mind the variable names in the code 😁)
I'm assuming you input list contains no duplicates. If input is [3,4,5,8,9,11,8,9,10] do we want unique sets as [[3,4,5],[8,9,10,11],[8,9]]? If yes, then I leave it to you as an exercise. Hint: Use counter/dictionary instead of set above and it's easy.
Perhaps not the most efficient, but you can sort, then split on the difference. Here's a solution using numpy:
example_input = [3, 4, 5, 8, 9, 11]
output = np.split(np.sort(example_input),
np.where(np.diff(np.sort(example_input)) > 1)[0] + 1)
What this is doing is finding where the difference between elements of the sorted array is greater than one, then splitting the input on that. We add one to the element to split on the next set.
You can then map the arrays to sets if you prefer that.
sets = [set(x) for x in output]
# [{3, 4, 5}, {8, 9}, {11}]
You can start with the first number and keep incrementing it by 1 until the new value you get is not in the original list anymore.
Remove the values which were in the original list.
Repeat the same process for the remaining values.
This should get your started
import typing
def find_unique(elems: list[int]) -> set[int]:
... # fill in your code here
def find_all_unique(elems: list[int]) -> typing.Iterator[set[int]]:
while (elems):
yield find_unique(elems)
if __name__ == '__main__':
my_list = [3,4,5,8,9,11]
print list(find_all_unique(my_list))
You can sort the input list first, and initialize a set s with the first element.
Then go through the sorted list for the second to the last element, compare the number num with its previous number prev_num.
If num == prev_num (if the input list contains duplicates), then we just ignore it.
If num == prev_num + 1, then we add num to the same set s
Otherwise, we know that we need to create a new set. We add the current set to the output list and create a new set.
def find_unique_sets(input_list):
output_list = []
if not input_list:
return output_list
input_list.sort()
s = {input_list[0]}
prev_num = input_list[0]
for i in range(1, len(input_list)):
num = input_list[i]
if num == prev_num:
continue
elif num == prev_num + 1:
s.add(num)
else:
output_list.append(s)
s = {num}
prev_num = num
output_list.append(s)
return output_list
print(find_unique_sets([3,4,5,8,9,11]))

How remove elements in array with out messing with the counter or list length

Codewars: Given a list lst and a number N, create a new list that contains each number of lst at most N times without reordering. For example if N = 2, and the input is [1,2,3,1,2,1,2,3], you take [1,2,3,1,2], drop the next [1,2] since this would lead to 1 and 2 being in the result 3 times, and then take 3, which leads to [1,2,3,1,2,3].
delete_nth ([1,1,1,1],2) # return [1,1]
delete_nth ([20,37,20,21],1) # return [20,37,21]
I'll loop through the array to find the elements that have more than "x" amount. But The problem for me is that when I want to remove that element it changes the length of the array there for throwing off my loop counter. And then when I try another way by creating another list and then again looping through the original list and seeing if the element has more than "x" amount then I'll copy that element into the new array leaving the old array alone and it keeping its index and the loop is fine but now I do not know how to stop copying the element once it hits its desired amount. Please help me. I have been on this answer for a week now.
Maybe you could try this snippet to see that helps?
Have not done too many edge cases - so please raise questions, if run into some edges.
def delete_nth(lst, N):
seen = {}
res = []
for x in lst:
if x not in seen :
seen[x] = 0
else:
seen[x] += 1
if seen[x] <N:
res.append(x)
return res
print(delete_nth([1, 1, 1, 1], 2)) # [1, 1]
print(delete_nth([20, 37, 20, 22], 1)) # [20, 37, 22]

Index out of range confusion

So i am new to programming, and i am having trouble with index out of range errors. Quick example:
I have a list, lst = (5,7,8,9,10).
I want to remove every even number, and every number to the right of an even number.
I would approach this problem by getting the index of every even number, 'i' , and removing lst[i] and lst [i+1]. That will not work when the last number is even because there is no lst [i+1] after the last element in the list.
I have run into this issue on several basic problems i have been working on. My approach to solving this is probably wrong, so i would like to know:
How can i/Can i solve the problem this way, whether it is efficient or not?
What would be the most efficient way to solve this problem?
Welcome to the club! Programming is a lot of fun and something you can always improve upon with incremental progress. I'm going to try to be exhaustive with my answer.
With lists (also known as arrays) remember that a list and its indexes are zero-based. What this means is that an array's indexes start at the number 0 (not number 1 like you would do in normal counting).
arr = [5, 7, 8, 9, 10]
# If you want to access the first element of the array
# then you would use the 0 index. If you want the Second
# element you use index 1.
print(arr[0]) # prints 5 or the 1st element
print(arr[1]) # prints 7 or the 2nd element
I would not use your stand looping technique like for or while in this case because you are removing elements are you are going for the array. If you delete the item as you are looping you are changing the length of the array.
Instead, you could create a new array from looping and only adding or appending odd values to this new array.
arr = [5, 7, 8, 9, 10]
new_arr = []
for idx, val in enumerate(arr):
if idx % 2 == 1:
new_arr.append(val)
return new_arr # yields [7,9] or this process creates a new array of odd elements
In addition, remember when you are using [i+1] while you are indexing through loop in makes sense to stop the loop an element early to avoid an out of index range error.
Do this (no error)
for idx in range(len(arr)-1):
# pseudocode
print(arr[i] + arr[i+1])
instead of this (out of index error). The reason being is that on the last element if you try to add 1 to last index and then access a value that does not exist then an error will be returned:
for idx in range(len(arr)):
# pseudocode
print(arr[i] + arr[i+1])
arr = [5, 7, 8, 9, 10]
# if you try to access arr[5]
# you will get an error because the index
# and element do not exist
# the last element of arr is arr[4] or arr[-1]
arr[5] # yields an out of index error
There are many Pythonic (almost like a colloqial phrase specific to python) ways to accomplish your goal that are more efficient below.
You can use slicing, spacing and the del (delete statment) to remove even number elements
>>> arr = [5, 7, 8, 9, 10]
>>> del arr[::2] # delete even numbers # if you wanted to delete odd numbers del arr[1::2]
>>> arr
[7, 9]
Or a list comprehension to create a new list while looping through some conditional to filter the even numbers out:
new_arr = [elem for idx, elem in enumerate(arr) if idx % 2 == 0]
The % operator is used to see if there is a remainder from division. So if idx is 10. Then 10 % 2 == 0 is true because 2 is able to divide into 10 five times and the remainder is 0. Therefore, the element is even. If you were checking for odd the condition would be:
idx % 2 == 1
You can find further explanation of these Python methods from this great Stack Overflow post here
One issue you may run into is your list indexes shifting on you during removal. One way around this is to sort the indexes to be removed in descending order and remove them first.
Here is an example of how you could accomplish what you are looking for:
myList = [5, 7, 8, 9, 10]
# use list comprehension to get indexes of even numbers into a list.
# num % 2 uses the modulus operator to find numbers divisible by 2
# with a remainder of 0.
even_number_indexes = [idx for idx, num in enumerate(myList) if num % 2 == 0]
# even_number_indexes: [2, 4]
# sort our new list descending
even_number_indexes.reverse()
# even_number_indexes: [4, 2]
# iterate over even_number_indexes and delete index and index + 1
# from myList by specifying a range [index:index + 2]
for index in even_number_indexes:
del myList[index:index + 2]
print(myList)
output: [5, 7]
You can check if i+1 is greater than (Edit: or equal to) the length of the list, and if it is, not execute the code.
You can also handle this in a try/except block.
As to the efficiency of this method of solving, seems fine to me. One gotcha in this approach is that people try to iterate over the list while modifying it, which can lead to unknown errors. If you're using the remove() function, you probably want to do it with a copy of the list.

How can I re-write this while loop using nested for loops?

I followed an algorithm with a while loop, but one of the parameters of the question was that I use nested for loops, and I'm not sure how to do that.
This is the while loop:
i = len(lst)
while i > 0:
big = lst.index(max(lst[0:i]))
lst[big], lst[i-1] = lst[i-1], lst[big]
i = i - 1
return lst
This is the question it's answering:
Input: [5,1,7,3]
First, find the largest number, which is 7.
Swap it and the number currently at the end of the list, which is 3. Now we have: [5,1,3,7]
Now, find the largest number, not including the 7, which is 5.
Swap it and the second to last number, which is 3. Now we have: [3,1,5,7].
Now, find the third largest number (excluding the first two), which is 3.
Swap it and the third to last number, which is 1.
Output: [1, 3, 5, 7]
What you're seeing in the algorithm is a selection sort. And here's your second solution which you asked (nested for loops):
def insertion_sort(arr):
l = len(arr)
for i in range(l-1, -1, -1):
m = -10000 # it should be lower than min(arr)
idx = -1
for key, val in enumerate(arr[:i+1]):
if m < val:
m = val
idx = key
if idx != -1:
arr[i], arr[idx] = arr[idx], arr[i]
return arr
And a quick test:
arr = list(range(10))[::-1]
print(arr)
# prints [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
result = insertion_sort(arr)
print(result)
# prints [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
This looks like a (rather slow) sorting algorithm - namely bubble sort. It's iterating from the end of the list lst. Then it's searching for the maximum value in the first n-1 elements, and swapping them with the end. It will, however, fail, if the maximum value is already at the end, because then it will automatically swap the max(n-1) with the n value. You'll need to add a check for this.
So from a first look, I'm not sure if i is defined before, but let's assume it's defined at the length of the list lst, as it seems to be. So let's start with the outer loop - as have a while loop that looks like it's counting down from i to 0. This is the opposite of an increasing for-loop, so we can create a reserved range:
rev_range = range(0,len(lst))
rev_range.reverse()
for j in rev_range:
# perform the sort
We now have the outer loop for the counting-down while loop. The sort itself iterates forward until it finds the maximum. This is a forward for loop.
# sorting
max_val_so_far_index=lst[j]
# lst[:j-1] gets the first j-1 elements of the list
for k in lst[:j-1]:
if lst[k] > lst[max_val_so_far_index]:
max_val_so_far_index = k
# now we have the index of the maximum value
# swap
temp = lst[j]
lst[j] = lst[max_val_so_far_index]
lst[max_val_so_far_index]=temp
Let's put the two components together to get:
rev_range = range(0,len(lst))
rev_range.reverse()
for j in rev_range:
# perform the sort
# sorting
#print j
max_val_so_far_index=j
# get the first j items
for k in range(j):
if lst[k] > lst[max_val_so_far_index]:
max_val_so_far_index = k
# now we have the index of the maximum value
# swap
temp = lst[j]
lst[j] = lst[max_val_so_far_index]
lst[max_val_so_far_index]=temp
At the end lst is sorted.
The algorithm in the question is just another form of a bubble sort. The original algorithm uses two nested for loops. You can find a good explaination here.

Categories

Resources