Is a list (potentially) divisible by another? Is a list (potentially) divisible by another? python python

Is a list (potentially) divisible by another?


Build bipartite graph structure - connect a[i] with all its divisors from b[].enter image description here

Then find maximum matching and check whether it is perfect matching (number of edges in matching is equal to the number of pairs (if graph is directed) or to doubled number).

Arbitrary chosen Kuhn algorithm implementation here.

Upd:
@Eric Duminil made great concise Python implementation here

This approach has polynomial complexity from O(n^2) to O(n^3) depending on chosen matching algorithm and number of edges (division pairs) against factorial complexity for brute-force algorithm.


Code

Building on @MBo's excellent answer, here's an implementation of bipartite graph matching using networkx.

import networkx as nxdef is_potentially_divisible(multiples, divisors):    if len(multiples) != len(divisors):        return False    g = nx.Graph()    g.add_nodes_from([('A', a, i) for i, a in enumerate(multiples)], bipartite=0)    g.add_nodes_from([('B', b, j) for j, b in enumerate(divisors)], bipartite=1)    edges = [(('A', a, i), ('B', b, j)) for i, a in enumerate(multiples)             for j, b in enumerate(divisors) if a % b == 0]    g.add_edges_from(edges)    m = nx.bipartite.maximum_matching(g)    return len(m) // 2 == len(multiples)print(is_potentially_divisible([6, 12, 8], [3, 4, 6]))# Trueprint(is_potentially_divisible([6, 12, 8], [3, 4, 3]))# Trueprint(is_potentially_divisible([10, 12, 6, 5, 21, 25], [2, 7, 5, 3, 12, 3]))# False

Notes

According to the documentation:

The dictionary returned by maximum_matching() includes a mapping for vertices in both the left and right vertex sets.

It means that the returned dict should be twice as large as A and B.

The nodes are converted from

[10, 12, 6, 5, 21, 25]

to:

[('A', 10, 0), ('A', 12, 1), ('A', 6, 2), ('A', 5, 3), ('A', 21, 4), ('A', 25, 5)]

in order to avoid collisions between nodes from A and B. The id is also added in order to keep nodes distinct in case of duplicates.

Efficiency

The maximum_matching method uses Hopcroft-Karp algorithm, which runs in O(n**2.5) in the worst case. The graph generation is O(n**2), so the whole method runs in O(n**2.5). It should work fine with large arrays. The permutation solution is O(n!) and won't be able to process arrays with 20 elements.

With diagrams

If you're interested in a diagram showing the best matching, you can mix matplotlib and networkx:

import networkx as nximport matplotlib.pyplot as pltdef is_potentially_divisible(multiples, divisors):    if len(multiples) != len(divisors):        return False    g = nx.Graph()    l = [('l', a, i) for i, a in enumerate(multiples)]    r = [('r', b, j) for j, b in enumerate(divisors)]    g.add_nodes_from(l, bipartite=0)    g.add_nodes_from(r, bipartite=1)    edges = [(a,b) for a in l for b in r if a[1] % b[1]== 0]    g.add_edges_from(edges)    pos = {}    pos.update((node, (1, index)) for index, node in enumerate(l))    pos.update((node, (2, index)) for index, node in enumerate(r))    m = nx.bipartite.maximum_matching(g)    colors = ['blue' if m.get(a) == b else 'gray' for a,b in edges]    nx.draw_networkx(g, pos=pos, arrows=False, labels = {n:n[1] for n in g.nodes()}, edge_color=colors)    plt.axis('off')    plt.show()    return len(m) // 2 == len(multiples)print(is_potentially_divisible([6, 12, 8], [3, 4, 6]))# Trueprint(is_potentially_divisible([6, 12, 8], [3, 4, 3]))# Trueprint(is_potentially_divisible([10, 12, 6, 5, 21, 25], [2, 7, 5, 3, 12, 3]))# False

Here are the corresponding diagrams:

enter image description hereenter image description hereenter image description here


Since you're comfortable with math, I just want to add a gloss to the other answers. Terms to search for are shown in bold.

The problem is an instance of permutations with restricted positions, and there's a whole lot that can be said about those. In general, a zero-one NxN matrix M can be constructed where M[i][j] is 1 if and only if position j is allowed for the element originally at position i. The number of distinct permutations meeting all the restrictions is then the permanent of M (defined the same way as the determinant, except that all terms are non-negative).

Alas - unlike as for the determinant - there are no known general ways to compute the permanent quicker than exponential in N. However, there are polynomial time algorithms for determining whether or not the permanent is 0.

And that's where the answers you got start ;-) Here's a good account of how the "is the permanent 0?" question is answered efficiently by considering perfect matchings in bipartite graphs:

https://cstheory.stackexchange.com/questions/32885/matrix-permanent-is-0

So, in practice, it's unlikely you'll find any general approach faster than the one @Eric Duminil gave in their answer.

Note, added later: I should make that last part clearer. Given any "restricted permutation" matrix M, it's easy to construct integer "divisibilty lists" corresponding to it. Therefore your specific problem is no easier than the general problem - unless perhaps there's something special about which integers may appear in your lists.

For example, suppose M is

0 1 1 11 0 1 11 1 0 11 1 1 0

View the rows as representing the first 4 primes, which are also the values in B:

B = [2, 3, 5, 7]

The first row then "says" that B[0] (= 2) can't divide A[0], but must divide A[1], A[2], and A[3]. And so on. By construction,

A = [3*5*7, 2*5*7, 2*3*7, 2*3*5]B = [2,     3,     5,     7]

corresponds to M. And there are permanent(M) = 9 ways to permute B such that each element of A is divisible by the corresponding element of the permuted B.