// -------------------------------------------------------------------------------------------------------------------- // // Copyright (c) VRMADA, All rights reserved. // // -------------------------------------------------------------------------------------------------------------------- using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using UltimateXR.Core; using UltimateXR.Core.Settings; using UnityEngine; using Random = UnityEngine.Random; namespace UltimateXR.Extensions.System.Collections { /// /// extensions. /// public static class EnumerableExt { #region Public Methods /// /// Compares two IEnumerable for equality, considering the order of elements. /// For dictionaries, compares key-value pairs regardless of their order. /// /// The first collection to compare /// The second collection to compare /// True if the collections are equal; otherwise, false public static bool ContentEqual(IEnumerable enumerableA, IEnumerable enumerableB) { return ContentEqual(enumerableA, enumerableB, (a, b) => a.ValuesEqual(b)); } /// /// Compares two IEnumerable for equality, considering the order of elements. /// For dictionaries, compares key-value pairs regardless of their order. /// Values are compared using a floating point precision threshold used by /// . /// /// The first collection to compare /// The second collection to compare /// /// The precision threshold for float comparisons in types supported by /// . /// /// True if the collections are equal; otherwise, false public static bool ContentEqual(IEnumerable enumerableA, IEnumerable enumerableB, float precisionThreshold) { return ContentEqual(enumerableA, enumerableB, (a, b) => a.ValuesEqual(b, precisionThreshold)); } /// /// Returns a random element from the collection. /// /// Collection to get the random element from /// Element type /// Random element from the collection /// /// Uses Unity's random number generator (). /// public static TIn RandomElement(this IEnumerable list) { return list.Any() ? list.ElementAt(Random.Range(0, list.Count())) : default(TIn); } /// /// Applies an on all elements in a collection. /// /// Elements to apply the action on /// Action to apply /// Element type /// Any of the parameters was null public static void ForEach(this IEnumerable list, Action action) { if (list == null) { throw new ArgumentException("Argument cannot be null.", nameof(list)); } if (action == null) { throw new ArgumentException("Argument cannot be null.", nameof(action)); } foreach (TIn value in list) { action(value); } } /// /// Asynchronously applies a function on all elements in a collection. /// /// Elements to apply the function on /// Function to apply /// Element type /// An awaitable task wrapping the Task.WhenAll applying the function on all elements in a collection public static Task ForEachAsync(this IEnumerable list, Func function) { return Task.WhenAll(list.Select(function)); } /// /// Asynchronously applies a function to all elements in a collection. /// /// Elements to apply the function on /// Function to apply /// Element type /// Function return type /// An awaitable task wrapping the Task.WhenAll applying the function on all elements in a collection public static Task ForEachAsync(this IEnumerable list, Func> function) { return Task.WhenAll(list.Select(function)); } /// /// Asynchronously applies an action on all elements in a collection. /// /// Elements to apply the action on /// Action to apply /// Element type /// Task wrapping the Task.WhenAll applying the action on all elements in a collection public static Task ForEachThreaded(this IEnumerable list, Action action) { void OnFaulted(Task runTask, int itemIndex) { if (UxrGlobalSettings.Instance.LogLevelCore >= UxrLogLevel.Warnings) { Debug.LogWarning($"{UxrConstants.CoreModule} ForEachThreaded::Item[{itemIndex}] faulted (see reason below):"); } if (UxrGlobalSettings.Instance.LogLevelCore >= UxrLogLevel.Errors) { Debug.LogException(runTask.Exception); } } return Task.WhenAll(list.Select((item, index) => Task.Run(() => action(item)).ContinueWith(runTask => OnFaulted(runTask, index), TaskContinuationOptions.OnlyOnFaulted))); } /// /// Asynchronously applies a function on all elements in a collection. /// /// Elements to apply the function on /// Function to apply /// Element type /// Function return type /// Task wrapping the Task.WhenAll applying the function on all elements in a collection public static Task ForEachThreaded(this IEnumerable list, Func function) { TOut OnFaulted(Task t, int itemIndex) { if (UxrGlobalSettings.Instance.LogLevelCore >= UxrLogLevel.Warnings) { Debug.LogWarning($"{UxrConstants.CoreModule} ForEachThreaded::Item[{itemIndex}] faulted (see reason below):"); } if (UxrGlobalSettings.Instance.LogLevelCore >= UxrLogLevel.Errors) { Debug.LogException(t.Exception); } return default; } return Task.WhenAll(list.Select((item, index) => Task.Run(() => function(item)).ContinueWith(t => OnFaulted(t, index), TaskContinuationOptions.OnlyOnFaulted))); } /// /// Returns the maximal element of the given sequence, based on the given projection. /// /// /// This overload uses the default comparer for the projected type. This operator uses deferred execution. The results /// are evaluated and cached on first use to returned sequence. /// /// Type of the source sequence /// Type of the projected element /// Source sequence /// Selector to use to pick the results to compare /// The maximal element, according to the projection. /// or is null public static TSource MaxBy(this IEnumerable source, Func selector) { return source.MaxBy(selector, null!); } /// /// Returns the maximal element of the given sequence, based on the given projection and the specified comparer for /// projected values. /// /// /// This operator uses deferred execution. The results are evaluated and cached on first use to returned sequence. /// /// Type of the source sequence /// Type of the projected element /// Source sequence /// Selector to use to pick the results to compare /// Comparer to use to compare projected values /// The maximal element, according to the projection. /// /// , or is null /// /// A delegate callback throws an exception. public static TSource MaxBy(this IEnumerable source, Func selector, IComparer comparer) { source.ThrowIfNull(nameof(source)); selector.ThrowIfNull(nameof(selector)); TSource result = default; TKey keyMax = default; comparer ??= Comparer.Default; bool isFirst = true; foreach (TSource s in source) { TKey key = selector(s); if (isFirst) { result = s; keyMax = key; isFirst = false; } else if (comparer.Compare(key, keyMax) > 0) { result = s; keyMax = key; } } return result; } /// /// Splits a list of strings using CamelCase. /// /// List of strings /// List of strings with added spacing public static IEnumerable SplitCamelCase(this IEnumerable strings) { foreach (string element in strings) { yield return element.SplitCamelCase(); } } #endregion #region Private Methods /// /// Compares two IEnumerable for equality, considering the order of elements. /// For dictionaries, compares key-value pairs regardless of their order. /// /// The first collection to compare /// The second collection to compare /// Comparison function /// True if the collections are equal; otherwise, false private static bool ContentEqual(IEnumerable enumerableA, IEnumerable enumerableB, Func comparer) { // If the collections are dictionaries, compare key-value pairs if (enumerableA is IDictionary dictionaryA && enumerableB is IDictionary dictionaryB) { // Ensure both dictionaries have the same number of elements if (dictionaryA.Count != dictionaryB.Count) { return false; } // Compare key-value pairs regardless of order foreach (DictionaryEntry entryA in dictionaryA) { if (!dictionaryB.Contains(entryA.Key) || !comparer(entryA.Value, dictionaryB[entryA.Key])) { return false; } } return true; } // If the collections are lists, do a quick test to check if they have different number of elements if (enumerableA is IList listA && enumerableB is IList listB) { if (listA.Count != listB.Count) { return false; } } // If the collections are HashSets, compare elements using SetEquals if (enumerableA is HashSet hashSetA && enumerableB is HashSet hashSetB) { return hashSetA.SetEquals(hashSetB); } // For non-dictionary, non-HashSet collections, compare elements IEnumerator enumeratorA = enumerableA.GetEnumerator(); IEnumerator enumeratorB = enumerableB.GetEnumerator(); while (enumeratorA.MoveNext()) { if (!enumeratorB.MoveNext() || !comparer(enumeratorA.Current, enumeratorB.Current)) { return false; } } // Ensure both collections have the same number of elements return !enumeratorB.MoveNext(); } #endregion } }