0

Hi I am trying to convert my iterative algorithm to recursive solution to achieve Dynamic Programming after it's done (Do suggest me other ways to reduce time complexity of this triple nested iteration). I am not good with recursion. I had tried to convert it but it is giving me index out of range errors.

Iterative Approach:

def foo(L):
          L=sorted(L)
          A = 0
          for i,x in enumerate(L):
            for j,y in enumerate(L):
                if x != y:
                    if (y%x ==0):
                        for k,z in enumerate(L):
                            if y != z:
                                if (z%y==0) and (y%x==0):
                                    A= A+1

          return A

Recursive Approach:

A =i =j= k =0 #Initializing globals
def foo(L):
              L=sorted(L)
              global A ,i,j,k
              x=y=z=L
              luckyOne(x,y,z)
              return A

def luckyOne(x,y,z):
    global i,j,k
    while(i< len(x) and j < len(y) and k < len(z)):
        while(x[i] != y[j]):
            luckyTwo(x[i:],y[j:],z[k:])
            i+=1 
            luckyOne(x[i:],y[j:],z[k:])
            # i+=1 
            # return A
        i+=1 
        luckyOne(x[i:],y[j:],z[k:])
    return 0

def luckyTwo(x,y,z):
    global i,j,k
    while (i< len(x) and j < len(y) and k < len(z)):
        while(y[j]%x[i]==0):
            luckyThree(x[i:],y[j:],z[k:])
            j+=1 
            luckyTwo(x[i:],y[j:],z[k:])
        j+=1 
        luckyTwo(x[i:],y[j:],z[k:])
    return 0

def luckyThree(x,y,z):
    global A ,i,j,k
    while (i< len(x) and j < len(y) and k < len(z)):
        while (y[j]!=z[k]):
            while((z[k]%y[j]==0) and (y[j]%x[i]==0)):
                A+=1
                print 'idr aya'
                k+=1 
                luckyThree(x[i:],y[j:],z[k:])
        k+=1 
        luckyThree(x[i:],y[j:],z[k:])
    return 0

The input should be like L=['1','2','3']

9
  • 1
    Why would you think that converting the algorithm to recursive version is going to reduce time complexity? The time complexity will probably be the same, and might even be worse, and recursion is way more expensive than a loop, so your constant factors will almost certainly be higher. Commented Jun 28, 2017 at 20:54
  • Because after achieving recursion i will try to add memoization to reduce time complexity. correct me if I am wrong. Also if you can suggest me other ways to reduce time complexity of this triple nested loop. Commented Jun 28, 2017 at 21:01
  • Note that the default recursion depth in python is only 1000... Commented Jun 28, 2017 at 21:05
  • 3
    First up, why are you calling enumerate when you never need the indices? Commented Jun 28, 2017 at 21:10
  • If L is only three items long, you don't need to make this faster. You need to make it more readable, which probably means using itertools.product() instead of nested loops. Commented Jun 28, 2017 at 21:11

3 Answers 3

3

This is the fastest version I can come up with:

def foo(lst):
    edges = {x: [y for y in lst if x != y and y % x == 0] for x in set(lst)}
    return sum(len(edges[y]) for x in lst for y in edges[x])

This should be significantly faster (1/7th the time in my test of lists with 100 elements).

The algorithm is essentially to build a directed graph where the nodes are the numbers in the list. Edges go from node A to node B iff the integer values of those nodes are different and A divides evenly into B.

Then traverse the graph. For each starting node A, find all the nodes B where there's an edge from A to B. On paper, we would then go to all the next nodes C, but we don't need to... we can just count how many edges are leaving node B and add that to our total.

EDIT

Depending on the distribution of values in the list, this is probably faster:

def foo(lst):
    counts = Counter(lst)
    edges = {x: [y for y in counts if x != y and y % x == 0] for x in counts}
    return sum(counts[x] * counts[y] * sum(counts[z] for z in edges[y]) for x in counts for y in edges[x])

Here, you can think of nodes as having a numeric value and a count. This avoids duplicate nodes for duplicate values in the input. Then we basically do the same thing but multiply by the appropriate count at each step.

EDIT 2

def foo(lst):
    counts = collections.Counter(lst)
    edges = collections.defaultdict(list)
    for x, y in itertools.combinations(sorted(counts), 2):
        if y % x == 0:
            edges[x].append(y)
    return sum(counts[x] * counts[y] * sum(counts[z] for z in edges[y]) for x in counts for y in edges[x])

Slight improvement thanks to @Blckknght. Sorting the unique values first saves some time in enumeration.

EDIT 3

See comments, but the original code in this question was actually wrong. Here's code that (I think) does the right thing based on the problem description which can be found in the comments:

def foo3(lst):
    count = 0

    for x, y, z in itertools.combinations(lst, 3):
        if y % x == 0 and z % y == 0:
            count += 1

    return count

print(foo3([1, 2, 3, 4, 5, 6]))  # 3
print(foo3([6, 5, 4, 3, 2, 1]))  # 0

EDIT 4

Much faster version of the previous code:

def foo4(lst):
    edges = [[] for _ in range(len(lst))]

    for i, j in itertools.combinations(range(len(lst)), 2):
        if lst[j] % lst[i] == 0:
            edges[i].append(j)

    return sum(len(edges[j]) for i in range(len(lst)) for j in edges[i])

EDIT 5

More compact version (seems to run in about the same amount of time):

def foo5(lst):
    edges = [[j for j in range(i + 1, len(lst)) if lst[j] % lst[i] == 0] for i in range(len(lst))]
    return sum(len(edges[j]) for i in range(len(lst)) for j in edges[i])
Sign up to request clarification or add additional context in comments.

22 Comments

B should also evenly divide C is this condition been catered here?
Yes. There's only an edge from B to C if B evenly divides into C.
foo(range(1,21)) == 40 for both
Yeah, I've tried many thousands of random lists ([random.randint(1, 100) for _ in range(100)]), and I have yet to find one that doesn't produce the same output as the original code.
I think I found the problem description: codereview.stackexchange.com/questions/144510/…. If so, I think the issue is that you're excluding pairs that are equal. (Your if x != y and if y != z shouldn't be there.)
|
3

Here's how I'd solve your problem. It should use O(N**2) time.

def count_triple_multiples(lst):
    count = collections.Counter(lst)
    double_count = collections.defaultdict(int)
    for x, y in itertools.combinations(sorted(count), 2):
        if y % x == 0:
            double_count[y] += count[x] * count[y]
    triple_count = 0
    for x, y in itertools.combinations(sorted(double_count), 2):
        if y % x == 0:
            triple_count += double_count[x] * count[y]
    return triple_count

My algorithm is very similar to the one smarx is using in his answer, but I keep a count of the number of edges incident to a given value rather than a list.

3 Comments

Nice! This appears to be a bit faster than my solution.
See the latest edit to my solution; it now seems to be slightly faster than yours.
Yeah, I figured some combination of our two ideas might be optimal, but I couldn't see quite how to get there. It looks like you may have found it.
0

As far as speeding things up goes (instead of going recursive), testing with 1000 entries, slicing the sorted list at each level cut time by more than half for me (gets rid of numbers less than y, z at their respective levels:

def foo(L):
    assert 0 not in L
    L=sorted(L)
    A = 0
    for i,x in enumerate(L):
        for j,y in enumerate(L[i + 1:]):
            if x != y and not y % x:
                for k,z in enumerate(L[i + j + 2:]):
                    if y != z and not z % y:
                        A = A + 1

    return A

4 Comments

It is faster but not fast enough.
Also shouldn't you just write L[i + 1:][j + 1:] as L[j+1:]?
@MuhammadShehrozSajjad j is relative to the sliced list, not the original list. But I just realized you can save on a slice by doing L[i + j + 2:] instead.
Still the other solutions given here are way faster than this on a larger input.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.