lodash xor array method aka symmetric difference

In lodash there is the _.xor method that can create a new array that is the symmetric difference of the given arrays. In other words it will be an array of elements that show up in the arrays that are unique to each array, but not elements that are shared across all the arrays, or in other words elements that are intersections.

1 - Basic lodash xor example

For a basic example of the lodash xor method consider two arrays one with elements that are the numbers [0,1], and another with the numbers [1,2]. If the arrays are given to the lodash xor method the resulting array should be [0,2]

1
2
3
4
5
6
let _ = require('lodash');
let xor = _.xor([0,1],[1,2]);
console.log(xor);
// [0,2]

I can not say that i get into situations in which I need to use a method like this thus far, but if I need to this is a kind of method where i might just use the lodash xor method and move on in a project. There are many methods in lodash where it is a bot of a grey area as to the question if I should even bother with lodash as there is a native method that can be used. It is also true that there are many methods in lodash where it is really not all that hard to do what the method does with plain od vanilla javaScript. However I am not so sure that this is one of thous methods in lodash,

2 - Looking under the hood with this one in the lodash source code

With a lot of lodash methods it is not always so hard to make a vanilla javaScript alternative, however with the lodash xor method it might not be so easy. Even if it is easy to make a stand alone method there might still be a lot to take into account when it comes to certain situations.

As a lodash end user I often just call methods like _.xor, get the result that I want, and then move on. However some times I take a look at the lodash source code to gain a deeper understanding and apprehension of what is going on with the lodash source code. Even if you do not use lodash, or maybe just a method or two now and then at all, the lodash source code is still worth checking out when it comes to reading code.

I was able to get a similar result by copying and pasting in much of the lodash internals like this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
// arrayIncludes
function arrayIncludes(array, value) {
const length = array == null ? 0 : array.length
return !!length && baseIndexOf(array, value, 0) > -1
}
// baseUniq
function baseUniq(array, iteratee, comparator) {
let index = -1
let includes = arrayIncludes
let isCommon = true
const { length } = array
const result = []
let seen = result
if (comparator) {
isCommon = false
includes = arrayIncludesWith
}
else if (length >= LARGE_ARRAY_SIZE) {
const set = iteratee ? null : createSet(array)
if (set) {
return setToArray(set)
}
isCommon = false
includes = cacheHas
seen = new SetCache
}
else {
seen = iteratee ? [] : result
}
outer:
while (++index < length) {
let value = array[index]
const computed = iteratee ? iteratee(value) : value
value = (comparator || value !== 0) ? value : 0
if (isCommon && computed === computed) {
let seenIndex = seen.length
while (seenIndex--) {
if (seen[seenIndex] === computed) {
continue outer
}
}
if (iteratee) {
seen.push(computed)
}
result.push(value)
}
else if (!includes(seen, computed, comparator)) {
if (seen !== result) {
seen.push(computed)
}
result.push(value)
}
}
return result
}
// isFlattenable
function isFlattenable(value) {
return Array.isArray(value) || isArguments(value) ||
!!(value && value[spreadableSymbol])
}
// baseDifference
const LARGE_ARRAY_SIZE = 200
function baseDifference(array, values, iteratee, comparator) {
let includes = arrayIncludes
let isCommon = true
const result = []
const valuesLength = values.length
if (!array.length) {
return result
}
if (iteratee) {
values = map(values, (value) => iteratee(value))
}
if (comparator) {
includes = arrayIncludesWith
isCommon = false
}
else if (values.length >= LARGE_ARRAY_SIZE) {
includes = cacheHas
isCommon = false
values = new SetCache(values)
}
outer:
for (let value of array) {
const computed = iteratee == null ? value : iteratee(value)
value = (comparator || value !== 0) ? value : 0
if (isCommon && computed === computed) {
let valuesIndex = valuesLength
while (valuesIndex--) {
if (values[valuesIndex] === computed) {
continue outer
}
}
result.push(value)
}
else if (!includes(values, computed, comparator)) {
result.push(value)
}
}
return result
}
function baseFlatten(array, depth, predicate, isStrict, result) {
predicate || (predicate = isFlattenable)
result || (result = [])
if (array == null) {
return result
}
for (const value of array) {
if (depth > 0 && predicate(value)) {
if (depth > 1) {
// Recursively flatten arrays (susceptible to call stack limits).
baseFlatten(value, depth - 1, predicate, isStrict, result)
} else {
result.push(...value)
}
} else if (!isStrict) {
result[result.length] = value
}
}
return result
}
// baseXor
function baseXor(arrays, iteratee, comparator) {
const length = arrays.length
if (length < 2) {
return length ? baseUniq(arrays[0]) : []
}
let index = -1
const result = new Array(length)
while (++index < length) {
const array = arrays[index]
let othIndex = -1
while (++othIndex < length) {
if (othIndex != index) {
result[index] = baseDifference(result[index] || array, arrays[othIndex], iteratee, comparator)
}
}
}
return baseUniq(baseFlatten(result, 1), iteratee, comparator)
}
let a = baseXor([[0, 1], [1, 2]]);
console.log(a);
// [0,2]

This is still not all of the code that might be used in some cases as I did not copy over all the internal methods that are used in the various other internal methods that the lodash baseXor method uses. I did not take the time to make my own stand alone vanilla javaScript xor method for this post, but the basic idea that I started with is there in the baseXor method that I started out with in my attempt with two nested while loops.

I might get around to updating this post with a stand alone vanilla javaScript method that is just the core of what is going on here, but for now this is one where I might just use lodash and move on when and if i am in a situation in which I need an xor method like this.