(Related project: NCase, NDocUtil)
Various C# Utility classes initially developed for the NCase project.
In the Nuget Package Manager Console:
Install-Package NUtil
Here is the list of utilities:
- Pairwise Generator
- ForEach
- Quadratic Processing
- Processing of Chained Dictionaries
- String Processing
Consider (S1, S2 ... Sn) n finite sets, the PairwiseGenerator
generates an enumeration of tuples (s1, s2 ... sn) which ensures that any pair (si, sj) from any pair of set (Si, Sj) appears at least once.
var setCardinals = new int[] {3, 2, 2};
IEnumerable<int[]> tuples = new PairwiseGenerator().Generate(setCardinals);
foreach (int[] t in tuples)
Console.WriteLine("Tuple #{0}: {1}, {2}, {3}", i++, t[0], t[1], t[2]);
Outout:
Tuple #0: 0, 0, 0
Tuple #1: 0, 1, 1
Tuple #2: 1, 0, 0
Tuple #3: 1, 1, 1
Tuple #4: 2, 0, 0
Tuple #5: 2, 1, 1
Tuple #6: 0, 0, 1
Tuple #7: 0, 1, 0
This pairwise generator is used in NCase as an alternative to the default cartesian product, in order to reduce the amount of generated test cases. More about pairwise testing here and there.
- The algorithm tries to distribute the re-use of pairs among the whole set of pairs
- The generation of tuple is lazy, resulting in low initial pre-processing time (only the available set of pairs is generated at startup time)
- Further quantitative and qualitative analysis as well as comparison with existing algorithms would be required to precisely evaluate the properties of the algorithm
Every utility framework re-implements the following ForEach extension method:
var set = Enumerable.Range(0, 10);
set.ForEach(v => Console.Write(v));
Output:
0123456789
The following overload enables placing an action between the processing of the items:
var set = Enumerable.Range(0, 10);
set.ForEach( v => Console.Write(v),
() => Console.Write(", "));
Output:
0, 1, 2, 3, 4, 5, 6, 7, 8, 9
It is an alternative to the Linq Aggregate(...)
function and the string.Join(...)
static method, in order to perform aggregations. Following usage has no counterpart in the C# framework:
var set = Enumerable.Range(0, 10);
set.ForEach( v => SendToServer(v),
() => Thread.Sleep(10));
var set1 = new [] {"a", "b"};
var set2 = new [] {0, 1};
var product = set1.CartesianProduct(set2, (s1, s2) => new { s1, s2 });
foreach (var pair in product)
Console.WriteLine("({0}, {1})", pair.s1, pair.s2);
Output:
(a, 0)
(a, 1)
(b, 0)
(b, 1)
Remark: it is nothing else than a wrapper around:
return from in1 in first
from in2 in second
select selector(in1, in2);
But the wrapper enables to elegantly chain the cartesian product with other transformations.
var set1 = new [] {0, 1, 2};
var product = set1.TriangularProductWithoutDiagonal((s1, s2) => new { s1, s2 });
foreach (var pair in product)
Console.WriteLine("({0}, {1})", pair.s1, pair.s2);
Output:
(0, 1)
(0, 2)
(1, 2)
Let's say, you need a model for the triplet (country, city, street). You can use the following model:
var stats = new Dictionary<string, // Country
Dictionary<string, // City
HashSet<string>>>(); // Street
The purpose of the CascadeExtensions
class is to provide extension methods that reduces as much as possible the need of if-else
statements to handle this kind of model composed of chained dictionaries.
model.CascadeAdd("FR").CascadeAdd("Paris").Add("Rue de la paix");
model.CascadeAdd("FR").CascadeAdd("Paris").Add("Rue de Paradis");
model.CascadeAdd("DE").CascadeAdd("Mainz").Add("Gutenbergplatz");
bool isRemoved1 = model
.CascadeRemove("FR")
.CascadeRemove("Paris")
.CascadeRemove("Rue de la paix");
Console.WriteLine("isRemoved1= {0}", isRemoved1);
bool isRemoved2 = model
.CascadeRemove("FR")
.CascadeRemove("Paris")
.CascadeRemove("Trafalgar Square");
Console.WriteLine("isRemoved2= {0}", isRemoved2);
Output:
isRemoved1= True
isRemoved2= False
You can get items from the model by using the indexer defined in the Dictionary
class. But this indexer is unsafe: it throws an exception if the key does not exist.
var streets1 = model["FR"]["Paris"];
Console.WriteLine("Street in Paris: {0}", string.Join(", ", streets1));
try
{
var streets2 = model["Switzerland"]["Lausanne"]; // UNREGISTERED COUNTRY!
}
catch (KeyNotFoundException e)
{
Console.WriteLine("Streets in Lausanne: KeyNotFoundException has been thrown");
}
Output:
Street in Paris: Rue de la paix, Rue de Paradis
Streets in Lausanne: KeyNotFoundException has been thrown
If you need a safe get implementation, you can use the .CascadeGetOrDefault()
extension method:
var safeStreets1 = model.CascadeGetOrDefault("FR")
.CascadeGetOrDefault("Paris");
Console.WriteLine("Streets in Paris : {0}", string.Join(", ", safeStreets1));
var safeStreets2 = model.CascadeGetOrDefault("Switzerland") // UNREGISTERED COUNTRY!
.CascadeGetOrDefault("Lausanne");
if(safeStreets2 == null)
Console.WriteLine("No street found in Lausanne");
Output:
Streets in Paris : Rue de la paix, Rue de Paradis
No street found in Lausanne
string country,city, street;
bool ok = model.CascadeTryFirst(out country)
.CascadeTryFirst(out city)
.CascadeTryFirst(out street);
Console.WriteLine("CascadeTryFirst: ok={0}, country={1}, city={2}, street={3}",
ok, country, city, street);
CascadeTryFirst: ok=True, country=FR, city=Paris, street=Rue de la paix
The .Lines()
extension method enables splitting a string into an enumeration of lines, whereas .JoinLines()
enables to join a enumeration of lines into a single string:
string txt = "one line\nand a second line";
IEnumerable<string> lines = txt.Lines();
string rejoinedLines = lines.JoinLines();
The .Desindent()
allows to removed the indentation of a string:
string txt = " I was originally indented!";
string desindentedTxt = txt.Desindent(tabIndentation:4);
Console.WriteLine("Before: {0}\nAfter :{1}", txt, desindentedTxt);
Output:
Before: I was originally indented!
After :I was originally indented!