← Go Back

How to Implement LINQ methods in JavaScript - Part 4

Broken Post?Let me know

Photo by Chris Lawton on Unsplash This is the 4th part of the series and I will cover common Set operations in this article.

Here are the methods covered so far.

  1. Part 1 〰️ Select, Aggregate, Where, OrderBy (Ascending, Descending)
  2. Part 2 〰️ Any, Distinct, Concat, SelectMany
  3. Part 3 〰️ Reverse, Zip, Min/Max
  4. Part 4 〰️ Union, Intersect, Except
  5. Part 5 〰️ Sum, Average, Count
  6. Part 6 〰️ First, Last, DefaultIfEmpty, Skip, Take
  7. Part 7 〰️ Empty, Repeat, Range
  8. Part 8 〰️ All, Contains, SequenceEqual

🔴 Overview

Here are the methods covered. [table id=4 /] There is no one-to-one equivalent in JavaScript, so I added Lodash equivalents in the table this time. For production codes, use Lodash as their implementation is thoroughly battle-tested.

The sample collections used in this part are shown as below. (Orders is same as last 3 series and I added DomesticOrders and InternationalOrders for examples this time)

C

private static List<Order> Orders = new List<Order>{
new Order(id: 1, quantity: 40, orderDate: new DateTime(2018, 1,1,1,1,1,1)),
new Order(id: 2, quantity: 20, orderDate: new DateTime(2018, 2,2,2,2,2,2)),
new Order(id: 3, quantity: 30, orderDate: new DateTime(2018, 3,3,3,3,3,3)),
new Order(id: 4, quantity: 10, orderDate: new DateTime(2018, 4,4,4,4,4,4)),
new Order(id: 5, quantity: 20, orderDate: new DateTime(2018, 5,5,5,5,5,5)),
};
private static List<Order> DomesticOrders = new List<Order>{
new Order(id: 1, quantity: 40, orderDate: new DateTime(2018, 1,1,1,1,1,1)),
new Order(id: 11, quantity: 20, orderDate: new DateTime(2018, 11,2,2,2,2,2)),
new Order(id: 111, quantity: 450, orderDate: new DateTime(2018, 11,1,2,2,2,2)),
new Order(id: 1111, quantity: 230, orderDate: new DateTime(2018, 11,11,2,2,2,2)),
};
private static List<Order> InternationalOrders = new List<Order>{
new Order(id: 3, quantity: 30, orderDate: new DateTime(2018, 3,3,3,3,3,3)),
new Order(id: 33, quantity: 300, orderDate: new DateTime(2018, 3,3,3,3,33,3)),
new Order(id: 4, quantity: 10, orderDate: new DateTime(2018, 4,4,4,4,4,4)),
new Order(id: 44, quantity: 100, orderDate: new DateTime(2018, 4,4,4,4,44,4)),
new Order(id: 5, quantity: 20, orderDate: new DateTime(2018, 5,5,5,5,5,5)),
new Order(id: 55, quantity: 200, orderDate: new DateTime(2018, 5,5,5,5,55,5)),
};
view raw OrdersAll.cs hosted with ❤ by GitHub

JavaScript

const orders = [
{ id: 1, quantity: 40, orderDate: new Date(2018, 1, 1, 1, 1, 1) },
{ id: 2, quantity: 20, orderDate: new Date(2018, 2, 2, 2, 2, 2) },
{ id: 3, quantity: 30, orderDate: new Date(2018, 3, 3, 3, 3, 3) },
{ id: 4, quantity: 10, orderDate: new Date(2018, 4, 4, 4, 4, 4) },
{ id: 5, quantity: 20, orderDate: new Date(2018, 5, 5, 5, 5, 5) }
];
const domesticOrders = [
{ id: 1, quantity: 40, orderDate: new Date(2018, 1, 1, 1, 1, 1) },
{ id: 11, quantity: 20, orderDate: new Date(2018, 11, 2, 2, 2, 2) },
{ id: 111, quantity: 450, orderDate: new Date(2018, 11, 1, 2, 2, 2) },
{ id: 1111, quantity: 230, orderDate: new Date(2018, 11, 11, 2, 2, 2) }
];
const internationalOrders = [
{ id: 3, quantity: 30, orderDate: new Date(2018, 3, 3, 3, 3, 3) },
{ id: 33, quantity: 300, orderDate: new Date(2018, 3, 3, 3, 3, 3) },
{ id: 4, quantity: 10, orderDate: new Date(2018, 4, 4, 4, 4, 4) },
{ id: 44, quantity: 100, orderDate: new Date(2018, 4, 4, 4, 4, 4) },
{ id: 5, quantity: 20, orderDate: new Date(2018, 5, 5, 5, 5, 5) },
{ id: 55, quantity: 200, orderDate: new Date(2018, 5, 5, 5, 5, 5) }
];
view raw ordersAll.js hosted with ❤ by GitHub

🔴 Examples

🔸 Union

Union combines two sequences (of type IEnumerable<T>) into one without duplicates.

private static void UnionDemo(List<Order> domesticOrders, List<Order> internationalOrders)
{
var allOrders = domesticOrders.Union(internationalOrders);
PrintOrders(allOrders);
}
view raw UnionDemo.cs hosted with ❤ by GitHub

Array.prototype.union = function(other) {
return [...new Set([...this, ...other])];
};
// SQL has "UNION ALL" that combines two sets without dropping duplicates.
// Array.prototype.unionAll = function(other) {
// return [...this, ...other];
// };
function unionDemo(domesticOrders, internationalOrders) {
const allOrders = domesticOrders.union(internationalOrders);
printOrders(allOrders);
}
view raw unionDemo.js hosted with ❤ by GitHub

Results

💡 C# Result
==================== Union DEMO - Display Domestic & International Orders ====================
Order ID: 1, Quantity: 40, Order Date: 01 Jan 2018 01:01 AM pst
Order ID: 11, Quantity: 20, Order Date: 02 Nov 2018 02:02 AM pst
Order ID: 111, Quantity: 450, Order Date: 01 Nov 2018 02:02 AM pst
Order ID: 1111, Quantity: 230, Order Date: 11 Nov 2018 02:02 AM pst
Order ID: 3, Quantity: 30, Order Date: 03 Mar 2018 03:03 AM pst
Order ID: 33, Quantity: 300, Order Date: 03 Mar 2018 03:03 AM pst
Order ID: 4, Quantity: 10, Order Date: 04 Apr 2018 04:04 AM pst
Order ID: 44, Quantity: 100, Order Date: 04 Apr 2018 04:04 AM pst
Order ID: 5, Quantity: 20, Order Date: 05 May 2018 05:05 AM pst
Order ID: 55, Quantity: 200, Order Date: 05 May 2018 05:05 AM pst
💡 JavaScript Result
==================== Array.prototype.union (LINQ 'Union' Equivalent) DEMO - Display Domestic & International Orders ====================
Order ID: 1, Quantity: 40, Order Date: Thu Feb 01 2018 01:01:01 GMT-0500 (Eastern Standard Time)
Order ID: 11, Quantity: 20, Order Date: Sun Dec 02 2018 02:02:02 GMT-0500 (Eastern Standard Time)
Order ID: 111, Quantity: 450, Order Date: Sat Dec 01 2018 02:02:02 GMT-0500 (Eastern Standard Time)
Order ID: 1111, Quantity: 230, Order Date: Tue Dec 11 2018 02:02:02 GMT-0500 (Eastern Standard Time)
Order ID: 3, Quantity: 30, Order Date: Tue Apr 03 2018 03:03:03 GMT-0400 (Eastern Daylight Time)
Order ID: 33, Quantity: 300, Order Date: Tue Apr 03 2018 03:03:03 GMT-0400 (Eastern Daylight Time)
Order ID: 4, Quantity: 10, Order Date: Fri May 04 2018 04:04:04 GMT-0400 (Eastern Daylight Time)
Order ID: 44, Quantity: 100, Order Date: Fri May 04 2018 04:04:04 GMT-0400 (Eastern Daylight Time)
Order ID: 5, Quantity: 20, Order Date: Tue Jun 05 2018 05:05:05 GMT-0400 (Eastern Daylight Time)
Order ID: 55, Quantity: 200, Order Date: Tue Jun 05 2018 05:05:05 GMT-0400 (Eastern Daylight Time)
view raw UnionDemo results hosted with ❤ by GitHub

I've extended the Array prototype to make the JavaScript version look similar to the LINQ version. 📝NOTE: "Union" in Lodash is named _.union.

🔸 Intersect

"Intersect" compares two sequences and return another sequence with a "common" value.

How do you check for the "commonness"? Intersect in LINQ provides an overload that lets you specify how to compare each value in two sequences by passing an object of type implementing IEqualityComparer<T> interface.

private static void IntersectDemo(List<Order> ordersOnHold, List<Order> domesticOrders, List<Order> internationalOrders)
{
var orderComparer = new OrderEqualityCompaprer();
var usOrdersOnHold = ordersOnHold.Intersect(domesticOrders, orderComparer);
var internationalOrdersOnHold = ordersOnHold.Intersect(internationalOrders, orderComparer);
const int indentBy = 4;
const char dividerCharacter = '*';
PrintHeaderFooter("US Orders on hold", () => PrintOrders(usOrdersOnHold, indentBy), indentBy, dividerCharacter);
PrintHeaderFooter("International Orders on hold", () => PrintOrders(internationalOrdersOnHold, indentBy), indentBy, dividerCharacter);
}
view raw IntersectDemo.cs hosted with ❤ by GitHub

Array.prototype.intersect = function(other, idSelector = obj => obj) {
const otherSet = new Set([...other.map(idSelector)]);
// Reference: http://2ality.com/2015/01/es6-set-operations.html
const intersection = new Set(this.filter(object => otherSet.has(idSelector(object))));
return [...intersection];
};
function intersectDemo(ordersOnHold, domesticOrders, internationalOrders) {
const orderIdSelector = order => order.id;
const usOrdersOnHold = ordersOnHold.intersect(domesticOrders, orderIdSelector);
const internationalOrdersOnHold = ordersOnHold.intersect(internationalOrders, orderIdSelector);
const indentBy = 4;
const dividerCharacter = "*";
printHeaderFooter("US Orders on hold", () => printOrders(usOrdersOnHold, indentBy), indentBy, dividerCharacter);
printHeaderFooter("International Orders on hold", () => printOrders(internationalOrdersOnHold, indentBy), indentBy, dividerCharacter);
}
view raw intersectDemo.js hosted with ❤ by GitHub

Results

💡 C# Result
==================== Intersect (intersection) DEMO - Find All Orders on Hold ====================
******************** US Orders on hold ********************
Order ID: 1, Quantity: 40, Order Date: 01 Jan 2018 01:01 AM pst
******************** International Orders on hold ********************
Order ID: 3, Quantity: 30, Order Date: 03 Mar 2018 03:03 AM pst
Order ID: 4, Quantity: 10, Order Date: 04 Apr 2018 04:04 AM pst
Order ID: 5, Quantity: 20, Order Date: 05 May 2018 05:05 AM pst
💡 JavaScript Result
==== Array.prototype.intersect (LINQ 'Intersect' Equivalent) DEMO - Find All Orders on Hold ======
******************** US Orders on hold ********************
Order ID: 1, Quantity: 40, Order Date: Thu Feb 01 2018 01:01:01 GMT-0500 (Eastern Standard Time)
******************** International Orders on hold ********************
Order ID: 3, Quantity: 30, Order Date: Tue Apr 03 2018 03:03:03 GMT-0400 (Eastern Daylight Time)
Order ID: 4, Quantity: 10, Order Date: Fri May 04 2018 04:04:04 GMT-0400 (Eastern Daylight Time)
Order ID: 5, Quantity: 20, Order Date: Tue Jun 05 2018 05:05:05 GMT-0400 (Eastern Daylight Time)

Our contrived order array contains objects with a property called id so I passed a callback (idSelector) to let Array.prototype.intersect to use it to make a comparison for each value in two sequences.

📝NOTE: While Intersect in LINQ is similar to _.intersectionWith in Lodash, JavaScript version is roughly equivalent to _.intersectionBy.

🔸 Except (Difference)

"Except" compares two sequences and return a new sequence with values that exists in the caller but not in the compared.

private static void ExceptDemo(List<Order> ordersOnHold, List<Order> domesticOrders, List<Order> internationalOrders)
{
var allOrders = domesticOrders.Union(internationalOrders);
var allOrdersNotOnHold = allOrders.Except(ordersOnHold, new OrderEqualityCompaprer());
PrintOrders(allOrdersNotOnHold);
}
view raw ExceptDemo.cs hosted with ❤ by GitHub

Array.prototype.except = function(other, idSelector = obj => obj) {
const otherSet = new Set([...other.map(idSelector)]);
// Reference: http://2ality.com/2015/01/es6-set-operations.html
const difference = new Set(this.filter(object => !otherSet.has(idSelector(object))));
return [...difference];
};
function exceptDemo(ordersOnHold, domesticOrders, internationalOrders) {
const orderIdSelector = order => order.id;
const allOrders = domesticOrders.union(internationalOrders);
const allOrdersNotOnHold = allOrders.except(ordersOnHold, orderIdSelector);
printOrders(allOrdersNotOnHold);
}
view raw exceptDemo.js hosted with ❤ by GitHub

Results

💡 C# Result
==================== Except (subtraction) DEMO - Get all orders NOT on hold ====================
Order ID: 11, Quantity: 20, Order Date: 02 Nov 2018 02:02 AM pst
Order ID: 111, Quantity: 450, Order Date: 01 Nov 2018 02:02 AM pst
Order ID: 1111, Quantity: 230, Order Date: 11 Nov 2018 02:02 AM pst
Order ID: 33, Quantity: 300, Order Date: 03 Mar 2018 03:03 AM pst
Order ID: 44, Quantity: 100, Order Date: 04 Apr 2018 04:04 AM pst
Order ID: 55, Quantity: 200, Order Date: 05 May 2018 05:05 AM pst
💡 JavaScript Result
==================== Array.prototype.except (LINQ 'Except' Equivalent) DEMO - Get all orders NOT on hold ====================
Order ID: 11, Quantity: 20, Order Date: Sun Dec 02 2018 02:02:02 GMT-0500 (Eastern Standard Time)
Order ID: 111, Quantity: 450, Order Date: Sat Dec 01 2018 02:02:02 GMT-0500 (Eastern Standard Time)
Order ID: 1111, Quantity: 230, Order Date: Tue Dec 11 2018 02:02:02 GMT-0500 (Eastern Standard Time)
Order ID: 33, Quantity: 300, Order Date: Tue Apr 03 2018 03:03:03 GMT-0400 (Eastern Daylight Time)
Order ID: 44, Quantity: 100, Order Date: Fri May 04 2018 04:04:04 GMT-0400 (Eastern Daylight Time)
Order ID: 55, Quantity: 200, Order Date: Tue Jun 05 2018 05:05:05 GMT-0400 (Eastern Daylight Time)
view raw Except Demo Result hosted with ❤ by GitHub

The implementation of Array.prototype.except is almost same as intersect.

The only difference between Array.prototype.intersect and Array.prototype.except is whether to include the record in the other set or not.

Take a close look at callback in filter method.

// Difference between "intersect" & "except" implementation in JavaScript.
// While "intersect" check if "otherSet" has the value
// "except" does the opposite;
// ⚠️ Notice "!otherSet" in "const difference = new Set(this.filter(object => !otherSet.has(idSelector(object))));"
Array.prototype.intersect = function(other, idSelector = obj => obj) {
...
const intersection = new Set(this.filter(object => otherSet.has(idSelector(object))));
return ...
};
Array.prototype.except = function(other, idSelector = obj => obj) {
...
const difference = new Set(this.filter(object => !otherSet.has(idSelector(object))));
return ...
};

📝NOTE: While Except in LINQ is similar to _.differenceWith in Lodash, JavaScript version is roughly equivalent to _.differenceBy.

🔴 Closing Remark

"Union", "Intersect", and "Except" are the most common Set operations. I hope this article helped you understand how to implement Set operations in JavaScript.

JavaScript implementations in the examples above are not production ready as it's not optimized/tested. So as I mentioned before, use Lodash for Set operations in JavaScript.

Any feedback or error reports are always welcome.

The full source code and instructions on how to run them are on GitHub.