using System; using System.Collections.Generic; using System.Linq; using Hazel; // ReSharper disable ConvertIfStatementToReturnStatement // ReSharper disable ForCanBeConvertedToForeach namespace EHR; public static class CollectionExtensions { /// The to search /// The type of the keys in the /// The type of the values in the extension(Dictionary dictionary) { /// /// Returns the key of a dictionary by its value /// /// The used to search for the corresponding key /// /// The key of the that corresponds to the given , or the /// default value of if the is not found in the /// /// public TKey GetKeyByValue(TValue value) { foreach (KeyValuePair pair in dictionary) { if (pair.Value.Equals(value)) return pair.Key; } return default(TKey); } // foreach will only throw System.InvalidOperationException: collection was modified; enumeration operation may not execute. // if the number of elements inside the dictionary changes. // If Count stays the same for the entire iteration, it can be safely enumerated without ToList or ToArray. // Changing values does NOT modify the collection! /// /// Sets the value for all existing keys in a dictionary to a specific value /// /// public void SetAllValues(TValue value) { foreach (TKey key in dictionary.Keys) dictionary[key] = value; } /// /// Adjusts the value for all existing keys in a dictionary /// /// The function to adjust the values with public void AdjustAllValues(Func adjust) { foreach (TKey key in dictionary.Keys) dictionary[key] = adjust(dictionary[key]); } /// /// Adds a range of elements to a dictionary /// /// The dictionary containing the elements to add /// /// Whether to override existing keys in the with the /// same keys in the dictionary. If true, the same keys in the /// will be overwritten with the values from the dictionary. /// If false, the same keys in the will be kept and the values from the /// dictionary will be ignored /// /// The with the elements from the dictionary added public Dictionary AddRange(Dictionary other, bool overrideExistingKeys = true) { foreach ((TKey key, TValue value) in other) { if (overrideExistingKeys || !dictionary.ContainsKey(key)) dictionary[key] = value; } return dictionary; } } /// The collection /// The type of the collection extension(IEnumerable collection) { /// /// Returns a random element from a collection /// /// /// A random element from the collection, or the default value of if the collection is /// empty /// public T RandomElement() { if (collection is IReadOnlyList list) return list.RandomElement(); return collection.ToList().RandomElement(); } /// /// Combines multiple collections into a single collection /// /// The other collections to add to /// /// A collection containing all elements of and all /// /// public IEnumerable CombineWith(params IEnumerable[] collections) { return collection.Concat(collections.Flatten()); } /// /// Executes an action for each element in a collection /// /// The action to execute for each element [Annotations.CollectionAccess(Annotations.CollectionAccessType.Read)] public void Do([Annotations.InstantHandle] Action action) { if (collection is List list) { for (var i = 0; i < list.Count; i++) action(list[i]); return; } foreach (T element in collection) action(element); } /// /// Executes an action for each element in a collection if the predicate is true /// /// Whether to use a fast loop or linq /// The predicate to check for each element /// The action to execute for each element that satisfies the predicate [Annotations.CollectionAccess(Annotations.CollectionAccessType.Read)] public void DoIf(Func predicate, [Annotations.InstantHandle] Action action, bool fast = true) { if (fast) { if (collection is List list) { for (var i = 0; i < list.Count; i++) { T element = list[i]; if (predicate(element)) action(element); } return; } foreach (T element in collection) { if (predicate(element)) action(element); } return; } collection.Where(predicate).ToArray().Do(action); } /// /// Splits a collection into two collections based on a predicate /// /// The predicate to split the collection by /// /// A tuple containing two collections: one with elements that satisfy the predicate, and one with elements that /// do not /// public (List TrueList, List FalseList) Split(Func predicate) { var list1 = new List(); var list2 = new List(); foreach (T element in collection) { if (predicate(element)) list1.Add(element); else list2.Add(element); } return (list1, list2); } /// /// Splits a collection into two integers based on a predicate /// /// The predicate to split the collection by /// /// A tuple containing two integers: one that is the number of elements that satisfy the predicate, and one that is the number of elements that /// do not /// public (int TrueCount, int FalseCount) SplitCount(Func predicate) { var int1 = 0; var int2 = 0; foreach (T element in collection) { if (predicate(element)) int1++; else int2++; } return (int1, int2); } /// /// Determines whether a collection contains any elements that satisfy a predicate and returns the first element that /// satisfies the predicate /// /// The predicate to check for each element /// /// The first element that satisfies the predicate, or the default value of /// if no elements satisfy the predicate /// /// true if the collection contains any elements that satisfy the predicate, false otherwise [Annotations.CollectionAccess(Annotations.CollectionAccessType.Read)] public bool FindFirst([Annotations.InstantHandle] Func predicate, out T element) { if (collection is List list) { for (var i = 0; i < list.Count; i++) { T item = list[i]; if (predicate(item)) { element = item; return true; } } element = default(T); return false; } foreach (T item in collection) { if (predicate(item)) { element = item; return true; } } element = default(T); return false; } /// /// Takes the specified number of random elements from the collection, and adds them to a new List. /// /// The number of random elements to pick. /// A new List with the specified number of random elements from the original collection. public List TakeRandom(int count) { if (collection == null || count <= 0) return []; var reservoir = new List(count); int i = 0; foreach (var item in collection) { if (i < count) { reservoir.Add(item); } else { int j = IRandom.Instance.Next(i + 1); if (j < count) reservoir[j] = item; } i++; } return reservoir; } /// /// Partitions a collection into a specified number of parts /// /// The number of parts to partition the collection into /// A collection of collections, each containing a part of the original collection public IEnumerable> Partition(int parts) { List list = collection.ToList(); int length = list.Count; if (parts <= 0 || length == 0) yield break; if (parts > length) parts = length; int size = length / parts; int remainder = length % parts; var index = 0; for (var i = 0; i < parts; i++) { int partSize = size + (i < remainder ? 1 : 0); yield return list.Skip(index).Take(partSize); index += partSize; } } /// /// Removes an element from a collection /// /// The element to remove /// /// A collection containing all elements of except for /// public IEnumerable Without(T element) { return collection.Where(x => !x.Equals(element)); } /// /// Shuffles all elements in a collection randomly /// /// A new, shuffled collection as a public List Shuffle() { if (collection is not List list) list = collection.ToList(); int n = list.Count; var r = IRandom.Instance; while (n > 1) { n--; int k = r.Next(n + 1); (list[n], list[k]) = (list[k], list[n]); } return list; } } /// The collection /// The type of the collection extension(IReadOnlyList collection) { /// /// Partitions a list into a specified number of parts /// /// The number of parts to partition the list into /// A list of lists, each containing a part of the original list public IEnumerable> Partition(int parts) { int length = collection.Count; if (parts <= 0 || length == 0) yield break; if (parts > length) parts = length; int size = length / parts; int remainder = length % parts; var index = 0; for (var i = 0; i < parts; i++) { int partSize = size + (i < remainder ? 1 : 0); yield return collection.Skip(index).Take(partSize); index += partSize; } } /// /// Takes the specified number of random elements from the collection. /// /// The number of random elements to pick. /// A new collection with the specified number of random elements from the original collection. public IEnumerable TakeRandom(int count) { if (collection == null || collection.Count == 0 || count <= 0) yield break; int n = collection.Count; // If asking for >= all elements, just return everything if (count >= n) { for (int i = 0; i < n; i++) yield return collection[i]; yield break; } // If k is small relative to n → pick unique indices via HashSet // If k is large → invert selection (pick excluded indices instead) if (count <= n / 2) { var chosen = new HashSet(); while (chosen.Count < count) { int idx = IRandom.Instance.Next(n); if (chosen.Add(idx)) yield return collection[idx]; } } else { // More efficient to exclude (n - count) items int excludeCount = n - count; var excluded = new HashSet(); while (excluded.Count < excludeCount) excluded.Add(IRandom.Instance.Next(n)); for (int i = 0; i < n; i++) { if (!excluded.Contains(i)) yield return collection[i]; } } } /// /// Returns a random element from a collection /// /// /// A random element from the collection, or the default value of if the collection is /// empty /// public T RandomElement() { if (collection.Count == 0) return default(T); return collection[IRandom.Instance.Next(collection.Count)]; } } /// /// Flattens a collection of collections into a single collection /// /// The collection of collections to flatten /// The type of the elements in the collections /// A single collection containing all elements of the collections in public static IEnumerable Flatten(this IEnumerable> collection) { return collection.SelectMany(x => x); } public static void NotifyPlayers(this IEnumerable players, string text, float time = 6f, bool overrideAll = false, bool log = true, bool setName = true) { var sender = CustomRpcSender.Create("NotifyPlayers", SendOption.Reliable); var hasValue = false; foreach (PlayerControl player in players) { hasValue |= sender.Notify(player, text, time, overrideAll, log, setName); // RpcSetName will handle oversized packets } sender.SendMessage(dispose: !hasValue); } public static void NotifyPlayers(this List players, string text, float time = 6f, bool overrideAll = false, bool log = true, bool setName = true) { var sender = CustomRpcSender.Create("NotifyPlayers", SendOption.Reliable); var hasValue = false; for (int index = 0; index < players.Count; index++) { hasValue |= sender.Notify(players[index], text, time, overrideAll, log, setName); // RpcSetName will handle oversized packets } sender.SendMessage(dispose: !hasValue); } #region ToValidPlayers /// /// Converts a collection of player IDs to a collection of instances /// /// /// public static IEnumerable ToValidPlayers(this IEnumerable playerIds) { return playerIds.Select(Utils.GetPlayer).Where(x => x); } #endregion #region Without /// /// Removes an element from a collection /// /// The collection to remove the element from /// The element to remove /// /// A collection containing all elements of except for /// public static IEnumerable Without(this IEnumerable collection, PlayerControl element) { return collection.Where(x => x.PlayerId != element.PlayerId); } /// /// Removes an element from a collection /// /// The collection to remove the element from /// The element to remove /// /// A collection containing all elements of except for /// public static IEnumerable Without(this IEnumerable collection, PlainShipRoom element) { return collection.Where(x => x != element); } #endregion #region Shuffle /// /// Shuffles all elements in a collection randomly /// /// The collection to be shuffled /// The type of the collection /// The same collection with its elements shuffled public static List Shuffle(this List collection) { int n = collection.Count; var r = IRandom.Instance; while (n > 1) { n--; int k = r.Next(n + 1); (collection[n], collection[k]) = (collection[k], collection[n]); } return collection; } /// /// Shuffles all elements in an array randomly /// /// The array to be shuffled /// The type of the array /// The same array with its elements shuffled public static T[] Shuffle(this T[] collection) { int n = collection.Length; var r = IRandom.Instance; while (n > 1) { n--; int k = r.Next(n + 1); (collection[n], collection[k]) = (collection[k], collection[n]); } return collection; } /// /// Shuffles all elements in an IList randomly /// /// The IList to be shuffled /// The type of the IList /// The same IList with its elements shuffled public static IList Shuffle(this IList collection) { int n = collection.Count; var r = IRandom.Instance; while (n > 1) { n--; int k = r.Next(n + 1); (collection[n], collection[k]) = (collection[k], collection[n]); } return collection; } #endregion } public static class Loop { public static void Times(int count, Action action) { for (var i = 0; i < count; i++) action(i); } }