Why I get the list unchanged when using this code? - python

I want to delate the duplicated elements in a list using this structure but the list remains unchanged when I use this code. Could anyone help me please?
e.g.,item=[1,2,3,4,5,6,7,8,9,1,2,6,7]
def duplicated(item):
i=0
j=0
while i<len(item):
while j<len(item):
if item[j]==item[i] and i!=j:
del item[j]
j+=1
i+=1
return item

To address why your code isn't working, it is due to the fact that you initialise j at the start of the function, so after the first iteration of the i loop, it is len(item)-1 and then never get's reset for future loops.
This means that you miss many duplicates.
So since we need to initialise it each loop, we still need to know what to. If we initialise it as 0, then we are checking for duplicates behind the current j value in the list so this is a waste of time. To improve efficiency, we should initialise it as i+1 so that we check numbers after i for duplicates as we already know that there are no duplicates of the value at i before the index i in the list. This also simplifies the if as we no longer need to check i != j.
And one final thing is that when you delete j with del, all the indexes after are now shifted down by one. So we also need to subtract 1 from j so that we now check the element straight after the one we just deleted which is now at the same index of the one we just deleted (since they are shifted down).
So, in code:
def duplicated(item):
i = 0
while i < len(item):
j = i + 1
while j < len(item):
if item[j] == item[i]:
del item[j]
j -= 1
j += 1
i += 1
return item
and it works with the examples people have given:
>>> duplicated([1,2,3,4,5,6,7,8,9,1,2,6,7])
[1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> duplicated([1,2,3,4,5,6,7,8,9,1,1,2,6,7])
[1, 2, 3, 4, 5, 6, 7, 8, 9]
However, this solution is current of complexity O(n^2) since we are using nested loops so the time taken is proportional to the size of the input list squared.
But if we were able to modify the algorithm to use only one loop, we would reduce the complexity to O(n). This can be done by using a set for lookup. Adding and checking if an element is in the set is O(1) (average case) so we can use them to speed things up.
So if we have a set which contains the elements we have already seen, then for each future element, if it is already in the seen set, we delete it, otherwise we add it to this set.
So, in code:
def duplicated(item):
seen = set()
i = 0
while i < len(item):
if item[i] in seen:
del item[i]
else:
seen.add(item[i])
i += 1
return item
And I can confirm that this passes the test cases above as well.
One last thing I should point out is that when deleting the element here, I did not subtract from the pointer, this is because before we subtracted as we knew it would later be incremented and we wanted it to be the same, however here, it is only incremented in the else block, so if we don't do anything, it will stay the same.

Actually variable j should start with next item of what i picks.
def duplicated(item):
i=0
while i<len(item):
j = i+1
while j<len(item):
if item[j]==item[i] and i!=j:
del item[j]
j -= 1
j+=1
i+=1
return item

You should reinitialize your j var at each turn inside of the i loop, otherwise, j is always going to be equal to len(item) after the first iteration.
def duplicated(item):
i=0
while i<len(item):
j=0
while j<len(item):
if item[j]==item[i] and i!=j:
print(i,j )
del item[j]
j+=1
i+=1
return item
However, The best way, if you don't care about your list order, to do what you want will probably be to convert your list to a set and then back to a list, has a set can only have distinct elements.
def duplicated(item):
return list(set(item))

You need to reinitilaze j at start of nested while loop each time:
def duplicated(item):
i=0
j=0
while i<len(item)-1:
j=i+1
while j<len(item):
if item[j]==item[i]:
del item[j]
j -= 1
j+=1
i+=1
return item
OUT
[1, 2, 3, 4, 5, 6, 7, 8, 9]
However you can try below simpler code it will remain the insertion order of list
def duplicated(item):
unique_list=[]
for i in item:
if i not in unique_list:
unique_list.append(i)
return unique_list

Related

Insertion Sort in Python with for loops

I'm learning Python from a web source which implemented the Insertion Sort algorithm using a combination of for loop and while loop. I thought of practicing the code by myself and I coded an algorithm using only for loops. I need some feedback on whether my code is correct, and whether its valid.
def insertionSort(lst):
for i in range(1,len(lst)):
temp = lst[i]
for j in range(0,i):
if lst[j] > temp:
lst[i], lst[j] = lst[j], lst[i]
return lst
lst = [8, 6, 4, 20, 24, 2, 10, 12]
print(insertionSort(lst))
The output is: [2, 4, 6, 8, 10, 12, 20, 24]
Your algorithm could be called insertion sort in a broad sense, but it is different from what is commonly understood by insertion sort, as it compares the temp value with all previous values, while standard insertion sort only compares temp with the greater values among the previous values, and with one additional value that will become temp's predecessor (if there is one).
This means your implementation will have a best case time complexity that is O(𝑛²), while the best case time complexity of the standard algorithm is O(𝑛). That best case occurs when the input is already sorted.
The typical insertion sort algorithm will have the inner loop going backwards, visiting values in descending order, and stopping when it finds a value that is less than (or equal to) the value to move (temp). During this loop the swaps are done with 2 consecutive values, and this can be improved by delaying the insertion of temp so that values only have to be copied one place to the right until the insertion point is found.
An implementation of that in Python could look like this:
def insertionSort(lst):
for i, temp in enumerate(lst):
for j in range(i - 1, -1, -1):
if lst[j] <= temp: # Found insertion point?
lst[j + 1] = temp
break
lst[j + 1] = lst[j] # Make room for temp
else: # temp is the least value so far: insert at the start
lst[0] = temp
return lst
Correctness testing
To test yourself whether your implementation correctly sorts a list, you can bombard your function with random input. For instance like this:
import random
for size in range(100):
lst = list(range(size))
random.shuffle(lst)
finallist = lst[:]
insertionSort(finallist)
if finallist != sorted(finallist):
print("Not sorted correctly:", lst, "to", finallist)
break
else:
print("All tests passed successfully")

Why won't my code go through every element in the for loop?

The principle of this sorting algorithm is simple: starting from a float list inlist to be sorted, the elements of inlist will be extracted one at a time, and placed into a new list outlist (originally empty) such that outlist always remain a sorted list.
This algorithm is supposed to go through every element in the list. However, it just stops half way.
def insertion_sort(inlist):
outlist = []
for i in inlist:
x = inlist.pop(0)
outlist.append(x)
return sorted(outlist)
print(insertion_sort([1,2,6,3,5,4]))
The output is [1,2,6] but i want the output to be [1,2,3,4,5,6]
What is wrong with my code?
Thank you so much for helping.
The correct way of insertion sort is below:
def insertion_sort(arr):
for i in range(1, len(arr)):
key = arr[i]
# Move elements of arr[0..i-1], that are
# greater than key, to one position ahead
# of their current position
j = i-1
while j >=0 and key < arr[j] :
arr[j+1] = arr[j]
j -= 1
arr[j+1] = key
return arr
arr = [12, 11, 13, 5, 6]
print(insertion_sort(arr))
However if you to correct you code then change for i in inlist: with for i in range(len(inlist)):
Now this i traverse complete inlist, while in you case it is running only half.
I don't understand what you want to achieve but try this:
def insertion_sort(inlist):
outlist = []
for i in range(len(inlist)):
x = inlist.pop(0)
outlist.append(x)
return sorted(outlist)
print(insertion_sort([1,2,6,3,5,4]))
Anyway i think the foreach cycle dynamically checks the list lengths but with range-len you get a fix number on the start of the cycle.

How to update array Index in loop (IndexError: list index out of range)

I should not use advance function, as this is a logical test during interview.
Trying to remove all digits which appear more than once in array.
testcase:
a=[1,1,2,3,2,4,5,6,7]
code:
def dup(a):
i=0
arraySize = len(a)
print(arraySize)
while i < arraySize:
#print("1 = ",arraySize)
k=i+1
for k in range(k,arraySize):
if a[i] == a[k]:
a.remove(a[k])
arraySize -= 1
#print("2 = ",arraySize)
i += 1
print(a)
result should be : 1,2,3,4,5,6,7
But i keep getting index out of range. i know that it is because the array list inside the loop changed, so the "while" initial index is different with the new index.
The question is : any way to sync the new index length (array inside the loop) with the parent loop (index in "while" loop) ?
The only thing i can think of is to use function inside the loop.
any hint?
Re-Calculating Array Size Per Iteration
It looks like we have a couple issues here. The first issue is that you can't update the "stop" value in your inner loop (the range function). So first off, let's remove that and use another while loop to give us the ability to re-calculate our array size every iteration.
Re-Checking Values Shifted Into Removed List Spot
Next, after you fix that you will run into a larger issue. When you use remove it moves a value from the end of the list or shifts the entire list to the left to use the removed spot, and you are not re-checking the value that got moved into the old values removed spot. To resolve this, we need to decrement i whenever we remove an element, this makes sure we are checking the value that gets placed into the removed elements spot.
remove vs del
You should use del over remove in this case. remove iterates over the list and removes the first occurrence of the value and it looks like we already know the exact index of the value we want to remove. remove might work, but it's usage here over complicates things a bit.
Functional Code with Minimal Changeset
def dup(a):
i = 0
arraySize = len(a)
print(arraySize)
while i < arraySize:
k = i + 1
while k < arraySize: # CHANGE: use a while loop to have greater control over the array size.
if a[i] == a[k]:
print("Duplicate found at indexes %d and %d." % (i, k))
del a[i] # CHANGE: used del instead of remove.
i -= 1 # CHANGE: you need to recheck the new value that got placed into the old removed spot.
arraySize -= 1
break
k += 1
i += 1
return a
Now, I'd like to note that we have some readability and maintainability issues with the code above. Iterating through an array and manipulating the iterator in the way we are doing is a bit messy and could be prone to simple mistakes. Below are a couple ways I'd implement this problem in a more readable and maintainable manner.
Simple Readable Alternative
def remove_duplicates(old_numbers):
""" Simple/naive implementation to remove duplicate numbers from a list of numbers. """
new_numbers = []
for old_number in old_numbers:
is_duplicate = False
for new_number in new_numbers:
if old_number == new_number:
is_duplicate = True
if is_duplicate == False:
new_numbers.append(old_number)
return new_numbers
Optimized Low Level Alternative
def remove_duplicates(numbers):
""" Removes all duplicates in the list of numbers in place. """
for i in range(len(numbers) - 1, -1, -1):
for k in range(i, -1, -1):
if i != k and numbers[i] == numbers[k]:
print("Duplicate found. Removing number at index: %d" % i)
del numbers[i]
break
return numbers
You could copy contents in another list and remove duplicates from that and return the list. For example:
duplicate = a.copy()
f = 0
for j in range(len(a)):
for i in range(len(duplicate)):
if i < len(duplicate):
if a[j] == duplicate[i]:
f = f+1
if f > 1:
f = 0
duplicate.remove(duplicate[i])
f=0
print(duplicate)

Traversing a Python list and making in-place changes

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]

Looping through a list not in order in Python

I am very new to programming, so please bear with me...I have been learning Python and I just did an assessment that involved looping through a list using your current value as the next index value to go to while looping. This is roughly what the question was:
You have a zero-indexed array length N of positive and negative integers. Write a function that loops through the list, creates a new list, and returns the length of the new list. While looping through the list, you use your current value as the next index value to go to. It stops looping when A[i] = -1
For example:
A[0] = 1
A[1] = 4
A[2] = -1
A[3] = 3
A[4] = 2
This would create:
newlist = [1, 4, 2, -1]
len(newlist) = 4
It was timed and I was not able to finish, but this is what I came up with. Any criticism is appreciated. Like I said I am new and trying to learn. In the meantime, I will keep looking. Thanks in advance!
def sol(A):
i = 0
newlist = []
for A[i] in range(len(A)):
e = A[i]
newlist.append(e)
i == e
if A[i] == -1:
return len(newlist)
This might be the easiest way to do it if your looking for the least lines of code to write.
A = [1,4,-1,3,2]
B = []
n = 0
while A[n] != -1:
B.append(A[n])
n = A[n]
B.append(-1)
print(len(B))
First of all, note that for A[i] in range(len(A)) is a pattern you certainly want to avoid, as it is an obscure construct that will modify the list A by storing increasing integers into A[i]. To loop over elements of A, use for val in A. To loop over indices into A, use for ind in xrange(len(A)).
The for loop, normally the preferred Python looping construct, is not the right tool for this problem because the problem requires iterating over the sequence in an unpredictable order mandated by the contents of the sequence. For this, you need to use the more general while loop and manage the list index yourself. Here is an example:
def extract(l):
newlist = []
ind = 0
while l[ind] != -1:
newlist.append(l[ind])
ind = l[ind]
newlist.append(-1) # the problem requires the trailing -1
print newlist # for debugging
return len(newlist)
>>> extract([1, 4, -1, 3, 2])
[1, 4, 2, -1]
4
Note that collecting the values into the new list doesn't really make sense in any kind of real-world scenario because the list is not visible outside the function in any way. A more sensible implementation would simply increment a counter in each loop pass and return the value of the counter. But since the problem explicitly requests maintaining the list, code like the above will have to do.
It's simpler to just use a while loop:
data = [1,4,-1,3,2]
ls = []
i = 0
steps = 0
while data[i] != -1:
ls.append(data[i])
i = data[i]
steps += 1
assert steps < len(data), "Infinite loop detected"
ls.append(-1)
print ls, len(ls)

Categories

Resources