2
0

EnumerableExtensions.cs 14 KB

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