web-dev-qa-db-fra.com

Comment puis-je comparer deux ensembles de 1000 nombres l'un par rapport à l'autre?

Je dois vérifier environ 1000 numéros contre 1000 autres numéros.

J'ai chargé les deux et les ai comparés côté serveur:

foreach( $numbers1 as $n1 ) {
  foreach( $numbers2 as $n2 ) {
    if( $n1 == $n2 ) {
      doBla();
    }
  }
}

Cela a pris beaucoup de temps et j'ai donc essayé de faire la même comparaison côté client en utilisant deux éléments cachés div. Puis comparé en utilisant JavaScript. Il faut encore 45 secondes pour charger la page (en utilisant des éléments div cachés).

Je n'ai pas besoin de charger les nombres qui ne sont pas les mêmes.

Y a-t-il un algorithme plus rapide? Je songe à les comparer côté base de données et charge juste les numéros d'erreur, puis lance un appel Ajax pour les numéros non-erreurs restants. Mais une base de données MySQL est-elle assez rapide?

64
baklap

Triez les listes en premier. Ensuite, vous pouvez remonter les deux listes depuis le début, en comparant au fur et à mesure.

La boucle ressemblerait à quelque chose comme ça:

var A = getFirstArray().sort(), B = getSecondArray().sort();

var i = 0, j = 0;
while (i < A.length && j < B.length) {
    if (A[i] === B[j]) {
        doBla(A[i]);
        i++; j++;
    }
    else if (A[i] < B[j]) {
        i++;
    }
    else
        j++;
}

(C'est JavaScript; vous pouvez aussi le faire côté serveur, mais je ne connais pas PHP.)

Edit - Juste pour être juste envers tous les fans de hashtable (que je respecte bien sûr), c'est assez facile de le faire en JavaScript:

var map = {};
for (var i = 0; i < B.length; ++i) map[B[i]] = true; // Assume integers.
for (var i = 0; i < A.length; ++i) if (map[A[i]]) doBla(A[i]);

Ou si les nombres sont ou pourraient être des flottants:

var map = {};
for (var i = 0; i < B.length; ++i) map['' + B[i]] = true; // Assume integers.
for (var i = 0; i < A.length; ++i) if (map['' + A[i]]) doBla(A[i]);

Comme les nombres sont assez bon marché pour le hachage (même en JavaScript, convertir en chaîne avant le hachage est étonnamment bon marché), ce serait assez rapide.

129
Pointy
85
Phill Pafford

En termes de base de données, il est possible de joindre 1000 lignes à 1000 lignes supplémentaires. Tout système de base de données moderne peut gérer cela.

select x from table1
inner join table2
on table1.x = table2.y

table1 et table2 sont les lignes concernées et pourraient être la même table.

27
Preet Sangha

Ce que vous avez ne devrait pas prendre aussi longtemps - que fait doBla ()? Je soupçonne que ça prend du temps? Comparer deux séries de 1000000 nombres avec le même algorithme ne prend pas de temps du tout.

C'est hilarant - le nombre de techniques d'optimisation en guise de réponse - le problème n'est pas votre algorithme - c'est ce que doBla () fait qui prend le temps d'un facteur bien plus grand que toute optimisation vous aiderait :) esp. étant donné que les ensembles ne font que 1000 jours et que vous devez les trier en premier ..

26
markmnl

Peut-être juste intersecter les valeurs du tableau pour trouver les nombres existant dans les deux tableaux?

$result = array_intersect($numbers1, $numbers2);
foreach ($result as $val)
  doBla();
23
sod

Si vous triez d'abord list2 puis effectuez une recherche binaire pour chaque numéro de list1, vous constaterez une augmentation considérable de la vitesse.

Je suis pas a PHP mec, mais cela devrait vous donner une idée:

sort($numbers2);

foreach($numbers1 as $n1)
{
   if (BinarySearch($numbers2, $n1) >= 0) {
     doBla();
 }
}

Évidemment, n'étant pas un mec PHP, je ne connais pas la bibliothèque, mais je suis sûr que le tri et la recherche binaire devraient être assez faciles à trouver.

Note: Si vous n'êtes pas familier avec une recherche binaire; vous triez list2 car les recherches binaires doivent fonctionner sur des listes triées.

9
Giovanni Galbo

Arrêtez - pourquoi faites-vous cela?

Si les numéros se trouvent déjà dans une base de données SQL, effectuez une jointure et laissez la base de données déterminer l'itinéraire le plus efficace.

S'ils ne sont pas dans une base de données, alors je parie que vous êtes en train de dévier quelque part et que vous devriez vraiment reconsidérer la façon dont vous êtes arrivé ici.

5
dethSwatch

Triez-les d'abord.

5
JRL

Je ne suis pas un expert PHP, il faudra donc peut-être un peu de débogage, mais vous pouvez le faire facilement en O(n):

// Load one array into a hashtable, keyed by the number: O(n).
$keys1 = [];
foreach($numbers1 as $n1) $keys1[$n1] = true;

// Find the intersections with the other array:
foreach($numbers2 as $n2) { // O(n)
  if (isset($keys1[$n2]) { // O(1)
     doBla();
  }
}

Quoi qu'il en soit, l'intersection n'est pas celle où votre temps se passe. Même une mauvaise implémentation de O (n ^ 2) comme celle que vous avez maintenant devrait pouvoir passer à 1000 nombres en une seconde.

5
munificent
$same_numbers = array_intersect($numbers1, $$numbers2);

foreach($same_numbers as $n)
{
  doBla();
}
4
Cesar

Triez les deux listes, puis parcourez les deux listes en même temps en utilisant le modèle de mise à jour séquentielle ancien-maître nouveau-maître . Tant que vous pouvez trier les données, c'est le moyen le plus rapide puisque vous ne parcourez la liste qu'une seule fois, jusqu'à la plus grande longueur de la liste la plus grande.

3
skamradt

Votre code est tout simplement plus compliqué que nécessaire.

En supposant que ce que vous recherchez, c’est que les nombres de chaque position correspondent (et pas seulement que le tableau contienne les mêmes nombres), vous pouvez aplatir votre boucle en une simple.

<?php
// Fill two arrays with random numbers as proof.
$first_array = array(1000);
$second_array = array(1000);
for($i=0; $i<1000; $i++) $first_array[$i] = Rand(0, 1000);
for($i=0; $i<1000; $i++) $second_array[$i] = Rand(0, 1000);

// The loop you care about.
for($i=0; $i<1000; $i++) if ($first_array[$i] != $second_array[$i]) echo "Error at $i: first_array was {$first_array[$i]}, second was {$second_array[$i]}<br>";

?>

En utilisant le code ci-dessus, vous ne ferez que 1 000 fois en boucle, par opposition à 1 000 fois en boucle.

Maintenant, si vous devez simplement vérifier qu’un nombre apparaît ou non dans les tableaux, utilisez array_diff et array_intersect comme suit:

<?php
// Fill two arrays with random numbers as proof.
$first_array = array(1000);
$second_array = array(1000);
for($i=0; $i<1000; $i++) $first_array[$i] = Rand(0, 1000);
for($i=0; $i<1000; $i++) $second_array[$i] = Rand(0, 1000);

$matches = array_intersect($first_array, $second_array);
$differences = array_diff($first_array, $second_array);

?>
2
Jack Shedd

Vous pouvez le faire en O(n) fois si vous utilisez le tri par compartiment. En supposant que vous connaissiez la valeur maximale que les nombres peuvent prendre (bien qu'il existe des moyens de contourner ce problème).

http://en.wikipedia.org/wiki/Bucket_sort

2
ashanan

Peut-être que je ne vois pas quelque chose ici, mais cela ressemble à un cas classique d'intersection d'ensemble. Voici quelques lignes en Perl qui le feront.

foreach $ e (@a, @b) {$ union {$ e} ++ && $ isect {$ e} ++}

@union = keys% union; @isect = keys% isect;

A la fin de ces lignes de code, @isect contiendra tous les nombres qui sont à la fois dans @a et @b. Je suis sûr que cela est traduisible en php plus ou moins directement. FWIW, ceci est mon morceau de code préféré du livre de recettes Perl.

2
Shahbaz

Je vais créer une interface graphique dans Visual Basic, voir si je peux suivre les numéros

1
edahs

Un meilleur moyen serait de faire quelque chose comme ceci:

// 1. Create a hash map from one of the lists.
var hm = { };
for (var i in list1) {
  if (!hm[list1[i]]) {
    hm[list1[i]] = 1;
  } else { hm[list1[i]] += 1; }
}

// 2. Lookup each element in the other list.
for (var i in list2) {
  if (hm[list2[i]] >= 1) {
    for (var j = 0; j < hm[list2[i]]; ++j) {
      doBla();
    }
  }
}

Ceci est garanti O(n) [en supposant que l'insertion d'une recherche dans une carte de hachage est O(1) amortie].

Mise à jour: le pire cas de cet algorithme serait O (n2) et il n’ya aucun moyen de réduire - à moins de changer la sémantique du programme. En effet, dans le pire des cas, le programme appellera doBla () n2 nombre de fois si tous les numéros des deux listes sont identiques. Cependant, si les deux listes ont des numéros uniques (c’est-à-dire généralement uniques dans une liste), le temps d’exécution tend alors vers O (n).

1
dhruvbird

Je pense qu'il serait beaucoup plus facile d'utiliser la fonction intégrée array_intersect. En utilisant votre exemple, vous pourriez faire:

$results = array_intersect($numbers1, $numbers2);
foreach($results as $rk => $rv) {
    doSomething($rv);
}
1
David

Fusionnez les deux listes, commencez au début des deux listes, puis recherchez dans chaque liste des numéros similaires au même moment.

Donc, en pseudocode, ça donnerait quelque chose comme ...

Mergesort (List A);
Mergesort (list B)

$Apos = 0;
$Bpos = 0;

while( $Apos != A.Length && $Bpos != B.length) // while you have not reached the end of either list
{
if (A[$Apos] == B[$Bpos])// found a match
doSomething();

else if (A[$Apos] > B[$Bpos]) // B is lower than A, so have B try and catch up to A.
$Bpos++;

else if (A[$Apos] < B[$Bpos]) // the value at A is less than the value at B, so increment B
$Apos++;

}

Si j'ai raison, la vitesse de cet algorithme est O (n logn).

1
waffles

Je ne suis pas sûr de savoir pourquoi Mrk Mnl a été voté mais l'appel de fonction est la surcharge ici. 

Repoussez les nombres correspondants dans un autre tableau et faites-les sur après les comparaisons. En tant que test // sur doBla () et voyez si vous rencontrez le même problème de performance.

1
Martin Blank

Fusionner, trier puis compter

<?php
    $first = array('1001', '1002', '1003', '1004', '1005');
    $second = array('1002', '1003', '1004', '1005', '1006');
    $merged = array_merge($first, $first, $second);
    sort($merged);
    print_r(array_count_values($merged));
?>

Sortie/les valeurs avec un nombre de trois sont celles que vous voulez

Array
(
    [1001] => 2
    [1002] => 3
    [1003] => 3
    [1004] => 3
    [1005] => 3
    [1006] => 1
)
0
jaymz

Ce problème peut être divisé en 2 tâches. La première tâche consiste à trouver toutes les combinaisons (n ​​^ 2-n)/2. Pour n = 1000, la solution est x = 499500. La deuxième tâche consiste à parcourir tous les nombres x et à les comparer avec la fonction doBla ().

function getWayStr(curr) {
 var nextAbove = -1;
 for (var i = curr + 1; i < waypoints.length; ++i) {
  if (nextAbove == -1) {
    nextAbove = i;
   } else {
     wayStr.Push(waypoints[i]);
     wayStr.Push(waypoints[curr]);
   }
  }
  if (nextAbove != -1) {
    wayStr.Push(waypoints[nextAbove]);
    getWayStr(nextAbove);
    wayStr.Push(waypoints[curr]);
  }
 } 
0
Bytemain

Ce code appelle doBla() une fois pour chaque fois qu'une valeur dans $numbers1 est trouvée dans $numbers2:

// get [val => occurences, ...] for $numbers2
$counts = array_count_values($numbers2);
foreach ($numbers1 as $n1) {
    // if $n1 occurs in $numbers2...
    if (isset($counts[$n1])) {
        // call doBla() once for each occurence
        for ($i=0; $i < $counts[$n1]; $i++) {
            doBla();
        }
    }
}

Si vous devez seulement appeler doBla() une fois si une correspondance est trouvée:

foreach ($numbers1 as $n1) {
    if (in_array($n1, $numbers2))
        doBla();
}

Si $numbers1 et $numbers2 ne contiennent que des valeurs uniques, ou si le nombre de fois qu'une valeur spécifique apparaît dans les deux tableaux n'est pas important, array_intersect() fera le travail:

$dups = array_intersect($numbers1, $numbers2);
foreach ($dups as $n)
    doBla();

Je suis d’accord avec plusieurs publications précédentes pour dire que les appels à doBla() prennent probablement plus de temps qu’itérer sur les tableaux.

0
gregjor

Utilisez WebAssembly plutôt que JavaScript.

0
Hasan Savran
  1. Créez deux collections en double, de préférence avec des temps de recherche rapides, comme HashSet ou éventuellement TreeSet. Évitez les listes car leurs temps de consultation sont très médiocres.

  2. Lorsque vous trouvez des éléments, supprimez-les des deux ensembles. Cela peut réduire les temps de recherche en réduisant le nombre d'éléments à explorer lors de recherches ultérieures.

0
Edwin Buck

Si vous essayez d'obtenir une liste de nombres sans doublons, vous pouvez utiliser un hachage:

$unique = array();
foreach ($list1 as $num) {
  $unique[$num] = $num;
}
foreach ($list2 as $num) {
  $unique[$num] = $num;
}
$unique = array_keys($unique);

Ça va être légèrement (très légèrement) plus lent que la méthode de la promenade en rangée, mais c'est plus propre à mon avis.

0
brianloveswords

Serait-il possible de mettre ces nombres dans deux tables de base de données, puis de faire un INNER JOIN? Cela sera très efficace et ne fournira que les chiffres contenus dans les deux tableaux. C'est une tâche parfaite pour une base de données.

0
m.edmondson