Skip to content

Commit

Permalink
Add DataTable and Enumerable helpers
Browse files Browse the repository at this point in the history
  • Loading branch information
engineering87 committed Nov 8, 2024
1 parent 1143262 commit 598aa02
Show file tree
Hide file tree
Showing 3 changed files with 253 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="MSTest.TestAdapter" Version="3.6.1" />
<PackageReference Include="MSTest.TestFramework" Version="3.6.1" />
<PackageReference Include="MSTest.TestAdapter" Version="3.6.2" />
<PackageReference Include="MSTest.TestFramework" Version="3.6.2" />
</ItemGroup>

<ItemGroup>
Expand Down
134 changes: 134 additions & 0 deletions SharpHelpers/SharpHelpers/DataTableHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Reflection;

namespace SharpCoding.SharpHelpers
Expand Down Expand Up @@ -51,6 +52,139 @@ public static DataTable SetColumnsOrder(this DataTable table, string[] columnNam
list.Add(objClass);
}
return list;
}

/// <summary>
/// Converts the DataTable to a CSV format string.
/// </summary>
/// <param name="table"></param>
/// <param name="delimiter"></param>
/// <returns></returns>
public static string ToCsv(this DataTable table, string delimiter = ",")
{
if (table == null) throw new ArgumentNullException(nameof(table));

var csv = new List<string>();
var headers = string.Join(delimiter, table.Columns.Cast<DataColumn>().Select(c => c.ColumnName));
csv.Add(headers);

foreach (DataRow row in table.Rows)
{
var line = string.Join(delimiter, row.ItemArray.Select(field => field?.ToString()));
csv.Add(line);
}
return string.Join(Environment.NewLine, csv);
}

/// <summary>
/// Adds a new column to the DataTable with the specified default value.
/// </summary>
/// <param name="table"></param>
/// <param name="columnName"></param>
/// <param name="defaultValue"></param>
/// <typeparam name="T"></typeparam>
public static void AddColumn<T>(this DataTable table, string columnName, T defaultValue = default)
{
if (table == null) throw new ArgumentNullException(nameof(table));

var column = new DataColumn(columnName, typeof(T)) { DefaultValue = defaultValue };
table.Columns.Add(column);
foreach (DataRow row in table.Rows)
{
row[columnName] = defaultValue;
}
}

/// <summary>
/// Merges multiple DataTables with the same schema into one.
/// </summary>
/// <param name="tables"></param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
public static DataTable MergeTables(IEnumerable<DataTable> tables)
{
if (tables == null) throw new ArgumentNullException(nameof(tables));

var resultTable = tables.First().Clone();
foreach (var table in tables)
{
if (!AreSchemasCompatible(resultTable, table))
throw new ArgumentException("Tables have incompatible schemas.");

foreach (DataRow row in table.Rows)
{
resultTable.ImportRow(row);
}
}
return resultTable;
}

private static bool AreSchemasCompatible(DataTable table1, DataTable table2)
{
if (table1.Columns.Count != table2.Columns.Count) return false;

for (int i = 0; i < table1.Columns.Count; i++)
{
if (table1.Columns[i].ColumnName != table2.Columns[i].ColumnName ||
table1.Columns[i].DataType != table2.Columns[i].DataType)
return false;
}
return true;
}

/// <summary>
/// Filters the rows in the DataTable based on a predicate.
/// </summary>
/// <param name="table"></param>
/// <param name="predicate"></param>
/// <returns></returns>
public static DataTable Filter(this DataTable table, Func<DataRow, bool> predicate)
{
if (table == null) throw new ArgumentNullException(nameof(table));
if (predicate == null) throw new ArgumentNullException(nameof(predicate));

var filteredTable = table.Clone();
foreach (DataRow row in table.AsEnumerable().Where(predicate))
{
filteredTable.ImportRow(row);
}
return filteredTable;
}

/// <summary>
/// Checks if the DataTable is empty (contains no rows).
/// </summary>
/// <param name="table"></param>
/// <returns></returns>
public static bool IsEmpty(this DataTable table)
{
if (table == null) throw new ArgumentNullException(nameof(table));

return table.Rows.Count == 0;
}

/// <summary>
/// Removes duplicate rows based on specified columns.
/// </summary>
/// <param name="table"></param>
/// <param name="columnNames"></param>
/// <returns></returns>
public static DataTable RemoveDuplicates(this DataTable table, params string[] columnNames)
{
if (table == null) throw new ArgumentNullException(nameof(table));

var distinctTable = table.Clone();
var uniqueRows = new HashSet<string>();

foreach (DataRow row in table.Rows)
{
var key = string.Join("|", columnNames.Select(c => row[c]?.ToString() ?? ""));
if (uniqueRows.Add(key))
{
distinctTable.ImportRow(row);
}
}
return distinctTable;
}
}
}
117 changes: 117 additions & 0 deletions SharpHelpers/SharpHelpers/EnumerableHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -268,5 +268,122 @@ public static int Sum<T>(this IEnumerable<T> source, Func<T, int> selector)

return source.Select(selector).Sum();
}

/// <summary>
/// Returns the maximum element based on a given selector function.
/// </summary>
/// <typeparam name="TSource"></typeparam>
/// <typeparam name="TKey"></typeparam>
/// <param name="source"></param>
/// <param name="selector"></param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public static TSource MaxBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector)
where TKey : IComparable<TKey>
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (selector == null) throw new ArgumentNullException(nameof(selector));

return source.Aggregate((maxItem, nextItem) => selector(nextItem).CompareTo(selector(maxItem)) > 0 ? nextItem : maxItem);
}

/// <summary>
/// Returns the minimum element based on a given selector function.
/// </summary>
/// <typeparam name="TSource"></typeparam>
/// <typeparam name="TKey"></typeparam>
/// <param name="source"></param>
/// <param name="selector"></param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public static TSource MinBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector)
where TKey : IComparable<TKey>
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (selector == null) throw new ArgumentNullException(nameof(selector));

return source.Aggregate((minItem, nextItem) => selector(nextItem).CompareTo(selector(minItem)) < 0 ? nextItem : minItem);
}

/// <summary>
/// Finds the index of the first element that satisfies a given predicate.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source"></param>
/// <param name="predicate"></param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public static int FindIndex<T>(this IEnumerable<T> source, Func<T, bool> predicate)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (predicate == null) throw new ArgumentNullException(nameof(predicate));

int index = 0;
foreach (var item in source)
{
if (predicate(item)) return index;
index++;
}
return -1;
}

/// <summary>
/// Checks if the source contains any of the specified items.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source"></param>
/// <param name="items"></param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public static bool ContainsAny<T>(this IEnumerable<T> source, params T[] items)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (items == null) throw new ArgumentNullException(nameof(items));

var set = new HashSet<T>(items);
return source.Any(set.Contains);
}

/// <summary>
/// Checks if the source contains all of the specified items.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source"></param>
/// <param name="items"></param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public static bool ContainsAll<T>(this IEnumerable<T> source, params T[] items)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (items == null) throw new ArgumentNullException(nameof(items));

var set = new HashSet<T>(source);
return items.All(set.Contains);
}

/// <summary>
/// Returns the median of a sequence of numbers.
/// </summary>
/// <param name="source"></param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public static double Median(this IEnumerable<int> source)
{
if (source == null) throw new ArgumentNullException(nameof(source));

var sortedList = source.OrderBy(n => n).ToList();
int count = sortedList.Count;
if (count == 0)
throw new InvalidOperationException("The source sequence is empty.");

if (count % 2 == 0)
{
return (sortedList[count / 2 - 1] + sortedList[count / 2]) / 2.0;
}
else
{
return sortedList[count / 2];
}
}
}
}

0 comments on commit 598aa02

Please sign in to comment.