If you google the word predicate you get a definition similar to the following:
"something which is affirmed or denied concerning an argument of a
proposition."
which is a valid definition, but for our sake we can just
refer to it as
"a function that takes in one or more parameters and returns true or
false".
here's a couple of examples:
private static bool isEvenLength(string value)
{
return value.Length % 2 == 0;
}
private static bool isOddLength(string value)
{
return value.Length % 2 == 1;
}
That's pretty much all there is to that, functions that return a boolean value.
Don't believe me? cause you really shouldn't I'm just some guy with a blog on
the internet which we all know is 50% half-truths and 40% flat out lies; making
it only about 35% true.
namespace pav.predicateExample;
class Program
{
private static bool isEvenLength(string value)
{
return value.Length % 2 == 0;
}
private static bool isOddLength(string value)
{
return value.Length % 2 == 1;
}
static void Main(string[] args)
{
string[] names = { "Mike", "Pawel", "Magda", "Tomek", "Marin", "Ivan", "Trish", "Jake" };
//define our predicate
Predicate<string> pEvenLength = Program.isEvenLength;
Console.WriteLine("\nNames that have an even number of chars");
foreach (var name in Array.FindAll(names, pEvenLength))
Console.WriteLine(name);
//pass a function in as a predicate
Console.WriteLine("\nNames that have an odd number of chars");
foreach (var name in Array.FindAll(names, isOddLength))
Console.WriteLine(name);
//pass an inline function in as a predicate
Console.WriteLine("\nNames that start with 'M'");
foreach (var name in Array.FindAll(names, n => n.Substring(0, 1) == "M"))
Console.WriteLine(name);
}
}
We have three uses of predicates above
// 1 define our predicate, here we define a predicate pEvenLength and we set it's value
// to our isEvenLength function.
Predicate<string> pEvenLength = Program.isEvenLength;
Console.WriteLine("\nNames that have an even number of chars");
foreach (var name in Array.FindAll(names, pEvenLength))
Console.WriteLine(name);
// 2 we pass a function in as a predicate, here rather than defining a predicate,
// we simply pass our isOddLength function directly into our FindAll function.
Console.WriteLine("\nNames that have an odd number of chars");
foreach (var name in Array.FindAll(names, isOddLength))
Console.WriteLine(name);
// 3 pass an inline function as a predicate, rather than explicitly creating a function
// we simply define one inline pass it in as a parameter to our FindAll function.
Console.WriteLine("\nNames that start with 'M'");
foreach (var name in Array.FindAll(names, n => n.Substring(0, 1) == "M"))
Console.WriteLine(name);
If we run our application, we get the following.
That's all there is to predicates, it's just a function that return true or false, often time's it's used to filter array, or validate data. One thing that we did not mention explicitly, is that defined predicates can only accept one argument, meaning that if for example we had an array of numbers and we wanted to filter all of the numbers that are multiple of and 4 we'd have to do something like the following.
namespace pav.predicateExample2;
class Program
{
static void Main(string[] args)
{
var numbers = Enumerable.Range(0, 100).ToArray();
Predicate<int> isMultipleOf3Predicate = (x) => x % 3 == 0;
Predicate<int> isMultipleOf4Predicate = (x) => x % 4 == 0;
var multiplesOf3and4 = Array.FindAll(numbers, isMultipleOf3Predicate)
.Concat(Array.FindAll(numbers, isMultipleOf4Predicate)).Distinct().ToArray();
Array.ForEach<int>(multiplesOf3and4, i => Console.Write(i + " "));
}
}
we defined two predicates, ran our array through both of them, concatenated the results then had to remove the duplicates with a distinct and then again sort to get a sorted list. An alternative solution to circumvent this one argument restriction is create a function which accepts multiple arguments and returns one predicate.
namespace pav.predicateExample;
class Program
{
public static Predicate<int> MultipleOf(params int[] multiples)
{
return x =>
{
foreach (var m in multiples)
if (x % m == 0)
return true;
return false;
};
}
static void Main(string[] args)
{
var numbers = Enumerable.Range(0, 100).ToArray();
var multiples = Array.FindAll(numbers, MultipleOf(3, 4));
Array.ForEach<int>(multiples, i => Console.Write(i + " "));
}
}
This not only simplifies our codebase, but also eliminates the need to call distinct or sort, decreasing the number of times we have to iterate over our array.
Below we can compare our two solutions.
namespace pav.predicateExample2;
class Program
{
// predicate factory function
public static Predicate<int> MultipleOfPredicateFactory(params int[] multiples)
{
return x =>
{
foreach (var m in multiples)
if (x % m == 0)
return true;
return false;
};
}
static void Main(string[] args)
{
var numbers = Enumerable.Range(0, 100).ToArray();
// using multiple predicates
Predicate<int> isMultipleOf3Predicate = (x) => x % 3 == 0;
Predicate<int> isMultipleOf4Predicate = (x) => x % 4 == 0;
var multiplesOf3and4 = Array.FindAll(numbers, isMultipleOf3Predicate)
.Concat(Array.FindAll(numbers, isMultipleOf4Predicate))
.Distinct().ToArray();
Array.Sort(multiplesOf3and4);
Array.ForEach<int>(multiplesOf3and4, i => Console.Write(i + " "));
Console.WriteLine();
// using a predicate factory function
var multiples = Array.FindAll(numbers, MultipleOfPredicateFactory(3, 4));
Array.ForEach<int>(multiples, i => Console.Write(i + " "));
}
}