Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does `yield from` have O(1) time complexity?

Consider the following code snippet.

from typing import Iterable


def geometric_progression(
    start: float, multiplier: float, num_elements: int
) -> Iterable[float]:
    assert num_elements >= 0
    if num_elements > 0:
        yield start
        yield from geometric_progression(
            start * multiplier, multiplier, num_elements - 1
        )

This function returns the first num_elements of the geometric progression starting with start and multipliying by multiplier each time. It's easy to see that the last element will be passed through one yield-statement and num_elements-1 yield-from-statements. Does this function have O(num_elements) time complexity, or does it have O(num_elements**2) time complexity due to a "ladder" of nested yield-from-statements of depths 0, 1, 2, ..., num_elements-2, num_elements-1?


EDIT: I've come up with a simpler code snippet to demonstrate what I am asking.

from typing import Iterable, Any

def identity_with_nested_yield_from(depth: int, iterable: Iterable[Any]) -> Iterable[Any]:
    assert depth >= 1
    if depth == 1:
        yield from iterable
    else:
        yield from identity_with_nested_yield_from(depth-1, iterable)

Is this function O(depth + length of iterable), or is it O(depth * length of iterable)?

like image 488
CrabMan Avatar asked May 04 '20 11:05

CrabMan


People also ask

Can we perform insertion in O 1 time complexity?

Worst time complexity for inserting into list is O(n-i) , where n is the length of the list and i is the index at which you are inserting. So in case if you are inserting at the last index, that makes it O(1) .

What is yield from in Python?

The yield keyword in Python is similar to a return statement used for returning values in Python which returns a generator object to the one who calls the function which contains yield, instead of simply returning a value.

What is the difference between yield and return Python?

Yield is generally used to convert a regular Python function into a generator. Return is generally used for the end of the execution and “returns” the result to the caller statement. It replace the return of a function to suspend its execution without destroying local variables.

What is the time complexity of A in list?

The average time complexity of the in operator for lists is O(n) .

What is Ω (n) time complexity?

For example: We have an algorithm that has Ω (n²) running time complexity, then it is also true that the algorithm has an Ω (n) or Ω (log n) or Ω (1) time complexity. It describes the limiting behavior of a function, when the argument tends towards a particular value or infinity.

Why is the time complexity of an algorithm O(1)?

When you say that Time complexity of algorithm is O (1) that does mean that all the steps that are present in your algorithm will complete in constant amount of time and each line of the code will be solved in a single step . These steps could be like equals to , addition , subtraction .. etc

When the time complexity increases linearly with the input size?

When the time complexity increases linearly with the input size then the algorithm is supposed to have a Linear time complexity. Consider that we have an algorithm, and we are calculating the time it takes to sort items.

Why do we take time complexity into consideration?

So, to get desired results from the algorithm in optimum amount of time, we take time complexity into consideration. How to measure time complexity? Do we measure absolute time? No, we consider number of steps in algorithm and input size.


2 Answers

I could've sworn there was an optimization in place to shortcut these kinds of yield from chains, but testing shows no such optimization, and I couldn't find anything in the places I thought the optimization was implemented either.

The generators on each level of a yield from chain must be suspended and resumed individually to pass yield and send values up and down the chain. Your function has O(num_elements**2) time complexity. Also, it hits a stack overflow once the call stack reaches a depth of 1000.

like image 126
user2357112 supports Monica Avatar answered Oct 19 '22 13:10

user2357112 supports Monica


yield from is formally equivalent to a loop of response = yield child.send(response), plus error propagation and handling. When consumed in iteration, the response is always None and no errors are propagated/handled. This is equivalent to a for loop.

# `yield from child` without error handling/response
for x in child:
    yield x

Thus, each yield from has the time/space complexity of iterating its argument. Stacking yield from of a size n child a total of m times thus has a time complexity of O(nm).

like image 20
MisterMiyagi Avatar answered Oct 19 '22 12:10

MisterMiyagi