using System; using System.Collections.Generic; using System.Linq; using System.Text; using Developpez.Dotnet.Threading; using System.Collections; using System.Threading; namespace Developpez.Dotnet.Collections { /// /// Fournit des méthodes d'extension pour les types enumérables /// public static class EnumerableExtensions { #region Divers /// /// Indique si une collection est nulle ou vide /// /// La collection à tester /// true si la collection est nulle ou vide, false sinon public static bool IsNullOrEmpty(this ICollection collection) { return (collection == null || collection.Count == 0); } /// /// Obtient une version synchronisée de la liste /// /// Type générique de la liste /// liste /// liste synchronisée public static IList Synchronized(this IList list) { return new SyncList(list); } /// /// Permute deux éléments d'une liste /// /// Type des éléments de la liste /// La liste contenant les éléments à permuter /// L'index du premier élément /// L'index du second élément public static void Swap(this IList list, int index1, int index2) { T tmp = list[index1]; list[index1] = list[index2]; list[index2] = tmp; } #if !DOTNET4 // La méthode Zip a été ajoutée dans .NET 4.0 et ne sera donc plus nécessaire /// /// Fusionne 2 séquences selon la fonction de projection spécifiée, en faisant correspondre /// chaque élément de la première séquence à l'élément de même index dans la deuxième séquence. /// /// Type des éléments de la première séquence /// Type des éléments de la deuxième séquence /// Type des éléments de la séquence fusionnée /// première séquence /// deuxième séquence /// fonction de projection pour fusionner des éléments des 2 séquences /// Une séquence d'éléments fusionnée /// Si les 2 séquences ne sont pas de même longueur public static IEnumerable Zip(this IEnumerable first, IEnumerable second, Func selector) { first.CheckArgumentNull("first"); second.CheckArgumentNull("second"); selector.CheckArgumentNull("selector"); return first.ZipIterator(second, selector); } private static IEnumerable ZipIterator(this IEnumerable first, IEnumerable second, Func selector) { using (var enum1 = first.GetEnumerator()) using (var enum2 = second.GetEnumerator()) { while (enum1.MoveNext() && enum2.MoveNext()) { yield return selector(enum1.Current, enum2.Current); } } } #endif #endregion #region FormatAll /// /// Renvoie une chaine contenant chaque élément de l'énumération formaté avec /// le format spécifié /// /// L'énumération à formater /// Le format à appliquer à chaque élément, par exemple "x2" ou "{0:x2}" /// Le séparateur à insérer entre chaque élément /// La chaine contenant tous les éléments formatés public static string FormatAll(this IEnumerable enumerable, string format, string separator) { if (format.IsNullOrEmpty()) { format = "{0}"; } else if (!(format.StartsWith("{0") && format.EndsWith("}"))) { format = "{0:" + format + "}"; } separator = separator ?? String.Empty; StringBuilder sb = new StringBuilder(); foreach (var item in enumerable) { sb.AppendFormat(format, item); sb.Append(separator); } sb.Remove(sb.Length - separator.Length, separator.Length); return sb.ToString(); } /// /// Renvoie une chaine contenant chaque élément de l'énumération formaté avec /// le delegate spécifié /// /// Type des éléments de la séquence /// L'énumération à formater /// Le delegate à appliquer à chaque élément pour le convertir en chaine de caractères /// Le séparateur à insérer entre chaque élément /// La chaine contenant tous les éléments formatés public static string FormatAll(this IEnumerable enumerable, Func formatter, string separator) { if (formatter == null) return enumerable.FormatAll((string)null, separator); separator = separator ?? String.Empty; StringBuilder sb = new StringBuilder(); foreach (var item in enumerable) { sb.Append(formatter(item)); sb.Append(separator); } sb.Remove(sb.Length - separator.Length, separator.Length); return sb.ToString(); } #endregion #region Append, Prepend /// /// Ajoute un élément à la suite de la séquence spécifiée /// /// Type des éléments de la séquence /// Séquence d'origine /// Elément à ajouter /// La séquence d'origine suivie de l'élément spécifiée public static IEnumerable Append(this IEnumerable source, T item) { return source.Concat(new[] { item }); } /// /// Insère un élément au début de la séquence spécifiée /// /// Type des éléments de la séquence /// Séquence d'origine /// Elément à insérer /// La séquence d'origine précédée de l'élément spécifiée public static IEnumerable Prepend(this IEnumerable source, T item) { return new[] { item }.Concat(source); } #endregion #region ForEachParallel /// /// Exécute la méthode callback de manière parallele sur tous les éléments /// de la collection /// /// Type de collection /// Séquence d'éléments sur laquelle effectuer une action en parallèle /// Méthode à rappeller sur chacun des éléments public static void ForEachParallel(this IEnumerable enumerable, ThreadedWorker.CallBackMethod callback) { enumerable.ForEachParallel(callback, true); } /// /// Exécute la méthode callback de manière parallele sur tous les éléments /// de la collection /// /// Type de collection /// Séquence d'éléments sur laquelle effectuer une action en parallèle /// Méthode à rappeller sur chacun des éléments /// True si l'opération doit être synchrone (attente de la fin de l'opération sur tous les éléments) /// ou non (aucune attente, l'exécution se poursuit) public static void ForEachParallel(this IEnumerable enumerable, ThreadedWorker.CallBackMethod callback, bool waitForCompletion) { enumerable.ForEachParallel(callback, waitForCompletion, -1); } /// /// Exécute la méthode callback de manière parallele sur tous les éléments /// de la collection /// /// Type de collection /// Séquence d'éléments sur laquelle effectuer une action en parallèle /// Méthode à rappeller sur chacun des éléments /// True si l'opération doit être synchrone (attente de la fin de l'opération sur tous les éléments) /// ou non (aucune attente, l'exécution se poursuit) /// Nombre maximum de thread de travail dédié à cette tâche. Toute valeure inférieure à 1 est ignorée public static void ForEachParallel(this IEnumerable enumerable, ThreadedWorker.CallBackMethod callback, bool waitForCompletion, int maxWorkerThreads) { callback.CheckArgumentNull("callback"); using (ThreadedWorker worker = new ThreadedWorker()) { if (maxWorkerThreads > 0) worker.MaxThreadCount = maxWorkerThreads; worker.ForEach(enumerable, callback, waitForCompletion); } } /// /// Exécute la méthode callback de manière parallele sur tous les éléments /// de la collection /// /// Séquence d'éléments sur laquelle effectuer une action en parallèle /// Méthode à rappeller sur chacun des éléments public static void ForEachParallel(this IEnumerable enumerable, ThreadedWorker.CallBackMethod callback) { enumerable.ForEachParallel(callback, true); } /// /// Exécute la méthode callback de manière parallele sur tous les éléments /// de la collection /// /// Séquence d'éléments sur laquelle effectuer une action en parallèle /// Méthode à rappeller sur chacun des éléments /// True si l'opération doit être synchrone (attente de la fin de l'opération sur tous les éléments) /// ou non (aucune attente, l'exécution se poursuit) public static void ForEachParallel(this IEnumerable enumerable, ThreadedWorker.CallBackMethod callback, bool waitForCompletion) { enumerable.ForEachParallel(callback, waitForCompletion, -1); } /// /// Exécute la méthode callback de manière parallele sur tous les éléments /// de la collection /// /// Séquence d'éléments sur laquelle effectuer une action en parallèle /// Méthode à rappeller sur chacun des éléments /// True si l'opération doit être synchrone (attente de la fin de l'opération sur tous les éléments) /// ou non (aucune attente, l'exécution se poursuit) /// Nombre maximum de thread de travail dédié à cette tâche. Toute valeure inférieure à 1 est ignorée public static void ForEachParallel(this IEnumerable enumerable, ThreadedWorker.CallBackMethod callback, bool waitForCompletion, int maxWorkerThreads) { callback.CheckArgumentNull("callback"); using (ThreadedWorker worker = new ThreadedWorker()) { if (maxWorkerThreads > 0) worker.MaxThreadCount = maxWorkerThreads; worker.ForEach(enumerable.Cast(), callback, waitForCompletion); } } #endregion #region Shuffle /// /// Mélange une liste. /// /// Type des éléments de la séquence /// La liste à mélanger /// Cette méthode utilise l'algorithme de Fisher–Yates (http://en.wikipedia.org/wiki/Fisher-Yates_shuffle) /// public static void Shuffle(this IList list) { Random rnd = new Random(); for (int i = list.Count - 1; i > 0; i--) { int swapIndex = rnd.Next(i + 1); list.Swap(i, swapIndex); } } #endregion #region BinarySearch et variantes /// /// Effectue une recherche binaire sur une liste triée pour trouver un élément selon /// la valeur d'une de ses propriétés. La liste doit être triée selon cette propriété. /// /// Type des éléments de la collection /// Type de la clé de recherche /// Liste dans laquelle rechercher l'élément /// Fonction permettant d'obtenir la clé de recherche /// Valeur de la clé recherchée /// Le premier élément correspondant, s'il existe. Sinon, lève une InvalidOperationException. public static T BinarySearch(this IList list, Func keySelector, TKey key) where TKey : IComparable { T result; if (list.TryBinarySearch(keySelector, key, out result)) return result; throw new InvalidOperationException(ExceptionMessages.NoMatchingItemInList); } /// /// Effectue une recherche binaire sur une liste triée pour trouver un élément selon /// la valeur d'une de ses propriétés. La liste doit être triée selon cette propriété. /// Si aucun élément correspondant n'est trouvé, une valeur par défaut est renvoyée. /// /// Type des éléments de la collection /// Type de la clé de recherche /// Liste dans laquelle rechercher l'élément /// Fonction permettant d'obtenir la clé de recherche /// Valeur de la clé recherchée /// Le premier élément correspondant, s'il existe. Sinon, la valeur par défaut du type T. public static T BinarySearchOrDefault(this IList list, Func keySelector, TKey key) where TKey : IComparable { return list.BinarySearchOrDefault(keySelector, key, default(T)); } /// /// Effectue une recherche binaire sur une liste triée pour trouver un élément selon /// la valeur d'une de ses propriétés. La liste doit être triée selon cette propriété. /// Si aucun élément correspondant n'est trouvé, la valeur par défaut spécifiée est renvoyée. /// /// Type des éléments de la collection /// Type de la clé de recherche /// Liste dans laquelle rechercher l'élément /// Fonction permettant d'obtenir la clé de recherche /// Valeur de la clé recherchée /// La valeur par défaut à renvoyer si aucun élément correspondant n'est trouvé /// Le premier élément correspondant, s'il existe. Sinon, la valeur par défaut spécifiée. public static T BinarySearchOrDefault(this IList list, Func keySelector, TKey key, T defaultValue) where TKey : IComparable { T result; if (list.TryBinarySearch(keySelector, key, out result)) return result; return defaultValue; } /// /// Effectue une recherche binaire sur une liste triée pour tenter de trouver un élément /// selon la valeur d'une de ses propriétés. La liste doit être triée selon cette propriété. /// /// Type des éléments de la collection /// Type de la clé de recherche /// Liste dans laquelle rechercher l'élément /// Fonction permettant d'obtenir la clé de recherche /// Valeur de la clé recherchée /// Paramètre de sortie qui prend la valeur du premier élément trouvé. /// true si un élément correspondant est trouvé. Sinon, false. public static bool TryBinarySearch(this IList list, Func keySelector, TKey key, out T result) where TKey : IComparable { result = default(T); int min = 0; int max = list.Count; while (min < max) { int mid = (max + min) / 2; T midItem = list[mid]; TKey midKey = keySelector(midItem); int comp = midKey.CompareTo(key); if (comp < 0) { min = mid + 1; } else if (comp > 0) { max = mid - 1; } else { result = midItem; return true; } } if (min == max && keySelector(list[min]).CompareTo(key) == 0) { result = list[min]; return true; } return false; } #endregion #region Conversions de et vers des chaînes CSV /// /// Convertit une liste en une chaîne CSV avec le séparateur spécifié /// /// Liste à convertir /// Séparateur à utiliser /// Une chaine CSV représentant les éléments de la liste public static string ToCsvString(this IEnumerable list, string separator) { StringBuilder sb = new StringBuilder(); foreach (object o in list) { if (sb.Length > 0) sb.Append(separator); sb.Append(o); } return sb.ToString(); } /// /// Convertit une chaine CSV en une liste d'objets du type spécifié /// /// Type des éléments de la liste /// Chaîne CSV à convertir /// Séparateur à utiliser /// Une liste contenant les éléments de la chaîne CSV public static IEnumerable ListFromCsv(this string source, string separator) { List lst = new List(); string[] sItems = source.Split(new string[] { separator }, StringSplitOptions.None); foreach (string sItem in sItems) { T item; if (string.IsNullOrEmpty(sItem)) { item = default(T); } else { item = (T)Convert.ChangeType(sItem, typeof(T)); } lst.Add(item); } return lst; } #endregion #region IsOrdered et variantes /// /// Vérifie que la liste est triée, en utilisant le comparateur spécifié /// /// Le type des éléments de la liste /// La liste à vérifier /// Le comparateur à utiliser /// true si la liste est triée, false sinon public static bool IsOrdered(this IEnumerable list, IComparer comparer) { return list.IsOrdered(comparer.ToComparison()); } /// /// Vérifie que la liste est triée, en utilisant la comparaison spécifiée /// /// Le type des éléments de la liste /// La liste à vérifier /// La comparaison à utiliser /// true si la liste est triée, false sinon public static bool IsOrdered(this IEnumerable list, Comparison comparison) { bool isFirstPass = true; T previous = default(T); foreach (T item in list) { if (!isFirstPass) { if (comparison(previous, item) > 0) return false; } isFirstPass = false; previous = item; } return true; } /// /// Vérifie que la liste est triée, en utilisant le comparateur par défaut /// /// Le type des éléments de la liste /// La liste à vérifier /// true si la liste est triée, false sinon public static bool IsOrdered(this IEnumerable list) { return list.IsOrdered(Comparer.Default); } /// /// Vérifie que la liste est triée en ordre descendant, en utilisant le comparateur spécifié /// /// Le type des éléments de la liste /// La liste à vérifier /// Le comparateur à utiliser /// true si la liste est triée, false sinon public static bool IsOrderedDescending(this IEnumerable list, IComparer comparer) { return list.IsOrdered(comparer.Reverse()); } /// /// Vérifie que la liste est triée en ordre descendant, en utilisant le comparateur par défaut /// /// Le type des éléments de la liste /// La liste à vérifier /// true si la liste est triée, false sinon public static bool IsOrderedDescending(this IEnumerable list) { return list.IsOrdered(Comparer.Default.Reverse()); } /// /// Vérifie que la liste est triée selon la clé de tri spécifiée, en utilisant le comparateur spécifié /// /// Le type des éléments de la liste /// Le type de la clé de tri /// La liste à vérifier /// Le comparateur à utiliser /// Une fonction pour extraire la clé de tri de l'élément /// true si la liste est triée selon le critère spécifié, false sinon public static bool IsOrderedBy(this IEnumerable list, Func keySelector, IComparer comparer) { return list.Select(keySelector).IsOrdered(comparer); } /// /// Vérifie que la liste est triée selon la clé de tri spécifiée, en utilisant le comparateur par défaut /// /// Le type des éléments de la liste /// Le type de la clé de tri /// La liste à vérifier /// Une fonction pour extraire la clé de tri de l'élément /// true si la liste est triée selon le critère spécifié, false sinon public static bool IsOrderedBy(this IEnumerable list, Func keySelector) { return list.IsOrderedBy(keySelector, Comparer.Default); } /// /// Vérifie que la liste est triée en ordre descendant selon la clé de tri spécifiée, en utilisant le comparateur spécifié /// /// Le type des éléments de la liste /// Le type de la clé de tri /// La liste à vérifier /// Le comparateur à utiliser /// Une fonction pour extraire la clé de tri de l'élément /// true si la liste est triée selon le critère spécifié, false sinon public static bool IsOrderedByDescending(this IEnumerable list, Func keySelector, IComparer comparer) { return list.IsOrderedBy(keySelector, comparer.Reverse()); } /// /// Vérifie que la liste est triée en ordre descendant selon la clé de tri spécifiée, en utilisant le comparateur par défaut /// /// Le type des éléments de la liste /// Le type de la clé de tri /// La liste à vérifier /// Une fonction pour extraire la clé de tri de l'élément /// true si la liste est triée selon le critère spécifié, false sinon public static bool IsOrderedByDescending(this IEnumerable list, Func keySelector) { return list.IsOrderedBy(keySelector, Comparer.Default.Reverse()); } #endregion #region SelectAggregate /// /// Applique une fonction d'accumulation sur une séquence, en renvoyant le résultat /// de chaque étape de l'accumulation. /// /// Type des éléments de source /// Type de la valeur d'accumulation /// Séquence sur laquelle appliquer l'accumulation /// Valeur d'accumulation initiale /// Fonction d'accumulation à appeler sur chaque élément /// Séquence des valeurs d'accumulation à chaque étape. /// Cette méthode est similaire à Enumerable.Aggregate, mais cette dernière ne renvoie que le résultat /// final, alors que SelectAggregate renvoie le résultat de chaque étape. public static IEnumerable SelectAggregate( this IEnumerable source, TAccumulate seed, Func func) { source.CheckArgumentNull("source"); func.CheckArgumentNull("func"); return source.SelectAggregateIterator(seed, func); } private static IEnumerable SelectAggregateIterator( this IEnumerable source, TAccumulate seed, Func func) { TAccumulate previous = seed; foreach (var item in source) { TAccumulate result = func(previous, item); previous = result; yield return result; } } /// /// Applique une fonction d'accumulation sur une séquence, en renvoyant le résultat /// de chaque étape de l'accumulation. /// /// Type des éléments de source /// Type de la valeur d'accumulation /// Séquence sur laquelle appliquer l'accumulation /// Fonction d'accumulation à appeler sur chaque élément /// Séquence des valeurs d'accumulation à chaque étape. /// Cette méthode est similaire à Enumerable.Aggregate, mais cette dernière ne renvoie que le résultat /// final, alors que SelectAggregate renvoie le résultat de chaque étape. public static IEnumerable SelectAggregate( this IEnumerable source, Func func) { return source.SelectAggregate(default(TAccumulate), func); } /// /// Applique une fonction d'accumulation sur une séquence, en renvoyant le résultat /// de chaque étape de l'accumulation. La fonction d'accumulation prend en paramètre /// les 2 valeurs d'accumulation précédentes. /// /// Type des éléments de source /// Type de la valeur d'accumulation /// Séquence sur laquelle appliquer l'accumulation /// Valeur d'accumulation initiale (itération -1) /// Valeur d'accumulation initiale (itération -2) /// Fonction d'accumulation à appeler sur chaque élément /// Séquence des valeurs d'accumulation à chaque étape. public static IEnumerable SelectAggregate( this IEnumerable source, TAccumulate seed1, TAccumulate seed2, Func func) { source.CheckArgumentNull("source"); func.CheckArgumentNull("func"); return source.SelectAggregateIterator(seed1, seed2, func); } private static IEnumerable SelectAggregateIterator( this IEnumerable source, TAccumulate seed1, TAccumulate seed2, Func func) { TAccumulate previous1 = seed1; TAccumulate previous2 = seed2; foreach (var item in source) { TAccumulate result = func(previous1, previous2, item); previous2 = previous1; previous1 = result; yield return result; } } /// /// Applique une fonction d'accumulation sur une séquence, en renvoyant le résultat /// de chaque étape de l'accumulation. La fonction d'accumulation prend en paramètre /// les 2 valeurs d'accumulation précédentes. /// /// Type des éléments de source /// Type de la valeur d'accumulation /// Séquence sur laquelle appliquer l'accumulation /// Fonction d'accumulation à appeler sur chaque élément /// Séquence des valeurs d'accumulation à chaque étape. public static IEnumerable SelectAggregate( this IEnumerable source, Func func) { return source.SelectAggregate(default(TAccumulate), default(TAccumulate), func); } #endregion #region FirstOrDefault, LastOrDefault, ElementAtOrDefault, SingleOrDefault /// /// Retourne le premier élément d'une séquence, ou la valeur par défaut spécifiée si la séquence ne contient aucun élément. /// /// Type des éléments de source /// Séquence à partir de laquelle retourner le premier élément /// La valeur par défaut à renvoyer si la séquence est vide /// Le premier élément de source s'il existe, sinon la valeur par défaut spécifiée public static T FirstOrDefault(this IEnumerable source, T defaultValue) { return source.DefaultIfEmpty(defaultValue).First(); } /// /// Retourne le premier élément de la séquence à satisfaire à une condition, ou la valeur par défaut spécifiée /// si aucun élément correspondant n'est trouvé. /// /// Type des éléments de source /// Séquence à partir de laquelle retourner un élément /// Condition à vérifier /// La valeur par défaut à renvoyer si aucun élément ne satisfait la condition /// Le premier élément de source à satisfaire la condition, ou la valeur par défaut spécifiée si aucun /// élément ne satisfait la condition public static T FirstOrDefault(this IEnumerable source, Func predicate, T defaultValue) { return source.Where(predicate).FirstOrDefault(defaultValue); } /// /// Retourne le dernier élément d'une séquence, ou la valeur par défaut spécifiée si la séquence ne contient aucun élément. /// /// Type des éléments de source /// Séquence à partir de laquelle retourner le dernier élément /// La valeur par défaut à renvoyer si la séquence est vide /// Le dernier élément de source s'il existe, sinon la valeur par défaut spécifiée public static T LastOrDefault(this IEnumerable source, T defaultValue) { return source.DefaultIfEmpty(defaultValue).Last(); } /// /// Retourne le dernier élément de la séquence à satisfaire à une condition, ou la valeur par défaut spécifiée /// si aucun élément correspondant n'est trouvé. /// /// Type des éléments de source /// Séquence à partir de laquelle retourner un élément /// Condition à vérifier /// La valeur par défaut à renvoyer si aucun élément ne satisfait la condition /// Le dernier élément de source à satisfaire la condition, ou la valeur par défaut spécifiée si aucun /// élément ne satisfait la condition public static T LastOrDefault(this IEnumerable source, Func predicate, T defaultValue) { return source.Where(predicate).LastOrDefault(defaultValue); } /// /// Retourne l'élément situé à un index spécifié dans une séquence, ou la valeur par défaut spécifiée si l'index est hors limites. /// /// Type des éléments de source /// Séquence à partir de laquelle retourner un élément /// Index de l'élément à récupérer /// Valeur par défaut à renvoyer si l'index est hors-limite /// L'élément à l'index spécifié s'il existe ; sinon, la valeur par défaut spécifiée. public static T ElementAtOrDefault(this IEnumerable source, int index, T defaultValue) { if (index < 0) throw new ArgumentOutOfRangeException("index"); return source.Skip(index).DefaultIfEmpty(defaultValue).First(); } /// /// Retourne l'unique élément d'une séquence, ou la valeur par défaut spécifiée si la séquence n'a aucun élément. /// Lève une exception si la séquence contient plus d'un élément /// /// Type des éléments de source /// Séquence à partir de laquelle retourner un élément /// Valeur par défaut à retourner si la séquence est vide /// L'unique élément de la séquence, ou la valeur par défaut spécifiée si la séquence est vide public static T SingleOrDefault(this IEnumerable source, T defaultValue) { int i = 0; T value = defaultValue; foreach (var item in source) { if (i == 0) { value = item; } else { throw new InvalidOperationException(ExceptionMessages.InputSequenceHasMoreThanOneElement); } i++; } return value; } /// /// Retourne l'unique élément d'une séquence qui satisfait une condition, ou la valeur par défaut /// spécifiée si aucun élément ne satisfait la condition. /// Lève une exception si plusieurs éléments satisfont la condition. /// /// Type des éléments de source /// Séquence à partir de laquelle retourner un élément /// La condition à vérifier /// Valeur par défaut à retourner si aucun élément ne satisfait la condition /// L'unique élément de la séquence, ou la valeur par défaut spécifiée si aucun élément ne satisfait la condition public static T SingleOrDefault(this IEnumerable source, Func predicate, T defaultValue) { return source.Where(predicate).SingleOrDefault(defaultValue); } #endregion #region WithMax, WithMin /// /// Renvoie l'élément de la liste ayant la plus grande valeur pour la fonction spécifiée /// /// Type des éléments de la liste /// Type de retour de la fonction à évaluer /// Liste d'éléments /// Fonction évaluée pour chaque élément /// L'élément ayant la plus petite valeur pour la fonction spécifiée public static T WithMax(this IEnumerable source, Func selector) { var max = default(TValue); var withMax = default(T); bool first = true; foreach (var item in source) { var value = selector(item); int compare = Comparer.Default.Compare(value, max); if (compare > 0 || first) { max = value; withMax = item; } first = false; } return withMax; } /// /// Renvoie l'élément de la liste ayant la plus petite valeur pour la fonction spécifiée /// /// Type des éléments de la liste /// Type de retour de la fonction à évaluer /// Liste d'éléments /// Fonction évaluée pour chaque élément /// L'élément ayant la plus petite valeur pour la fonction spécifiée public static T WithMin(this IEnumerable source, Func selector) { var min = default(TValue); var withMin = default(T); bool first = true; foreach (var item in source) { var value = selector(item); int compare = Comparer.Default.Compare(value, min); if (compare < 0 || first) { min = value; withMin = item; } first = false; } return withMin; } #endregion #region QuickSort /// /// Trie la liste en utilisant l'algorithme Quicksort /// /// Type des éléments de la liste /// Liste à trier public static void QuickSort(this IList list) { Comparison comparison = Comparer.Default.ToComparison(); QuickSort(list, comparison); } /// /// Trie la liste selon la clé spécifiée en utilisant l'algorithme Quicksort /// /// Type des éléments de la liste /// Type de la clé de tri /// Liste à trier /// Fonction qui renvoie la clé de tri public static void QuickSortBy(this IList list, Func keySelector) { Comparison keyComparison = Comparer.Default.ToComparison(); Comparison comparison = (a, b) => keyComparison(keySelector(a), keySelector(b)); QuickSort(list, comparison); } /// /// Trie la liste en utilisant l'algorithme Quicksort, avec le comparateur spécifié /// /// Type des éléments de la liste /// Liste à trier /// Comparateur à utiliser public static void QuickSort(this IList list, IComparer comparer) { QuickSort(list, comparer.ToComparison()); } /// /// Trie la liste en utilisant l'algorithme Quicksort, avec la fonction de comparaison spécifiée /// /// Type des éléments de la liste /// Liste à trier /// Fonction de comparaison à utiliser public static void QuickSort(this IList list, Comparison comparison) { QuickSort(list, 0, list.Count - 1, comparison); } private static void QuickSort(IList list, int left, int right, Comparison comparison) { if (right > left) { int pivot = left; QuickSortPartition(list, left, right, ref pivot, comparison); QuickSort(list, left, pivot - 1, comparison); QuickSort(list, pivot + 1, right, comparison); } } private static void QuickSortPartition(IList list, int left, int right, ref int pivot, Comparison comparison) { T pivotValue = list[pivot]; list.Swap(pivot, right); int tmpIndex = left; for (int i = left; i < right; i++) { if (comparison(list[i], pivotValue) <= 0) { list.Swap(i, tmpIndex); tmpIndex++; } } list.Swap(tmpIndex, right); pivot = tmpIndex; } #endregion #region AsIndexed, Unindex /// /// Associe un index à un élément d'une séquence /// /// Type des éléments de la séquence public class IndexedItem { internal IndexedItem(int index, T value) { this.Index = index; this.Value = value; } /// /// Index de l'élément /// public int Index { get; private set; } /// /// Valeur de l'élément /// public T Value { get; private set; } } /// /// Associe à chaque élément de la séquence son index dans la séquence /// /// Type des éléments de la séquence /// Séquence d'éléments à indexer /// Une séquence projetée où chaque élément est associé à son index public static IEnumerable> AsIndexed(this IEnumerable source) { return source.Select((v, i) => new IndexedItem(i, v)); } /// /// Dissocie chaque élément de la séquence de son index. Cette méthode effectue l'opération inverse de AsIndexed. /// /// Type des éléments de la séquence /// Séquence d'éléments à dissocier de leur index /// Une séquence d'éléments sans leur index public static IEnumerable Unindex(this IEnumerable> source) { return source.Select(x => x.Value); } #endregion #region TakeEvery, SkipEvery, GroupEvery /// /// Extrait un élément sur frequency à partir de la séquence d'entrée, à partir du premier élément. /// /// Type des éléments de la séquence /// Séquence d'entrée /// Fréquence des éléments à extraire /// Une séquence contenant un élément sur frequency de la séquence d'entrée public static IEnumerable TakeEvery(this IEnumerable source, int frequency) { return source.TakeEvery(frequency, 0); } /// /// Extrait un élément sur frequency à partir de la séquence d'entrée, à partir de la position spécifiée. /// /// Type des éléments de la séquence /// Séquence d'entrée /// Fréquence des éléments à extraire /// Position du premier élément à extraire /// Une séquence contenant un élément sur frequency de la séquence d'entrée à partir de la position spécifiée public static IEnumerable TakeEvery(this IEnumerable source, int frequency, int start) { if (frequency <= 0) throw new ArgumentException("frequency"); if (start < 0) throw new ArgumentException("start"); return source .Skip(start) .Where((item, index) => index % frequency == 0); } /// /// Renvoie les éléments de la séquence d'entrée, en sautant un élément sur frequency à partir de la position spécifiée. /// /// Type des éléments de la séquence /// Séquence d'entrée /// Fréquence des éléments à sauter /// Une séquence les éléments de la séquence d'entrée, en en sautant un sur frequency à partir de la position spécifiée public static IEnumerable SkipEvery(this IEnumerable source, int frequency) { return source.SkipEvery(frequency, 0); } /// /// Renvoie les éléments de la séquence d'entrée, en sautant un élément sur frequency à partir du premier élément. /// /// Type des éléments de la séquence /// Séquence d'entrée /// Fréquence des éléments à sauter /// Position du premier élément à sauter /// Une séquence les éléments de la séquence d'entrée, en en sautant un sur frequency à partir du premier. public static IEnumerable SkipEvery(this IEnumerable source, int frequency, int start) { if (frequency <= 0) throw new ArgumentException("frequency"); return source .Where((item, index) => index < start ? true : (index - start) % frequency != 0); } /// /// Renvoie les éléments de la séquence d'entrée par groupes de groupLength éléments. /// /// Type des éléments de la séquence /// Séquence d'éléments à grouper /// Longueur des groupes /// Une séquence de groupes de groupLength éléments /// Si le nombre total d'éléments dans la séquence n'est pas un multiple de groupLength, le dernier groupe renvoyé contiendra moins de groupLength éléments. public static IEnumerable> GroupEvery(this IEnumerable source, int groupLength) { if (groupLength <= 0) throw new ArgumentOutOfRangeException("groupLength"); return source .AsIndexed() .GroupBy(x => x.Index / groupLength) .Select(g => g.Unindex()); } #endregion } }