using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using Developpez.Dotnet.Threading; using System.Collections; namespace Developpez.Dotnet.Collections { /// /// Fournit des méthodes d'extension pour les types enumérables /// public static class EnumerableExtensions { #region Miscellaneous /// /// 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; } #endregion #region Zip // Contournement d'un bug dans Resharper: la directive #if entre le commentaire de doc et la // méthode provoque ces deux avertissements (mais le comportement du compilateur est correct) #pragma warning disable 1587 #pragma warning disable 1591 /// /// 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, les éléments surnuméraires de la séquence la plus longue sont ignorés. /// Cette méthode est absente de la version compilée pour .NET 4, car elle est déjà définie dans le framework 4.0 (Enumerable.Zip) /// // La méthode Zip a été ajoutée dans .NET 4.0 et ne sera donc plus nécessaire // Par contre elle est utilisée en interne et doit donc rester présente #if DOTNET4 internal #else public #endif static IEnumerable Zip(this IEnumerable first, IEnumerable second, Func selector) { first.CheckArgumentNull("first"); second.CheckArgumentNull("second"); selector.CheckArgumentNull("selector"); return first.ZipIterator(second, selector); } #pragma warning restore 1591 #pragma warning restore 1587 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); } } } #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) { list.Shuffle(new Random()); } /// /// Mélange une liste en spécifiant le générateur de nombres aléatoires à utiliser. /// /// Type des éléments de la séquence /// La liste à mélanger /// Le générateur de nombres aléatoires à utiliser /// 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) { for (int i = list.Count - 1; i > 0; i--) { int swapIndex = rnd.Next(i + 1); list.Swap(i, swapIndex); } } #endregion #region BinarySearch and related methods /// /// 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 Conversion from/to CSV strings /// /// 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[] { 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 and related methods /// /// 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; var comparer = Comparer.Default; foreach (var item in source) { var value = selector(item); int compare = comparer.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) { Index = index; 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 #region SkipFirst, SkipLast, SkipAt /// /// Renvoie les éléments de la séquence d'entrée en sautant le premier. /// /// Type des éléments de la séquence /// Séquence d'entrée /// La séquence d'entrée sans le premier élément public static IEnumerable SkipFirst(this IEnumerable source) { source.CheckArgumentNull("source"); return source.Skip(1); } /// /// Renvoie les éléments de la séquence d'entrée en sautant le premier élément qui vérifie le prédicat spécifié. /// /// Type des éléments de la séquence /// Séquence d'entrée /// Prédicat à évaluer pour déterminer s'il faut sauter l'élément /// La séquence d'entrée sans le premier élément qui vérifie le prédicat public static IEnumerable SkipFirst(this IEnumerable source, Func predicate) { source.CheckArgumentNull("source"); predicate.CheckArgumentNull("predicate"); return source.SkipFirstIterator(predicate); } private static IEnumerable SkipFirstIterator(this IEnumerable source, Func predicate) { bool skipped = false; foreach (var item in source) { if (!skipped && predicate(item)) { skipped = true; continue; } yield return item; } } /// /// Renvoie les éléments de la séquence d'entrée en sautant le dernier élément qui vérifie le prédicat spécifié. /// /// Type des éléments de la séquence /// Séquence d'entrée /// Prédicat à évaluer pour déterminer s'il faut sauter l'élément /// La séquence d'entrée sans le dernier élément qui vérifie le prédicat public static IEnumerable SkipLast(this IEnumerable source, Func predicate) { source.CheckArgumentNull("source"); predicate.CheckArgumentNull("predicate"); return source.SkipLastIterator(predicate); } private static IEnumerable SkipLastIterator(this IEnumerable source, Func predicate) { var queue = new Queue(); var last = default(T); bool any = false; foreach (var item in source) { if (predicate(item)) { if (any) yield return last; while (queue.Count > 0) yield return queue.Dequeue(); last = item; any = true; } else { if (any) queue.Enqueue(item); else yield return item; } } while (queue.Count > 0) yield return queue.Dequeue(); } /// /// Renvoie les éléments de la séquence d'entrée en sautant l'élément qui se trouve à la position spécifiée. /// /// Type des éléments de la séquence /// Séquence d'entrée /// Position de l'élément à sauter /// La séquence d'entrée sans l'élément qui se trouvait à la position spécifiée public static IEnumerable SkipAt(this IEnumerable source, int index) { return source.Where((it, i) => i != index); } #endregion #region TakeLast /// /// Renvoie le nombre spécifié d'éléments de la fin d'une séquence /// /// Type des éléments de la séquence /// Séquence d'entrée /// Nombre maximum d'éléments à renvoyer /// Les count derniers éléments de la séquence public static IEnumerable TakeLast(this IEnumerable source, int count) { source.CheckArgumentNull("source"); if (count < 0) throw new ArgumentOutOfRangeException( "count", string.Format(ExceptionMessages.NumberMustBePositiveOrZero, "count")); return source.TakeLastIterator(count); } private static IEnumerable TakeLastIterator(this IEnumerable source, int count) { if (count <= 0) yield break; var queue = new Queue(); foreach (var item in source) { if (queue.Count == count) queue.Dequeue(); queue.Enqueue(item); } foreach (var item in queue) yield return item; } #endregion #region ReplaceAt /// /// Remplaçe l'élément à la position spécifiée d'une séquence par un élément spécifié. /// /// Type des éléments de la séquence /// Séquence d'entrée /// Position à laquelle effectuer le remplacement /// Elément qui remplace l'élément à la position spécifiée /// Séquence dans laquelle un élément a été remplacé public static IEnumerable ReplaceAt(this IEnumerable source, int index, T item) { return source.Select((it, i) => i == index ? item : it); } #endregion #region InsertAt /// /// Insère un élément à la position spécifiée d'une séquence. /// /// Type des éléments de la séquence /// Séquence d'entrée /// Position à laquelle insérer l'élément /// Elément à insérer /// Séquence dans laquelle un élément a été inséré public static IEnumerable InsertAt(this IEnumerable source, int index, T item) { source.CheckArgumentNull("source"); index.CheckArgumentOutOfRange("index", 0, int.MaxValue); return source.InsertAtIterator(index, item); } private static IEnumerable InsertAtIterator(this IEnumerable source, int index, T item) { int i = 0; foreach (var it in source) { if (i++ == index) yield return item; yield return it; } } #endregion #region None /// /// Indique si une séquence ne contient aucun élément /// /// Type des éléments de la séquence /// Séquence à vérifier /// true si source ne contient aucun élément ; sinon, false public static bool None(this IEnumerable source) { return !source.Any(); } /// /// Indique s'il n'existe dans la séquence aucun élément qui satisfait le prédicat /// /// Type des éléments de la séquence /// Séquence à vérifier /// Prédicat à évaluer pour chaque élément de la séquence /// true si la séquence ne contient aucun élément qui satisfait le prédicat ; sinon, false public static bool None(this IEnumerable source, Func predicate) { return !source.Any(predicate); } #endregion #region ContainsAny /// /// Indique si une séquence contient au moins un des éléments spécifiés, en utilisant le comparateur par défaut /// /// Type des élements de la séquence /// Séquence dans laquelle rechercher des éléments /// Eléments à rechercher /// true si source contient au moins un des éléments de items, false sinon public static bool ContainsAny(this IEnumerable source, IEnumerable items) { return ContainsAny(source, items, EqualityComparer.Default); } /// /// Indique si une séquence contient au moins un des éléments spécifiés, en utilisant le comparateur spécifié /// /// Type des élements de la séquence /// Séquence dans laquelle rechercher des éléments /// Eléments à rechercher /// Comparateur à utiliser /// true si source contient au moins un des éléments de items, false sinon public static bool ContainsAny(this IEnumerable source, IEnumerable items, IEqualityComparer comparer) { return source.Intersect(items, comparer).Any(); } #endregion #region AddRange /// /// Ajoute plusieurs éléments à une collection /// /// Type des éléments de la collection /// Collection à laquelle ajouter des éléments /// Eléments à ajouter public static void AddRange(this ICollection collection, IEnumerable items) { items.CheckArgumentNull("items"); foreach (var item in items) { collection.Add(item); } } #endregion #region ArraySegment related methods /// /// Renvoie le segment demandé d'un tableau /// /// Type des éléments du tableau /// Tableau dont on veut obtenir un segment /// Index de début du segment /// Nombre d'éléments dans le segment /// Le segment demandé du tableau public static ArraySegment GetSegment(this T[] array, int from, int count) { return new ArraySegment(array, from, count); } /// /// Renvoie un segment d'un tableau à partir de la position spécifiée /// /// Type des éléments du tableau /// Tableau dont on veut obtenir un segment /// Index de début du segment /// Le segment de tableau qui commence à la position spécifiée public static ArraySegment GetSegment(this T[] array, int from) { return GetSegment(array, from, array.Length - from); } /// /// Renvoie un segment de tableau représentant la totalité du tableau /// /// Type des éléments du tableau /// Tableau dont on veut obtenir un segment /// Un segment représentant la totalité du tableau public static ArraySegment GetSegment(this T[] array) { return new ArraySegment(array); } /// /// Renvoie une séquence qui énumère les éléments d'un segment de tableau /// /// Type des éléments du tableau /// Segment de tableau à énumérer /// Une séquence correspondant aux éléments du segment de tableau public static IEnumerable AsEnumerable(this ArraySegment arraySegment) { return arraySegment.Array.Skip(arraySegment.Offset).Take(arraySegment.Count); } /// /// Renvoie un nouveau tableau à partir d'un segment de tableau /// /// Type des éléments du tableau /// Segment de tableau à partir duquel est créé le nouveau tableau /// Un tableau contenant tous les éléments du segment d'origine public static T[] ToArray(this ArraySegment arraySegment) { T[] array = new T[arraySegment.Count]; Array.Copy(arraySegment.Array, arraySegment.Offset, array, 0, arraySegment.Count); return array; } #endregion #region IndexOf /// /// Renvoie l'index de la première occurence de l'élément spécifié dans la séquence /// /// Le type des éléments de la séquence /// La séquence dans laquelle chercher l'élément /// L'élément à rechercher /// L'index de la première occurence de l'élément dans la séquence, ou -1 si l'élément ne s'y trouve pas. public static int IndexOf(this IEnumerable source, T item) { return source.AsIndexed() .Where(x => x.Value.SafeEquals(item)) .Select(x => x.Index) .FirstOrDefault(-1); } /// /// Renvoie l'index du premier élément pour lequel le prédicat spécifié est vérifié /// /// Le type des éléments de la séquence /// La séquence dans laquelle chercher l'élément /// Le prédicat à évaluer /// L'index du premier élément pour lequel le prédicat est vérifié, ou -1 si aucun élément ne vérifie le prédicat. public static int IndexOf(this IEnumerable source, Func predicate) { return source.AsIndexed() .Where(x => predicate(x.Value)) .Select(x => x.Index) .FirstOrDefault(-1); } /// /// Renvoie l'index de la dernière occurence de l'élément spécifié dans la séquence /// /// Le type des éléments de la séquence /// La séquence dans laquelle chercher l'élément /// L'élément à rechercher /// L'index de la dernière occurence de l'élément dans la séquence, ou -1 si l'élément ne s'y trouve pas. public static int LastIndexOf(this IEnumerable source, T item) { return source.AsIndexed() .Where(x => x.Value.SafeEquals(item)) .Select(x => x.Index) .LastOrDefault(-1); } /// /// Renvoie l'index du dernier élément pour lequel le prédicat spécifié est vérifié /// /// Le type des éléments de la séquence /// La séquence dans laquelle chercher l'élément /// Le prédicat à évaluer /// L'index du dernier élément pour lequel le prédicat est vérifié, ou -1 si aucun élément ne vérifie le prédicat. public static int LastIndexOf(this IEnumerable source, Func predicate) { return source.AsIndexed() .Where(x => predicate(x.Value)) .Select(x => x.Index) .LastOrDefault(-1); } #endregion #region CopyTo /// /// Copie tous les éléments de la séquence vers le tableau spécifié /// /// Type des éléments de la séquence /// Séquence à partir de laquelle copier les éléments /// Tableau vers lequel copier les éléments /// Position dans le tableau à partir de laquelle les éléments sont copiés public static void CopyTo(this IEnumerable source, T[] array, int arrayIndex) { int lowerBound = array.GetLowerBound(0); int upperBound = array.GetUpperBound(0); if (arrayIndex < lowerBound || arrayIndex > upperBound) throw new ArgumentOutOfRangeException("arrayIndex"); int i = 0; foreach (var item in source) { if (arrayIndex + i > upperBound) throw new ArgumentException(ExceptionMessages.ArrayCapacityInsufficient); array[arrayIndex + i] = item; i++; } } #endregion #region ShiftLeft, ShiftRight /// /// Décale les éléments d'un tableau d'une position vers la gauche, /// en remplaçant le dernier élément par la valeur par défaut du type /// des éléments du tableau. Cette méthode ne modifie pas le tableau d'origine /// mais renvoie un nouveau tableau. /// /// Type des éléments du tableau /// Tableau à décaler /// Une copie décalée du tableau d'origine public static T[] ShiftLeft(this T[] array) { return array.ShiftLeft(default(T)); } /// /// Décale les éléments d'un tableau d'une position vers la gauche, /// en remplaçant le dernier élément par la valeur spécifiée. /// Cette méthode ne modifie pas le tableau d'origine mais renvoie un /// nouveau tableau. /// /// Type des éléments du tableau /// Tableau à décaler /// Valeur à insérer à la fin du tableau /// Une copie décalée du tableau d'origine public static T[] ShiftLeft(this T[] array, T value) { T[] newArray = new T[array.Length]; Array.Copy(array, 1, newArray, 0, array.Length - 1); newArray[array.Length - 1] = value; return newArray; } /// /// Décale les éléments d'un tableau d'une position vers la droite, /// en remplaçant le premier élément par la valeur par défaut du type /// des éléments du tableau. Cette méthode ne modifie pas le tableau d'origine /// mais renvoie un nouveau tableau. /// /// Type des éléments du tableau /// Tableau à décaler /// Une copie décalée du tableau d'origine public static T[] ShiftRight(this T[] array) { return array.ShiftRight(default(T)); } /// /// Décale les éléments d'un tableau d'une position vers la droite, /// en remplaçant le premier élément par la valeur spécifiée. /// Cette méthode ne modifie pas le tableau d'origine mais renvoie un /// nouveau tableau. /// /// Type des éléments du tableau /// Tableau à décaler /// Valeur à insérer au début du tableau /// Une copie décalée du tableau d'origine public static T[] ShiftRight(this T[] array, T value) { T[] newArray = new T[array.Length]; Array.Copy(array, 0, newArray, 1, array.Length - 1); newArray[0] = value; return newArray; } #endregion #region RotateLeft, RotateRight /// /// Effectue une rotation d'un tableau d'une position vers la gauche. /// Cette méthode ne modifie pas le tableau d'origine mais renvoie un /// nouveau tableau. /// /// Type des éléments du tableau /// Tableau sur lequel effectuer une rotation /// Une copie du tableau d'origine sur laquelle une rotation a été effectuée public static T[] RotateLeft(this T[] array) { return array.ShiftLeft(array.First()); } /// /// Effectue une rotation d'un tableau d'une position vers la droite. /// Cette méthode ne modifie pas le tableau d'origine mais renvoie un /// nouveau tableau. /// /// Type des éléments du tableau /// Tableau sur lequel effectuer une rotation /// Une copie du tableau d'origine sur laquelle une rotation a été effectuée public static T[] RotateRight(this T[] array) { return array.ShiftRight(array.Last()); } #endregion #region Apply /// /// Applique l'action spécifiée "au passage" sur chaque élément de la séquence /// /// Type des éléments de la séquence /// Séquence d'origine /// Action à effectuer sur chaque élément de la séquence /// La séquence d'origine /// Cette méthode s'utilise uniquement avec des types référence. Pour les types valeur, /// le paramètre de l'action doit être passé par référence. public static IEnumerable Apply(this IEnumerable source, Action action) where T : class { action.CheckArgumentNull("action"); return source.ApplyIterator(action); } private static IEnumerable ApplyIterator(this IEnumerable source, Action action) where T : class { foreach (var item in source) { action(item); yield return item; } } /// /// Représente une action avec un paramètre passé par référence /// /// Type du paramètre /// Paramètre de l'action public delegate void ByRefAction(ref T param); /// /// Applique l'action spécifiée "au passage" sur chaque élément de la séquence /// /// Type des éléments de la séquence /// Séquence d'origine /// Action à effectuer sur chaque élément de la séquence /// La séquence d'origine /// Cette méthode s'utilise uniquement avec des types valeur. Pour les types référence, /// le paramètre de l'action doit être passé par valeur. public static IEnumerable Apply(this IEnumerable source, ByRefAction action) where T : struct { action.CheckArgumentNull("action"); return source.ApplyIterator(action); } private static IEnumerable ApplyIterator(this IEnumerable source, ByRefAction action) where T : struct { foreach (var item in source) { T it = item; action(ref it); yield return it; } } #endregion #region IndexOfSequence /// /// Renvoie la position de début de la première occurence de la sous-séquence spécifiée dans la séquence source, /// en utilisant le comparateur par défaut. /// /// Type des éléments de la séquence /// Séquence source dans laquelle on recherche une sous-séquence /// Sous-séquence à rechercher /// La position de la première occurence de sequence dans source, ou -1 si la sous-séquence n'est pas trouvée. public static int IndexOfSequence(this IEnumerable source, IEnumerable sequence) { return source.IndexOfSequence(sequence, EqualityComparer.Default); } /// /// Renvoie la position de début de la première occurence de la sous-séquence spécifiée dans la séquence source, /// en utilisant le comparateur spécifié. /// /// Type des éléments de la séquence /// Séquence source dans laquelle on recherche une sous-séquence /// Sous-séquence à rechercher /// Comparateur à utiliser /// La position de la première occurence de sequence dans source, ou -1 si la sous-séquence n'est pas trouvée. public static int IndexOfSequence(this IEnumerable source, IEnumerable sequence, IEqualityComparer comparer) { var seq = sequence.ToArray(); int p = 0; // current position in source sequence int i = 0; // current position in searched sequence var prospects = new List(); // list of prospective matches foreach (var item in source) { // ReSharper disable AccessToModifiedClosure // Remove bad prospective matches prospects.RemoveAll(k => !comparer.Equals(item, seq[p - k])); // ReSharper restore AccessToModifiedClosure // Is it the start of a prospective match ? if (comparer.Equals(item, seq[0])) { prospects.Add(p); } // Does current character continues partial match ? if (comparer.Equals(item, seq[i])) { i++; // Do we have a complete match ? if (i == seq.Length) { // Bingo ! return p - seq.Length + 1; } } else // Mismatch { // Do we have prospective matches to fall back to ? if (prospects.Count > 0) { // Yes, use the first one int k = prospects[0]; i = p - k + 1; } else { // No, start from beginning of searched sequence i = 0; } } p++; } // No match return -1; } #endregion #region Hierarchy related methods /// /// Représente un noeud dans une hiérarchie d'objets /// /// Type des objets de la hiérarchie public class Node { internal Node() { } /// /// Objet contenu par ce noeud /// public T Item { get; internal set; } /// /// Niveau du noeud dans la hiérarchie (0 pour un noeud racine) /// public int Level { get; internal set; } /// /// Noeud parent dans la hiérarchie (null pour un noeud racine) /// public Node Parent { get; internal set; } /// /// Noeuds enfants dans la hiérarchie /// public IList> Children { get; internal set; } } /// /// Renvoie une collection de noeuds représentant les objets de la collection source sous forme hiérarchique, /// selon la relation spécifiée /// /// Type des éléments de la collection source /// Collection d'objets à hiérarchiser /// Prédicat pour identifier les éléments racines /// Relation qui lie un objet parent à ses enfants /// Une collection de noeuds représentant les objets de la collection source sous forme hiérarchique public static IEnumerable> ToHierarchy( this IEnumerable source, Func startWith, Func connectBy) { return source.ToHierarchy(startWith, connectBy, null); } private static IEnumerable> ToHierarchy( this IEnumerable source, Func startWith, Func connectBy, Node parent) { int level = (parent == null ? 0 : parent.Level + 1); if (source == null) throw new ArgumentNullException("source"); if (startWith == null) throw new ArgumentNullException("startWith"); if (connectBy == null) throw new ArgumentNullException("connectBy"); foreach (T value in from item in source where startWith(item) select item) { var children = new List>(); var newNode = new Node { Level = level, Parent = parent, Item = value, Children = children.AsReadOnly() }; T tmpValue = value; children.AddRange(source.ToHierarchy(possibleSub => connectBy(tmpValue, possibleSub), connectBy, newNode)); yield return newNode; } } /// /// Ecrit une hiérarchie d'objets dans le TextWriter spécifié /// /// Type des objets de la hiérarchie /// Collection de noeuds à afficher /// TextWriter dans lequel écrire la hiérarchie /// Chaine d'indentation à utiliser /// Fonction qui sélectionne le membre à afficher public static void DumpHierarchy(this IEnumerable> nodes, TextWriter writer, string indent, Func display) { nodes.DumpHierarchy(writer, indent, display, 0); } /// /// Ecrit une hiérarchie d'objets dans le TextWriter spécifié /// /// Type des objets de la hiérarchie /// Collection de noeuds à afficher /// TextWriter dans lequel écrire la hiérarchie /// Fonction qui sélectionne le membre à afficher public static void DumpHierarchy(this IEnumerable> nodes, TextWriter writer, Func display) { nodes.DumpHierarchy(writer, " ", display, 0); } /// /// Ecrit une hiérarchie d'objets dans le TextWriter spécifié /// /// Type des objets de la hiérarchie /// Collection de noeuds à afficher /// TextWriter dans lequel écrire la hiérarchie /// Chaine d'indentation à utiliser public static void DumpHierarchy(this IEnumerable> nodes, TextWriter writer, string indent) { nodes.DumpHierarchy(writer, indent, item => item.ToString(), 0); } /// /// Ecrit une hiérarchie d'objets dans le TextWriter spécifié /// /// Type des objets de la hiérarchie /// Collection de noeuds à afficher /// TextWriter dans lequel écrire la hiérarchie public static void DumpHierarchy(this IEnumerable> nodes, TextWriter writer) { nodes.DumpHierarchy(writer, " ", item => item.ToString(), 0); } /// /// Ecrit une hiérarchie d'objets dans la console /// /// Type des objets de la hiérarchie /// Collection de noeuds à afficher /// Chaine d'indentation à utiliser /// Fonction qui sélectionne le membre à afficher public static void DumpHierarchy(this IEnumerable> nodes, string indent, Func display) { nodes.DumpHierarchy(Console.Out, indent, display, 0); } /// /// Ecrit une hiérarchie d'objets dans la console /// /// Type des objets de la hiérarchie /// Collection de noeuds à afficher /// Fonction qui sélectionne le membre à afficher public static void DumpHierarchy(this IEnumerable> nodes, Func display) { nodes.DumpHierarchy(Console.Out, " ", display, 0); } /// /// Ecrit une hiérarchie d'objets dans la console /// /// Type des objets de la hiérarchie /// Collection de noeuds à afficher /// Chaine d'indentation à utiliser public static void DumpHierarchy(this IEnumerable> nodes, string indent) { nodes.DumpHierarchy(Console.Out, indent, item => item.ToString(), 0); } /// /// Ecrit une hiérarchie d'objets dans la console /// /// Type des objets de la hiérarchie /// Collection de noeuds à afficher public static void DumpHierarchy(this IEnumerable> nodes) { nodes.DumpHierarchy(Console.Out, " ", item => item.ToString(), 0); } private static void DumpHierarchy(this IEnumerable> nodes, TextWriter writer, string indent, Func display, int level) { foreach (var node in nodes) { for (int i = 0; i < level; i++) writer.Write(indent); writer.WriteLine(display(node.Item)); if (node.Children != null) node.Children.DumpHierarchy(writer, indent, display, level + 1); } } /// /// Aplanit une hiérarchie d'objets de même type en énumérant tous les noeuds /// de la hiérarchie, dans l'ordre de parcours indiqué. /// /// Type des éléments de la hiérarchie /// Collection des éléments racines de la hiérarchie /// Fonction qui renvoie les enfants d'un noeud de la hiérarchie /// Mode de parcours de la hiérarchie /// Une séquence contenant tous les noeuds de la hiérarchie public static IEnumerable Flatten(this IEnumerable source, Func> childrenSelector, TreeTraversalMode traversalMode) { source.CheckArgumentNull("source"); childrenSelector.CheckArgumentNull("childrenSelector"); switch (traversalMode) { case TreeTraversalMode.DepthFirst: return source.DepthFirstFlattenIterator(childrenSelector); case TreeTraversalMode.BreadthFirst: return source.BreadthFirstFlattenIterator(childrenSelector); default: throw new ArgumentOutOfRangeException("traversalMode"); } } private static IEnumerable BreadthFirstFlattenIterator(this IEnumerable source, Func> childrenSelector) { var queue = new Queue(source); while (queue.Count > 0) { var item = queue.Dequeue(); yield return item; foreach (var child in childrenSelector(item)) { queue.Enqueue(child); } } } private static IEnumerable DepthFirstFlattenIterator(this IEnumerable source, Func> childrenSelector) { var list = new LinkedList(source); while (list.Count > 0) { var item = list.First.Value; list.RemoveFirst(); yield return item; var node = list.First; foreach (var child in childrenSelector(item)) { if (node != null) list.AddBefore(node, child); else list.AddLast(child); } } } #endregion #region Split /// /// Divise une séquence d'éléments en plusieurs parties à chaque fois qu'un séparateur /// est rencontré. /// /// Type des éléments de la séquence /// Séquence d'éléments à diviser /// Prédicat qui détermine si un élément de la liste est un séparateur /// Une séquence de parties de la séquence d'origine public static IEnumerable> Split(this IEnumerable source, Func isSeparator) { source.CheckArgumentNull("source"); isSeparator.CheckArgumentNull("isSeparator"); return source.SplitIterator(isSeparator); } private static IEnumerable> SplitIterator(this IEnumerable source, Func isSeparator) { List list = new List(); foreach (var item in source) { if(isSeparator(item)) { if (list.Count > 0) { yield return list.AsReadOnly(); list = new List(); } } else { list.Add(item); } } if (list.Count > 0) yield return list.AsReadOnly(); } #endregion #region DistinctBy /// /// Retourne des éléments distincts d'une séquence, en les comparant selon la clé spécifiée, /// et en comparant les clés avec le comparateur spécifié. /// /// Type des éléments de la séquence /// Type de la clé /// Séquence de laquelle supprimer les éléments en double /// Fonction qui renvoie la clé à utiliser pour comparer les éléments /// Comparateur à utiliser pour comparer les clés /// Une séquence contenant des éléments distincts de la séquence d'origine public static IEnumerable Distinct(this IEnumerable source, Func keySelector, IEqualityComparer keyComparer) { keySelector.CheckArgumentNull("keySelector"); keyComparer.CheckArgumentNull("keyComparer"); return source.Distinct(new KeyEqualityComparer(keySelector, keyComparer)); } /// /// Retourne des éléments distincts d'une séquence, en les comparant selon la clé spécifiée /// /// Type des éléments de la séquence /// Type de la clé /// Séquence de laquelle supprimer les éléments en double /// Fonction qui renvoie la clé à utiliser pour comparer les éléments /// Une séquence contenant des éléments distincts de la séquence d'origine public static IEnumerable Distinct(this IEnumerable source, Func keySelector) { keySelector.CheckArgumentNull("keySelector"); return source.Distinct(new KeyEqualityComparer(keySelector)); } private class KeyEqualityComparer : IEqualityComparer { private readonly Func _keySelector; private readonly IEqualityComparer _keyComparer; public KeyEqualityComparer(Func keySelector) : this(keySelector, EqualityComparer.Default) { } public KeyEqualityComparer(Func keySelector, IEqualityComparer keyComparer) { keySelector.CheckArgumentNull("keySelector"); keyComparer.CheckArgumentNull("keyComparer"); _keySelector = keySelector; _keyComparer = keyComparer; } public bool Equals(T x, T y) { return _keyComparer.Equals(_keySelector(x), _keySelector(y)); } public int GetHashCode(T obj) { return _keyComparer.GetHashCode(_keySelector(obj)); } } #endregion } }