Bien que la réponse de Jon Skeet soit un excellent conseil pour la pratique quotidienne avec un nombre d'éléments faible à modéré (jusqu'à quelques millions), ce n'est pas l'approche la plus rapide ni la plus efficace en termes de ressources. Un inconvénient évident est le fait que l'obtention de la différence totale nécessite deux passages sur les données (voire trois si les éléments qui sont égaux sont également intéressants). Il est clair que cela peut être évité par une réimplémentation personnalisée de la fonction Except
mais il n'en reste pas moins que la création d'un ensemble de hachage nécessite beaucoup de mémoire et que le calcul des hachages prend du temps.
Pour les très grands ensembles de données (dans les milliards d'éléments), il est généralement utile d'examiner les circonstances particulières. Voici quelques idées qui pourraient vous inspirer : Si les éléments peuvent être comparés (ce qui est presque toujours le cas dans la pratique), il convient alors de trier les listes et d'appliquer l'approche zip suivante :
/// <returns>The elements of the specified (ascendingly) sorted enumerations that are
/// contained only in one of them, together with an indicator,
/// whether the element is contained in the reference enumeration (-1)
/// or in the difference enumeration (+1).</returns>
public static IEnumerable<Tuple<T, int>> FindDifferences<T>(IEnumerable<T> sortedReferenceObjects,
IEnumerable<T> sortedDifferenceObjects, IComparer<T> comparer)
{
var refs = sortedReferenceObjects.GetEnumerator();
var diffs = sortedDifferenceObjects.GetEnumerator();
bool hasNext = refs.MoveNext() && diffs.MoveNext();
while (hasNext)
{
int comparison = comparer.Compare(refs.Current, diffs.Current);
if (comparison == 0)
{
// insert code that emits the current element if equal elements should be kept
hasNext = refs.MoveNext() && diffs.MoveNext();
}
else if (comparison < 0)
{
yield return Tuple.Create(refs.Current, -1);
hasNext = refs.MoveNext();
}
else
{
yield return Tuple.Create(diffs.Current, 1);
hasNext = diffs.MoveNext();
}
}
}
Cela peut par exemple être utilisé de la manière suivante :
const int N = <Large number>;
const int omit1 = 231567;
const int omit2 = 589932;
IEnumerable<int> numberSequence1 = Enumerable.Range(0, N).Select(i => i < omit1 ? i : i + 1);
IEnumerable<int> numberSequence2 = Enumerable.Range(0, N).Select(i => i < omit2 ? i : i + 1);
var numberDiffs = FindDifferences(numberSequence1, numberSequence2, Comparer<int>.Default);
Un benchmarking sur mon ordinateur a donné le résultat suivant pour N = 1M :
Méthode
Moyenne
Erreur
Ecart-type
Ratio
Gen 0
Gen 1
Gen 2
Alloué
DiffLinq
115,19 ms
0,656 ms
0,582 ms
1.00
2800.0000
2800.0000
2800.0000
67110744 B
DiffZip
23,48 ms
0,018 ms
0,015 ms
0.20
-
-
-
720 B
Et pour N = 100M :
Méthode
Moyenne
Erreur
Ecart-type
Ratio
Gen 0
Gen 1
Gen 2
Alloué
DiffLinq
12.146 s
0.0427 s
0.0379 s
1.00
13000.0000
13000.0000
13000.0000
8589937032 B
DiffZip
2.324 s
0.0019 s
0.0018 s
0.19
-
-
-
720 B
Notez que cet exemple bénéficie bien sûr du fait que les listes sont déjà triées et que les entiers peuvent être comparés très efficacement. Mais c'est exactement le but recherché : si vous disposez de circonstances favorables, veillez à les exploiter.
Quelques commentaires supplémentaires : La vitesse de la fonction de comparaison est clairement pertinente pour les performances globales, il peut donc être bénéfique de l'optimiser. La flexibilité de le faire est un avantage de l'approche de zippage. De plus, la parallélisation me semble plus réalisable, même si elle n'est pas facile et ne vaut peut-être pas l'effort et les frais généraux. Néanmoins, un moyen simple d'accélérer le processus d'un facteur 2 environ consiste à diviser les listes en deux moitiés (si cela peut être fait efficacement) et à comparer les parties en parallèle, l'une traitant de l'avant vers l'arrière et l'autre dans l'ordre inverse.
1 votes
Si vous rencontrez cette question et que vous envisagez d'ajouter une nouvelle réponse, veuillez noter qu'ils ne demandent pas de a mais le le plus rapide manière.