| | 1 | | namespace Songhay.Extensions; |
| | 2 | |
|
| | 3 | | /// <summary> |
| | 4 | | /// Extensions of <see cref="IEnumerable{T}"/>. |
| | 5 | | /// </summary> |
| | 6 | | /// <remarks> |
| | 7 | | /// When this ‘greatest hits collection’ is found to be limited, |
| | 8 | | /// upgrade to MoreLinq [ see https://github.com/morelinq/MoreLINQ ] |
| | 9 | | /// </remarks> |
| | 10 | | // ReSharper disable once InconsistentNaming |
| | 11 | | public static class IEnumerableOfTExtensions |
| | 12 | | { |
| | 13 | | /// <summary> |
| | 14 | | /// Flattens the specified source. |
| | 15 | | /// </summary> |
| | 16 | | /// <typeparam name="TSource">The type of the source.</typeparam> |
| | 17 | | /// <param name="source">The source.</param> |
| | 18 | | /// <param name="childGetter">The child getter.</param> |
| | 19 | | /// <remarks> |
| | 20 | | /// When <c>source</c> is not already an array, |
| | 21 | | /// this member will mercilessly allocate a snapshot of <c>TSource[]</c>. |
| | 22 | | /// To avoid this memory pressure, upgrade to the <c>Flatten</c> method |
| | 23 | | /// of MoreLinq [ see https://github.com/morelinq/MoreLINQ/blob/master/MoreLinq/Flatten.cs#L91 ] |
| | 24 | | /// </remarks> |
| | 25 | | public static IEnumerable<TSource> Flatten<TSource>(this IEnumerable<TSource>? source, |
| | 26 | | Func<TSource, IEnumerable<TSource>>? childGetter) |
| 0 | 27 | | { |
| 0 | 28 | | if (source == null) return Enumerable.Empty<TSource>(); |
| 0 | 29 | | var snapshot = source as TSource[] ?? source.ToArray(); |
| | 30 | |
|
| 0 | 31 | | var flattenedList = new List<TSource>(snapshot); |
| | 32 | |
|
| 0 | 33 | | snapshot.ForEachInEnumerable(i => |
| 0 | 34 | | { |
| 0 | 35 | | var children = childGetter?.Invoke(i); |
| 0 | 36 | | if (children != null) flattenedList.AddRange(children.Flatten(childGetter)); |
| 0 | 37 | | }); |
| | 38 | |
|
| 0 | 39 | | return flattenedList; |
| 0 | 40 | | } |
| | 41 | |
|
| | 42 | | /// <summary> |
| | 43 | | /// Flattens the specified source. |
| | 44 | | /// </summary> |
| | 45 | | /// <typeparam name="TSource">The type of the source.</typeparam> |
| | 46 | | /// <param name="source">The source.</param> |
| | 47 | | /// <param name="childGetter">The child getter.</param> |
| | 48 | | /// <param name="flattenedHead">The flattened head.</param> |
| | 49 | | public static IEnumerable<TSource> Flatten<TSource>(this IEnumerable<TSource> source, |
| | 50 | | Func<TSource, IEnumerable<TSource>> childGetter, TSource flattenedHead) => |
| 0 | 51 | | new[] {flattenedHead}.Concat(source.Flatten(childGetter)); |
| | 52 | |
|
| | 53 | | /// <summary> |
| | 54 | | /// Performs the <see cref="Action"/> |
| | 55 | | /// on each item in the enumerable object. |
| | 56 | | /// </summary> |
| | 57 | | /// <typeparam name="TEnumerable">The type of the enumerable.</typeparam> |
| | 58 | | /// <param name="enumerable">The enumerable.</param> |
| | 59 | | /// <param name="action">The action.</param> |
| | 60 | | /// <remarks> |
| | 61 | | /// “I am philosophically opposed to providing such a method, for two reasons. |
| | 62 | | /// …The first reason is that doing so violates the functional programming principles |
| | 63 | | /// that all the other sequence operators are based upon. Clearly the sole purpose of a call |
| | 64 | | /// to this method is to cause side effects.” |
| | 65 | | /// —Eric Lippert, “foreach” vs “ForEach” [http://blogs.msdn.com/b/ericlippert/archive/2009/05/18/foreach-vs-foreach |
| | 66 | | /// </remarks> |
| | 67 | | public static void ForEachInEnumerable<TEnumerable>(this IEnumerable<TEnumerable>? enumerable, |
| | 68 | | Action<TEnumerable>? action) |
| 17229 | 69 | | { |
| 17229 | 70 | | if (enumerable == null) return; |
| 17229 | 71 | | if (action == null) return; |
| | 72 | |
|
| 69399 | 73 | | foreach (var item in enumerable) |
| 8856 | 74 | | { |
| 8856 | 75 | | action?.Invoke(item); |
| 8856 | 76 | | } |
| 17229 | 77 | | } |
| | 78 | |
|
| | 79 | | /// <summary> |
| | 80 | | /// Performs the <see cref="Action"/> |
| | 81 | | /// on each item in the enumerable object. |
| | 82 | | /// </summary> |
| | 83 | | /// <typeparam name="TEnumerable">The type of the enumerable.</typeparam> |
| | 84 | | /// <param name="enumerable">The enumerable.</param> |
| | 85 | | /// <param name="action">The action.</param> |
| | 86 | | /// <remarks> |
| | 87 | | /// This member is ruthlessly derived from <c>MoreLinq.ForEach{T}</c> |
| | 88 | | /// [ see https://github.com/morelinq/MoreLINQ/blob/master/MoreLinq/ForEach.cs#L50 ]. |
| | 89 | | /// </remarks> |
| | 90 | | public static void ForEachInEnumerable<TEnumerable>(this IEnumerable<TEnumerable>? enumerable, |
| | 91 | | Action<TEnumerable, int>? action) |
| 0 | 92 | | { |
| 0 | 93 | | if (enumerable == null) return; |
| 0 | 94 | | if (action == null) return; |
| | 95 | |
|
| 0 | 96 | | var index = 0; |
| 0 | 97 | | foreach (var element in enumerable) |
| 0 | 98 | | { |
| 0 | 99 | | action.Invoke(element, index++); |
| 0 | 100 | | } |
| 0 | 101 | | } |
| | 102 | |
|
| | 103 | | /// <summary> |
| | 104 | | /// Partitions the specified source. |
| | 105 | | /// </summary> |
| | 106 | | /// <typeparam name="T"></typeparam> |
| | 107 | | /// <param name="source">The source.</param> |
| | 108 | | /// <param name="size">The size.</param> |
| | 109 | | /// <remarks> |
| | 110 | | /// This member is by Jon Skeet. |
| | 111 | | /// [http://stackoverflow.com/questions/438188/split-a-collection-into-n-parts-with-linq] |
| | 112 | | /// </remarks> |
| | 113 | | public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> source, int size) |
| 0 | 114 | | { |
| 0 | 115 | | T[]? array = null; |
| | 116 | |
|
| 0 | 117 | | int count = 0; |
| 0 | 118 | | foreach (T item in source) |
| 0 | 119 | | { |
| 0 | 120 | | if (array == null) |
| 0 | 121 | | { |
| 0 | 122 | | array = new T[size]; |
| 0 | 123 | | } |
| | 124 | |
|
| 0 | 125 | | array[count] = item; |
| 0 | 126 | | count++; |
| 0 | 127 | | if (count == size) |
| 0 | 128 | | { |
| 0 | 129 | | yield return new ReadOnlyCollection<T>(array); |
| 0 | 130 | | array = null; |
| 0 | 131 | | count = 0; |
| 0 | 132 | | } |
| 0 | 133 | | } |
| | 134 | |
|
| 0 | 135 | | if (array == null) yield break; |
| | 136 | |
|
| 0 | 137 | | Array.Resize(ref array, count); |
| 0 | 138 | | yield return new ReadOnlyCollection<T>(array); |
| 0 | 139 | | } |
| | 140 | |
|
| | 141 | | /// <summary> |
| | 142 | | /// Projects the previous item with the current item. |
| | 143 | | /// </summary> |
| | 144 | | /// <typeparam name="TSource">The type of the source.</typeparam> |
| | 145 | | /// <typeparam name="TResult">The type of the result.</typeparam> |
| | 146 | | /// <param name="source">The source.</param> |
| | 147 | | /// <param name="projection">The projection.</param> |
| | 148 | | /// <remarks> |
| | 149 | | /// “This enables you to perform your projection using only a single pass of the source sequence, |
| | 150 | | /// which is always a bonus (imagine running it over a large log file). |
| | 151 | | /// Note that it will project a sequence of length n into a sequence of length n-1— |
| | 152 | | /// you may want to prepend a ‘dummy’ first element, for example. (Or change the method to include one.) |
| | 153 | | /// Here’s an example of how you'd use it: |
| | 154 | | /// <code> |
| | 155 | | /// var query = list.SelectWithPrevious((prev, cur) => new { ID = cur.ID, Date = cur.Date, DateDiff = (cur.Date - |
| | 156 | | /// </code> |
| | 157 | | /// Note that this will include the final result of one ID with the first result of the next ID… |
| | 158 | | /// you may wish to group your sequence by ID first.” |
| | 159 | | /// —Jon Skeet, “Calculate difference from previous item with LINQ” |
| | 160 | | /// [http://stackoverflow.com/questions/3683105/calculate-difference-from-previous-item-with-linq/3683217#3683217] |
| | 161 | | /// </remarks> |
| | 162 | | public static IEnumerable<TResult> SelectWithPrevious<TSource, TResult>(this IEnumerable<TSource> source, |
| | 163 | | Func<TSource, TSource, TResult> projection) |
| 0 | 164 | | { |
| 0 | 165 | | using var iterator = source.GetEnumerator(); |
| | 166 | |
|
| 0 | 167 | | if (!iterator.MoveNext()) |
| 0 | 168 | | { |
| 0 | 169 | | yield break; |
| | 170 | | } |
| | 171 | |
|
| 0 | 172 | | TSource previous = iterator.Current; |
| 0 | 173 | | while (iterator.MoveNext()) |
| 0 | 174 | | { |
| 0 | 175 | | yield return projection(previous, iterator.Current); |
| 0 | 176 | | previous = iterator.Current; |
| 0 | 177 | | } |
| 0 | 178 | | } |
| | 179 | |
|
| | 180 | | /// <summary> |
| | 181 | | /// Converts the <see cref="IEnumerable{TEnumerable}"/> |
| | 182 | | /// into <see cref="ICollection{TEnumerable}"/>. |
| | 183 | | /// </summary> |
| | 184 | | /// <typeparam name="TEnumerable"></typeparam> |
| | 185 | | /// <param name="enumerable"></param> |
| | 186 | | /// <remarks> |
| | 187 | | /// For details, see “When To Use IEnumerable, ICollection, IList And List” |
| | 188 | | /// [http://www.claudiobernasconi.ch/2013/07/22/when-to-use-ienumerable-icollection-ilist-and-list/] |
| | 189 | | /// </remarks> |
| | 190 | | public static ICollection<TEnumerable> ToCollection<TEnumerable>(this IEnumerable<TEnumerable>? enumerable) => |
| 0 | 191 | | enumerable == null ? Enumerable.Empty<TEnumerable>().ToList() : enumerable.ToList(); |
| | 192 | |
|
| | 193 | | /// <summary> |
| | 194 | | /// Converts the <see cref="IEnumerable{TSource}"/> into a display string. |
| | 195 | | /// </summary> |
| | 196 | | /// <typeparam name="TSource">The type of the source.</typeparam> |
| | 197 | | /// <param name="data">The source.</param> |
| | 198 | | public static string ToDisplayString<TSource>(this IEnumerable<TSource> data) where TSource : class => |
| 0 | 199 | | data.ToDisplayString(indent: 0); |
| | 200 | |
|
| | 201 | | /// <summary> |
| | 202 | | /// Converts the <see cref="IEnumerable{TSource}"/> into a display string. |
| | 203 | | /// </summary> |
| | 204 | | /// <typeparam name="TSource">The type of the source.</typeparam> |
| | 205 | | /// <param name="source">The source.</param> |
| | 206 | | /// <param name="indent">The indent.</param> |
| | 207 | | public static string ToDisplayString<TSource>(this IEnumerable<TSource> source, byte indent) where TSource : class |
| 0 | 208 | | { |
| 0 | 209 | | var indentation = string.Join(string.Empty, Enumerable.Repeat(" ", indent).ToArray()); |
| 0 | 210 | | var builder = new StringBuilder(); |
| | 211 | |
|
| 0 | 212 | | var snapshot = source as TSource[] ?? source.ToArray(); |
| | 213 | |
|
| 0 | 214 | | if (snapshot.Any()) builder.Append($"{indentation}{snapshot.Length} child items:"); |
| | 215 | |
|
| 0 | 216 | | snapshot.ForEachInEnumerable(i => builder.Append($"{Environment.NewLine}{indentation}{i}")); |
| | 217 | |
|
| 0 | 218 | | if (builder.Length > 0) builder.AppendLine(); |
| | 219 | |
|
| 0 | 220 | | return builder.ToString(); |
| 0 | 221 | | } |
| | 222 | | } |