//****************************************************************************************************** // CollectionExtensions.cs - Gbtc // // Copyright © 2012, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the Eclipse Public License -v 1.0 (the "License"); you may // not use this file except in compliance with the License. You may obtain a copy of the License at: // // http://www.opensource.org/licenses/eclipse-1.0.php // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 02/23/2003 - J. Ritchie Carroll // Generated original version of source code. // 01/23/2005 - J. Ritchie Carroll // Migrated 2.0 version of source code from 1.1 source (GSF.Shared.Common). // 08/17/2007 - Darrell Zuercher // Edited code comments. // 09/11/2008 - J. Ritchie Carroll // Converted to C# extension functions. // 02/17/2009 - Josh L. Patterson // Edited Code Comments. // 02/20/2009 - J. Ritchie Carroll // Added predicate based IndexOf that extends IList. // 04/02/2009 - J. Ritchie Carroll // Added seed based scramble and unscramble IList extensions. // 06/05/2009 - Pinal C. Patel // Added generic AddRange() extension method for IList. // 06/09/2009 - Pinal C. Patel // Added generic GetRange() extension method for IList. // 08/05/2009 - Josh L. Patterson // Update comments. // 09/14/2009 - Stephen C. Wills // Added new header and license agreement. // 11/17/2009 - Pinal C. Patel // Added generic UpdateRange() extension method for IList. // 03/31/2009 - J. Ritchie Carroll // Added Majority() and Minority() extension methods to IEnumerable. // 03/18/2011 - J. Ritchie Carroll // Added dictionary Merge() extensions method for IDictionary and Any/All extensions for BitArray. // 03/22/2011 - J. Ritchie Carroll // Modified array copy extension to handle zero length (i.e., empty) source arrays. // 10/17/2011 - J. Ritchie Carroll // Added the ability to specify a default value for Majority and Minority when none exists. // 12/13/2012 - Starlynn Danyelle Gilliam // Modified Header. // //****************************************************************************************************** using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; using Random = GSF.Security.Cryptography.Random; namespace GSF.Collections { /// /// Defines extension functions related to manipulation of arrays and collections. /// public static class CollectionExtensions { /// /// Merges elements of multiple dictionaries into a single dictionary with no duplicate key values overwritten. /// /// Type of to merge. /// Type of keys. /// Type of values. /// Source dictionary to merge with another dictionary. /// Other dictionaries to merge with source dictionary. /// /// A merged collection of all unique dictionary elements, all merged left to the source with no duplicate /// key values overwritten (i.e., first encounted key value pair is the one that remains in the returned merged dictionary). /// public static T Merge(this T source, params IDictionary[] others) where T : IDictionary, new() { return source.Merge(false, others); } /// /// Merges elements of multiple dictionaries into a single dictionary. /// /// Type of to merge. /// Type of keys. /// Type of values. /// Source dictionary to merge with another dictionary. /// Set to true to overwrite duplicate key values as they are encountered. /// Other dictionaries to merge with source dictionary. /// A merged collection of all unique dictionary elements, all merged left to the source. public static T Merge(this T source, bool overwriteExisting, params IDictionary[] others) where T : IDictionary, new() { T mergedDictionary = new T(); List> allDictionaries = new List>(); allDictionaries.Add(source); allDictionaries.AddRange(others); foreach (IDictionary dictionary in allDictionaries) { foreach (KeyValuePair kvPair in dictionary) { if (overwriteExisting) { mergedDictionary[kvPair.Key] = kvPair.Value; } else { if (!mergedDictionary.ContainsKey(kvPair.Key)) mergedDictionary.Add(kvPair); } } } return mergedDictionary; } /// /// Returns true if any item in is equal to . /// /// Source . /// value to test for. /// true if any item in is equal to . public static bool Any(this BitArray source, bool value) { return source.Cast().Any(item => item == value); } /// /// Returns true if all items in are equal to . /// /// Source . /// value to test for. /// true if all items in are equal to . public static bool All(this BitArray source, bool value) { return source.Cast().All(item => item == value); } /// /// Returns the majority value in the collection, or default type value if no item represents the majority. /// /// of elements in the . /// An enumeration over which to find the majority element. /// The majority value in the collection. public static T Majority(this IEnumerable source) { return source.Majority(default(T)); } /// /// Returns the majority value in the collection, or if no item represents the majority. /// /// of elements in the . /// An enumeration over which to find the majority element. /// Default value to return if no item represents the majority. /// The majority value in the collection. public static T Majority(this IEnumerable source, T defaultValue) { T majority = defaultValue; if ((object)source != null && source.Count() > 1) { Dictionary itemCounts = new Dictionary(); int count; // Count each number of items in the list foreach (T item in source) { if (itemCounts.TryGetValue(item, out count)) { count++; itemCounts[item] = count; } else itemCounts.Add(item, 1); } // Find the largest number of items in the list KeyValuePair maxItem = itemCounts.Max((a, b) => (a.Value < b.Value ? -1 : (a.Value > b.Value ? 1 : 0))); // If item with largest count has a plural majority, then it is the majority item if (maxItem.Value > 1) majority = maxItem.Key; } else majority = source.FirstOrDefault(); return majority; } /// /// Returns the minority value in the collection, or default type value if no item represents the minority. /// /// of elements in the . /// An enumeration over which to find the minority element. /// The minority value in the collection. public static T Minority(this IEnumerable source) { return source.Minority(default(T)); } /// /// Returns the minority value in the collection, or if no item represents the minority. /// /// of elements in the . /// An enumeration over which to find the minority element. /// Default value to return if no item represents the minority. /// The minority value in the collection. public static T Minority(this IEnumerable source, T defaultValue) { T minority = defaultValue; if ((object)source != null && source.Count() > 1) { Dictionary itemCounts = new Dictionary(); int count; // Count each number of items in the list foreach (T item in source) { if (itemCounts.TryGetValue(item, out count)) { count++; itemCounts[item] = count; } else itemCounts.Add(item, 1); } // Find the smallest number of items in the list KeyValuePair minItem = itemCounts.Min((a, b) => (a.Value < b.Value ? -1 : (a.Value > b.Value ? 1 : 0))); minority = minItem.Key; } else minority = source.FirstOrDefault(); return minority; } /// /// Adds the specified to the . /// /// of elements in the . /// The collection to which the are to be added. /// The elements to be added to the . public static void AddRange(this IList collection, IEnumerable items) { foreach (T item in items) { collection.Add(item); } } /// /// Updates starting at the with the specified . /// /// of elements in the . /// The collection whose elements are to be updated with the specified . /// The zero-based index in the at which elements are to be updated. /// The elements that will replace the elements starting at the . /// The specified is present in the . public static void UpdateRange(this IList collection, int index, IEnumerable items) { foreach (T item in items) { collection[index++] = item; } } /// /// Returns elements in the specified range from the . /// /// of elements in the . /// The collection from which elements are to be retrieved. /// The 0-based index position in the from which elements are to be retrieved. /// The number of elements to be retrieved from the starting at the . /// An object. public static IList GetRange(this IList collection, int index, int count) { List result = new List(); for (int i = index; i < index + count; i++) { result.Add(collection[i]); } return result; } /// /// Returns the index of the first element of the sequence that satisfies a condition or -1 if no such element is found. /// /// A to find an index in. /// A function to test each element for a condition. /// Index of the first element in that matches the specified ; otherwise, -1. /// The type of the elements of . public static int IndexOf(this IList source, Func predicate) { for (int index = 0; index < source.Count; index++) { if (predicate(source[index])) return index; } return -1; } /// /// Returns a copy of the . /// /// of the to be copied. /// The source whose elements are to be copied. /// The source array index from where the elements are to be copied. /// The number of elements to be copied starting from the startIndex. /// An of elements copied from the specified portion of the source . /// /// Returned will be extended as needed to make it the specified , but /// it will never be less than the source length - . /// /// /// is outside the range of valid indexes for the source -or- /// is less than 0. /// public static T[] Copy(this T[] source, int startIndex, int length) { if (startIndex < 0 || length < 0) throw new ArgumentOutOfRangeException(startIndex < 0 ? "startIndex" : "length", "Cannot be negative"); if (source.Length == 0) return new T[length]; if (startIndex >= source.Length) throw new ArgumentOutOfRangeException("startIndex", "Not a valid index into source buffer"); // Create a new array that will be returned with the specified array elements. T[] copyOfSource = new T[source.Length - startIndex < length ? source.Length - startIndex : length]; Array.Copy(source, startIndex, copyOfSource, 0, copyOfSource.Length); return copyOfSource; } /// Returns the smallest item from the enumeration. /// The generic type used. /// An enumeration that is compared against. /// A delegate that takes two generic types to compare, and returns an integer based on the comparison. /// Returns a generic type. public static TSource Min(this IEnumerable source, Func comparer) { TSource minItem = default(TSource); IEnumerator enumerator = source.GetEnumerator(); if (enumerator.MoveNext()) { minItem = enumerator.Current; while (enumerator.MoveNext()) { if (comparer(enumerator.Current, minItem) < 0) minItem = enumerator.Current; } } return minItem; } /// Returns the smallest item from the enumeration. /// The generic type used. /// An enumeration that is compared against. /// A comparer object. /// Returns a generic type. public static TSource Min(this IEnumerable source, IComparer comparer) { return source.Min(comparer.Compare); } /// Returns the largest item from the enumeration. /// The generic type used. /// An enumeration that is compared against. /// A delegate that takes two generic types to compare, and returns an integer based on the comparison. /// Returns a generic type. public static TSource Max(this IEnumerable source, Func comparer) { TSource maxItem = default(TSource); IEnumerator enumerator = source.GetEnumerator(); if (enumerator.MoveNext()) { maxItem = enumerator.Current; while (enumerator.MoveNext()) { if (comparer(enumerator.Current, maxItem) > 0) maxItem = enumerator.Current; } } return maxItem; } /// Returns the largest item from the enumeration. /// The generic type used. /// An enumeration that is compared against. /// A comparer object. /// Returns a generic type. public static TSource Max(this IEnumerable source, IComparer comparer) { return source.Max(comparer.Compare); } /// Converts an enumeration to a string, using the default delimeter ("|") that can later be /// converted back to a list using LoadDelimitedString. /// The generic type used. /// The source object to be converted into a delimited string. /// Returns a that is result of combining all elements in the list delimited by the '|' character. public static string ToDelimitedString(this IEnumerable source) { return source.ToDelimitedString('|'); } /// Converts an enumeration to a string that can later be converted back to a list using /// LoadDelimitedString. /// The generic type used. /// The source object to be converted into a delimited string. /// The delimiting character used. /// Returns a that is result of combining all elements in the list delimited by . public static string ToDelimitedString(this IEnumerable source, char delimiter) { return ToDelimitedString(source, delimiter); } /// Converts an enumeration to a string that can later be converted back to a list using /// LoadDelimitedString. /// The generic type used. /// The source object to be converted into a delimited string. /// The delimiting used. /// Returns a that is result of combining all elements in the list delimited by . public static string ToDelimitedString(this IEnumerable source, string delimiter) { return ToDelimitedString(source, delimiter); } /// Converts an enumeration to a string that can later be converted back to a list using /// LoadDelimitedString. /// The generic enumeration type used. /// The generic delimiter type used. /// The source object to be converted into a delimited string. /// The delimeter of type TDelimiter used. /// Returns a that is result of combining all elements in the list delimited by . private static string ToDelimitedString(IEnumerable source, TDelimiter delimiter) { if (Common.IsReference(delimiter) && (object)delimiter == null) throw new ArgumentNullException("delimiter", "delimiter cannot be null"); StringBuilder delimetedString = new StringBuilder(); foreach (TSource item in source) { if (delimetedString.Length > 0) delimetedString.Append(delimiter); delimetedString.Append(item); } return delimetedString.ToString(); } /// Appends items parsed from delimited string, created with ToDelimitedString, using the default /// delimeter ("|") into the given list. /// Items that are converted are added to list. The list is not cleared in advance. /// The generic type used. /// The list we are adding items to. /// The delimited string to parse for items. /// Delegate that takes one parameter and converts from string to type TSource. public static void LoadDelimitedString(this IList destination, string delimitedString, Func convertFromString) { destination.LoadDelimitedString(delimitedString, '|', convertFromString); } /// Appends items parsed from delimited string, created with ToDelimitedString, into the given list. /// Items that are converted are added to list. The list is not cleared in advance. /// The generic type used. /// The list we are adding items to. /// The delimited string to parse for items. /// The value to look for in the as the delimiter. /// Delegate that takes one parameter and converts from string to type TSource. public static void LoadDelimitedString(this IList destination, string delimitedString, char delimeter, Func convertFromString) { if ((object)delimitedString == null) throw new ArgumentNullException("delimitedString", "delimitedString cannot be null"); if (destination.IsReadOnly) throw new ArgumentException("Cannot add items to a read only list"); foreach (string item in delimitedString.Split(delimeter)) { destination.Add(convertFromString(item.Trim())); } } /// Appends items parsed from delimited string, created with ToDelimitedString, into the given list. /// Items that are converted are added to list. The list is not cleared in advance. /// The generic type used. /// The list we are adding items to. /// The delimited string to parse for items. /// An array of delimiters to look for in the as the delimiter. /// Delegate that takes a and converts to type TSource. public static void LoadDelimitedString(this IList destination, string delimitedString, string[] delimiters, Func convertFromString) { if ((object)delimiters == null) throw new ArgumentNullException("delimiters", "delimiters cannot be null"); if ((object)delimitedString == null) throw new ArgumentNullException("delimitedString", "delimitedString cannot be null"); if (destination.IsReadOnly) throw new ArgumentException("Cannot add items to a read only list"); foreach (string item in delimitedString.Split(delimiters, StringSplitOptions.None)) { destination.Add(convertFromString(item.Trim())); } } /// /// Rearranges all the elements in the list into a random order. /// /// The generic type of the list. /// The input list of generic types to scramble. /// This function uses a cryptographically strong random number generator to perform the scramble. public static void Scramble(this IList source) { if (source.IsReadOnly) throw new ArgumentException("Cannot modify items in a read only list"); int x, y; TSource currentItem; // Mixes up the data in random order. for (x = 0; x < source.Count; x++) { // Calls random function from GSF namespace. y = Random.Int32Between(0, source.Count - 1); if (x != y) { // Swaps items currentItem = source[x]; source[x] = source[y]; source[y] = currentItem; } } } /// /// Rearranges all the elements in the list into a repeatable pseudo-random order. /// /// The input list of generic types to scramble. /// A number used to calculate a starting value for the pseudo-random number sequence. /// The generic type of the list. /// This function uses the generator to perform the scramble using a sequence that is repeatable. public static void Scramble(this IList source, int seed) { if (source.IsReadOnly) throw new ArgumentException("Cannot modify items in a read only list"); System.Random random = new System.Random(seed); int x, y, count = source.Count; TSource currentItem; // Mixes up the data in random order. for (x = 0; x < count; x++) { // Calls random function from System namespace. y = random.Next(count); if (x != y) { // Swaps items currentItem = source[x]; source[x] = source[y]; source[y] = currentItem; } } } /// /// Rearranges all the elements in the list previously scrambled with back into their original order. /// /// The input list of generic types to unscramble. /// The same number used in call to scramble original list. /// The generic type of the list. /// This function uses the generator to perform the unscramble using a sequence that is repeatable. public static void Unscramble(this IList source, int seed) { if (source.IsReadOnly) throw new ArgumentException("Cannot modify items in a read only list"); System.Random random = new System.Random(seed); List sequence = new List(); int x, y, count = source.Count; TSource currentItem; // Generate original scramble sequence. for (x = 0; x < count; x++) { // Calls random function from System namespace. sequence.Add(random.Next(count)); } // Unmix the data order (traverse same sequence in reverse order). for (x = count - 1; x >= 0; x--) { y = sequence[x]; if (x != y) { // Swaps items currentItem = source[x]; source[x] = source[y]; source[y] = currentItem; } } } /// Compares two arrays. /// The first type array to compare to. /// The second type array to compare against. /// An which returns 0 if they are equal, 1 if is larger, or -1 if is larger. /// The generic type of the list. public static int CompareTo(this TSource[] array1, TSource[] array2) { return CompareTo(array1, array2, Comparer.Default); } /// Compares two arrays. /// The first to compare to. /// The second to compare against. /// An interface that exposes a method to compare the two arrays. /// An which returns 0 if they are equal, 1 if is larger, or -1 if is larger. /// This is a default comparer to make arrays comparable. public static int CompareTo(this Array array1, Array array2, IComparer comparer) { if ((object)comparer == null) throw new ArgumentNullException("comparer"); if ((object)array1 == null && (object)array2 == null) { return 0; } else if ((object)array1 == null) { return -1; } else if ((object)array2 == null) { return 1; } else { if (array1.Rank == 1 && array2.Rank == 1) { if (array1.Length == array2.Length) { int comparison = 0; for (int x = 0; x < array1.Length; x++) { comparison = comparer.Compare(array1.GetValue(x), array2.GetValue(x)); if (comparison != 0) break; } return comparison; } else { // For arrays that do not have the same number of elements, the array with most elements // is assumed to be larger. return array1.Length.CompareTo(array2.Length); } } else { throw new ArgumentException("Cannot compare multidimensional arrays"); } } } } }