Greedy Makespan algorithm - python

I am needing to implement this greedy algorithm in python, but am having trouble understanding how to find the 'processor' for which M[j] is the least. Algorithm provided below...
greedy_min_make_span(T, m):
# T is an array of n numbers, m >= 2
A = [Nil, ... , Nil] # Initialize the assignments to nil (array size n)
M = [ 0, 0, ...., 0] # initialize the current load of each processor to 0 (array size m)
for i = 1 to n
find processor j for which M[j] is the least.
A[i] = j
M[j] = M[j] + T[i]
# Assignment achieves a makespan of max(M[1], .. M[m])
return A
def greedy_makespan_min(times, m):
# times is a list of n jobs.
assert len(times) >= 1
assert all(elt >= 0 for elt in times)
assert m >= 2
n = len(times)
# please do not reorder the jobs in times or else tests will fail.
# Return a tuple of two things:
# - Assignment list of n numbers from 0 to m-1
# - The makespan of your assignment
A = n*[0]
M = m*[0]
i = 1
for i in range(i, n):
j = M.index(min(M))
A[i] = j
M[j] = M[j] + times[i]
return (A, M)
FIXED: The error i'm getting right now is "list assignment index out of range" when I am trying to assign A[i] to j.
Utility function:
def compute_makespan(times, m, assign):
times_2 = m*[0]
for i in range(len(times)):
proc = assign[i]
time = times[i]
times_2[proc] = times_2[proc] + time
return max(times_2)
Test cases that I have...
def do_test(times, m, expected):
(a, makespan) = greedy_makespan_min(times,m )
print('\t Assignment returned: ', a)
print('\t Claimed makespan: ', makespan)
assert compute_makespan(times, m, a) == makespan, 'Assignment returned is not consistent with the reported makespan'
assert makespan == expected, f'Expected makespan should be {expected}, your core returned {makespan}'
print('Passed')
print('Test 1:')
times = [2, 2, 2, 2, 2, 2, 2, 2, 3]
m = 3
expected = 7
do_test(times, m, expected)
print('Test 2:')
times = [1]*20 + [5]
m = 5
expected =9
do_test(times, m, expected)
Right now I am failing the test cases. My assignment returned is not consistent with the reported makespan. My assignment returned is: [0, 0, 1, 2, 0, 1, 2, 0, 1] and my claimed makespan is: [6, 7, 4]. My compute makespan is returning 8 when it is expecting 7. Any ideas where I'm implementing this algorithm wrong?

Change A = n*[] to A = n*[0].
Instead of creating a list with length n, A = n*[] would create an empty list. Since you're assigning A[i] = j in each iteration, the change would functionally make no difference to the output.

Related

Min Makespan Algorithm [duplicate]

I am needing to implement this greedy algorithm in python, but am having trouble understanding how to find the 'processor' for which M[j] is the least. Algorithm provided below...
greedy_min_make_span(T, m):
# T is an array of n numbers, m >= 2
A = [Nil, ... , Nil] # Initialize the assignments to nil (array size n)
M = [ 0, 0, ...., 0] # initialize the current load of each processor to 0 (array size m)
for i = 1 to n
find processor j for which M[j] is the least.
A[i] = j
M[j] = M[j] + T[i]
# Assignment achieves a makespan of max(M[1], .. M[m])
return A
def greedy_makespan_min(times, m):
# times is a list of n jobs.
assert len(times) >= 1
assert all(elt >= 0 for elt in times)
assert m >= 2
n = len(times)
# please do not reorder the jobs in times or else tests will fail.
# Return a tuple of two things:
# - Assignment list of n numbers from 0 to m-1
# - The makespan of your assignment
A = n*[0]
M = m*[0]
i = 1
for i in range(i, n):
j = M.index(min(M))
A[i] = j
M[j] = M[j] + times[i]
return (A, M)
FIXED: The error i'm getting right now is "list assignment index out of range" when I am trying to assign A[i] to j.
Utility function:
def compute_makespan(times, m, assign):
times_2 = m*[0]
for i in range(len(times)):
proc = assign[i]
time = times[i]
times_2[proc] = times_2[proc] + time
return max(times_2)
Test cases that I have...
def do_test(times, m, expected):
(a, makespan) = greedy_makespan_min(times,m )
print('\t Assignment returned: ', a)
print('\t Claimed makespan: ', makespan)
assert compute_makespan(times, m, a) == makespan, 'Assignment returned is not consistent with the reported makespan'
assert makespan == expected, f'Expected makespan should be {expected}, your core returned {makespan}'
print('Passed')
print('Test 1:')
times = [2, 2, 2, 2, 2, 2, 2, 2, 3]
m = 3
expected = 7
do_test(times, m, expected)
print('Test 2:')
times = [1]*20 + [5]
m = 5
expected =9
do_test(times, m, expected)
Right now I am failing the test cases. My assignment returned is not consistent with the reported makespan. My assignment returned is: [0, 0, 1, 2, 0, 1, 2, 0, 1] and my claimed makespan is: [6, 7, 4]. My compute makespan is returning 8 when it is expecting 7. Any ideas where I'm implementing this algorithm wrong?
Change A = n*[] to A = n*[0].
Instead of creating a list with length n, A = n*[] would create an empty list. Since you're assigning A[i] = j in each iteration, the change would functionally make no difference to the output.

In numpy, most computationally efficient way to find the array with shortest non-zero sequence in array of arrays

Say that I have an array of arrays
import numpy as np
z = np.array(
[
[1, 1, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 0],
[1, 1, 1, 0, 0, 0],
[1, 1, 1, 1, 1, 1],
]
)
Where 1s start on the left side of each array, and 0s on the right side if any. For many applications, this is how arrays are padded so that each array is of the same length in an array of arrays.
How would I get the shortest sequence of non-zeros for such an array.
In this case, the shortest sequence is the first array, which has a length of 2.
The obvious answer is to iterate over each array and find the index of the first zero, but I feel that there's probably a method that takes more advantage of numpy's c processing.
Benchmark with a 5000×5000 array:
74.3 ms Dani
33.8 ms user19077881
2.6 ms Kelly1
1.4 ms Kelly2
My Kelly1 is an O(m+n) saddleback search from top-right to bottom-left:
def Kelly1(z):
m, n = z.shape
j = n - 1
for i in range(m):
while not z[i, j]:
j -= 1
if j < 0:
return 0
return j + 1
(Michael Szczesny said it can trivially be made ~150x faster (if I remember correctly) by using Numba. I'm not equipped to test that myself, though.)
My Kelly2 is an O(m log n) horizontal binary search, using NumPy to check whether a column is full of non-zeros:
def Kelly2(z):
m, n = z.shape
lo, hi = 0, n
while lo < hi:
mid = (lo + hi) // 2
if z[:, mid].all():
lo = mid + 1
else:
hi = mid
return lo
(Could be shorter by using bisect with a key, but I don't have Python 3.10 to test right now.)
Note: Dani and user19077881 return different results: The smallest number of non-zeros in any row, or the row with the fewest non-zeros. I followed Dani's lead, as that's the accepted answer. It doesn't really matter, as you can compute one result from the other very quickly (by finding the index of the first zero in the column or row, respectively).
Full benchmark code (Try it online!):
import numpy as np
from timeit import timeit
import random
m, n = 5000, 5000
def genz():
lo = random.randrange(n*5//100, n//3)
return np.array(
[
[1]*ones + [0]*(n-ones)
for ones in random.choices(range(lo, n+1), k=m)
]
)
def Dani(z):
return np.count_nonzero(z, axis=1).min()
def user19077881(z):
z_sums = z.sum(axis = 1)
z_least = np.argmin(z_sums)
return z_least
def Kelly1(z):
m, n = z.shape
j = n - 1
for i in range(m):
while not z[i, j]:
j -= 1
if j < 0:
return 0
return j + 1
def Kelly2(z):
m, n = z.shape
lo, hi = 0, n
while lo < hi:
mid = (lo + hi) // 2
if z[:, mid].all():
lo = mid + 1
else:
hi = mid
return lo
funcs = Dani, user19077881, Kelly1, Kelly2
for _ in range(3):
z = genz()
for f in funcs:
t = timeit(lambda: f(z), number=1)
print('%5.1f ms ' % (t * 1e3), f.__name__)
print()
Use np.count_nonzero + np.min:
res = np.count_nonzero(z, axis=1).min()
print(res)
Output
2
The function count_nonzero returns an array like:
[2 5 3 6]
then simply find the minimum value.
If you want the index of the row, use np.argmin instead.
If you want to know which sub-array has the least zeros then you could use:
z_sums = z.sum(axis = 1)
z_least = np.argmin(z_sums)

Optimizing permutation generator where total of each permutation totals to same value

I'm wanting to create a list of permutations or cartesian products (not sure which one applies here) where the sum of values in each permutation totals to a provided value.
There should be three parameters required for the function.
Sample Size: The number of items in each permutation
Desired Sum: The total that each permutation should add up to
Set of Numbers: The set of numbers that can be included with repetition in the permutations
I have an implementation working below but it seems quite slow I would prefer to use an iterator to stream the results but I would also need a function that would be able to calculate the total number of items that the iterator would produce.
def buildPerms(sample_size, desired_sum, set_of_number):
blank = [0] * sample_size
return recurseBuildPerms([], blank, set_of_number, desired_sum)
def recurseBuildPerms(perms, blank, values, desired_size, search_index = 0):
for i in range(0, len(values)):
for j in range(search_index, len(blank)):
if(blank[j] == 0):
new_blank = blank.copy()
new_blank[j] = values[i]
remainder = desired_size - sum(new_blank)
new_values = list(filter(lambda x: x <= remainder, values))
if(len(new_values) > 0):
recurseBuildPerms(perms, new_blank, new_values, desired_size, j)
elif(sum(new_blank) <= desired_size):
perms.append( new_blank)
return perms
perms = buildPerms(4, 10, [1,2,3])
print(perms)
## Output
[[1, 3, 3, 3], [2, 2, 3, 3], [2, 3, 2, 3],
[2, 3, 3, 2], [3, 1, 3, 3], [3, 2, 2, 3],
[3, 2, 3, 2], [3, 3, 1, 3], [3, 3, 2, 2],
[3, 3, 3, 1]]
https://www.online-python.com/9cmOev3zlg
Questions:
Can someone help me convert my solution into an iterator?
Is it possible to have a calculation to know the total number of items without seeing the full list?
Here is one way to break this down into two subproblems:
Find all restricted integer partitions of target_sum into sample_size summands s.t. all summands come from set_of_number.
Compute multiset permutations for each partition (takes up most of the time).
Problem 1 can be solved with dynamic programming. I used multiset_permutations from sympy for part 2, although you might be able to get better performance by writing your own numba code.
Here is the code:
from functools import lru_cache
from sympy.utilities.iterables import multiset_permutations
#lru_cache(None)
def restricted_partitions(n, k, *xs):
'partitions of n into k summands using only elements in xs (assumed positive integers)'
if n == k == 0:
# case of unique empty partition
return [[]]
elif n <= 0 or k <= 0 or not xs:
# case where no partition is possible
return []
# general case
result = list()
x = xs[0] # element x we consider including in a partition
i = 0 # number of times x should be included
while True:
i += 1
if i > k or x * i > n:
break
for rest in restricted_partitions(n - x * i, k - i, *xs[1:]):
result.append([x] * i + rest)
result.extend(restricted_partitions(n, k, *xs[1:]))
return result
def buildPerms2(sample_size, desired_sum, set_of_number):
for part in restricted_partitions(desired_sum, sample_size, *set_of_number):
yield from multiset_permutations(part)
# %timeit sum(1 for _ in buildPerms2(8, 16, [1, 2, 3, 4])) # 16 ms
# %timeit sum(1 for _ in buildPerms (8, 16, [1, 2, 3, 4])) # 604 ms
The current solution requires computing all restricted partitions before iteration can begin, but it may still be practical if restricted partitions can be computed quickly. It may be possible to compute partitions iteratively as well, although this may require more work.
On the second question, you can indeed count the number of such permutations without generating them all:
# present in the builtin math library for Python 3.8+
#lru_cache(None)
def binomial(n, k):
if k == 0:
return 1
if n == 0:
return 0
return binomial(n - 1, k) + binomial(n - 1, k - 1)
#lru_cache(None)
def perm_counts(n, k, *xs):
if n == k == 0:
# case of unique empty partition
return 1
elif n <= 0 or k <= 0 or not xs:
# case where no partition is possible
return 0
# general case
result = 0
x = xs[0] # element x we consider including in a partition
i = 0 # number of times x should be included
while True:
i += 1
if i > k or x * i > n:
break
result += binomial(k, i) * perm_counts(n - x * i, k - i, *xs[1:])
result += perm_counts(n, k, *xs[1:])
return result
# assert perm_counts(15, 6, *[1,2,3,4]) == sum(1 for _ in buildPerms2(6, 15, [1,2,3,4])) == 580
# perm_counts(1000, 100, *[1,2,4,8,16,32,64])
# 902366143258890463230784240045750280765827746908124462169947051257879292738672
The function used to count all restricted permutations looks very similar to the function that generates partitions above. The only significant change is in the following line:
result += binomial(k, i) * perm_counts(n - x * i, k - i, *xs[1:])
There are i copies of x to include and k possible positions where x's may end up. To account for this multiplicity, the number of ways to resolve the recursive sub-problem is multiplied by k choose i.

Fill in an array using loop with multiple variables (new to Python, old to C++ (back in the day))

Basically what I want to do is create something like this in python (this is basic idea and not actual code):
n = 3
i = n + 1
a = [1, 3, 3, 1]
b = [1, 2, 1]
while n > 1:
Check if n is even
- if n is even, then for all i in range(0,n), insert values into an array using the formula below
- b[n-i] = a[n-i-1] + a[n-i], this value will replace the previously given value of b[] above the code.
- Print out the array
- After each area is filled, n+=1, i=n+1 are applied, then the loop continues
Check if n is odd
- same process except formula is
- a[n-i] = b[n-i-1] + a[n-i], this value will replace the previously given value of a[] above the code.
- Print out the array
- After each area is filled, n+=1, i=n+1 are applied, then the loop continues
This process will loop and print each and continue on, the arrays will essentially look like this:
b = [1, 4, 6, 4, 1], a = [1 5, 10, 10, 5, 1], b = [1, 6, 15, 20, 20, 15, 6, 1], etc.
Here is the code that I currently have, however I'm getting an 'out of range' error.
n = 3
i = n + 1
b = [1, 2, 1]
a = [1, 3, 3, 1]
while n > 1:
if n%2==0:
print("even")
for i in range(0,n):
b[n-i].append(a[n-i-1]+a[n-i])
else:
print("odd")
for i in range(0,n):
print("yay")
a[n-i].append(b[n-i-1]+b[n-i])
if n%2==0:
print(b)
else:
print(a)
n +=1
i = n + 1
print("loop")
The random prints throughout the code are to test and see if it is even making it into the process. There were from a previous code and I just haven't removed them yet.
Hopefully you can help me, I can't find anything online about a loop that constantly increases the size of an array and fills it at the same time.
Sorry struggling with the code that's in the sample. From your description I can see that you want to generate Pascal's triangle. Here's a short snippet that will do this.
a = [1, 1]
for _ in range(10):
a = [1] + [x+y for (x,y) in zip(a[:-1], a[1:])] + [1]
print a
a[:-1] refers to the whole array except the last element and a[1:] refers to whole array except first element. zip combines first elements from each array into a tuple and so on. All that remains is to add them and pad the row with ones one the outside. _ is used to tell Python, I don't care about this variable - useful if you want to be explicit that you are not using the range value for anything except flow control.
Maria's answer is perfect, I think. If you want to start with your code, you can rewrite your code as below to get similar result. FYI.
n = 3
b = [1, 2, 1]
while 1 < n < 10:
if n % 2 == 0:
print("even")
b = [0] * (n + 1)
for i in range(0, n + 1):
if i == 0:
b[i] = a[0]
elif i == n:
b[i] = a[i - 1]
else:
b[n - i] = a[i - 1] + a[i]
else:
print("odd")
a = [0] * (n + 1)
for i in range(0, n + 1):
if i == 0:
a[i] = b[0]
elif i == n:
a[i] = b[i - 1]
else:
a[i] = b[i - 1] + b[i]
if n % 2 == 0:
print(b)
else:
print(a)
n += 1
print("loop")

Merge sort implementation in python giving incorrect result

I am trying to implement the merge sort algorithm described in these notes by Jeff Erickson on page 3. but even though the algorithm is correct and my implementation seems correct, I am getting the input list as output without any change. Can someone point out the anomalies, if any, in it.
def merge(appnd_lst, m):
result = []
n = len(appnd_lst)
i, j = 0, m
for k in range(0, n):
if j < n:
result.append(appnd_lst[i])
i += 1
elif i > m:
result.append(appnd_lst[j])
j += 1
elif appnd_lst[i] < appnd_lst[j]:
result.append(appnd_lst[i])
i += 1
else:
result.append(appnd_lst[j])
j += 1
return result
def mergesort(lst):
n = len(lst)
if n > 1:
m = int(n / 2)
left = mergesort(lst[:m])
right = mergesort(lst[m:])
appnd_lst = left
appnd_lst.extend(right)
return merge(appnd_lst, m)
else:
return lst
if __name__ == "__main__":
print mergesort([3, 4, 8, 0, 6, 7, 4, 2, 1, 9, 4, 5])
There are three errors in your merge function a couple of indexing errors and using the wrong comparison operator. Remember python list indices go from 0 .. len(list)-1.
* ...
6 if j > n-1: # operator wrong and off by 1
* ...
9 elif i > m-1: # off by 1
* ...

Categories