EnumerableExtensions.cs 9.1 KB

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