Skip to content

Commit 598aa02

Browse files
committed
Add DataTable and Enumerable helpers
1 parent 1143262 commit 598aa02

File tree

3 files changed

+253
-2
lines changed

3 files changed

+253
-2
lines changed

SharpHelpers/SharpHelpers.UnitTest/SharpHelpers.UnitTest.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88

99
<ItemGroup>
1010
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
11-
<PackageReference Include="MSTest.TestAdapter" Version="3.6.1" />
12-
<PackageReference Include="MSTest.TestFramework" Version="3.6.1" />
11+
<PackageReference Include="MSTest.TestAdapter" Version="3.6.2" />
12+
<PackageReference Include="MSTest.TestFramework" Version="3.6.2" />
1313
</ItemGroup>
1414

1515
<ItemGroup>

SharpHelpers/SharpHelpers/DataTableHelper.cs

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System;
44
using System.Collections.Generic;
55
using System.Data;
6+
using System.Linq;
67
using System.Reflection;
78

89
namespace SharpCoding.SharpHelpers
@@ -51,6 +52,139 @@ public static DataTable SetColumnsOrder(this DataTable table, string[] columnNam
5152
list.Add(objClass);
5253
}
5354
return list;
55+
}
56+
57+
/// <summary>
58+
/// Converts the DataTable to a CSV format string.
59+
/// </summary>
60+
/// <param name="table"></param>
61+
/// <param name="delimiter"></param>
62+
/// <returns></returns>
63+
public static string ToCsv(this DataTable table, string delimiter = ",")
64+
{
65+
if (table == null) throw new ArgumentNullException(nameof(table));
66+
67+
var csv = new List<string>();
68+
var headers = string.Join(delimiter, table.Columns.Cast<DataColumn>().Select(c => c.ColumnName));
69+
csv.Add(headers);
70+
71+
foreach (DataRow row in table.Rows)
72+
{
73+
var line = string.Join(delimiter, row.ItemArray.Select(field => field?.ToString()));
74+
csv.Add(line);
75+
}
76+
return string.Join(Environment.NewLine, csv);
77+
}
78+
79+
/// <summary>
80+
/// Adds a new column to the DataTable with the specified default value.
81+
/// </summary>
82+
/// <param name="table"></param>
83+
/// <param name="columnName"></param>
84+
/// <param name="defaultValue"></param>
85+
/// <typeparam name="T"></typeparam>
86+
public static void AddColumn<T>(this DataTable table, string columnName, T defaultValue = default)
87+
{
88+
if (table == null) throw new ArgumentNullException(nameof(table));
89+
90+
var column = new DataColumn(columnName, typeof(T)) { DefaultValue = defaultValue };
91+
table.Columns.Add(column);
92+
foreach (DataRow row in table.Rows)
93+
{
94+
row[columnName] = defaultValue;
95+
}
96+
}
97+
98+
/// <summary>
99+
/// Merges multiple DataTables with the same schema into one.
100+
/// </summary>
101+
/// <param name="tables"></param>
102+
/// <returns></returns>
103+
/// <exception cref="ArgumentException"></exception>
104+
public static DataTable MergeTables(IEnumerable<DataTable> tables)
105+
{
106+
if (tables == null) throw new ArgumentNullException(nameof(tables));
107+
108+
var resultTable = tables.First().Clone();
109+
foreach (var table in tables)
110+
{
111+
if (!AreSchemasCompatible(resultTable, table))
112+
throw new ArgumentException("Tables have incompatible schemas.");
113+
114+
foreach (DataRow row in table.Rows)
115+
{
116+
resultTable.ImportRow(row);
117+
}
118+
}
119+
return resultTable;
120+
}
121+
122+
private static bool AreSchemasCompatible(DataTable table1, DataTable table2)
123+
{
124+
if (table1.Columns.Count != table2.Columns.Count) return false;
125+
126+
for (int i = 0; i < table1.Columns.Count; i++)
127+
{
128+
if (table1.Columns[i].ColumnName != table2.Columns[i].ColumnName ||
129+
table1.Columns[i].DataType != table2.Columns[i].DataType)
130+
return false;
131+
}
132+
return true;
133+
}
134+
135+
/// <summary>
136+
/// Filters the rows in the DataTable based on a predicate.
137+
/// </summary>
138+
/// <param name="table"></param>
139+
/// <param name="predicate"></param>
140+
/// <returns></returns>
141+
public static DataTable Filter(this DataTable table, Func<DataRow, bool> predicate)
142+
{
143+
if (table == null) throw new ArgumentNullException(nameof(table));
144+
if (predicate == null) throw new ArgumentNullException(nameof(predicate));
145+
146+
var filteredTable = table.Clone();
147+
foreach (DataRow row in table.AsEnumerable().Where(predicate))
148+
{
149+
filteredTable.ImportRow(row);
150+
}
151+
return filteredTable;
152+
}
153+
154+
/// <summary>
155+
/// Checks if the DataTable is empty (contains no rows).
156+
/// </summary>
157+
/// <param name="table"></param>
158+
/// <returns></returns>
159+
public static bool IsEmpty(this DataTable table)
160+
{
161+
if (table == null) throw new ArgumentNullException(nameof(table));
162+
163+
return table.Rows.Count == 0;
164+
}
165+
166+
/// <summary>
167+
/// Removes duplicate rows based on specified columns.
168+
/// </summary>
169+
/// <param name="table"></param>
170+
/// <param name="columnNames"></param>
171+
/// <returns></returns>
172+
public static DataTable RemoveDuplicates(this DataTable table, params string[] columnNames)
173+
{
174+
if (table == null) throw new ArgumentNullException(nameof(table));
175+
176+
var distinctTable = table.Clone();
177+
var uniqueRows = new HashSet<string>();
178+
179+
foreach (DataRow row in table.Rows)
180+
{
181+
var key = string.Join("|", columnNames.Select(c => row[c]?.ToString() ?? ""));
182+
if (uniqueRows.Add(key))
183+
{
184+
distinctTable.ImportRow(row);
185+
}
186+
}
187+
return distinctTable;
54188
}
55189
}
56190
}

SharpHelpers/SharpHelpers/EnumerableHelper.cs

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,5 +268,122 @@ public static int Sum<T>(this IEnumerable<T> source, Func<T, int> selector)
268268

269269
return source.Select(selector).Sum();
270270
}
271+
272+
/// <summary>
273+
/// Returns the maximum element based on a given selector function.
274+
/// </summary>
275+
/// <typeparam name="TSource"></typeparam>
276+
/// <typeparam name="TKey"></typeparam>
277+
/// <param name="source"></param>
278+
/// <param name="selector"></param>
279+
/// <returns></returns>
280+
/// <exception cref="ArgumentNullException"></exception>
281+
public static TSource MaxBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector)
282+
where TKey : IComparable<TKey>
283+
{
284+
if (source == null) throw new ArgumentNullException(nameof(source));
285+
if (selector == null) throw new ArgumentNullException(nameof(selector));
286+
287+
return source.Aggregate((maxItem, nextItem) => selector(nextItem).CompareTo(selector(maxItem)) > 0 ? nextItem : maxItem);
288+
}
289+
290+
/// <summary>
291+
/// Returns the minimum element based on a given selector function.
292+
/// </summary>
293+
/// <typeparam name="TSource"></typeparam>
294+
/// <typeparam name="TKey"></typeparam>
295+
/// <param name="source"></param>
296+
/// <param name="selector"></param>
297+
/// <returns></returns>
298+
/// <exception cref="ArgumentNullException"></exception>
299+
public static TSource MinBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector)
300+
where TKey : IComparable<TKey>
301+
{
302+
if (source == null) throw new ArgumentNullException(nameof(source));
303+
if (selector == null) throw new ArgumentNullException(nameof(selector));
304+
305+
return source.Aggregate((minItem, nextItem) => selector(nextItem).CompareTo(selector(minItem)) < 0 ? nextItem : minItem);
306+
}
307+
308+
/// <summary>
309+
/// Finds the index of the first element that satisfies a given predicate.
310+
/// </summary>
311+
/// <typeparam name="T"></typeparam>
312+
/// <param name="source"></param>
313+
/// <param name="predicate"></param>
314+
/// <returns></returns>
315+
/// <exception cref="ArgumentNullException"></exception>
316+
public static int FindIndex<T>(this IEnumerable<T> source, Func<T, bool> predicate)
317+
{
318+
if (source == null) throw new ArgumentNullException(nameof(source));
319+
if (predicate == null) throw new ArgumentNullException(nameof(predicate));
320+
321+
int index = 0;
322+
foreach (var item in source)
323+
{
324+
if (predicate(item)) return index;
325+
index++;
326+
}
327+
return -1;
328+
}
329+
330+
/// <summary>
331+
/// Checks if the source contains any of the specified items.
332+
/// </summary>
333+
/// <typeparam name="T"></typeparam>
334+
/// <param name="source"></param>
335+
/// <param name="items"></param>
336+
/// <returns></returns>
337+
/// <exception cref="ArgumentNullException"></exception>
338+
public static bool ContainsAny<T>(this IEnumerable<T> source, params T[] items)
339+
{
340+
if (source == null) throw new ArgumentNullException(nameof(source));
341+
if (items == null) throw new ArgumentNullException(nameof(items));
342+
343+
var set = new HashSet<T>(items);
344+
return source.Any(set.Contains);
345+
}
346+
347+
/// <summary>
348+
/// Checks if the source contains all of the specified items.
349+
/// </summary>
350+
/// <typeparam name="T"></typeparam>
351+
/// <param name="source"></param>
352+
/// <param name="items"></param>
353+
/// <returns></returns>
354+
/// <exception cref="ArgumentNullException"></exception>
355+
public static bool ContainsAll<T>(this IEnumerable<T> source, params T[] items)
356+
{
357+
if (source == null) throw new ArgumentNullException(nameof(source));
358+
if (items == null) throw new ArgumentNullException(nameof(items));
359+
360+
var set = new HashSet<T>(source);
361+
return items.All(set.Contains);
362+
}
363+
364+
/// <summary>
365+
/// Returns the median of a sequence of numbers.
366+
/// </summary>
367+
/// <param name="source"></param>
368+
/// <returns></returns>
369+
/// <exception cref="ArgumentNullException"></exception>
370+
public static double Median(this IEnumerable<int> source)
371+
{
372+
if (source == null) throw new ArgumentNullException(nameof(source));
373+
374+
var sortedList = source.OrderBy(n => n).ToList();
375+
int count = sortedList.Count;
376+
if (count == 0)
377+
throw new InvalidOperationException("The source sequence is empty.");
378+
379+
if (count % 2 == 0)
380+
{
381+
return (sortedList[count / 2 - 1] + sortedList[count / 2]) / 2.0;
382+
}
383+
else
384+
{
385+
return sortedList[count / 2];
386+
}
387+
}
271388
}
272389
}

0 commit comments

Comments
 (0)