2
0

EnumerableExtensions.cs 14 KB

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