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);
}
}