EnumerableExtensions.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. using System.Collections.Concurrent;
  2. namespace MeloongCore.Extensions;
  3. public static class EnumerableExtensions {
  4. #region 去重
  5. /// <summary>
  6. /// 通过比较器进行去重。
  7. /// 该方法的效率较低,建议仅在小型列表上使用,或换用 DistinctBy。
  8. /// </summary>
  9. public static IEnumerable<T> Distinct<T>(this IList<T> list, Func<T, T, bool> comparer) {
  10. var seen = new List<T>();
  11. foreach (var item in list) {
  12. if (!seen.Any(s => comparer(s, item))) {
  13. seen.Add(item);
  14. yield return item;
  15. }
  16. }
  17. }
  18. /// <summary>
  19. /// 按对象的指定值去重。
  20. /// </summary>
  21. public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector) {
  22. var seen = new HashSet<TKey>();
  23. foreach (var element in source) {
  24. if (seen.Add(selector(element))) yield return element;
  25. }
  26. }
  27. /// <summary>
  28. /// 按对象的指定值去重。
  29. /// </summary>
  30. public static ParallelQuery<TSource> DistinctBy<TSource, TKey>(this ParallelQuery<TSource> source, Func<TSource, TKey> selector) {
  31. var seen = new ConcurrentDictionary<TKey, bool>();
  32. return source.Where(element => seen.TryAdd(selector(element), true));
  33. }
  34. #endregion
  35. #region 最大 / 最小
  36. /// <summary>
  37. /// 选择所有最大值对应的对象。
  38. /// 若没有元素则返回空列表。
  39. /// </summary>
  40. public static List<TSource> MaxByAll<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector) where TKey : IComparable<TKey> {
  41. var results = new List<TSource>();
  42. using var enumerator = source.GetEnumerator();
  43. if (!enumerator.MoveNext()) return results;
  44. TSource maxItem = enumerator.Current;
  45. TKey maxValue = selector(maxItem);
  46. results.Add(maxItem);
  47. while (enumerator.MoveNext()) {
  48. TSource currentItem = enumerator.Current;
  49. TKey currentValue = selector(currentItem);
  50. int comparisonResult = currentValue.CompareTo(maxValue);
  51. if (comparisonResult > 0) {
  52. maxValue = currentValue;
  53. results.Clear();
  54. results.Add(currentItem);
  55. } else if (comparisonResult == 0) {
  56. results.Add(currentItem);
  57. }
  58. }
  59. return results;
  60. }
  61. /// <summary>
  62. /// 选择所有最大值对应的对象。
  63. /// 若没有元素则返回空列表。
  64. /// </summary>
  65. public static List<TSource> MaxByAll<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector, IComparer<TKey> comparer) {
  66. var results = new List<TSource>();
  67. using var enumerator = source.GetEnumerator();
  68. if (!enumerator.MoveNext()) return results;
  69. TSource maxItem = enumerator.Current;
  70. TKey maxValue = selector(maxItem);
  71. results.Add(maxItem);
  72. while (enumerator.MoveNext()) {
  73. TSource currentItem = enumerator.Current;
  74. TKey currentValue = selector(currentItem);
  75. int comparisonResult = comparer.Compare(currentValue, maxValue);
  76. if (comparisonResult > 0) {
  77. maxValue = currentValue;
  78. results.Clear();
  79. results.Add(currentItem);
  80. } else if (comparisonResult == 0) {
  81. results.Add(currentItem);
  82. }
  83. }
  84. return results;
  85. }
  86. /// <summary>
  87. /// 选择所有最小值对应的对象。
  88. /// 若没有元素则返回空列表。
  89. /// </summary>
  90. public static List<TSource> MinByAll<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector) where TKey : IComparable<TKey> {
  91. var results = new List<TSource>();
  92. using var enumerator = source.GetEnumerator();
  93. if (!enumerator.MoveNext()) return results;
  94. TSource minItem = enumerator.Current;
  95. TKey minValue = selector(minItem);
  96. results.Add(minItem);
  97. while (enumerator.MoveNext()) {
  98. TSource currentItem = enumerator.Current;
  99. TKey currentValue = selector(currentItem);
  100. int comparisonResult = currentValue.CompareTo(minValue);
  101. if (comparisonResult < 0) {
  102. minValue = currentValue;
  103. results.Clear();
  104. results.Add(currentItem);
  105. } else if (comparisonResult == 0) {
  106. results.Add(currentItem);
  107. }
  108. }
  109. return results;
  110. }
  111. /// <summary>
  112. /// 选择所有最小值对应的对象。
  113. /// 若没有元素则返回空列表。
  114. /// </summary>
  115. public static List<TSource> MinByAll<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector, IComparer<TKey> comparer) {
  116. var results = new List<TSource>();
  117. using var enumerator = source.GetEnumerator();
  118. if (!enumerator.MoveNext()) return results;
  119. TSource minItem = enumerator.Current;
  120. TKey minValue = selector(minItem);
  121. results.Add(minItem);
  122. while (enumerator.MoveNext()) {
  123. TSource currentItem = enumerator.Current;
  124. TKey currentValue = selector(currentItem);
  125. int comparisonResult = comparer.Compare(currentValue, minValue);
  126. if (comparisonResult < 0) {
  127. minValue = currentValue;
  128. results.Clear();
  129. results.Add(currentItem);
  130. } else if (comparisonResult == 0) {
  131. results.Add(currentItem);
  132. }
  133. }
  134. return results;
  135. }
  136. /// <summary>
  137. /// 选择最大值对应的对象。
  138. /// 若没有元素则返回 null。
  139. /// </summary>
  140. public static TSource? MaxBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector) where TKey : IComparable<TKey> {
  141. using var enumerator = source.GetEnumerator();
  142. if (!enumerator.MoveNext()) return default;
  143. TSource maxItem = enumerator.Current;
  144. TKey maxValue = selector(maxItem);
  145. while (enumerator.MoveNext()) {
  146. TKey value = selector(enumerator.Current);
  147. if (value.CompareTo(maxValue) <= 0) continue;
  148. maxItem = enumerator.Current;
  149. maxValue = value;
  150. }
  151. return maxItem;
  152. }
  153. /// <summary>
  154. /// 选择最大值对应的对象。
  155. /// 若没有元素则返回 null。
  156. /// </summary>
  157. public static TSource? MaxBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector, IComparer<TKey> comparer) {
  158. using var enumerator = source.GetEnumerator();
  159. if (!enumerator.MoveNext()) return default;
  160. TSource maxItem = enumerator.Current;
  161. TKey maxValue = selector(maxItem);
  162. while (enumerator.MoveNext()) {
  163. TKey value = selector(enumerator.Current);
  164. if (comparer.Compare(value, maxValue) <= 0) continue;
  165. maxItem = enumerator.Current;
  166. maxValue = value;
  167. }
  168. return maxItem;
  169. }
  170. /// <summary>
  171. /// 选择最小值对应的对象。
  172. /// 若没有元素则返回 null。
  173. /// </summary>
  174. public static TSource? MinBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector) where TKey : IComparable<TKey> {
  175. using var enumerator = source.GetEnumerator();
  176. if (!enumerator.MoveNext()) { return default; }
  177. TSource minItem = enumerator.Current;
  178. TKey minValue = selector(minItem);
  179. while (enumerator.MoveNext()) {
  180. TKey value = selector(enumerator.Current);
  181. if (value.CompareTo(minValue) >= 0) { continue; }
  182. minItem = enumerator.Current;
  183. minValue = value;
  184. }
  185. return minItem;
  186. }
  187. /// <summary>
  188. /// 选择最小值对应的对象。
  189. /// 若没有元素则返回 null。
  190. /// </summary>
  191. public static TSource? MinBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector, IComparer<TKey> comparer) {
  192. using var enumerator = source.GetEnumerator();
  193. if (!enumerator.MoveNext()) { return default; }
  194. TSource minItem = enumerator.Current;
  195. TKey minValue = selector(minItem);
  196. while (enumerator.MoveNext()) {
  197. TKey value = selector(enumerator.Current);
  198. if (comparer.Compare(value, minValue) >= 0) { continue; }
  199. minItem = enumerator.Current;
  200. minValue = value;
  201. }
  202. return minItem;
  203. }
  204. #endregion
  205. #region 排序
  206. /// <summary>
  207. /// 使用默认比较器对集合进行排序。顺序为从小到大。
  208. /// </summary>
  209. public static IEnumerable<T> Ordered<T>(this IEnumerable<T> source) where T : IComparable<T>
  210. => source.OrderBy(x => x);
  211. /// <summary>
  212. /// 使用默认比较器对集合进行排序。顺序为从大到小。
  213. /// </summary>
  214. public static IEnumerable<T> OrderedDescending<T>(this IEnumerable<T> source) where T : IComparable<T>
  215. => source.OrderByDescending(x => x);
  216. #endregion
  217. #region Join
  218. /// <summary>
  219. /// 用指定的分割符将集合连接为字符串。
  220. /// </summary>
  221. public static string Join(this IEnumerable list, char split) {
  222. var builder = new StringBuilder();
  223. bool isFirst = true;
  224. foreach (var element in list) {
  225. if (isFirst)
  226. isFirst = false;
  227. else
  228. builder.Append(split);
  229. if (element is not null) builder.Append(element.ToString());
  230. }
  231. return builder.ToString();
  232. }
  233. /// <summary>
  234. /// 用指定的分割符将集合连接为字符串。
  235. /// </summary>
  236. public static string Join(this IEnumerable list, string split) {
  237. if (split.IsSingle()) return list.Join(split[0]);
  238. var builder = new StringBuilder();
  239. bool isFirst = true;
  240. foreach (var element in list) {
  241. if (isFirst)
  242. isFirst = false;
  243. else
  244. builder.Append(split);
  245. if (element is not null) builder.Append(element.ToString());
  246. }
  247. return builder.ToString();
  248. }
  249. #endregion
  250. #region Dictionary
  251. /// <summary>
  252. /// 从 <see cref="ConcurrentDictionary{TKey, TValue}"/> 中移除具有指定键的元素。
  253. /// 返回是否确实移除了元素。
  254. /// </summary>
  255. public static bool Remove<TKey, TValue>(this ConcurrentDictionary<TKey, TValue> dict, TKey key)
  256. => dict.TryRemove(key, out _);
  257. /// <summary>
  258. /// 将二维数组转换为字典。
  259. /// </summary>
  260. public static Dictionary<T, T> ToDictionary<T>(this T[,] arr) where T : notnull {
  261. Dictionary<T, T> result = [];
  262. if (arr.Length == 0) return result;
  263. if (arr.GetLength(1) != 2) throw new ArgumentException("数组必须为两列,第一列为 Key,第二列为 Value。");
  264. for (int i = 0; i < arr.GetLength(0); i++) result[arr[i, 0]] = arr[i, 1];
  265. return result;
  266. }
  267. /// <summary>
  268. /// 尝试从字典中获取某项,如果该项不存在,则返回默认值。
  269. /// </summary>
  270. public static TValue? GetOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, TValue? defaultValue = default)
  271. => dict.TryGetValue(key, out var result) ? result : defaultValue;
  272. /// <summary>
  273. /// 将值添加到字典的对应项的列表中。
  274. /// </summary>
  275. public static void AddIntoValueCollection<TKey, TValue>(this IDictionary<TKey, List<TValue>> dict, TKey key, TValue value) {
  276. if (dict.TryGetValue(key, out var collection)) {
  277. collection.Add(value);
  278. } else {
  279. dict.Add(key, [value]);
  280. }
  281. }
  282. #endregion
  283. /// <summary>
  284. /// 将 <see cref="FlagsAttribute"/> 枚举值解包为它包含的单一 Flag 成员的集合。
  285. /// 建议仅对没有极端值的正数枚举使用。
  286. /// </summary>
  287. /// <remarks>
  288. /// 会跳过:0、非 2 的幂的值、不在枚举值中的数、组合值、对应同一个枚举项的多个别名。
  289. /// </remarks>
  290. public static IEnumerable<T> Flags<T>(this T value) where T : struct, Enum
  291. => EnumUtils.GetAllFlags<T>().Where(element => value.HasFlag(element));
  292. /// <summary>
  293. /// 从集合中移除满足条件的元素。
  294. /// </summary>
  295. public static void RemoveIf<T>(this ICollection<T> source, Func<T, bool> predicate) {
  296. var itemsToRemove = source.Where(predicate).ToList();
  297. foreach (var item in itemsToRemove) source.Remove(item);
  298. }
  299. /// <summary>
  300. /// 仅保留满足条件的元素,移除不满足条件的元素。
  301. /// </summary>
  302. public static void KeepIf<T>(this ICollection<T> source, Func<T, bool> predicate) {
  303. var itemsToRemove = source.Where(item => !predicate(item)).ToList();
  304. foreach (var item in itemsToRemove) source.Remove(item);
  305. }
  306. /// <summary>
  307. /// 判断集合是否有且仅有一个元素。
  308. /// 在输入 null 时返回 false。
  309. /// </summary>
  310. public static bool IsSingle<T>(this IEnumerable<T>? source) {
  311. if (source is null) return false;
  312. if (source is IList<T> list) return list.Count == 1;
  313. using var enumerator = source.GetEnumerator();
  314. if (!enumerator.MoveNext()) return false;
  315. if (enumerator.MoveNext()) return false;
  316. return true;
  317. }
  318. /// <summary>
  319. /// 对集合的每个元素执行指定操作。
  320. /// </summary>
  321. public static IEnumerable<T> ForAll<T>(this IEnumerable<T> source, Action<T> action) {
  322. foreach (T item in source) action(item);
  323. return source;
  324. }
  325. }