Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@

SimpliSharp is a .NET library designed to simplify common programming tasks with a focus on performance and ease of use. As the library grows, it will be populated with a variety of tools and utilities to help developers write cleaner, more efficient code.

## Features

### SmartDataProcessor
## SmartDataProcessor

The `SmartDataProcessor<T>` is designed to process a queue of items in parallel, while automatically adjusting the level of concurrency to stay within a specified CPU usage limit.

Expand All @@ -27,3 +25,26 @@ for (...)
```

![Alt text for your image](https://raw.githubusercontent.com/cretucosmin3/SimpliSharp/refs/heads/main/assets/75-cpu-usage.png)


## Enumerable Extensions

### Batching

Batching enumerables using `Enumerables.Batch(batchSize)` or `Enumerables.BatchSliding(windowSize)` will simply yield the requested batches,

```csharp
string[] sample = ["Red", "Blue", "Purple", "Black", "Yellow", "Pink"];
string[][] batches = sample.Batch(2).ToArray();

// Batch 1: [Red, Blue, Purple]
// Batch 2: [Black, Yellow, Pink]
```

```csharp
int[] sample = [1, 2, 3];
int[][] batches = sample.BatchSliding(2).ToArray();

// Batch 1: [1, 2]
// Batch 2: [2, 3]
```
160 changes: 117 additions & 43 deletions samples/SimpliSharp.Demo/Program.cs
Original file line number Diff line number Diff line change
@@ -1,53 +1,127 @@
using SimpliSharp.Utilities.Process;
using SimpliSharp.Extensions.Batch;
using SimpliSharp.Utilities.Process;

class Program
Console.WriteLine("SimpliSharp Demo Application");
Console.WriteLine("---------------------------");

Console.WriteLine("Available Demos:");
Console.WriteLine("1. SmartDataProcessor Example");
Console.WriteLine("2. Enumerable.Batch");
Console.WriteLine("3. Enumerable.BatchSliding");

Console.WriteLine("[Enter] to exit");

var choice = Console.ReadKey();

switch (choice.Key)
{
static void Main()
case ConsoleKey.D1:
case ConsoleKey.NumPad1:
SmartDataProcessor_Example();
break;
case ConsoleKey.D2:
case ConsoleKey.NumPad2:
EnumerableBatch_Example();
break;
case ConsoleKey.D3:
case ConsoleKey.NumPad3:
EnumerableBatchSliding_Example();
break;
default:
Console.WriteLine("\nExiting...");
return;
}

static void SmartDataProcessor_Example()
{
Console.Clear();
Console.WriteLine("Starting data processing...");

using var processor = new SmartDataProcessor<int>(maxCpuUsage: 90);
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
var tasksCount = 2000;

for (int i = 0; i < tasksCount; i++)
{
Console.WriteLine("Starting data processing...");
int line = i;

using var processor = new SmartDataProcessor<int>(maxCpuUsage: 90);
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
var tasksCount = 2000;

for (int i = 0; i < tasksCount; i++)
processor.EnqueueOrWait(line, data =>
{
int line = i;
int simMax = Random.Shared.Next(5_000_000, 20_000_000);
double sum = 0;

processor.EnqueueOrWait(line, data =>
for (int j = 0; j < 10_000_000; j++)
{
int simMax = Random.Shared.Next(5_000_000, 20_000_000);
double sum = 0;

for (int j = 0; j < 10_000_000; j++)
{
double value = Math.Sqrt(j) * Math.Sin(j % 360) + Math.Log(j + 1);
if (value > 1000)
sum -= value / 3.0;
else
sum += value * 2.5;
}
});

Console.SetCursorPosition(0, Console.CursorTop);
Console.Write($"Processing item {i + 1} of {tasksCount} | queued: {processor.Metrics.QueueLength}");
}

processor.WaitForAllAsync().Wait();
stopwatch.Stop();

Console.WriteLine();
Console.WriteLine($"Processing completed in {stopwatch.Elapsed.TotalSeconds} seconds");

var finalMetrics = processor.Metrics;
Console.WriteLine();
Console.WriteLine("--- Final Metrics ---");
Console.WriteLine($"Max Concurrency: {finalMetrics.MaxConcurrency}");
Console.WriteLine($"Fastest Job: {finalMetrics.MinTaskTime:F2}ms");
Console.WriteLine($"Slowest Job: {finalMetrics.MaxTaskTime:F2}ms");
Console.WriteLine($"Average Job: {finalMetrics.AvgTaskTime:F2}ms");

Console.WriteLine("All processing done");
double value = Math.Sqrt(j) * Math.Sin(j % 360) + Math.Log(j + 1);
if (value > 1000)
sum -= value / 3.0;
else
sum += value * 2.5;
}
});

Console.SetCursorPosition(0, Console.CursorTop);
Console.Write($"Processing item {i + 1} of {tasksCount} | queued: {processor.Metrics.QueueLength}");
}

processor.WaitForAllAsync().Wait();
stopwatch.Stop();

Console.WriteLine();
Console.WriteLine($"Processing completed in {stopwatch.Elapsed.TotalSeconds} seconds");

var finalMetrics = processor.Metrics;
Console.WriteLine();
Console.WriteLine("--- Final Metrics ---");
Console.WriteLine($"Max Concurrency: {finalMetrics.MaxConcurrency}");
Console.WriteLine($"Fastest Job: {finalMetrics.MinTaskTime:F2}ms");
Console.WriteLine($"Slowest Job: {finalMetrics.MaxTaskTime:F2}ms");
Console.WriteLine($"Average Job: {finalMetrics.AvgTaskTime:F2}ms");

Console.WriteLine("All processing done");
}

static void EnumerableBatch_Example()
{
Console.Clear();
Console.WriteLine("Starting Enumerable.Batch demo...");

// yields: [ ["Red", "Blue"], ["Purple", "Black"], ["Yellow", "Pink"] ]


string[] sample = ["Red", "Blue", "Purple", "Black", "Yellow", "Pink"];
int batchSize = 3;

var batches = sample.Batch(batchSize).ToList();

Console.WriteLine($"Sample numbers: [{string.Join(", ", batches)}]");
Console.WriteLine($"Batch size: {batchSize}");
Console.WriteLine();

Console.WriteLine($"{batches.Count} Batches created:");
for (int i = 0; i < batches.Count; i++)
{
Console.WriteLine($"Batch {i + 1}: [{string.Join(", ", batches[i])}]");
}
}

static void EnumerableBatchSliding_Example()
{
Console.Clear();
Console.WriteLine("Starting Enumerable.BatchSliding demo...");

var numbers = Enumerable.Range(1, 3);
int batchSize = 2;

var batches = numbers.BatchSliding(batchSize).ToList();

Console.WriteLine();
Console.WriteLine($"Sample numbers: [{string.Join(", ", numbers)}]");
Console.WriteLine();

Console.WriteLine($"{batches.Count} batches created:");
for (int i = 0; i < batches.Count; i++)
{
Console.WriteLine($"Batch {i + 1}: [{string.Join(", ", batches[i])}]");
}
}
110 changes: 110 additions & 0 deletions src/SimpliSharp/Extensions/EnumerableBatch.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
namespace SimpliSharp.Extensions.Batch;

public static class EnumerableExtensions
{
/// <summary>
/// Splits an enumerable sequence into non-overlapping batches of a specified size.
/// </summary>
/// <remarks>
/// This method partitions the source sequence into chunks.
/// For example, `[1,2,3,4,5,6]` with a batch size of 3 results in `[1,2,3]` and `[4,5,6]`.
/// The last batch may contain fewer items.
/// This method uses deferred execution.
/// </remarks>
/// <typeparam name="T">The type of elements in the sequence.</typeparam>
/// <param name="source">The source enumerable sequence to batch.</param>
/// <param name="batchSize">The desired maximum size for each batch.</param>
/// <returns>An `IEnumerable<T[]>` where each element is an array representing a batch.</returns>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="source"/> is null.</exception>
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="batchSize"/> is less than or equal to 0.</exception>
public static IEnumerable<T[]> Batch<T>(this IEnumerable<T> source, int batchSize)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
}

if (batchSize <= 0)
{
throw new ArgumentOutOfRangeException(nameof(batchSize), "Batch size must be positive.");
}

// Handle array case for efficiency. No new copies of objects are made.
if (source is T[] sourceArray)
{
for (int i = 0; i < sourceArray.Length;)
{
int currentBatchSize = Math.Min(batchSize, sourceArray.Length - i);
T[] batch = new T[currentBatchSize];
Array.Copy(sourceArray, i, batch, 0, currentBatchSize);
yield return batch;
i += currentBatchSize;
}
}
else
{
// Handle non-array enumerables
List<T> currentBatch = new List<T>(batchSize);
using IEnumerator<T> enumerator = source.GetEnumerator();
while (enumerator.MoveNext())
{
currentBatch.Add(enumerator.Current);
if (currentBatch.Count == batchSize)
{
yield return currentBatch.ToArray();
currentBatch.Clear(); // Reuse the list
}
}

// Yield the last partial batch if it has items
if (currentBatch.Count > 0)
{
yield return currentBatch.ToArray();
}
}
}

/// <summary>
/// Creates overlapping batches from an enumerable sequence using a sliding window approach.
/// </summary>
/// <remarks>
/// This method generates batches by taking sections of the source sequence.
/// For example, `[1,2,3,4,5,6]` with a window size of 3 results in `[1,2,3]`, `[2,3,4]`, `[3,4,5]`, and `[4,5,6]`.
/// Unlike the `Batch` method, all returned batches will have the exact size of <paramref name="windowSize"/>.
/// If the source sequence contains fewer items than the <paramref name="windowSize"/>, no batches will be returned.
/// This method uses deferred execution.
/// </remarks>
/// <typeparam name="T">The type of elements in the sequence.</typeparam>
/// <param name="source">The source enumerable sequence.</param>
/// <param name="windowSize">The exact size for each sliding window batch.</param>
/// <returns>An `IEnumerable<T[]>` where each element is an array representing a sliding window batch.</returns>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="source"/> is null.</exception>
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="windowSize"/> is less than or equal to 0.</exception>
public static IEnumerable<T[]> BatchSliding<T>(this IEnumerable<T> source, int windowSize)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
}

if (windowSize <= 0)
{
throw new ArgumentOutOfRangeException(nameof(windowSize), "Window size must be positive.");
}

// Eagerly convert to an array to allow for indexed access, which is required for a sliding window.
var sourceArray = source.ToArray();

// Determine the number of possible windows we can create.
int possibleWindows = sourceArray.Length - windowSize + 1;

// Iterate from the first possible window to the last.
for (int i = 0; i < possibleWindows; i++)
{
// Create a new array for the current window.
T[] window = new T[windowSize];
Array.Copy(sourceArray, i, window, 0, windowSize);
yield return window;
}
}
}
Loading