From a list of integers and a single sum value, I have to return the first two values in order of appearance that adds up to the sum. source of the task
I think the most optimal way to scan the list is:
index 0, index 1
index 0, index 2
index 1, index 2
index 0, index 3
index 1, index 3
index 2, index 3
and so on. Am I right so far?
Then I used memoization to cut numbers appearing more than twice.
The code I wrote is functional but times-out on the more advanced tests. Here it is:
def sum_pairs(ints, s):
d={}
n2_index = 0
d[ints[0]] = 1
while True:
n2_index += 1
if ints[n2_index] not in d.keys():
d[ints[n2_index]] = 0
if d[ints[n2_index]] == 2:
if n2_index == len(ints)-1:
return None
continue
for n1_index in range (0, n2_index):
if ints[n1_index] + ints[n2_index] == s:
return [ints[n1_index], ints[n2_index]]
d[ints[n2_index]] += 1
if n2_index == len(ints)-1:
return None
I would appreciate it greatly if you could help me understand my mistake/mistakes and how to approach this kind of task.
Cheers!
The way to do this is to remember all the numbers you have seen before. This is normaly done in a set, a set is gives you O(1) (constant) look up time, so you determine very fast if you have seen a particular number or not already.
As you can through the list, you look in your set to see if you have seen the sum - current_value. If so you can output these two values, if not you add the current_value to the set and continue.
def sum(ints, s):
seen = set()
for current_value in ints:
if s - current_value in seen:
return s-current_value, current_value
else:
seen.add(current_value)
return None
I'm trying to get this function to return the index of the smallest value in a list. It works for every case besides if the smallest value is the last element of the list. Can anyone spot my mistake?
def rotation_point(rotated_list):
length = len(rotated_list)
for i in range (0,length-1):
if rotated_list[i]<rotated_list[(i+1)%length] and rotated_list[i]<rotated_list[i-1]:
return i
else:
pass
I know it has something to do with the first part of the if statement, but I can't see why it wouldnt work.
The if statement itself works just fine but you never actually check the last element because you use: range(0,length-1) for the range of the for loop. The range function already stops at length - 1. You should use: range(0,length).
Note that you also don't really need the 0 since range already starts at zero by default, and there is no need for the else: pass since the use of an else statement is not required, removing it would change nothing.
This is what a working (and cleaner) version of your function would look like:
def rotation_point(rotated_list):
length = len(rotated_list)
for i in range (length):
if rotated_list[i]<rotated_list[(i+1)%length] and rotated_list[i]<rotated_list[i-1]:
return i
Here are some more examples of the range function:
>>> range(0, 5-1)
[0, 1, 2, 3]
>>> range(0, 5)
[0, 1, 2, 3, 4]
>>> range(5)
[0, 1, 2, 3, 4]
Note that range(i, j) goes from i to j - 1. Since you are using range(0, length - 1), you are not traversing the entire array, just from the start to the second last index. A super easy fix is to add return length - 1 to the end.
def rotation_point(rotated_list):
length = len(rotated_list)
for i in range (0,length-1):
if rotated_list[i]<rotated_list[(i+1)%length] and rotated_list[i]<rotated_list[i-1]:
return i
else:
pass
return length - 1
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]
lst = [3, 4, 3, 5, 3]
def lstmaker(lst):
lst1 = []
x = 0
while x < len(lst):
if lst[x] == lst[x+1]:
lst1.append(lst[x])
else:
pass
x+=1
print lst1
lstmaker(lst)
I tried to make this simple program to find the the mode of list, but it just throws the index of out range error.
Thanks
For the last value of x, lst[x+1] x+1 is out of range. The while loop should be while x < len(lst) -1.
As a side note, to calculate the mode, you can simply do: max(set(lst), key=lst.count) as shown here.
The logic is incorrect.
For starters, the reason you're getting an index out of range issue is because of the line
if lst[x] == lst[x+1]
x is correctly incremented throughout your loop, but when x is at the last index, that +1 bit accesses an index that isn't in the list (e.g. index 5 of a list of size 5).
Additionally, what you were actually doing within the loop doesn't appear to be getting you towards a value for the mode. The mode is the element(s) in a list that occurs the most. One approach to tackling this problem could be using a dictionary (dict()) where the "keys" are the elements in your list, and the "values" are the amount of times each element occurs.
Try something like this:
# lst defined up here
occurrences = dict()
for x in lst:
if x in occurrences:
occurrences[x] += 1
else:
occurrences[x] = 1
mode = occurrences.keys()[0]
for k in occurrences:
if occurrences[k] >= mode:
mode = k
print(mode) # or return, etc.
This is perhaps not the most "Pythonic" solution, though it is an intuitive break-down of the logic involved in finding the mode, at least as if you were to do it by hand on paper.
I have a list of numbers (example: [-1, 1, -4, 5]) and I have to remove numbers from the list without changing the total sum of the list. I want to remove the numbers with biggest absolute value possible, without changing the total, in the example removing [-1, -4, 5] will leave [1] so the sum doesn't change.
I wrote the naive approach, which is finding all possible combinations that don't change the total and see which one removes the biggest absolute value. But that is be really slow since the actual list will be a lot bigger than that.
Here's my combinations code:
from itertools import chain, combinations
def remove(items):
all_comb = chain.from_iterable(combinations(items, n+1)
for n in xrange(len(items)))
biggest = None
biggest_sum = 0
for comb in all_comb:
if sum(comb) != 0:
continue # this comb would change total, skip
abs_sum = sum(abs(item) for item in comb)
if abs_sum > biggest_sum:
biggest = comb
biggest_sum = abs_sum
return biggest
print remove([-1, 1, -4, 5])
It corectly prints (-1, -4, 5). However I am looking for some clever, more efficient solution than looping over all possible item combinations.
Any ideas?
if you redefine the problem as finding a subset whose sum equals the value of the complete set, you will realize that this is a NP-Hard problem, (subset sum)
so there is no polynomial complexity solution for this problem .
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright © 2009 Clóvis Fabrício Costa
# Licensed under GPL version 3.0 or higher
def posneg_calcsums(subset):
sums = {}
for group in chain.from_iterable(combinations(subset, n+1)
for n in xrange(len(subset))):
sums[sum(group)] = group
return sums
def posneg(items):
positive = posneg_calcsums([item for item in items if item > 0])
negative = posneg_calcsums([item for item in items if item < 0])
for n in sorted(positive, reverse=True):
if -n in negative:
return positive[n] + negative[-n]
else:
return None
print posneg([-1, 1, -4, 5])
print posneg([6, 44, 1, -7, -6, 19])
It works fine, and is a lot faster than my first approach. Thanks to Alon for the wikipedia link and ivazquez|laptop on #python irc channel for a good hint that led me into the solution.
I think it can be further optimized - I want a way to stop calculating the expensive part once the solution was found. I will keep trying.
Your requirements don't say if the function is allowed to change the list order or not. Here's a possibility:
def remove(items):
items.sort()
running = original = sum(items)
try:
items.index(original) # we just want the exception
return [original]
except ValueError:
pass
if abs(items[0]) > items[-1]:
running -= items.pop(0)
else:
running -= items.pop()
while running != original:
try:
running -= items.pop(items.index(original - running))
except ValueError:
if running > original:
running -= items.pop()
elif running < original:
running -= items.pop(0)
return items
This sorts the list (big items will be at the end, smaller ones will be at the beginning) and calculates the sum, and removes an item from the list. It then continues removing items until the new total equals the original total. An alternative version that preserves order can be written as a wrapper:
from copy import copy
def remove_preserve_order(items):
a = remove(copy(items))
return [x for x in items if x in a]
Though you should probably rewrite this with collections.deque if you really want to preserve order. If you can guarantee uniqueness in your list, you can get a big win by using a set instead.
We could probably write a better version that traverses the list to find the two numbers closest to the running total each time and remove the closer of the two, but then we'd probably end up with O(N^2) performance. I believe this code's performance will be O(N*log(N)) as it just has to sort the list (I hope Python's list sorting isn't O(N^2)) and then get the sum.
I do not program in Python so my apologies for not offering code. But I think I can help with the algorithm:
Find the sum
Add numbers with the lowest value until you get to the same sum
Everything else can be deleted
I hope this helps
This can be solved using integer programming. You can define a binary variable s_i for each of your list elements x_i and minimize \sum_i s_i, limited by the constraint that \sum_i (x_i*s_i) is equal to the original sum of your list.
Here's an implementation using the lpSolve package in R:
library(lpSolve)
get.subset <- function(lst) {
res <- lp("min", rep(1, length(lst)), matrix(lst, nrow=1), "=", sum(lst),
binary.vec=seq_along(lst))
lst[res$solution > 0.999]
}
Now, we can test it with a few examples:
get.subset(c(1, -1, -4, 5))
# [1] 1
get.subset(c(6, 44, 1, -7, -6, 19))
# [1] 44 -6 19
get.subset(c(1, 2, 3, 4))
# [1] 1 2 3 4