web-dev-qa-db-fra.com

Une liste est-elle (potentiellement) divisible par une autre?

Problème

Disons que vous avez deux listes A = [a_1, a_2, ..., a_n] et B = [b_1, b_2, ..., b_n] d'entiers. Nous disons que A est potentiellement divisible par B s'il y a une permutation de B qui fait du a_i divisible par b_i pour tous i. Le problème est alors: est-il possible de réorganiser (c'est-à-dire permuter) B pour que a_i est divisible par b_i pour tous i? Par exemple, si vous avez

A = [6, 12, 8]
B = [3, 4, 6]

La réponse serait alors True, car B peut être réorganisé pour être B = [3, 6, 4] et nous aurions alors cela a_1 / b_1 = 2, a_2 / b_2 = 2, et a_3 / b_3 = 2, qui sont tous des entiers, donc A est potentiellement divisible par B.

À titre d'exemple qui devrait générer False, nous pourrions avoir:

A = [10, 12, 6, 5, 21, 25]
B = [2, 7, 5, 3, 12, 3]

La raison pour laquelle c'est False est que nous ne pouvons pas réorganiser B car 25 et 5 sont dans A, mais le seul diviseur dans B serait 5 , donc on serait laissé de côté.

Approche

Évidemment, l'approche simple serait d'obtenir toutes les permutations de B et de voir si l'on satisferait la divisibilité potentielle , quelque chose le long des lignes de:

import itertools
def is_potentially_divisible(A, B):
  perms = itertools.permutations(B)
  divisible = lambda ls: all( x % y == 0 for x, y in Zip(A, ls))
  return any(divisible(perm) for perm in perms)

Question

Quel est le moyen le plus rapide de savoir si une liste est potentiellement divisible par une autre liste? Des pensées? Je pensais s'il y avait un moyen intelligent de le faire avec les nombres premiers , mais je n'ai pas pu trouver de solution.

Très appréciée!


Edit: Cela n'a probablement pas de sens pour la plupart d'entre vous, mais pour être complet, je vais vous expliquer ma motivation. Dans la théorie des groupes, il y a une conjecture sur les groupes simples finis sur la présence ou non d'une bijection des caractères irréductibles et des classes de conjugaison du groupe de sorte que chaque degré de caractère divise la taille de classe correspondante. Par exemple, pour U6 (4)voici à quoi devraient ressembler A et B. De très grandes listes, faites attention !

67
McGuire

Créer une structure graphique bipartite - connectez a[i] avec tous ses diviseurs de b[]. enter image description here

Ensuite, trouvez correspondance maximale et vérifiez si elle est correspondance parfaite (le nombre d'arêtes en correspondance est égal au nombre de paires ( si le graphique est dirigé) ou vers un nombre doublé).

Arbitraire choisi implémentation de l'algorithme de Kuhn ici .

Mise à jour:
@ Eric Duminil a fait une grande concision Implémentation Python ici

Cette approche a une complexité polynomiale de O (n ^ 2) à O (n ^ 3) en fonction de l'algorithme d'appariement choisi et du nombre d'arêtes (paires de division) par rapport à la complexité factorielle pour l'algorithme de force brute.

69
MBo

Code

En s'appuyant sur l'excellent @ MBo réponse , voici une implémentation de la correspondance de graphes bipartite utilisant networkx .

import networkx as nx

def 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]))
# True
print(is_potentially_divisible([6, 12, 8], [3, 4, 3]))
# True
print(is_potentially_divisible([10, 12, 6, 5, 21, 25], [2, 7, 5, 3, 12, 3]))
# False

Remarques

Selon la documentation :

Le dictionnaire renvoyé par maximum_matching () inclut un mappage pour les sommets dans les ensembles de sommets gauche et droit.

Cela signifie que le dict retourné doit être deux fois plus grand que A et B.

Les nœuds sont convertis de

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

à:

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

afin d'éviter les collisions entre les nœuds de A et B. L'identifiant est également ajouté afin de garder les nœuds distincts en cas de doublons.

Efficacité

La méthode maximum_matching Utilise algorithme Hopcroft-Karp , qui s'exécute dans O(n**2.5) dans le pire des cas. La génération du graphe est O(n**2), donc toute la méthode s'exécute dans O(n**2.5). Il devrait fonctionner correctement avec de grands tableaux. La solution de permutation est O(n!) et ne pourra pas traiter les tableaux à 20 éléments.

Avec des diagrammes

Si vous êtes intéressé par un diagramme montrant la meilleure correspondance, vous pouvez mélanger matplotlib et networkx:

import networkx as nx
import matplotlib.pyplot as plt

def 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]))
# True
print(is_potentially_divisible([6, 12, 8], [3, 4, 3]))
# True
print(is_potentially_divisible([10, 12, 6, 5, 21, 25], [2, 7, 5, 3, 12, 3]))
# False

Voici les schémas correspondants:

enter image description hereenter image description hereenter image description here

30
Eric Duminil

Puisque vous êtes à l'aise avec les mathématiques, je veux juste ajouter un gloss aux autres réponses. Les termes à rechercher sont indiqués en en gras .

Le problème est une instance de permutations avec des positions restreintes , et il y a beaucoup à dire à ce sujet. En général, une matrice zéro-un NxNM peut être construite où M[i][j] Vaut 1 si et seulement si la position j est autorisée pour l'élément initialement à position i. Le nombre de permutations distinctes répondant à toutes les restrictions est alors le permanent de M (défini le de la même manière que le déterminant, sauf que tous les termes sont non négatifs).

Hélas - contrairement au déterminant - il n'existe aucun moyen général connu de calculer le permanent plus rapidement que l'exponentiel dans N. Cependant, il existe des algorithmes polynomiaux temporels pour déterminer si le permanent est 0 ou non.

Et c'est là que les réponses que vous avez obtenues commencez ;-) Voici un bon compte rendu de la façon dont le "est le 0 permanent?" la réponse à la question est efficace en considérant les correspondances parfaites dans les graphiques bipartites:

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

Donc, dans la pratique, il est peu probable que vous trouviez une approche générale plus rapide que celle que @Eric Duminil a donnée dans sa réponse.

Remarque, ajoutée plus tard: je devrais clarifier cette dernière partie. Étant donné n'importe quelle matrice de "permutation restreinte" M, il est facile de construire des "listes de divisibilité" entières qui lui correspondent. Par conséquent, votre problème spécifique n'est pas plus facile que le problème général - à moins qu'il n'y ait peut-être quelque chose de spécial concernant les entiers qui peuvent apparaître dans vos listes.

Par exemple, supposons que M soit

0 1 1 1
1 0 1 1
1 1 0 1
1 1 1 0

Affichez les lignes comme représentant les 4 premiers nombres premiers, qui sont également les valeurs de B:

B = [2, 3, 5, 7]

La première ligne "indique" alors que B[0] (= 2) ne peut pas diviser A[0], Mais doit diviser A[1], A[2] Et A[3] . Etc. Par construction,

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

correspond à M. Et il y a permanent(M) = 9 façons de permuter B de telle sorte que chaque élément de A est divisible par l'élément correspondant du permuté B.

17
Tim Peters

Ce n'est pas la réponse ultime mais je pense que cela pourrait être quelque chose de valable. Vous pouvez d'abord lister les facteurs (1 et lui-même inclus) de tous les éléments de la liste [(1,2,5,10),(1,2,3,6,12),(1,2,3,6),(1,5),(1,3,7,21),(1,5,25)]. La liste que nous recherchons doit contenir l'un des facteurs (à diviser également). Comme nous n'avons pas de facteurs dans la liste, nous vérifions ([2,7,5,3,12,3]) Cette liste peut être filtrée comme suit:

[(2,5),(2,3,12),(2,3),(5),(3,7),(5)]

Ici, 5 est nécessaire à deux endroits (où nous n'avons aucune option), mais nous n'avons qu'un 5, nous pouvons donc nous arrêter ici et dire que le cas est faux ici.

Supposons que nous avions à la place [2,7,5,3,5,3]:

Ensuite, nous aurions l'option en tant que telle:

[(2,5),(2,3),(2,3),(5),(3,7),(5)]

Depuis 5 est nécessaire à deux endroits:

[(2),(2,3),(2,3),{5},(3,7),{5}]{} signifie la position assurée.

Aussi 2 est assuré:

[{2},(2,3),(2,3),{5},(3,7),{5}] Maintenant que 2 est pris les deux places de 3 sont assurées:

[{2},{3},{3},{5},(3,7),{5}] Maintenant bien sûr 3 sont prises et 7 est assurée:

[{2},{3},{3},{5},{7},{5}]. ce qui est toujours conforme à notre liste, donc la casse est vraie. N'oubliez pas que nous examinerons les consistances avec notre liste à chaque itération où nous pourrons facilement sortir.

3
officialaimm

Vous pouvez essayer ceci:

import itertools

def potentially_divisible(A, B):
    A = itertools.permutations(A, len(A))
   return len([i for i in A if all(c%d == 0 for c, d in Zip(i, B))]) > 0

l1 = [6, 12, 8]
l2 = [3, 4, 6]

print(potentially_divisible(l1, l2))

Sortie:

True

Un autre exemple:

l1 = [10, 12, 6, 5, 21, 25]
l2 = [2, 7, 5, 3, 12, 3]

print(potentially_divisible(l1, l2))

Sortie:

False
2
Ajax1234