Pythonic Alternatives to .NET LINQ Extension Methods

For .NET developers that are unfamiliar with Python, they may not be aware that the LINQ methods they use have already existed in the Python language for a long time.

For Python developers who may be unfamiliar with LINQ, they are a very convenient extension methods that allow you to process lists by filtering and modifying the resulting output list items.

Before we start, let’s make it clear that this is a hater-free zone. The examples are contrived and I am not attempting to make either language out as better or worse. As always, use the most comfortable and appropriate tool for the job.

Setup

For these examples, we will create a list of key/value pairs in Python and C# (for a .NET language) and display the results.

theList = ((1,2), (2,3), (3,4), (4,5))
((1, 2), (2, 3), (3, 4), (4, 5))
// C# doesn't have a compact way of creating KeyValuePairs.
IList<KeyValuePair<int,int>> theList = new List<KeyValuePair<int,int>>() {
    new KeyValuePair<int,int>(1, 2),
    new KeyValuePair<int,int>(2, 3),
    new KeyValuePair<int,int>(3, 4),
    new KeyValuePair<int,int>(4, 5) };
[1, 2], [2, 3], [3, 4], [4, 5]

Basic Filtering: Find All Matches

This covers the case where you want to find all list items that match some desired criteria. We will find all the items where the key value is even and save them to a second list.

result = [t for t in theList if t[0] % 2 == 0]
[(2, 3), (4, 5)]
var result = theList.Where(t => t.Key % 2 == 0).ToList();
[2, 3], [4, 5]

Intermediate Filtering: Modify Item Before Adding Result to List

So we can get all the matches. But what if we only wanted the value for the keys that match? This means search on the first item in the pair and return the second item. No problem. Python uses list comprehensions to iterate and perform functions on lists. (The Python ** operator means to the power of. C# uses the Pow() method in the Math library.)

result1 = [t[1] for t in theList if t[0] % 2 == 0]
result2 = [t[1]**2 for t in theList if t[0] % 2 == 0]
result3 = [t[1]**2 for t in theList if t[0] % 2 == 666]  # No items will match!
(result1) [3, 5]
(result2) [9, 25]
(result3) []  (This represents an empty list)
var result4 = theList.Where(t => t.Key % 2 == 0).Select(t => t.Value);
var result5 = theList.Where(t => t.Key % 2 == 0)
                     .Select(t => Math.Pow(t.Value, 2));
var result6 = theList.Where(t => t.Key % 2 == 666);
(result4) 3, 5
(result5) 9, 25
(result6) - (A empty list of same type as 'theList')

Find First Match then Stop

The previous searches iterate through the entire list to find all possible matches. Sometimes we only want to find the first match. If the list had a million items and we find the first item matches our criteria, we don’t always want to waste time and look for another match that will never occur.

Below we will use the Python next() function to return the first match without evaluating the entire source list.

next(t[1] for t in theList if t[0] == 2)
3

But what if the item is not in the list? If the item is not found, the next() method will throw an exception and the program will stop. For just this reason, next() allows the programmer to specify the value that should be returned if the match criteria are never met. In our case, we set the “default” value to the string “n/a”.

One important thing to note is that Python types are dynamic. They can change types at will. Typically with C# we specific a fixed type and can’t change it on the fly. (.NET has the DLR to allow dynamic types also if desired, though).

result1 = next((t[1] for t in theList if t[0] == 666), 'n/a')
result2 = next((t[1] for t in theList if t[0] == 666), None)
(result1) 'n/a'
(result2) None

.NET has a similar concept with the extension method FirstOrDefault(). Similarly, this method will stop iterating the list once it finds a value that meets the criteria. It is different from Python in that if no match is found, it will return the “Default” value for the data types it was matching against. In this case it is KeyValuePair<int, int>.

var result3 = theList.FirstOrDefault(t => t.Key % 2 == 666);
(result3) [0, 0]

Summary

This covers the most frequently used .NET LINQ extension methods for in-memory operations. However, LINQ has additional capabilities that are not covered here. Check them out some time.

TL;DR

The Python next() function fascinated me. I was initially confused about how it worked. The next function returns an iterator that is referencing a list member. Normally it might be used in a function that used the yield function to return a similar iterator. In our search cases, it returned an iterator referencing the first item that met our criteria.