I'am doing some JavaScript exercises and I stumbled upon this one "Write a JavaScript program to filter out the non-unique values in an array."
I tried and found a solution, which worked but it was to cumbersome. A better answer, according to the site, is the following:
const filter_Non_Unique = arr =>
arr.filter(l => arr.indexOf(l) === arr.lastIndexOf(l));
console.log(filter_Non_Unique([1,2,3,4,4,5,6,6])) // 1,2,3,5
Now I recked my head trying to understand why this solution works but I still don't get it.
Can somebody explain to me?
Thanks in advance.
If the element only occurs once in the array, the first index will be the same as the last index, the index will not change for both calls.
eg:
const arr = [1,2,3,4,4,5,6,6]
console.log(arr.indexOf(5))
console.log(arr.lastIndexOf(5))
Since both of of these functions return the same index, filter will keep the element in.
On the other hand if there are multiple values, those values will have different indexes, so the filter will return false, and remove it from the array:
const arr = [1,2,3,4,4,5,6,6]
console.log(arr.indexOf(4))
console.log(arr.lastIndexOf(4))
I've answered a question similar to the one you solved here, you could try that logic too.
Beside of the indexOf/lastIndexOf approach which needs a lot of iterating the array, you could take a two loop approach.
By getting an array of single items by using a hash table and three states, like
undefined, the standard value of not declared properties of an object,
true for the first found value,
false for all values who are repeated in the array.
Then filter by the value of the hash table.
const
filterNonUnique = array => {
var hash = {};
for (let v of array) hash[v] = hash[v] === undefined;
return array.filter(v => hash[v]);
}
console.log(filterNonUnique([1, 2, 3, 4, 4, 5, 6, 6, 7, 7, 7, 7]))
Related
In the following example, splice().splice() didn't work. What am I missing? How can I chain two splice methods in a single line? Thank you for any advice!
function test() {
var data = [0,1,2,3,4,5];
data.splice(0,2).splice(-1); // Removed only the first two elements, but not the last element.
console.log(data) // Returned [ 2, 3, 4, 5 ]
var data = [0,1,2,3,4,5];
data.splice(0,2); // Removed the first two elements.
data.splice(-1); // Removed the last element.
console.log(data) // Returned [ 2, 3, 4 ]
}
splice returns the removed elements, so chaining them in a single statement won't really work - you don't want to operate on the removed elements, you want to operate on the original array both times.
Usually, in this sort of situation, the right approach is to use slice instead, which is both easier to work with (just specify a start (inclusive) and end (exclusive) index) and is more functional (you get a new array instead of mutating an existing one - it's nice to avoid mutation when possible, makes code more understandable).
const data = [0,1,2,3,4,5];
console.log(data.slice(2,5));
You can do this, although I don't recommend this because it makes the code confusing
const data = [0,1,2,3,4,5];
console.log(data.splice(2,4).splice(0,3)); // Returned [2,3,4]
I have an array of arrays like this, const array = [[1,5],[7,9],[10,14]]; I need to check to see if the arrays are in order.
Basically, for the provided array, I need to compare the first array's second value, (5), with the second array's first value, (7), which I would then use to invalidate a form.
If the arrays or in order, const array = [[1,5],[6,9],[10,14]]; it returns false, it passes validation, if they aren't, validation fails and it returns true.
So far, I've done something like this.
const arrayIdxZeroValues = [];
const arrayIdxOneValues = [];
array.forEach((arr,idx) => {
arrayIdxZeroValues.push(arr[0])
arrayIdxOneValues.push(arr[1])
})
which separates the values fine. I then thought about using .shift() to remove the first value of arrayIdxZeroValues since it will never need to be compared, but I feel this is hacky and I'm not sure it's the right approach from here. Maybe .reduce() to compare the output arrays, but I haven't been able to get it to work right.
Any guidance would be much appreciated.
Thanks
Create an array that starts from the 2nd sub-array using Array.slice(). Use Array.some() and check if the 2nd item of the current sub-array is equal or less the 2nd item of the sub-array at the current index i of the original array. In this way you compare the 2nd with the 1st, the 3rd with the 2nd, and so on.
false - means the validation passed
true - means validation failed
const isFailed = arr => arr.slice(1)
.some(([n], i) => n !== arr[i][1] + 1)
console.log(isFailed([[1,5],[6,9],[10,14]])) // false
console.log(isFailed([[1,5],[7,9],[10,14]])) // true
You could check the actual value agains the last value of the previous array plus one and omit the check for the first inner array.
const
inOrder = array => array.every(([v], i, a) => !i || a[i - 1][1] + 1 === v);
console.log(inOrder([[1, 5], [7, 9], [10, 14]]));
console.log(inOrder([[1, 5], [6, 9], [10, 14]]));
For a reason specific to this application an array of data or nulls is used to display a list of forms. The difference is based on whether data was provided by a service or manually added, where null indicates everything was manually added via
a button not a service.
So ignoring the use case of an array of nulls it turns out that [null, null, null].splice(0, 1); removes the null at index 2 instead of 0 based on entering values into the different forms displayed based on the array length, and then seeing who disappears on delete.
It can be made to work by adding something unique like { index: theIndex } to the array instead of null. So splice now works correctly removing the item at index 0 instead of 2, but now I'm curious what splice does under the covers that it can't remove an index regardless of its value compared to the other indices.
Can anyone explain what splice is doing? Based on the spec for splice I don't really see why this happens.
(This follows from the comments in the question but is too large to be a comment).
So I think you are having a conceptual misconception (^^). Look at this examples:
let a = [1, 2, 3]
a.splice(0, 1) // => 1
a // => [2, 3]
let b = [1, 2, 3]
delete b[0] // => true
b // => [<1 empty slot>, 2, 3]
The splice function modifies the array in-place. Note that, although we spliced the first element, we got as a result an array of two elements.
Look now at this example
let a = [1, 1, 1]
a.splice(0, 1)
a // => [1, 1]
let b = [1, 1, 1]
b.splice(2, 1)
b // => [1, 1]
We are deleting the first element from a and the last from b, but of course there's no way of telling so just looking at the result.
In the case with the nulls, the same thing is happening. Some library (Angular) is trying to figure out which element you deleted, but there's no way of knowing. This is because null === null.
Now if you use an array of empty objects, for example, there would be a way of knowing. Since {} !== {}---because each time you cast a {} you are creating a unique object---, then you could know which element is missing in an array of empty objects. The case is similar with the array [1, 2, 3].
let a = [{}, {}, {}] // lets imagine that each object has a unique id
// and we have [{}#1, {}#2, {}#3]
let [obj1, obj2, obj3] = a
obj1 === obj2 // => false, because they have different ids.
a.splice(0, 1)
a // => [{}#2, {}#3]
a.includes(obj1) // => false, because {}#1 is no longer in a
So an alternative to using an array of nulls, would be to use an array of empty objects. I think that is why the code works for you when you use objects like { index: theIndex }. But of course all depends on how smart Angular is. I bet there is a more native way of deleting an element, as #KaiserKatze points out, "it's always a bad idea to directly remove or add elements in the array if it maps to your model."
You have to understand that when you are splicing the array you're only doing that--removing an element from an array. You're not removing the "form element" when splicing the array. Instead, some foreign code is reading the array and trying to figure out --under the hood-- what you intended to do.
Regarding the question that is to find the first duplicate number of numbers in an array for which the second occurrence, has the minimal index.
If I understand so far, the variable firstDuplicate is a function object that uses arrow notation to abbreviate 'var firstDuplicate = function (a){}', etc. Here's where my questions begin.
1) Creating a new set object automatically populates using the array the function was passed? How does the set method know to take the array passed to the function and make a set of it?
2) Now I understand in the for loop that each item in the array is being traversed and the current index is e, but here is where I begin to lose conceptually what is going on. Where in the following:
if (r.has(e))
where is the comparison happening exactly, that is the checking to see what the duplicative numbers are in this array, and the comparison that determines what the lowest index of the second occurrences of the duplicate are?
const test1 = [0, 3, 4, 10, 2, 4, 2, 3]
firstDuplicate = a => {
r = new Set()
for (e of a)
if (r.has(e))
return e
else
r.add(e)
return -1
}
console.log(firstDuplicate(test1));
Creating a new set object automatically populates using the array the function was passed? How does the set method know to take the array passed to the function and make a set of it?
Nothing is happening automatically here. The Set is populated in the else condition when you say r.add(e)
Now I understand in the for loop that each item in the array is being traversed and the current index is e...
e is the current element and not the current index. You're using a for..of statement and not a for..in statement. Difference between the two using a simple snippet
const a = [10, 5, 12];
console.log("for..in statement");
for (const i in a) {
// Here i is the index
console.log(i)
};
console.log("for..of statement");
for (const i of a) {
// Here i is the element
console.log(i)
};
...where is the comparison happening exactly, that is the checking to see what the duplicative numbers are in this array, and the comparison that determines what the lowest index of the second occurrences of the duplicate are?
The comparison happens at r.has(e). So the code checks if e was already encountered (hence also added in the Set) and returns e. This works because if e was already encountered once, then if it's encountered again before any other duplicate, it automatically means e is at the minimal index.
Here is a commented version of your code, to make it more clear
const test1 = [2, 3, 6, 10, 2, 3, 7, 9, 1]
firstDuplicate = a => {
// Create an empty Set
r = new Set()
// Iterate over every element of a
for (e of a) {
// Check if e was already encountered
if (r.has(e)) {
// If e was already encountered, this is the first time a duplicate
// has been found. This is definitely a duplicate at the minimal index,
// as a for loop iterates elements in order of their indices
return e
} else { // e is encountered the firt time
// Populate the Set with e, to say that it has been encountered atleast
// once
r.add(e)
}
}
// The if condition above never passed. That means array a has all
// unique elements. Return -1, assuming the array a will never contain
// -1 as one of it's elements.
return -1
}
console.log(firstDuplicate(test1));
I know that map returns a new array, and that forEach does not return anything (the docs say it returns undefined).
For example, if I had some code like this:
let test;
values.forEach((value, idx) => {
if (someNumber >= value) {
test = value;
}
});
Here I am just checking if someNumber is greater than some value, and if it is then set test = value. Is there another array method I should use here?
Or is it fine to use .forEach
Your example doesn't make sense because it finds the last value that is less than or equal to someNumber, repeatedly assigning to the test variable if more than one is found. Thus, your code is not truly expressing your intent well since other developers can be confused about what you're trying to achieve. In fact, other answers here have had differing opinions on your goal due to this ambiguity. You even said:
if the number is great than or equal to the value from the array at whatever index, stop, and set test equal to that value
But your code doesn't stop at the first value! It keeps going through the entire array and the result in test will be the last value, not the first one.
In general, making your loop refer to outside variables is not the best way to express your intent. It makes it harder for the reader to understand what you're doing. It's better if the function you use returns a value so that it's clear the variable is being assigned.
Here's a guide for you:
forEach
Use this when you want to iterate over all the values in order to do something with each of them. Don't use this if you are creating a new output value--but do use it if you need to modify existing items or run a method on each one, where the forEach has no logical output value. array.forEach at MDN says:
There is no way to stop or break a forEach() loop other than by throwing an exception. If you need such behavior, the forEach() method is the wrong tool, use a plain loop instead. If you are testing the array elements for a predicate and need a Boolean return value, you can use every() or some() instead. If available, the new methods find() or findIndex() can be used for early termination upon true predicates as well.
find
Use this when you want to find the first instance of something, and stop. What you said makes it sound like you want this:
let testResult = values.find(value => value <= someNumber);
This is far superior to setting the test value from inside the lambda or a loop. I also think that reversing the inequality and the variables is better because of the way we tend to think about lambdas.
some
These only give you a Boolean as a result, so you have to misuse them slightly to get an output value. It will traverse the array until the condition is true or the traversal is complete, but you have to do something a bit hacky to get any array value out. Instead, use find as above, which is intended to output the found value instead of simply a true/false whether the condition is met by any element in the array.
every
This is similar to some in that it returns a Boolean, but is what you would expect, it is only true if all the items in the array meet the condition. It will traverse the array until the condition is false or the traversal is complete. Again, don't misuse it by throwing away the Boolean result and setting a variable to a value. If you want to do something to every item in an array and return a single value, at that point you would want to use reduce. Also, notice that !arr.every(lambdacondition) is the same as arr.some(!lambdacondition).
reduce
The way your code is actually written—finding the last value that matches the condition—naturally lends itself to reduce:
let testResult = values.reduce(
(recent, value) => {
if (value <= someNumber) {
recent = value;
}
return recent;
},
undefined
);
This does the same job of finding the last value as your example code does.
map
map is for when you want to transform each element of an array into a new array of the same length. If you have any experience with C# it is much like the Linq-to-objects .Select method. For example:
let inputs = [ 1, 2, 3, 4];
let doubleInputs = inputs.map(value => value * 2);
// result: [ 2, 4, 6, 8]
New requirements
Given your new description of finding the adjacent values in a sorted array between which some value can be found, consider this code:
let sortedBoundaries = [ 10, 20, 30, 40, 50 ];
let inputValue = 37;
let interval = sortedBoundaries
.map((value, index) => ({ prev: value, next: sortedBoundaries[index + 1] }))
.find(pair => pair.prev < inputValue && inputValue <= pair.next);
// result: { prev: 20, next: 30 }
You can improve this to work on the ends so that a number > 50 or <= 10 will be found as well (for example, { prev: undefined, next: 10 }).
Final notes
By using this coding style of returning a value instead of modifying an outside variable, you not only communicate your intent better to other developers, you then get the chance to use const instead of let if the variable will not be reassigned afterward.
I encourage you to browse the documentation of the various Array prototype functions at MDN—doing this will help you sort them out. Note that each method I listed is a link to the MDN documentation.
I would suggest you to use Array#some, instead of Array#forEach.
Array#forEach keeps iterating the array even if given condition was fulfilled.
Array#some stops iteration when given condition was fulfilled.
One of the advantages would be connected with performance, another - depends on your purposes - Array#forEach keeps overwriting the result with every passed condition, Array#some assigns the first found value and stops the iteration.
let test,
values = [4,5,6,7],
someNumber = 5;
values.some((value, idx) => {
if (someNumber >= value) {
test = value;
return test;
}
});
console.log(test);
Another option would be to use the Array.some() method.
let test;
const someNumber = 10;
[1, 5, 10, 15].some(function (value) {
if (value > someNumber) {
return test = value
}
})
One advantage to the .some() method over your original solution is optimization, as it will return once the condition has been met.
How about Object?
you can search with for-of