Working with and manipulating arrays has always been one of my least favorite aspects of JavaScript development. Ever since I became a professional .NET developer and C# became my language of choice, my distaste for working with arrays in JavaScript only grew stronger due to the lack of access to one of my favorite parts of the .NET Framework: LINQ.
For those unfamiliar with LINQ, it's a library in the .NET Framework that allows a programmer to perform a large variety of complex operations on arrays, lists, and other collections with a minimal amount of code. This is done using a combination of methods such as GroupBy, OrderBy, and Join, and lambda expressions (anonymous functions).
For example, the following code sample creates a list of time entry objects and then uses LINQ methods to convert it into a list of anonymous objects—each object has the name of a service item and the percentage of minutes spent on that service item.
var entries = new List()
{
new TimeEntry("Eng Services", 15),
new TimeEntry("Eng Services", 240),
new TimeEntry("Eng Services", 180),
new TimeEntry("N/C Eng Services", 15),
new TimeEntry("N/C Eng Services", 30),
new TimeEntry("N/C Eng Services", 80),
new TimeEntry("N/C Eng Services", 120)
};
var totalMinutes = entries.Sum(e => e.DurationMinutes);
var serviceItemsBreakdown = entries.GroupBy(e => e.ServiceItem)
.Select(e => new { ServiceItem = e.Key, Percentage = (float)e.Sum(i => i.DurationMinutes) * 100 / (float)totalMinutes })
.ToList();
The above code results in a list of two objects: { ServiceItem = "Eng Services", Percentage = 63.97059 } and { ServiceItem = "N/C Eng Services", Percentage = 36.02941 }
If we were to do this same operation using vanilla JavaScript methods, it would be a lot more complex, and would probably use more than one for-loop. More importantly, it involves reinventing the wheel or copying someone else's algorithm, and that's usually a waste of your employer's money and/or your client's money. And for a .NET developer, there is also inherent frustration researching or writing an algorithm for something that would take you 10 seconds to do using LINQ.
Solution To The Problem
About two months ago, it dawned on me to check for any JavaScript libraries that provide LINQ-type methods, and it turns out that there are several. I decided to use Linq.js, which can be found at http://linqjs.codeplex.com/.
I like most that this particular library implements the majority of LINQ's methods. The signatures of the methods are even identical, so .NET developers can jump right into using it without much guidance.
As with any JavaScript library, there are pros and cons to using it.
Pros:
- At 21kb when minified, it's a pretty lightweight library.
- The learning curve is almost non-existent for C#/.NET developers.
- It's incredibly performant. Even performing complex operations on large arrays is extremely fast.
- It allows you to manipulate arrays and other types of collections with minimal code.
- It seems very stable (I haven't run into any bugs with it yet).
- It has been packaged for NPM (you can find that at https://www.npmjs.com/package/linq, though I personally haven't tested that version of it).
Cons:
- If you're already using a lot of libraries, the 21kb for this library might not be worth it for some projects.
- The last time it was updated by its creator was back in 2011, so it's not actively being developed.
- Non .NET developers may have a steeper learning curve.
I'd only recommend using this library for projects where you're doing a lot of array manipulation. For example, if you're using a library like Telerik's Kendo UI to create a dashboard with charts and graphs, and you need to twist your data into various shapes in order to render different chart types, then Linq.js will save you a decent bit of time.
Demo Application
To demonstrate some of Linq.js's more common functions, I've created a quick and dirty little application. You can download the code here.
When it comes down to it, Linq.js is all about data manipulation, so the first code I'll show you is the file Data.js where I define a couple of classes (Person and Industry being the most important) and create an array of each. This is the data that we will use to demonstrate the functionality of Linq.js.
var Person = function(firstName, lastName, occupation, ageInYears){
this.FirstName = firstName;
this.LastName = lastName != null ? lastName : '';
this.Occupation = occupation;
this.AgeInYears = ageInYears;
this.GetTableRow = function(){
return '' + this.FirstName + ''
+ '' + this.LastName + ''
+ '' + this.Occupation + ''
+ '' + this.AgeInYears + '';
}.bind(this);
};
var Industry = function(name, occupations){
this.Name = name;
this.Occupations = occupations;
}
var Group = function(key, count){
this.Key = key;
this.Count = count;
this.GetTableRow = function(){
return '' + this.Key + ''
+ '' + this.Count + '';
}.bind(this)
};
var people = [];
people.push(new Person('John', 'Doe', 'Doctor', 38));
people.push(new Person('Mary', 'Jane', 'Pharmacist', 29));
people.push(new Person('John', 'Downer', 'Sanitation Worker', 43));
people.push(new Person('Dee', 'Tective', 'Police Officer', 35));
people.push(new Person('Pikupin', 'Dropov', 'Russian Chaufer', 34));
people.push(new Person('Don', 'Lothario', 'Marriage Counselor', 40));
people.push(new Person('Doctor', null, 'Time traveler', 984));
people.push(new Person('Jane', 'Doe', 'Massage therapist', 29));
people.push(new Person('Christopher', 'Olsen', 'Application Developer', 34));
people.push(new Person('Kelly', 'Smith', 'Police Officer', 40));
var industries = [];
industries.push(new Industry('Healthcare', ['Doctor', 'Pharmacist']));
industries.push(new Industry('Government', ['Sanitation Worker']));
industries.push(new Industry('Law Enforcement', ['Police Officer']));
industries.push(new Industry('Service', [
'Russian Chaufer',
'Marriage Counselor',
'Massage therapist'
]));
industries.push(new Industry('Software', ['Application Developer']));
industries.push(new Industry('Sci Fi', ['Time traveler']));
So, we have an array of People with their first names, last names, occupations, and ages defined. We also have an array of Industries that each contain a name and an array of occupations that fit into it.
OrderBy
The data manipulation on these arrays is performed in the file Main.js. The first thing we want to do is take the People array and order (sort) it by LastName.
The following code uses the Linq.js OrderBy method to do exactly that:
var sortedByLastName = Enumerable.From(people).OrderBy(function(person){
return person.LastName;
}).ToArray();
In order to use a function from Linq.js, we have to turn it into an Enumerable, and we do that by calling the static method From on the Enumerable class and passing our array as a parameter. From there, we can use method chaining (just like in C#'s version of LINQ) to perform any number of operations.
In this example, we call OrderBy and pass it one Parameter: an anonymous function that takes one of the array's elements as a parameter, and returns the property for the item by which we want to sort.
This results in an Enumerable class instance, so the last thing we do is turn it back into a JavaScript array by calling the ToArray method.
The first thought that enters your head here may be, "Hey, wait a minute, this just does the same thing as Array.prototype.sort!" Which is true. However, it has two advantages.
Advantages
- OrderBy is a stable sort regardless of browser implementation, whereas Array.prototype.sort can cause you problems on some browsers (namely Chrome). You can read more about it in this blog, Sorting in JavaScript: Handling Google Chromes Unstable Sort.
- The signature of the function is much simpler than that of Array.Prototype.Sort, so you accomplish the same thing with less code.
In any case, when we run this code by opening the web page, we get the following result:
If you want to sort them in descending order, simply use the OrderByDescending method.
Where
Next, I want to take only the Persons whose last name starts with 'D', and order them by AgeInYears, but in descending order. So, the oldest people will be first, followed by younger people.
The following code accomplishes this:
var onlyDLastNameOrderBy = Enumerable.From(people).Where(function(person){
return person.LastName[0] == 'D';
}).OrderByDescending(function(person){
return person.AgeInYears;
}).ToArray();
Again, we start by using Enumerable.From(people) to convert our array to an Enumerable. Then, we use the Where function, passing it an anonymous function that returns the first letter of a person's last name. This function effectively "filters" the array, resulting in an Enumerable that contains only the people with last names starting with 'D'.
Finally, we use the OrderByDescending method to order the Persons by AgeInYears, in descending order. The signature for this method is identical to that of OrderBy. Then we use ToArray to convert our results back into a JavaScript array.
If we run this code, we get the following result:
GroupBy
Now, let's go beyond just returning different sub-sets of Persons—let's use LINQ to turn the array of Persons into something else entirely. Specifically, I want a list of ages, along with the number of Persons of that age.
Here's the code:
var groupedByAge = Enumerable.From(people).GroupBy(function(person){
return person.AgeInYears;
}, function(person){
return person;
}, function(key, group){
return new Group(key, group.Count());
}).OrderBy(function(group){
return group.Key;
}).ToArray();
Here, we use the GroupBy method. What this method does is group array elements by a given property, and then returns a new Enumerable of objects based on the grouping property and the sub-set of elements for each value of that property. We need to pass three anonymous functions into this method.
Anonymous Functions
- A function that returns the property we want to group by (AgeInYears in this case).
- A function that returns the object we want to include in the Group. In this case, I'm returning the entire person.
- A function that takes a Key (a value of your grouping property), and a Group (the collection of items that have that value for the grouping property), and returns a new object based on those two parameters. In this case, I'm returning a new Group, which has two properties: an age, and the number of people that are of that age.
It should be noted that I'm using another Linq.js method here: Count. This returns the number of items in the collection. I don't use the JavaScript Length property because that the Group parameter is not a JavaScript array, it's a LINQ Enumerable, so Length won't work on it.
Finally, I use OrderBy to order the groupings in ascending order, and we get the following result when we run the code:
We'll be using the GroupBy function again later to return percentages instead of raw counts.
Join
Next, I want to get a collection of all the people with the Industry that they're in. This is tricky because Industry is not a property on the Person object. Occupation is though, and each Industry has an array of occupations in it, so that's the only connection we have between our two sets of data.
We need to do a Join, though it could also be accomplished with some nested for-loops. For demonstration purposes, we're going to use the LINQ approach and do a Join.
First, though, we need to flatten our Industry array into something we can Join to the Person array.
var flattenedOccupations = Enumerable.From(industries).SelectMany(function(industry){
return Enumerable.From(industry.Occupations).Select(function(occupation){
return {
Name: occupation,
Industry: industry.Name
};
}).ToArray();
}).ToArray();
Here, we use the SelectMany function on the Industries array. SelectMany takes an array of objects which have an array as one of their properties and returns a single array of all the items in those sub-arrays.
In our example, we can use SelectMany to get all of the occupations in our array of Industries, but we take it a step further. Instead of just returning the occupations, we use the Select method (analogous to the Array.prototype.map function) to return an array of objects with the name of the occupation and the name of the industry. Our result is a "flattened" array of occupations and their corresponding industries.
Now we can do the actual Join:
var peopleJoinedWithIndustries = Enumerable.From(people).Join(flattenedOccupations,
function(person){
return person.Occupation;
},
function(occupation){
return occupation.Name;
},
function(person, occupation){
return {
FullName: person.FirstName + " " + person.LastName,
Occupation: occupation.Industry
};
}).ToArray();
The Join method takes four parameters.
Parameters
- The array that we want to join our array with (in this case, I want to join people with flattenedOccupations, so I pass in flattenedOccupations). This is also called the inner collection.
- The outer selector. This is an anonymous function that returns the property of our outer collection (people) that we want to use for joining. In this case, I return the Occupation property, because that's what I want to join on.
- The inner selector. This is an anonymous function that returns the property of our inner collection (flattenedOccupations) that we want to use for joining. In this case, I return the Name property.
- A result selector. This is an anonymous function that takes one item from each array and returns a new object based on those two objects. In this case, for each join result, I chose to return a new anonymous object with two properties: the person's full name, and the name of the industry
If we run the code, we get the following result:
In this particular example, we could have accomplished this more easily with a couple nested loops, but that's only because we had to flatten our array of Industries before we could do a Join.
In any case, now that we have an array matching people's names with the industry they're in, we can do something real fancy: get the percentage of people in each industry.
Here's the code:
var percentageByIndustry = Enumerable.From(peopleJoinedWithIndustries).GroupBy(function(person){
return person.Industry;
}, function(person){
return person;
}, function(industryName, group){
return {
Industry: industryName,
Percentage: ((group.Count() / (people.length != 0 ? people.length : 1)) * 100) + '%'
};
}).ToArray();
This is similar to the GroupBy we did earlier. We're grouping the peopleJoinedWithIndustries array by the Industry property, and returning a new array of objects. For each object that we're returning, we're assigning a Percentage property with the Count of the group passed into the group selector, divided by the number of people in the original people array (handling for an empty array to avoid dividing by zero), and then multiplied by 100 to get a nice percentage. This gives us the following result:
So, that's it for the demonstration. I only covered a few of the LINQ functions, but Linq.js features 90 methods in total, including MaxBy, MinBy, Average, Distinct, and Contains.
For a full list of functions, you can view the Linq.js reference sheet here.
Learn more about DMC's Web Application Development services.