1

Closed

Comparison methods compares with self

description

When using the intersect or except methods. I've noticed that items from the first array are compared with eachother when the second array is empty.

For example:
var newSeries = [ { id: function() { return 1; }, name: 'a'}, { id: function() { return 2; }, name: 'b' } ];
var currentSeries = [ { ident: function() { return 1; }, name: 'd' } ];
$linq(newSeries).except(currentSeries, function (y, x) { console.info(x, y); return x.id() == y.ident(); }).toArray();
I get this array:
[ { id: function() { return 2; }, name: 'b' } ]
But when the currentSeries array is empty:
var newSeries = [ { id: function() { return 1; }, name: 'a'}, { id: function() { return 2; }, name: 'b' } ];
var currentSeries = [ ];
$linq(newSeries).except(currentSeries, function (y, x) { console.info(x, y); return x.id() == y.ident(); }).toArray();
I get an error "Object #<Object> has no method 'ident'" because the first and second item in the newSeries array get compared with eachother.
Closed Mar 2, 2016 at 5:35 AM by battousai999

comments

battousai999 wrote Mar 8, 2014 at 3:13 AM

As you state, the first and second items in the newSeries array (of your example) do get compared using the comparer you passed to the except call. The code for $linq's except method does this because it's trying to mirror a behavior of the Except method in the .NET version of LINQ--namely, the behavior where the Except method only returns distinct elements from the "first" or "source" set. So, for instance, given the following code (which has duplicates in the first set):
var results = $linq([1, 2, 3, 4, 1, 2, 3, 4]).except([4]).toArray();
The 'results' variable would equal the array "[1, 2, 3]", rather than the array "[1, 2, 3, 1, 2, 3]". The duplicate elements will not show up in the results. The except method in $linq facilitates this distinctness behavior (when a comparer is given) by using the given comparer to test for equality between the current element of the first set being evaluated and the items from the first set that have made it into the set of results that will be returned by the except method.

One reason that your issue would not apply in the .NET version of LINQ is because the Except method only operates upon two sets of the same type of object. When I implemented $linq's except method, I did it with the spirit of that "sameness" in mind. In other words, the comparer is assumed either to be working with the same types of objects between the two sets or to be able to work with whatever item from the two sets it is given.

I can think of two different ways to resolve your issue. The first involves making your comparer able to handle either of your "types" of objects (in either function parameter position). For instance:
var newSeries = [ { id: function() { return 1; }, name: 'a'}, { id: function() { return 2; }, name: 'b' } ];
var currentSeries = [ ];

var idProjection = function (x)
{
    if (x.id !== undefined)
        return x.id();
    else if (x.ident !== undefined)
        return x.ident();
    else
        throw new Error('Invalid projection target.');
};

$linq(newSeries)
    .except(currentSeries, 
        function (y, x) { console.info(x, y); return idProjection(x) == idProjection(y); })
    .toArray();
The second way I can think to resolve your issue is to use a projection (using the $linq 'select' method) on the second set to map its items to something more similar "type-wise" to the items in the first set. For instance:
var newSeries = [ { id: function() { return 1; }, name: 'a'}, { id: function() { return 2; }, name: 'b' } ];
var currentSeries = [ ];

var projection = function (x)
{
    return { id: function () { return x.ident(); }, name: x.name };
};

$linq(newSeries)
    .except($linq(currentSeries).select(projection), 
        function (y, x) { console.info(x, y); return x.id() == y.id(); })
    .toArray();

marcselman wrote Mar 8, 2014 at 3:20 PM

That's a great explaination!
I've lookup up why Linq is implemented this way and found this post:
http://social.msdn.microsoft.com/Forums/en-US/aa7687b8-34be-4268-a6ed-ba0641edf12f/linq-operators-except-also-behaves-like-distinct?forum=csharpgeneral

I've ported the sample posted in that thread to $linq and came up with:
var newSeries = [ { id: function() { return 1; }, name: 'a'}, { id: function() { return 2; }, name: 'b' } ];
var currentSeries = [ { ident: function() { return 2; }, name: 'd' } ];
$linq(newSeries).where(function (x) { return !$linq(currentSeries).any(function (y) { return y.ident() == x.id(); }); }).toArray();
It seems to be working fine!
Thanks for the support and the great library.