Problem overview
The method onlyInFirst gets two arrays of integers, a and b, and returns a new array with the values of a that are not in b. Each value goes into the result once, in the order it first appears in a. If every value of a is also in b, the result is an empty array.
For a = [1, 2, 3, 4] and b = [2, 4] the answer is [1, 3]. For a = [5, 6, 5, 7] and b = [7] the answer is [5, 6]: 7 is in b, and 5 appears twice in a but only once in the result. For a = [1, 2] and b = [2, 3, 1] the answer is empty.
Approach
There are two questions for each number of a: is it in b, and is it already in the result? Both are membership questions, and a Set answers them fast. So the plan uses two sets. setB holds the values of b, where repeats collapse into one. seen holds the values already put in the result, so a repeat in a doesn't go in twice.
A number is added to the result only when both checks say no. Reading a from left to right keeps the order of first appearance.
Step by step
- Build
setBfrombwithnew Set(b). - Create
seen, an empty set for the values already in the result. - Create
result, an empty array. - Loop over
awithfor (let num of a). - Check both conditions joined by
&&:!setB.has(num)(b doesn't have it) and!seen.has(num)(it isn't in the result yet). Both must be true. - Push
numontoresult. - Add
numtoseen, so a later copy is skipped. - Return
resultafter the loop. It can be empty.
The solution in JavaScript
class Solution {
onlyInFirst(a, b) {
let setB = new Set(b);
let seen = new Set();
let result = [];
for (let num of a) {
if (!setB.has(num) && !seen.has(num)) {
result.push(num);
seen.add(num);
}
}
return result;
}
}
The operator && stops as soon as the left side is false, so a number that is in b never reaches the seen check. Either order of the two checks gives the same result.
Another way
Another way to write it removes the repeats of a first and then filters. A set built from a already drops later copies and keeps the first-appearance order, so only the b check is left:
function viaSet(a, b) {
const inB = new Set(b);
return [...new Set(a)].filter(num => !inB.has(num));
}
It returns 1 3, 5 6 and an empty array on the three examples. The lesson's loop does the same work in one pass and shows each set's job on its own line.
Testing
On the three examples the solution prints these lines:
1 3
5 6
followed by an empty line for the third example, because an empty array prints as nothing.
Common mistakes
- Forgetting
seen. Keeping only the values ofathat aren't inbis a one-liner withfilter, but on the second example it gives5 6 5: the repeat of 5 comes through. - Using the array
bdirectly withincludes. It works, but it looks at the values ofbone by one for each number ofa. A set checks in one step. - Checking the wrong array. The values to keep are the ones missing from
b. Values that appear only inbnever matter. - Returning inside the loop, which would end after the first value.
