EnumerableExtensions.cs 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. using System.Collections.Concurrent;
  2. using System.Globalization;
  3. namespace MeloongCore.Extensions;
  4. public static class EnumerableExtensions {
  5. #region Distinct
  6. /// <summary>
  7. /// 通过比较器进行去重。
  8. /// 该方法的效率较低,建议仅在小型列表上使用,或换用 DistinctBy。
  9. /// </summary>
  10. public static IEnumerable<T> Distinct<T>(this IList<T> list, Func<T, T, bool> comparer) {
  11. for (int i = list.Count - 1; i >= 0; i--) { // 反向遍历以保留最早出现的重复项
  12. if (!list.Skip(i + 1).Any(item => comparer(list[i], item))) yield return list[i];
  13. }
  14. }
  15. /// <summary>
  16. /// 按对象的指定值去重。
  17. /// </summary>
  18. public static IEnumerable<T> DistinctBy<T, TKey>(this IEnumerable<T> source, Func<T, TKey> selector) {
  19. var seen = new HashSet<TKey>();
  20. foreach (var element in source) {
  21. if (seen.Add(selector(element))) yield return element;
  22. }
  23. }
  24. /// <summary>
  25. /// 按对象的指定值去重。
  26. /// </summary>
  27. public static ParallelQuery<T> DistinctBy<T, TKey>(this ParallelQuery<T> source, Func<T, TKey> selector) {
  28. var seen = new ConcurrentDictionary<TKey, bool>();
  29. return source.Where(element => seen.TryAdd(selector(element), true));
  30. }
  31. #endregion
  32. #region MinBy / MaxBy
  33. /// <summary>
  34. /// 选择所有最大值对应的对象。
  35. /// 若没有元素则返回空列表。
  36. /// </summary>
  37. public static List<TKey> MaxByAll<TKey, TValue>(this IEnumerable<TKey> source, Func<TKey, TValue> selector) where TValue : IComparable<TValue> {
  38. var results = new List<TKey>();
  39. using var enumerator = source.GetEnumerator();
  40. if (!enumerator.MoveNext()) return results;
  41. TKey maxItem = enumerator.Current;
  42. TValue maxValue = selector(maxItem);
  43. results.Add(maxItem);
  44. while (enumerator.MoveNext()) {
  45. TKey currentItem = enumerator.Current;
  46. TValue currentValue = selector(currentItem);
  47. int comparisonResult = currentValue.CompareTo(maxValue);
  48. if (comparisonResult > 0) {
  49. maxValue = currentValue;
  50. results.Clear();
  51. results.Add(currentItem);
  52. } else if (comparisonResult == 0) {
  53. results.Add(currentItem);
  54. }
  55. }
  56. return results;
  57. }
  58. /// <summary>
  59. /// 选择所有最小值对应的对象。
  60. /// 若没有元素则返回空列表。
  61. /// </summary>
  62. public static List<TKey> MinByAll<TKey, TValue>(this IEnumerable<TKey> source, Func<TKey, TValue> selector) where TValue : IComparable<TValue> {
  63. var results = new List<TKey>();
  64. using var enumerator = source.GetEnumerator();
  65. if (!enumerator.MoveNext()) return results;
  66. TKey minItem = enumerator.Current;
  67. TValue minValue = selector(minItem);
  68. results.Add(minItem);
  69. while (enumerator.MoveNext()) {
  70. TKey currentItem = enumerator.Current;
  71. TValue currentValue = selector(currentItem);
  72. int comparisonResult = currentValue.CompareTo(minValue);
  73. if (comparisonResult < 0) {
  74. minValue = currentValue;
  75. results.Clear();
  76. results.Add(currentItem);
  77. } else if (comparisonResult == 0) {
  78. results.Add(currentItem);
  79. }
  80. }
  81. return results;
  82. }
  83. /// <summary>
  84. /// 选择最大值对应的对象。
  85. /// 若没有元素则返回 null。
  86. /// </summary>
  87. public static TKey? MaxBy<TKey, TValue>(this IEnumerable<TKey> source, Func<TKey, TValue> selector) where TValue : IComparable<TValue> {
  88. using var enumerator = source.GetEnumerator();
  89. if (!enumerator.MoveNext()) return default;
  90. TKey maxItem = enumerator.Current;
  91. TValue maxValue = selector(maxItem);
  92. while (enumerator.MoveNext()) {
  93. TValue value = selector(enumerator.Current);
  94. if (value.CompareTo(maxValue) <= 0) continue;
  95. maxItem = enumerator.Current;
  96. maxValue = value;
  97. }
  98. return maxItem;
  99. }
  100. /// <summary>
  101. /// 选择最小值对应的对象。
  102. /// 若没有元素则返回 null。
  103. /// </summary>
  104. public static TKey? MinBy<TKey, TValue>(this IEnumerable<TKey> source, Func<TKey, TValue> selector) where TValue : IComparable<TValue> {
  105. using var enumerator = source.GetEnumerator();
  106. if (!enumerator.MoveNext()) { return default; }
  107. TKey minItem = enumerator.Current;
  108. TValue minValue = selector(minItem);
  109. while (enumerator.MoveNext()) {
  110. TValue value = selector(enumerator.Current);
  111. if (value.CompareTo(minValue) >= 0) { continue; }
  112. minItem = enumerator.Current;
  113. minValue = value;
  114. }
  115. return minItem;
  116. }
  117. #endregion
  118. #region Join
  119. /// <summary>
  120. /// 用指定的分割符将集合连接为字符串。
  121. /// </summary>
  122. public static string Join(this IEnumerable list, char split) {
  123. var builder = new StringBuilder();
  124. bool isFirst = true;
  125. foreach (var element in list) {
  126. if (isFirst)
  127. isFirst = false;
  128. else
  129. builder.Append(split);
  130. if (element is not null) builder.Append(element.ToString());
  131. }
  132. return builder.ToString();
  133. }
  134. /// <summary>
  135. /// 用指定的分割符将集合连接为字符串。
  136. /// </summary>
  137. public static string Join(this IEnumerable list, string split) {
  138. if (split.IsSingle()) return list.Join(split[0]);
  139. var builder = new StringBuilder();
  140. bool isFirst = true;
  141. foreach (var element in list) {
  142. if (isFirst)
  143. isFirst = false;
  144. else
  145. builder.Append(split);
  146. if (element is not null) builder.Append(element.ToString());
  147. }
  148. return builder.ToString();
  149. }
  150. #endregion
  151. #region Dictionary
  152. /// <summary>
  153. /// 从 <see cref="ConcurrentDictionary{TKey, TValue}"/> 中移除具有指定键的元素。
  154. /// 返回是否确实移除了元素。
  155. /// </summary>
  156. public static bool Remove<TKey, TValue>(this ConcurrentDictionary<TKey, TValue> dict, TKey key)
  157. => dict.TryRemove(key, out _);
  158. /// <summary>
  159. /// 将二维数组转换为字典。
  160. /// </summary>
  161. public static Dictionary<T, T> ToDictionary<T>(this T[,] arr) where T : notnull {
  162. Dictionary<T, T> result = [];
  163. if (arr.Length == 0) return result;
  164. if (arr.GetLength(1) != 2) throw new ArgumentException("数组必须为两列,第一列为 Key,第二列为 Value。");
  165. for (int i = 0; i < arr.GetLength(0); i++) result[arr[i, 0]] = arr[i, 1];
  166. return result;
  167. }
  168. /// <summary>
  169. /// 尝试从字典中获取某项,如果该项不存在,则返回默认值。
  170. /// </summary>
  171. public static TValue GetOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, TValue defaultValue = default!)
  172. => dict.TryGetValue(key, out var result) ? result : defaultValue;
  173. /// <summary>
  174. /// 将值添加到字典的对应项的列表中。
  175. /// </summary>
  176. public static void AddIntoValueCollection<TKey, TValue>(this IDictionary<TKey, List<TValue>> dict, TKey key, TValue value) {
  177. if (dict.TryGetValue(key, out var collection)) {
  178. collection.Add(value);
  179. } else {
  180. dict.Add(key, [value]);
  181. }
  182. }
  183. #endregion
  184. /// <summary>
  185. /// 将 <see cref="FlagsAttribute"/> 枚举值解包为它包含的单一 Flag 成员的集合。
  186. /// 建议仅对没有极端值的正数枚举使用。
  187. /// </summary>
  188. /// <remarks>
  189. /// 会跳过:0、非 2 的幂的值、不在枚举值中的数、组合值、对应同一个枚举项的多个别名。
  190. /// </remarks>
  191. public static IEnumerable<T> Flags<T>(this T value) where T : struct, Enum {
  192. foreach (var element in Enum.GetValues(typeof(T)).Cast<T>().Distinct()) {
  193. var number = Convert.ToInt64(element, CultureInfo.InvariantCulture);
  194. if (number == 0 || (number & (number - 1)) != 0) continue; // 跳过 0 和非 2 的幂的值
  195. if (value.HasFlag(element)) yield return element;
  196. }
  197. }
  198. /// <summary>
  199. /// 判断集合是否有且仅有一个元素。
  200. /// 在输入 null 时返回 false。
  201. /// </summary>
  202. public static bool IsSingle<T>([AllowNull] this IEnumerable<T> source) {
  203. if (source is null) return false;
  204. if (source is IList<T> list) return list.Count == 1;
  205. using var enumerator = source.GetEnumerator();
  206. if (!enumerator.MoveNext()) return false;
  207. if (enumerator.MoveNext()) return false;
  208. return true;
  209. }
  210. /// <summary>
  211. /// 对集合的每个元素执行指定操作。
  212. /// </summary>
  213. public static IEnumerable<T> ForAll<T>(this IEnumerable<T> source, Action<T> action) {
  214. foreach (T item in source) {
  215. action(item);
  216. yield return item;
  217. }
  218. }
  219. }