What would be time complexity of a binary search that makes call to another helper function? - time-complexity

The helper retrieves value to be compared in the search function. here mem is an object.
def get_val(mem, c):
if c == "n":
return mem.get_name()
elif c == "z":
return mem.get_zip()
In the function below the helper function above is called in each iteration. Will this impact the time-complexity of the binary search or will it still be O(log n)
def bin_search(array, c, s):
first = 0
last = len(array)-1
found = False
while( first<=last and not found):
mid = (first + last)//2
val = get_val(array[mid], criteria)
if val == s:
return array[mid]
else:
if s < val:
last = mid - 1
else:
first = mid + 1
return None

Since you are calling get_val() once per iteration of your binary search, the total time complexity should be
O(log n * f(x)),
where f(x) is the time complexity of get_val(). If this is constant (does not depend on the input, such as the contents of array), then indeed your total time complexity is still O(log n).

Related

Time Complexity of a loop iteration by i^2

i=2;
while(i<n) {
i = i*i;
//O(1) complexity here
}
I'm new to time complexity and trying to figure out what this would be.
I know that if the iteration would've been i=2*i then it'd be O(log(n)) but I don't really know how I can calculate iterations of i^2.
Intuitively it'd also be O(log(n)) because it "iterates faster" but I don't know how to formally explain this.
Any help would be appreciated, thanks in advance
You can neatly translate this into the i = 2 * i case you mentioned by considering the mathematical log of i. Pseudocode for how this value changes:
log_i = log(2);
while (log_i < log_n) {
log_i = 2 * log_i;
// O(1) stuff here
}
It should be clear from this that the time complexity is O(log log n), assuming constant multiplication cost of course.
I think it's easier to approach this problem just using mathematics.
Consider your variable i. What sequence does it take? It seems to be
2, 4, 16, 162, ...
If you look at it for a bit, you notice that this sequence is just
2, 22, (22)2, ((22)2)2, ... or
21, 22, 24, 28, ... which is
220, 221, 222, 223, ...
so general term for this sequence is 22k where k = 0, 1, 2, ...
Now, how many iterations does your loop make? It will be in the order of k when 22k = n. So let us solve this equation:
22k = n (apply log2 to both sides)
2k = log2n (apply log2 to both sides again)
k = log2(log2n)
In big-O notation, the base of the logarithm doesn't matter, so we say your algorithm has a time complexity of:
O(log log n).
There are different ways to do i^2, The iterative approach (The one that you have) will have a time complexity of O(N) because we are iterating once till N.
Another method is recursive, something like:
static long pow(int x, int power) {
//System.out.println(x+":"+power);
//base condition
if (power == 0)
return 1 l;
if (power == 1)
return x;
power--;
return x * pow(x, power);}
this will also have a time complexity of O(N) because pow(x,n) is called recursively for each number from 1 to n.
The last method (and more efficient one) is the Divide and conquer which can improve the time complexity by only by calling pow(x, power/2).
static long pow(int x, int power) {
//System.out.println(x + ":" + power);
//base condition
if (power == 0)
return 1 L;
if (power == 1)
return x;
log res = pow(x, power / 2);
//if power is even
else if (power % 2 == 0)
return res * res;
else
return x * res * res; //if power is odd}
The time complexity in this case would be O(log N) because pow(x,n/2) is calculated and then stored for using the same result.

z3py: Symbolic expressions cannot be cast to concrete Boolean values

I'm having troubles to define the objective fucntion in a SMT problem with z3py.
Long story, short, I have to optimize the placing of smaller blocks inside a board that has fixed width but variable heigth.
I have an array of coordinates (represented by an array of integers of length 2) and a list of integers (representing the heigth of the block to place).
# [x,y] list of integer variables
P = [[Int("x_%s" % (i + 1)), Int("y_%s" % (i + 1))]
for i in range(blocks)]
y = [int(b) for a, b in data[2:]]
I defined the objective function like this:
obj= Int(max([P[i][1] + y[i] for i in range(blocks)]))
It calculates the max height of the board given the starting coordinate of the blocks and their heights.
I know it could be better, but I think the problem would be the same even with a different definition.
Anyway, if I run my code, the following error occurs on the line of the objective function:
" raise Z3Exception("Symbolic expressions cannot be cast to concrete Boolean values.") "
While debugging I've seen that is P[i][1] that gives an error and I think it's because the program reads "y_i + 3" (for example) and they can't be added togheter.
Point is: it's obvious that the objective function depends on the variables of the problem, so how can I get rid of this error? Is there another place where I should define the objective function so it waits to have the P array instantiated before doing anything?
Full code:
from z3 import *
from math import ceil
width = 8
blocks = 4
x = [3,3,5,5]
y = [3,5,3,5]
height = ceil(sum([x[i] * y[i] for i in range(blocks)]) / width) + 1
# [blocks x 2] list of integer variables
P = [[Int("x_%s" % (i + 1)), Int("y_%s" % (i + 1))]
for i in range(blocks)]
# value/ domain constraint
values = [And(0 <= P[i][0], P[i][0] <= width - 1, 0 <= P[i][1], P[i][1] <= height - 1)
for i in range(blocks)]
obj = Int(max([P[i][1] + y[i] for i in range(blocks)]))
board_problem = values # other constraints I've not included for brevity
o = Optimize()
o.add(board_problem)
o.minimize(obj)
if (o.check == 'unsat'):
print("The problem is unsatisfiable")
else:
print("Solved")
The problem here is that you're calling Python's max on symbolic values, which is not designed to work for symbolic expressions. Instead, define a symbolic version of max and use that:
# Return maximum of a vector; error if empty
def symMax(vs):
m = vs[0]
for v in vs[1:]:
m = If(v > m, v, m)
return m
obj = symMax([P[i][1] + y[i] for i in range(blocks)])
With this change your program will go through and print Solved when run.

Time and Space Complexity of a Recursive Algorithm

Convert n to its English words representation, where 0 <= n < 1,000,000,000
Python Solution:
class Solution:
def helper(self, n):
ones = ['', 'One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine']
teens = ['Ten', 'Eleven', 'Twelve', 'Thirteen', 'Fourteen', 'Fifteen', 'Sixteen', 'Seventeen', 'Eighteen', 'Nineteen']
tens = ['', '', 'Twenty', 'Thirty', 'Forty', 'Fifty', 'Sixty', 'Seventy', 'Eighty', 'Ninety']
res = ''
if n < 10:
res = ones[n]
elif n < 20:
res = teens[n - 10]
elif n < 100:
res = tens[n // 10] + ' ' + self.helper(n % 10)
elif n < 1000:
res = self.helper(n // 100) + ' Hundred ' + self.helper(n % 100)
elif n < 1000000:
res = self.helper(n // 1000) + ' Thousand ' + self.helper(n % 1000)
elif n < 1000000000:
res = self.helper(n // 1000000) + ' Million ' + self.helper(n % 1000000)
return res.strip()
def convert_to_word(self, n):
if n == 0:
return 'Zero'
return self.helper(n)
I've been trying to calculate the time and space complexity of this solution. I've seen different answers. Some say that the time complexity is O(1) because the helper function is called a fixed number of times (even if n is a large number). Others say it's O(log(n)).
The space complexity seems to be O(1)?
I am so confused. Please help me clarify. Thank you.
On all inputs n that are larger than 1000000000, the function returns immediately with an empty string, without any computations or recursive calls. So of course the time complexity is O(1), likewise the space complexity (since what happens for smaller n is completely irrelevant).
The case would be different and more interesting if you removed the line
elif n < 1000000000
so that for large n you would get a (virtually) unbounded resulting string with an unbounded number of Million-substrings (ignoring the fact that integers have a maximum size on a real computer, and ignoring the fact that you would get nonsensical number words). In this case you would get the time complexity O(log(n)^2) (since you're concatenating O(log n) strings of length O(log n)) and space complexity O(log n) (because of the call stack for the recursive calls). The time complexity could easily be reduced to O(log n) by handling the string concatenations more efficiently.
Some additional explanation
It seems from the comments that it's not obvious why the time complexity is O(1). If we say that the time complexity T(n) is in O(1), that means that there exists a constant c and a constant k such that for all n > k, T(n) <= c.
In this example, choosing c = 9 and k = 0 does the trick, or alternatively choosing c = 1 and k = 1000000000, hence the time complexity is O(1). It should now also be clear that the following function is also O(1) (albeit with a very large hidden constant factor):
void f(int n) {
if (n < 1000000000) {
for(int i = 0; i < n; i++) {
for(int k = 0; k < n; k++)
print(i);
}
}
else print("");
}

What's the time complexity of Dijkstra's Algorithm

Dijkstra((V, E)):
S = {} //O(1)
for each vertex v ∈ V: //O(V)
d[v] = ∞ //O(1)
d[source] = 0 //O(1)
while S != V: //O(V)
v = non visited vertex with the smallest d[v] //O(V)
for each edge (v, u): //O(E)
if u ∈/ S and d[v] + w(v, u) < d[u]:
d[u] = d[v] + w(v, u)
S = S ∪ {v}
Note: ∈/ means not in, i can't type it in the code.
This question maybe duplicates with some posts.
Understanding Time complexity calculation for Dijkstra Algorithm
Complexity Of Dijkstra's algorithm
Complexity in Dijkstras algorithm
I read them and even some posts on Quora, but still cannot understand. I put some comments in the pseudo code and tried to work it out. I really confuse on why it is O(E log V)
The "non visited vertex with the smallest d[v]" is actually O(1) if you use a min heap and insertion in the min heap is O(log V).
Therefore the complexity is as you correctly mentioned for the other loops:
O((V logV) + (E logV)) = O(E logV) // Assuming E > V which is reasonable
it is O((V logV) + (E logV)) = O(logV * (V + E)) for general graphs.
You wouldn't just assume that the graph is dense i.e. |E| = O(|V|^2) since most graphs in applications are actually sparse i.e. |E| = O(|V|).

How to declare constraints with variable as array index in Z3Py?

Suppose x,y,z are int variables and A is a matrix, I want to express a constraint like:
z == A[x][y]
However this leads to an error:
TypeError: object cannot be interpreted as an index
What would be the correct way to do this?
=======================
A specific example:
I want to select 2 items with the best combination score,
where the score is given by the value of each item and a bonus on the selection pair.
For example,
for 3 items: a, b, c with related value [1,2,1], and the bonus on pairs (a,b) = 2, (a,c)=5, (b,c) = 3, the best selection is (a,c), because it has the highest score: 1 + 1 + 5 = 7.
My question is how to represent the constraint of selection bonus.
Suppose CHOICE[0] and CHOICE[1] are the selection variables and B is the bonus variable.
The ideal constraint should be:
B = bonus[CHOICE[0]][CHOICE[1]]
but it results in TypeError: object cannot be interpreted as an index
I know another way is to use a nested for to instantiate first the CHOICE, then represent B, but this is really inefficient for large quantity of data.
Could any expert suggest me a better solution please?
If someone wants to play a toy example, here's the code:
from z3 import *
items = [0,1,2]
value = [1,2,1]
bonus = [[1,2,5],
[2,1,3],
[5,3,1]]
choices = [0,1]
# selection score
SCORE = [ Int('SCORE_%s' % i) for i in choices ]
# bonus
B = Int('B')
# final score
metric = Int('metric')
# selection variable
CHOICE = [ Int('CHOICE_%s' % i) for i in choices ]
# variable domain
domain_choice = [ And(0 <= CHOICE[i], CHOICE[i] < len(items)) for i in choices ]
# selection implication
constraint_sel = []
for c in choices:
for i in items:
constraint_sel += [Implies(CHOICE[c] == i, SCORE[c] == value[i])]
# choice not the same
constraint_neq = [CHOICE[0] != CHOICE[1]]
# bonus constraint. uncomment it to see the issue
# constraint_b = [B == bonus[val(CHOICE[0])][val(CHOICE[1])]]
# metric definition
constraint_sumscore = [metric == sum([SCORE[i] for i in choices ]) + B]
constraints = constraint_sumscore + constraint_sel + domain_choice + constraint_neq + constraint_b
opt = Optimize()
opt.add(constraints)
opt.maximize(metric)
s = []
if opt.check() == sat:
m = opt.model()
print [ m.evaluate(CHOICE[i]) for i in choices ]
print m.evaluate(metric)
else:
print "failed to solve"
Turns out the best way to deal with this problem is to actually not use arrays at all, but simply create integer variables. With this method, the 317x317 item problem originally posted actually gets solved in about 40 seconds on my relatively old computer:
[ 0.01s] Data loaded
[ 2.06s] Variables defined
[37.90s] Constraints added
[38.95s] Solved:
c0 = 19
c1 = 99
maxVal = 27
Note that the actual "solution" is found in about a second! But adding all the required constraints takes the bulk of the 40 seconds spent. Here's the encoding:
from z3 import *
import sys
import json
import sys
import time
start = time.time()
def tprint(s):
global start
now = time.time()
etime = now - start
print "[%ss] %s" % ('{0:5.2f}'.format(etime), s)
# load data
with open('data.json') as data_file:
dic = json.load(data_file)
tprint("Data loaded")
items = dic['items']
valueVals = dic['value']
bonusVals = dic['bonusVals']
vals = [[Int("val_%d_%d" % (i, j)) for j in items if j > i] for i in items]
tprint("Variables defined")
opt = Optimize()
for i in items:
for j in items:
if j > i:
opt.add(vals[i][j-i-1] == valueVals[i] + valueVals[j] + bonusVals[i][j])
c0, c1 = Ints('c0 c1')
maxVal = Int('maxVal')
opt.add(Or([Or([And(c0 == i, c1 == j, maxVal == vals[i][j-i-1]) for j in items if j > i]) for i in items]))
tprint("Constraints added")
opt.maximize(maxVal)
r = opt.check ()
if r == unsat or r == unknown:
raise Z3Exception("Failed")
tprint("Solved:")
m = opt.model()
print " c0 = %s" % m[c0]
print " c1 = %s" % m[c1]
print " maxVal = %s" % m[maxVal]
I think this is as fast as it'll get with Z3 for this problem. Of course, if you want to maximize multiple metrics, then you can probably structure the code so that you can reuse most of the constraints, thus amortizing the cost of constructing the model just once, and incrementally optimizing afterwards for optimal performance.