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?
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.
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
où table1
et table2
sont les lignes concernées et pourraient être la même table.
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 ..
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();
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.
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.
Triez-les d'abord.
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.
$same_numbers = array_intersect($numbers1, $$numbers2);
foreach($same_numbers as $n)
{
doBla();
}
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.
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);
?>
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).
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.
Je vais créer une interface graphique dans Visual Basic, voir si je peux suivre les numéros
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).
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);
}
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).
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.
<?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));
?>
Array
(
[1001] => 2
[1002] => 3
[1003] => 3
[1004] => 3
[1005] => 3
[1006] => 1
)
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]);
}
}
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.
Utilisez WebAssembly plutôt que JavaScript.
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.
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.
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.
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.