using System.Collections.Concurrent; using System.Globalization; namespace MeloongCore.Extensions; public static class EnumerableExtensions { #region Distinct /// /// 通过比较器进行去重。 /// 该方法的效率较低,建议仅在小型列表上使用,或换用 DistinctBy。 /// public static IEnumerable Distinct(this IList list, Func comparer) { for (int i = 0; i < list.Count; i++) { if (!list.Skip(i + 1).Any(item => comparer(list[i], item))) yield return list[i]; } } /// /// 按对象的指定值去重。 /// public static IEnumerable DistinctBy(this IEnumerable source, Func selector) { var seen = new HashSet(); foreach (var element in source) { if (seen.Add(selector(element))) yield return element; } } /// /// 按对象的指定值去重。 /// public static ParallelQuery DistinctBy(this ParallelQuery source, Func selector) { var seen = new ConcurrentDictionary(); return source.Where(element => seen.TryAdd(selector(element), true)); } #endregion #region MinBy / MaxBy /// /// 选择所有最大值对应的对象。 /// 若没有元素则返回空列表。 /// public static List MaxByAll(this IEnumerable source, Func selector) where TValue : IComparable { var results = new List(); using var enumerator = source.GetEnumerator(); if (!enumerator.MoveNext()) return results; TKey maxItem = enumerator.Current; TValue maxValue = selector(maxItem); results.Add(maxItem); while (enumerator.MoveNext()) { TKey currentItem = enumerator.Current; TValue currentValue = selector(currentItem); int comparisonResult = currentValue.CompareTo(maxValue); if (comparisonResult > 0) { maxValue = currentValue; results.Clear(); results.Add(currentItem); } else if (comparisonResult == 0) { results.Add(currentItem); } } return results; } /// /// 选择所有最小值对应的对象。 /// 若没有元素则返回空列表。 /// public static List MinByAll(this IEnumerable source, Func selector) where TValue : IComparable { var results = new List(); using var enumerator = source.GetEnumerator(); if (!enumerator.MoveNext()) return results; TKey minItem = enumerator.Current; TValue minValue = selector(minItem); results.Add(minItem); while (enumerator.MoveNext()) { TKey currentItem = enumerator.Current; TValue currentValue = selector(currentItem); int comparisonResult = currentValue.CompareTo(minValue); if (comparisonResult < 0) { minValue = currentValue; results.Clear(); results.Add(currentItem); } else if (comparisonResult == 0) { results.Add(currentItem); } } return results; } /// /// 选择最大值对应的对象。 /// 若没有元素则返回 null。 /// public static TKey? MaxBy(this IEnumerable source, Func selector) where TValue : IComparable { using var enumerator = source.GetEnumerator(); if (!enumerator.MoveNext()) return default; TKey maxItem = enumerator.Current; TValue maxValue = selector(maxItem); while (enumerator.MoveNext()) { TValue value = selector(enumerator.Current); if (value.CompareTo(maxValue) <= 0) continue; maxItem = enumerator.Current; maxValue = value; } return maxItem; } /// /// 选择最小值对应的对象。 /// 若没有元素则返回 null。 /// public static TKey? MinBy(this IEnumerable List, Func Selector) where TValue : IComparable { using var enumerator = List.GetEnumerator(); if (!enumerator.MoveNext()) { return default; } TKey minItem = enumerator.Current; TValue minValue = Selector(minItem); while (enumerator.MoveNext()) { TValue value = Selector(enumerator.Current); if (value.CompareTo(minValue) >= 0) { continue; } minItem = enumerator.Current; minValue = value; } return minItem; } #endregion #region Join /// /// 用指定的分割符将集合连接为字符串。 /// public static string Join(this IEnumerable list, char split) { var builder = new StringBuilder(); bool isFirst = true; foreach (var element in list) { if (isFirst) isFirst = false; else builder.Append(split); if (element is not null) builder.Append(element.ToString()); } return builder.ToString(); } /// /// 用指定的分割符将集合连接为字符串。 /// public static string Join(this IEnumerable list, string split) { if (split.IsSingle()) return list.Join(split[0]); var builder = new StringBuilder(); bool isFirst = true; foreach (var element in list) { if (isFirst) isFirst = false; else builder.Append(split); if (element is not null) builder.Append(element.ToString()); } return builder.ToString(); } #endregion #region Dictionary /// /// 从 中移除具有指定键的元素。 /// 返回是否确实移除了元素。 /// public static bool Remove(this ConcurrentDictionary dict, TKey key) => dict.TryRemove(key, out _); /// /// 将二维数组转换为字典。 /// public static Dictionary ToDictionary(this T[,] arr) where T : notnull { Dictionary result = []; if (arr.Length == 0) return result; if (arr.GetLength(1) != 2) throw new ArgumentException("数组必须为两列,第一列为 Key,第二列为 Value。"); for (int i = 0; i < arr.GetLength(0); i++) result[arr[i, 0]] = arr[i, 1]; return result; } /// /// 尝试从字典中获取某项,如果该项不存在,则返回默认值。 /// public static TValue GetOrDefault(this IDictionary dict, TKey key, TValue defaultValue = default!) => dict.TryGetValue(key, out var result) ? result : defaultValue; /// /// 将值添加到字典的对应项的列表中。 /// public static void AddIntoValueCollection(this IDictionary> dict, TKey key, TValue value) { if (dict.TryGetValue(key, out var collection)) { collection.Add(value); } else { dict.Add(key, [value]); } } #endregion /// /// 将 枚举值解包为它包含的单一 Flag 成员的集合。 /// 建议仅对没有极端值的正数枚举使用。 /// /// /// 会跳过:0、非 2 的幂的值、不在枚举值中的数、组合值、对应同一个枚举项的多个别名。 /// public static IEnumerable Flags(this T value) where T : struct, Enum { foreach (var element in Enum.GetValues(typeof(T)).Cast().Distinct()) { var number = Convert.ToInt64(element, CultureInfo.InvariantCulture); if (number == 0 || (number & (number - 1)) != 0) continue; // 跳过 0 和非 2 的幂的值 if (value.HasFlag(element)) yield return element; } } /// /// 判断集合是否有且仅有一个元素。 /// 在输入 null 时返回 false。 /// public static bool IsSingle([AllowNull] this IEnumerable source) { if (source is null) return false; if (source is IList list) return list.Count == 1; using var enumerator = source.GetEnumerator(); if (!enumerator.MoveNext()) return false; if (enumerator.MoveNext()) return false; return true; } /// /// 对集合的每个元素执行指定操作。 /// public static IEnumerable ForAll(this IEnumerable source, Action action) { foreach (T item in source) action(item); return source; } }