Faster way of searching than nested loops in python - python

I'm trying to find the optimal order for least expected cost for an array.
The input is:
input = [[390, 185, 624], [686, 351, 947], [276, 1023, 1024], [199, 148, 250]]
This is an array of four choices, the first number being a cost and the second two being the probability of getting the result, the first ([i][1]) of which is the numerator and the second ([i][2]) is the denominator.
The goal is to find the optimal order of these value/probability pairs that will provide the result at the least total cost.
def answer(input):
from itertools import permutations
length = len(input)
best_total = 999
for combination in permutations(input):
# print combination
total = 0.0
for i in range(0, length):
current_value = 1.0
for j in range(0, i):
current_value = current_value * (1.0 - \
(float(combination[j][1]) / float(combination[j][2])))
total = total + (float(combination[i][0]) * current_value)
if total > best_total:
i = length
# print total
if total <= best_total:
best_total = total
best_combination = combination
answer = map(input.index, best_combination)
return answer
Running:
print answer(input)
should return
[2, 3, 0, 1]
for the given input.
This is obviously an exhaustive search, which becomes very slow very quickly with more than four choices. I've considered binary search trees as the input for those is very similar, however I can't figure out how to implement it.
I've been working on this for four days and can't seem to come up with fast version that works for any input (assuming positive costs and probabilities).
This isn't for homework or anything, just a puzzle I've been trying to figure out.

I would determine the value of each case in the original array, store these values, and then sort the list. This is in python 3 so I don't know if that affects you.
Determining the value of each case in the original array and storing them:
inputA = [[390, 185, 624], [686, 351, 947], [276, 1023, 1024], [199, 148, 250]]
results = []
for idx,val in enumerate(inputA):
results.append((val[0]*val[1]/val[2], idx))
Sorting the list, extracting positions:
l = lambda t:t[1]
print(list(map(l,sorted(results,reverse=True))))
Iterating over the list is O(n), and the sort is O(nlogn). Map/list/print iterates over it again for O(n) so performance should be O(nlogn).

Related

How to group approximately adjacent list

I have a list that has approximately adjacent.
x=[10,11,13,70,71,73,170,171,172,174]
I need to separate this into lists which has minimum deviation (i.e)
y=[[10,11,13],[70,71,73],[170,171,172,174]]
You can see in y list grouped into 3 separate lists and break this list when meeting huge deviation.
Can you give me a tip or any source to solve this?
the zip function is your friend when you need to compare items of a list with their successor or predecessor:
x=[10,11,13,70,71,73,170,171,172,174]
threshold = 50
breaks = [i for i,(a,b) in enumerate(zip(x,x[1:]),1) if b-a>threshold]
groups = [x[s:e] for s,e in zip([0]+breaks,breaks+[None])]
print(groups)
[[10, 11, 13], [70, 71, 73], [170, 171, 172, 174]]
breaks will contain the index (i) of elements (b) that are greater than their predecessor (a) by more than the treshold value.
Using zip() again allows you to pair up these break indexes to form start/end ranges which you can apply to the original list to get your groupings.
Note that i used a fixed threshold to detect a "huge" deviation, but you can use a percentage or any formula/condition of your choice in place of if b-a>threshold. If the deviation calculation is complex, you will probably want to make a deviates() function and use it in the list comprehension: if deviates(a,b) so that it remains intelligible
If zip() and list comprehensions are too advanced, you can do the same thing using a simple for-loop:
def deviates(a,b): # example of a (huge) deviation detection function
return b-a > 50
groups = [] # resulting list of groups
previous = None # track previous number for comparison
for number in x:
if not groups or deviates(previous, number):
groups.append([number]) # 1st item or deviation, add new group
else:
groups[-1].append(number) # approximately adjacent, add to last group
previous = number # remember previous value for next loop
Something like this should do the trick:
test_list = [10, 11, 13, 70, 71, 73, 170, 171, 172, 174]
def group_approximately_adjacent(numbers):
if not numbers:
return []
current_number = numbers.pop(0)
cluster = [current_number]
clusters = [cluster]
while numbers:
next_number = numbers.pop(0)
if is_approximately_adjacent(current_number, next_number):
cluster.append(next_number)
else:
cluster = [next_number]
clusters.append(cluster)
current_number = next_number
return clusters
def is_approximately_adjacent(a, b):
deviation = 0.25
return abs(a * (1 + deviation)) > abs(b) > abs(a * (1 - deviation))

Python algorithm to approximate closest parallel equivalence of resistors from a list

The formula for series equivalence of resistors:
series equivalence = sum(resistors)
For parallel it is 1/(sum(1/resistors[i]))
I wrote code to return a list of resistors that is closest to a specified target value from a list, within a specified tolerance.
percentage_difference = lambda xi,xf: 100*(xf-xi)/xi
def series_equivalance(R,target,tolerance):
"""
R = list of resistors present
target = target value
tolerance = range += % of target that is acceptable
This function returns a list of resistors
"""
tol = tolerance/100 #converting tolerance to decimal
if target < min(R):
return "Your target is too small for series equivalence, Try parallel equivalence"
else:
r = R #dummy/copy R
toriginal = target #dummy values for arguments made to not change arguments
approximate = 0 #this is for exit condition, target in and of itself could be used but that would make algo unstable
resistors_list = [] #list to return at the end
while True: #Infinite loop because multiple exit conditions
if (approximate >= (1-tol)*target and approximate <= (1+tol)*target) :#exit condition
break
if len(R) == 0: #If all values are used up
return "All values used up, list: {}, approximate: {}".format(resistors_list,series_sum(resistors_list))
difference_from_target = [abs(toriginal-i) for i in R] #finding absolute difference of target from list of R values
for i,v in enumerate(difference_from_target):
if v == min(difference_from_target): #adding lowest differences to list
approximate += r[i] #increment approximate by value from resistors with least difference
toriginal -= r[i] #remove that from target dummy target
resistors_list.append(r[i]) #adding to list to be returned
r.remove(r[i])
break
return "Resistors to use are {}, Approximated value: {}, %Δ of {}%".format(resistors_list,sum(resistors_list),percentage_difference(target,int(sum(resistors_list))))
So for example series_equivalance([1,2,3,4,5],7,0)will return [5,2].
I want to a function that can do the same for parallel equivalence. How would I go about it?
Edit: I made a blog post which expands on the mip solution and can solve for minimum resistors satisfying a tolerance.
I've solved this two ways
Using your function and feeding it inverse values
Using a mixed integer linear program
Using your function and feeding it inverse values
This is a half way solution that just feeds in 1/R for the resistor
values and gives it a target resistance of 1/target.
Then, take reciprocal of the result and you have your resistor values.
Doesn't work with the tolerance value properly. Need to comment out the
"Your target is too small" check for it to work.
def parallel_equivalance(R, target, tolerance):
R_recip = [1/x for x in R]
target_recip = 1/target
tolerance_recip = tolerance # TODO: have a think about how to handle this.
result_recip = series_equivalance(R_recip, target_recip, tolerance_recip)
# resistors_to_use = [1/x for x in result_recip]
print(parallel_equivalance([1, 2, 3, 4, 5, 6, 7], 1.5555, 1e-2)) gives Resistors to use are [5, 2], Approximated value: 7, %Δ of 0.0%
Using a mixed integer linear program
For this method I use a mixed integer linear program to pick out which
resistors to use, such that (1/r1 + 1/r2 + ...) is as close
as possible to 1/target. Solves very quickly (<1s) even when given
ten thousand resistors to choose from.
You could modify this to pick the least number of resistors
(minimise sum(R_in_use)) with the constraint that the
error value must be within some margin (say, error >= -eps and error <= +eps)
You'll need to pip install mip
import mip
def parallel_equivalance(R, target):
R_recip = [1/x for x in R]
target_recip = 1/target
m = mip.Model() # Create new mixed integer/linear model.
# Will take value of 1 when corresponding resistor is in use, otherwise 0.
R_in_use = [m.add_var(var_type=mip.BINARY) for _ in R_recip]
opt_r = sum([b * r for b, r in zip(R_in_use, R_recip)]) # This will be the optimal resistance
error = opt_r - target_recip # Want to minimise the absolute value of this error.
# create a variable which is greater than than the absolute value of the error.
# Because we will be minimizing, this will be forced down to equal the
# absolute value. Common trick, google "linear programming absolute value".
abs_eror = m.add_var(lb=0)
m += abs_eror >= error
m += abs_eror >= -1 * error
# Objective of the optimisation is to minimise the absolute error.
m.objective = mip.minimize(abs_eror)
m.verbose = False # Turn off verbose logging output.
sol_status = m.optimize()
print(sol_status) # This should be `optimal`.
# Get the solution values telling us which resistors are in use.
R_in_use_sol = [float(v) for v in R_in_use]
# Pick out the values of the resistors corresponding to the resistors
# that the optimiser decided to use.
R_to_use = [r for r, i in zip(R, R_in_use_sol) if i > 0]
solved_resistance = 1/sum(1/x for x in R_to_use)
solved_error = 100 * (solved_resistance - target) / target
print(f'Resistors {R_to_use} in parallel will produce '
f'R={solved_resistance:.3f}. '
f'Aiming for R={target:.3f}, '
f'error of {solved_error:.2f}%')
return R_to_use
def main():
print(f'mip version {mip.version}')
sol = parallel_equivalance([1, 2, 3, 4, 5, 6, 7], 1.5555)
sol = parallel_equivalance([1, 2, 3, 4, 5, 6, 7], 1.9)
sol = parallel_equivalance(list(range(1, 100)), 123)
sol = parallel_equivalance(list(range(1, 1000)), 5.954520294)
sol = parallel_equivalance(list(range(1, 10_000)), 5.954520294)
if __name__ == '__main__':
main()
mip version 1.13.0
OptimizationStatus.OPTIMAL
Resistors [2, 7] in parallel will produce R=1.556. Aiming for R=1.556, error of 0.00%
OptimizationStatus.OPTIMAL
Resistors [3, 5] in parallel will produce R=1.875. Aiming for R=1.900, error of -1.32%
OptimizationStatus.OPTIMAL
Resistors [99] in parallel will produce R=99.000. Aiming for R=123.000, error of -19.51%
OptimizationStatus.OPTIMAL
Resistors [27, 40, 41, 68, 69, 83, 123, 166, 172, 219, 277, 384, 391, 435, 453, 782, 837] in parallel will produce R=5.954. Aiming for R=5.955, error of -0.01%
OptimizationStatus.OPTIMAL
Resistors [7, 2001, 2021, 2065, 2130, 2152, 2160, 2176, 2191, 2202, 2216, 2245, 2270, 2279, 2282, 2283, 2313, 2342, 2351, 2381, 2414, 2417, 2497, 2728, 2789, 3449, 3514, 3566, 3575, 3621, 3701, 3789, 3812, 3868, 3879, 3882, 3903, 3936, 3952, 3959, 4128, 4145, 4152, 4158, 4183, 4373, 4382, 4430, 4441, 4498, 4525, 4678, 4722, 4887, 4953, 5138, 5178, 5253, 5345, 5358, 5543, 5593, 5620, 5774, 6002, 6247, 6364, 6580, 6715, 6740, 6819, 6904, 7187, 7293, 7380, 7468, 7533, 7782, 7809, 7846, 7895, 7914, 8018, 8067, 8242, 8309, 8414, 8507, 8515, 8590, 8627, 8872, 8893, 8910, 8952, 9171, 9282, 9311, 9376, 9477, 9550, 9657, 9736, 9792, 9822, 9876, 9982, 9988] in parallel will produce R=5.957. Aiming for R=5.955, error of 0.04%

How to make sure that a list of generated numbers follow a uniform distribution

I have a list of 150 numbers from 0 to 149. I would like to use a for loop with 150 iterations in order to generate 150 lists of 6 numbers such that,t in each iteration k, the number k is included as well as 5 different random numbers. For example:
S0 = [0, r1, r2, r3, r4, r5] # r1, r2,..., r5 are random numbers between 0 and 150
S1 = [1, r1', r2', r3', r4', r5'] # r1', r2',..., r5' are new random numbers between 0 and 150
...
S149 = [149, r1'', r2'', r3'', r4'', r5'']
In addition, the numbers in each list have to be different and with a minimum distance of 5. This is the code I am using:
import random
import numpy as np
final_list = []
for k in range(150):
S = [k]
for it in range(5):
domain = [ele for ele in range(150) if ele not in S]
d = 0
x = k
while d < 5:
d = np.Infinity
x = random.sample(domain, 1)[0]
for ch in S:
if np.abs(ch - x) < d:
d = np.abs(ch - x)
S.append(x)
final_list.append(S)
Output:
[[0, 149, 32, 52, 39, 126],
[1, 63, 16, 50, 141, 79],
[2, 62, 21, 42, 35, 71],
...
[147, 73, 38, 115, 82, 47],
[148, 5, 78, 115, 140, 43],
[149, 36, 3, 15, 99, 23]]
Now, the code is working but I would like to know if it's possible to force that number of repetitions that each number has through all the iterations is approximately the same. For example, after using the previous code, this plot indicates how many times each number has appeared in the generated lists:
As you can see, there are numbers that have appeared more than 10 times while there are others that have appeared only 2 times. Is it possible to reduce this level of variation so that this plot can be approximated as a uniform distribution? Thanks.
First, I am not sure that your assertion that the current results are not uniformly distributed is necessarily correct. It would seem prudent to me to try and examine the histogram over several repetitions of the process, rather than just one.
I am not a statistician, but when I want to approximate uniform distribution (and assuming that the functions in random provide uniform distribution), what I try to do is to simply accept all results returned by random functions. For that, I need to limit the choices given to these functions ahead of calling them. This is how I would go about your task:
import random
import numpy as np
N = 150
def random_subset(n):
result = []
cands = set(range(N))
for i in range(6):
result.append(n) # Initially, n is the number that must appear in the result
cands -= set(range(n - 4, n + 5)) # Remove candidates less than 5 away
n = random.choice(list(cands)) # Select next number
return result
result = np.array([random_subset(n) for n in range(N)])
print(result)
Simply put, whenever I add a number n to the result set, I take out of the selection candidates, an environment of the proper size, to ensure no number of a distance of less than 5 can be selected in the future.
The code is not optimized (multiple set to list conversions) but it works (as per my uderstanding).
You can force it to be precisely uniform, if you so desire.
Apologies for the mix of globals and locals, this seemed the most readable. You would want to rewrite according to how variable your constants are =)
import random
SIZE = 150
SAMPLES = 5
def get_samples():
pool = list(range(SIZE)) * SAMPLES
random.shuffle(pool)
items = []
for i in range(SIZE):
selection, pool = pool[:SAMPLES], pool[SAMPLES:]
item = [i] + selection
items.append(item)
return items
Then you will have exactly 5 of each (and one more in the leading position, which is a weird data structure).
>>> set(collections.Counter(vv for v in get_samples() for vv in v).values())
{6}
The method above does not guarantee the last 5 numbers are unique, in fact, you would expect ~10/150 to have a duplicate. If that is important, you need to filter your distribution a little more and decide how well you value tight uniformity, duplicates, etc.
If your numbers are approximately what you gave above, you also can patch up the results (fairly) and hope to avoid long search times (not the case for SAMPLES sizes closer to OPTIONS size)
def get_samples():
pool = list(range(SIZE)) * SAMPLES
random.shuffle(pool)
i = 0
while i < len(pool):
if i % SAMPLES == 0:
seen = set()
v = pool[i]
if v in seen: # swap
dst = random.choice(range(SIZE))
pool[dst], pool[i] = pool[i], pool[dst]
i = dst - dst % SAMPLES # Restart from swapped segment
else:
seen.add(v)
i += 1
items = []
for i in range(SIZE):
selection, pool = pool[:SAMPLES], pool[SAMPLES:]
assert len(set(selection)) == SAMPLES, selection
item = [i] + selection
items.append(item)
return items
This will typically take less than 5 passes through to clean up any duplicates, and should leave all arrangements satisfying your conditions equally likely.

How to find highest power of 2 less than n in a list?

I have a list likes
lst = [20, 40, 110]
I want to find the highest power of 2 in the list satisfied as
For the first number, the highest power of 2 will get the first element of the list as input. So the result is 16 (closest to 20)
For the next numbers, it will get the summation of previous result (i.e 16) and current number (.i.e 40) so the closest number will be 32 (closest 40 +16)
So the output what I expect is
lst_pow2 = [16, 32, 128]
This is my current code to find the highest number of a number, but for my problem it should change something because my input is list. Any suggestion? Thanks
# Python3 program to find highest
# power of 2 smaller than or
# equal to n.
import math
def highestPowerof2(n):
p = int(math.log(n, 2));
return int(pow(2, p));
So what I tried but it does not do the summation
lst_power2 = [highestPowerof2(lst[i]) for i in range(len(lst))]
You can perhaps use the following :
lst_power2 = [highestPowerof2(lst[i]+((i>0) and highestPowerof2(lst[i-1]))) for i in range(len(lst))]
instead of
lst_power2 = [highestPowerof2(lst[i]) for i in range(len(lst))]
You may want to modify your approach thus:
Modify your function to take 2 integers. prev_power and curr_num (this was n in your code)
Calculate the power of 2 for the first number and add to a result list
Now pass this number and the next number in the list to your highestPowerof2 function
Use an extra variable that keeps track of the value to be added, and build your logic while iterating.
lst = [20, 40, 110]
import math
def highestPowerof2(n):
p = int(math.log(n, 2)) #you do not need semi colons in python
return int(pow(2, p))
acc = 0 #to keep track of what was the last highest* power
result = []
for n in lst:
result.append(highestPowerof2(n + acc))
acc = result[-1]
print(result)
#Output:
[16, 32, 128]
This question has an accepted answer but I thought this would be a good problem that could also be solved by using a generator. The accepted answer is definitely compact but I though it would be fun to give this solution as well.
lst = [20, 40, 110]
import math
def highestPowerof2(lst):
last = 0
for element in lst:
p = int(math.log(element + last, 2))
last = int(pow(2, p)) # Remember the last value
yield last
lst_power2 = [i for i in highestPowerof2(lst)]
print(lst_power2)
You could use reduce() too:
functools.reduce(lambda res,n:res+[highestPowerof2(n+res[-1])],lst,[0])[1:]
which is short, just the [1:] is ugly at the end
Or as:
functools.reduce(lambda res,n:res+[highestPowerof2(n+(len(res) and res[-1]))],lst,[])
which does not need the slicing, but it is less readable inside.
Full example:
import math,functools
def highestPowerof2(n):
p = int(math.log(n, 2))
return int(pow(2, p))
lst = [20, 40, 110]
print(functools.reduce(lambda res,n:res+[highestPowerof2(n+res[-1])],lst,[0])[1:])
print(functools.reduce(lambda res,n:res+[highestPowerof2(n+(len(res) and res[-1]))],lst,[]))

repeat function adding values if remainder of sum is less than defined number

first post here and sorry if this has been asked before but my searching turned up nothing (maybe I was using the incorrect search terms). I wrote this script to generate a list of numbers from a predefined list, with no consecutive items, until their sum reaches a certain value. I'm now wondering how I might be able to make the function repeat, if the remainder of the sum and the value are below a certain amount.
Here is the code:
import random
spans = [120, 125, 240, 380, 315, 320, 405, 450]
length = 1000
def addVals(nums,total):
sum = 0
vals = []
i = 0
while sum<=total:
if len(vals) == 0:
i = random.choice(nums)
vals.append(i)
sum+=i
last = i
else:
nums.pop(nums.index(last))
i = random.choice(nums)
vals.append(i)
nums.append(last)
last = i
sum+=i
return vals
valList = addVals(spans,length)
valSum = sum(valList)
valLeft = valSum - length
print valList
print valSum
print valLeft
So if a run of that code gives me:
[315, 120, 320, 380]
1135
135
I'd like to rerun the function if the remainder (valLeft) is greater than 100. I tried using if and using while, but 'if' gave me errors, and 'while' got hung up. Any ideas and/or links to potential solutions would be greatly appreciated! Would also gladly accept any criticism of the way my script is written as is, particularly the method I used to avoid consecutive values.
I tried to rewrite your code in a cleaner way
import random
spans = [120, 125, 240, 380, 315, 320, 405, 450]
length = 1000
def addVals(nums,total):
vals = []
p = last = sum = 0
while sum <= total:
while last == p:
p = random.choice(nums)
sum += p
vals.append(p)
last = p
return vals
valLeft = length
while valLeft >= 100:
valList = addVals(spans,length)
valSum = sum(valList)
valLeft = valSum - length
print valList
print valSum
print valLeft
I think it works as you were expecting.
Your code had one flaw, I don't think you notice that the pop method is actually changing your array, and it will eventually run out of elements. I don't think that was intended.
That's why I added another while to keep getting random choices until it gets one different from the last one.
Hope you like it.
One option would be to add a while loop right above your print statements that just reruns addVals() until valLeft is less than or equal to 100:
while valLeft>100:
valList = addVals(spans,length)
valSum = sum(valList)
valLeft = valSum - length
The issue with this is if there is no possible way to get an array with a sum of length with a valLeft of less than 100 then you enter an infinite while loop.
For example: spans = [ 410, 415, 405, 400, 450]

Categories