Given a list x, I want to sort it with selection sort, and then count the number of swaps made within the sort. So I came out with something like this:
count=0
a=0
n=len(x)
while (n-a)>0:
#please recommend a better way to swap.
i = (min(x[a:n]))
x[i], x[a] = x[a], x[i]
a += 1
#the count must still be there
count+=1
print (x)
Could you help me to find a way to manage this better? It doesn't work that well.
The problem is NOT about repeated elements. Your code doesn't work for lists with all elements distinct, either. Try x = [2,6,4,5].
i = (min(x[a:n]))
min() here gets the value of the minimum element in the slice, and then you use it as an index, that doesn't make sense.
You are confusing the value of an element, with its location. You must use the index to identify the location.
seq = [2,1,0,0]
beg = 0
n = len(seq)
while (n - beg) > 0:
jdx = seq[beg:n].index((min(seq[beg:n]))) # use the remaining unsorted right
seq[jdx + beg], seq[beg] = seq[beg], seq[jdx + beg] # swap the minimum with the first unsorted element.
beg += 1
print(seq)
print('-->', seq)
As the sorting progresses, the left of the list [0:beg] is sorted, and the right side [beg:] is being sorted, until completion.
jdx is the location (the index) of the minimum of the remaining of the list (finding the min must happen on the unsorted right part of the list --> [beg:])
Related
I am trying to solve assignment problem, the code I wrote takes extremely long to run. I think it's due to nested loop I used. Is there another way to rewrite the code to make it more efficient.
The question I am trying to solve. Basically, starting at first element to compare with every element to its right. if it is larger than the rest, it will be "dominator". Then the second element to compare with every element to its right again. All the way to the last element which will be automatically become "dominator"
def count_dominators(items):
if len(items) ==0:
return len(items)
else:
k = 1
for i in range(1,len(items)):
for j in items[i:]:
if items[i-1]>j:
k = k+1
else:
k = k+0
return k
You can use a list comprehension to check if each item is a "dominator", then take the length - you have to exclude the final one to avoid taking max of an empty list, then we add 1 because we know that the final one is actually a dominator.
num_dominators = len([i for i in range(len(items) - 1) if items[i] > max(items[i + 1:])]) + 1
This is nice because it fits on one line, but the more efficient (single pass through the list) way to do it is to start at the end and every time we find a new number bigger than any we have seen before, count it:
biggest = items[-1]
n = 1
for x in reversed(items):
if x > biggest:
biggest = x
n+=1
return n
I have a function match that takes in a list of numbers and a target number and I want to write a function that finds within the array two numbers that add to that target.
Here is my approach:
>>> def match(values, target=3):
... for i in values:
... for j in values:
... if j != i:
... if i + j == target:
... return print(f'{i} and {j}')
... return print('no matching pair')
Is this solution valiant? Can it be improved?
The best approach would result in O(NlogN) solution.
You sort the list, this will cost you O(NlogN)
Once the list is sorted you get two indices, the former points to the first element, the latter -- to the latest element and you check to see if the sum of the elements matches whatever is your target. If the sum is above the target, you move the upper index down, if the sum is below the target -- you move the lower index up. Finish when the upper index is equal to the lower index. This operation is linear and can be done in O(N) time.
All in all, you have O(NlogN) for the sorting and O(N) for the indexing, bringing the complexity of the whole solution to O(NlogN).
There is room for improvement. Right now, you have a nested loop. Also, you do not return when you use print.
As you iterate over values, you are getting the following:
values = [1, 2, 3]
target = 3
first_value = 1
difference: 3 - 1 = 2
We can see that in order for 1 to add up to 3, a 2 is required. Rather than iterating over the values, we can simply ask 2 in values.
def match(values, target):
values = set(values)
for value in values:
summand = target - value
if summand in values:
break
else:
print('No matching pair')
print(f'{value} and {summand}')
Edit: Converted values to a set since it has handles in quicker than if it were looking it up in a list. If you require the indices of these pairs, such as in the LeetCode problem you should not convert it to a set, since you will lose the order. You should also use enumerate in the for-loop to get the indices.
Edit: summand == value edge case
def match(values, target):
for i, value in enumerate(values):
summand = target - value
if summand in values[i + 1:]:
break
else:
print('No matching pair')
return
print(f'{value} and {summand}')
I'm not sure about the space complexity of these two selection sort implementations:
def selection_sort(lst):
n = len(lst)
for i in range(n):
m_index = i
for j in range(i+1,n):
if lst[m_index] > lst[j]:
m_index = j
swap(lst, i, m_index)
return None
and this one:
def selection_sort2(lst):
n = len(lst)
for i in range(n):
m = min(lst[i:n])
m_index = lst.index(m) #find the index of the minimum
lst[i], lst[m_index] = lst[m_index], lst[i]
return None
and, regarding the second code, where are the previous slices being saved, once m gets a new slice?
Thanks!
The first point to make is that your second function contains a bug in its use of index. Running this:
def selection_sort2(lst):
n = len(lst)
for i in range(n):
m = min(lst[i:n])
m_index = lst.index(m) #find the index of the minimum
lst[i], lst[m_index] = lst[m_index], lst[i]
return
l = [5,4,1,3,4]
selection_sort2(l)
print(l)
prints out
[1, 3, 5, 4, 4]
This is because you have misunderstood the index function. What it does is to find the first occurrence of the supplied value (here m) in the supplied list (here lst). So what your code is doing is first of all to create a slice and find its min. Then the slice goes out of scope and is garbage collected. Then you find the value in the whole list (in the wrong place in this example).
We can fix this by restricting the index to the slice, though bear in mind that this is not good code, as I will explain next.
m_index = lst.index(m,i) #find the index of the minimum
With this change, the function works, but it has two problems. The first is that the slicing does (as you suspected) create a copy and so doubles the memory requirement of the code. But the second problem is that once you find the minimum value, you then pointlessly iterate through the slice a second time to find the index of the place where you found the minimum, so also doubling the run time.
The copying can be fixed by replacing the slice with a generator expression. So instead of a slice we just produce the values one at a time.
Then we can arrange to find the index of the minimum by carrying it along with the value in a tuple. Then minimising the tuples provides us with the index at the same time. The resulting code looks like this:
def selection_sort2(lst):
n = len(lst)
for i in range(n):
m,m_index = min((lst[j],j) for j in range(i,n))
lst[i], lst[m_index] = lst[m_index], lst[i]
return
However, this code is functionally more or less the same as your first example and probably not any clearer - so why change?
How would you go about comparing two adjacent elements in a list in python? How would save or store the value of that item while going through a for loop? I'm trying not to use the zip method and just using an ordinary for loop.
comparing_two_elements = ['Hi','Hello','Goodbye','Does it really work finding longest length of string','Jet','Yes it really does work']
longer_string = ''
for i in range(len(comparing_two_elements)-1):
if len(prior_string) < len(comparing_two_elements[i + 1]):
longer_string = comparing_two_elements[i+1]
print(longer_string)
The below works simply by 'saving' the first element of your list as the longest element, as it will be the first time you loop over your list, and then on subsequent iterations it will compare the length of that item to the length of the next item in the list.
longest_element = None
for element in comparing_two_elements:
if not longest_element:
longest_element = element
continue
if len(longest_element) < len(element):
longest_element = element
If you want to go the "interesting" route, you could do it with combination of other functions, for eg
length_map = map(len, comparing_two_elements)
longest_index = length_map.index(max(length_map))
longest_element = comparing_two_elements[longest_index]
Use the third, optional step argument to range - and don't subtract 1 from len(...) ! Your logic is incomplete: what if the first of a pair of strings is longer? you don't do anything in that case.
It's not clear what you're trying to do. This for loop runs through i = 0, 2, 4, ... up to but excluding len(comparing_two_elements) (assumed to be even!), and prints the longer of each adjacent pair:
for i in range(0, len(comparing_two_elements), 2):
if len(comparing_two_elements[i]) < len(comparing_two_elements[i + 1]):
idx = i
else:
idx = i + 1
print(comparing_two_elements[idx])
This may not do exactly what you want, but as several people have observed, it's unclear just what that is. At least it's something you can reason about and adapt.
If you just want the longest string in a sequence seq, the whole adjacent pairs rigamarole is pointless; simply use:
longest_string = max(seq, key=len)
I'm fairly new to programming; I've only been studying Python for a few weeks. I've been given an exercise recently that asks me to generate a list of integers, and then manually sort the numbers from lowest to highest in a separate list.
import random
unordered = list(range(10))
ordered = []
lowest = 0
i = 0
random.shuffle(unordered)
lowest = unordered[0]
while i in unordered:
if unordered[i] < lowest:
lowest = unordered[i]
i += 1
if i >= len(unordered):
i = 0
ordered.append(lowest)
unordered.remove(lowest)
lowest = unordered[i]
print(ordered)
This is what I have so far, and to be quite frank, it doesn't work at all. The pseudocode I have been given is this:
Create an empty list to hold the ordered elements
While there are still elements in the unordered list
Set a variable, lowest, to the first element in the unordered list
For each element in the unordered list
If the element is lower than lowest
Assign the value of that element to lowest
Append lowest to the ordered list
Remove lowest from the unordered list
Print out the ordered list
The biggest issue I'm having so far is that my counter doesn't reliably give me a way to pick out the lowest number from my list unordered. And then I'm having issues with indexing my list i.e. the index being out of range. Can anyone give me a bit of feedback on where I'm going wrong?
Also, I was given this info which I'm not really sure about:
You can use an established method to sort the list called the Selection Sort.
I'm not supposed to be using Python's built in sort methods this time around. It's all supposed to be done manually. Thanks for any help!
You can do this without having to create another list.
x = [5, 4, 3, 2, 5, 1]
n = len(x)
# Traverse through all list elements
for i in range(n):
# Traverse the list from 0 to n-i-1
# (The last element will already be in place after first pass, so no need to re-check)
for j in range(0, n-i-1):
# Swap if current element is greater than next
if x[j] > x[j+1]:
x[j], x[j+1] = x[j+1], x[j]
print(x)
This works with duplicates and descending lists. It also includes a minor optimization to avoid an unnecessary comparison on the last element.
Note: this answer and all the others use bubble sort, which is simple but inefficient. If you're looking for performance, you're much better off with another sorting algorithm. See which is best sorting algorithm and why?
You've just got some of the order wrong: you need to append to your ordered list each time around
import random
unordered = list(range(10))
ordered = []
i = 0
random.shuffle(unordered)
print unordered
lowest = unordered[0]
while len(unordered) > 0:
if unordered[i] < lowest:
lowest = unordered[i]
i += 1
if i == len(unordered):
ordered.append(lowest)
unordered.remove(lowest)
if unordered:
lowest = unordered[0]
i = 0
print(ordered)
you're not supposed to create a new algorithm for sorting list, just implement this one :
http://en.wikipedia.org/wiki/Bubble_sort
I found this is working pretty well for any number of inputs
x = [3, 4, 100, 34, 45]
for i in range(len(x) - 1):
if x[i] > x[i + 1]:
x[i],x[i + 1] = x[i + 1], x[i]
print (x)
Above code won't work if you have repetitive elements.
ordered=[]
i=0
j=0
x = [100, 3, 4, 100, 34, 45]
lowest=x[0]
while len(x)>0:
for i in range(0,len(x)):
if x[i]<=lowest:
lowest=x[i]
ordered.append(lowest)
x.remove(lowest)
if len(x)>1:
lowest=x[0]
print(ordered)
def sort(x):
l=len(x)
for i in range(l):
for j in range((i+1),l):
if x[i]>x[j]:
l1=x[i]
x[i]=x[j]
x[j]=l1
print(x)
l=[8,4,2,6,5,1,12,18,78,45]
sort(l)
From the first number in the list, run a loop to find the lowest value. After that swap them with the first number in the list. Repeat this loop method for remaining numbers in the list.
nlist=[int(a) for a in input('Please insert your list of numbers ').split()]
for loop1 in range (0,len(nlist)-1): # outer loop
min=nlist[loop1]
for loop2 in range (loop1 + 1,len(nlist)): # inner loop to compare
if min > nlist[loop2]:
min=nlist[loop2]
index=loop2
if nlist[loop1] != min:
swap=nlist[loop1]
nlist[loop1]=min
nlist[index]=swap
print('Your hand-sorted list is',nlist)