Javascript - find latest object depending of value - javascript

I've tried to find a way to search the latest value of object, and what I found almost all using reverse() or by using i-- in for loop, but I want to avoid it somehow
I can archive it using two var like this:
var a = [{a:true},{a:true},{a:false}]
var b = a.filter(el=>el.a == true)
console.log(b[b.length-1])
Is there a way to use only one var like this?
var a = [{a:true},{a:true},{a:false}]
a.latestValue(el=>el.a == true)

use find to get only one match.
If you don't like the order, you can reverse it, too.
var a = [{
a:true,
id: 1
},{
a:true,
id: 2,
},{
a:false,
id: 3
}]
const latestValue = a.find(el => el.a === true)
const lastValue = a.reverse().find(el => el.a === true)
console.log(latestValue);
console.log(lastValue);

You're basically looking for something like .find, except a .find that starts at the last item and iterates backwards, rather than starting at the first item and iterating forwards. Although there are built-in functions like lastIndexOf (similar to indexOf, except starts searching from the last element) and reduceRight (same, but for reduce), no such thing exists for .find, so your best option is to write your own function. It's easy enough to write, doesn't mutate the original array (like .reverse() does) and doesn't require creating an intermediate array:
function findRight(arr, callback) {
for (let i = arr.length - 1; i--; i >= 0) {
if (callback(arr[i], i, arr)) return arr[i];
}
}
var a = [{id: 1, a:true},{id: 2, a:true},{id: 3, a:false}];
console.log(
findRight(a, el => el.a === true)
);
I guess it would be possible to (ab)use reduceRight for this, though I wouldn't recommend it:
var a = [{id: 1, a:true},{id: 2, a:true},{id: 3, a:false}];
console.log(
a.reduceRight((a, el) => a || (el.a && el), null)
);

I know already answered but thought it can be achieved in a different way, So here is my solution
You can use JavaScript array map function to get the index of latest value like this
NOTE : I have modified your array to contain more elements
var a = [{a:true},{a:true},{a:false},{a:false},{a:false},{a:true},{a:true},{a:false}];
var latestIndexOfTrue = a.map(function(e) { return e.a; }).lastIndexOf(true)
console.log(latestIndexOfTrue);
/* above will give you the last index of the value you want (here i have tried with
* value true) and it will give you the index as 6 */
if you want whole object then you can get it with bellow code
console.log(a[latestIndexOfTrue]);

Related

How to duplicate elements in a js array without creating "dependent elements"?

I am trying to modify a single element from an array whose elements were previously duplicated n times. To perform the array duplication I just relied on a custom function duplicateElements(array, times)from this post (see #Bamieh answer). As shown in the exemple below, the problem is I can't modify a single element from the array without modifying other elements:
function duplicateElements(array, times) {
return array.reduce((res, current) => {
return res.concat(Array(times).fill(current));
}, []);
}
var myvar = duplicateElements([{ a: 1 }, { a: 2 }], 2);
myvar[0].a = 3;
console.log(myvar);
// (4) [{…}, {…}, {…}, {…}]
// 0: {a: 3}
// 1: {a: 3}
// 2: {a: 2}
// 3: {a: 2}
// length: 4
As you can see myvar[1].a was also modified although this wasn't intended. How can I avoid this issue?
The problem is that you're passing the reference to the original object in Array(times).fill(current) .
In this case the two copies of the first {a:2} are the same copy of the original (They reference to the same space in memory) so if you change one, the two of them will change as they reference the same object in memory.
You have to make a deepcloning function or maybe spread the object inside a new one. You can change your original function to work with objects and primitives like this:
function duplicateElements(elementsArray, times) {
//Make a new placeholder array
var newArray = [];
//Loop the array of elements you want to duplicate
for (let index = 0; index < elementsArray.length; index++) {
//Current element of the array of element
var currentElement = elementsArray[index];
//Current type of the element to check if it is an object or not
var currentType = typeof currentElement
//Loop over the times you want to copy the element
for (let index = 0; index < times; index++) {
//If the new element is not an object
if (currentType !== "object" && currentType){
//append the element
newArray.push(currentElement)
//if it is an Object
} else if (currentType === "object" && currentType){
//append an spreaded new Object https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
newArray.push({...currentElement})
}
}
}
return newArray;
}
This is not the optimal way to do this, but I think that maybe you're new to javascript and is better to learn the old way of looping before using more Array functionalities (as the answer from Jonas Wilms, that is also a good answer).
I would recommend javascript.info and eloquent javascript to learn more about the language
The main reason for this as specified in the Array.fill documentation is that when dealing with objects it will copy by reference:
When fill gets passed an object, it will copy the reference and fill
the array with references to that object.
With lodash (and _.cloneDeep) that is one line like this:
let dubFn = (arr, t=1) =>
_.concat(arr, _.flatMap(_.times(t, 0), x => _.cloneDeep(arr)))
let r1 = dubFn([{a:1},{b:3}]) // no parameter would mean just 1 dub
let r2 = dubFn([{a:1},{b:3},5,[1]], 2) // 2 dublicates
r1[0].a = 3
r2[0].a = 3
console.log(r1)
console.log(r2)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
Note that this now works with arrays/objects and primitives.
The idea is to use _.concat to return a new concatenated version of the input array with a combination of few functions which on the end return an array of cloned objects. We use _.times to return an array of in this case t elements and then for each of those elements we replace with a deep clone of the array. _.flatMap is needed to flatten the end result since we end up having array of arrays after the _.times call.
With ES6 you can do something like this:
let dubElements = (arr, t) =>
[...arr, ...new Array(t).fill().flatMap(x => arr.map(y => ({...y})))]
let r1 = dubElements([{a:1},{b:3}])
let r2 = dubElements([{a:1},{b:3}],2)
r1[0].a = 3
r2[0].a = 3
console.log(r1)
console.log(r2)
Where we concat arrays via the spread operator and we use new Array(t) to create the new duplicates array and make sure we fill it with undefined in this case after which we flatMap the results (which we map through the clone via the spread operator again.
Note that this works for your use case specifically. If you want to make it more generic you have to expand more in the last map function etc.
If you want to preserve the order of the elements you can do something like this:
let dubElements = (arr, t=1) => {
let _result = []
arr.forEach(x => {
for(let i=0; i<t+1; i++) {
_result.push({...x})
}
})
return _result
}
let result = dubElements([{a:1},{b:3}],2)
result[0].a = 3
console.log(result)
Replace
Array(times).fill(current)
which will add one reference to current multiple times to the array with:
Array.from({ length: times }, () => ({...current }))
which will shallow clone current. Note that the code will then only work with objects though, not with primitives.
I'd do:
const duplicateElements = (array, length) =>
array.flatMap(current => Array.from({ length }, () => ({ ...current }));

Using map reduce etc, how would you find the first item matching a certain criteria in a nested array, and stop once found?

How would you find the first item matching a certain criteria in a nested array, and stop once found?
In a 1D array, this is what the Array.find function is for, but how would you do it for a 2D array, and, even neater, for n-dimension array?
Also, I'm trying to come up with a neat solution using es6 and array functions such as find, map, reduce etc, rather than using more traditional loops and variables to maintain state (see one such old-school solution below).
The data may look something like this
const data = [
{arr: [{val:6,name:'aaa'},{val:4,name:'bbb'},{val:8,name:'ccc'}]},
{arr: [{val:3,name:'mmm'},{val:5,name:'nnn'},{val:9,name:'ppp'},{val:5,name:'ooo'}]}
]
I'm hoping I can do something similar to array.find (and its predicate / testing function), but I need to go deeper and find eg the first item with val=5. For the data above, I'd expect to get the item with name 'nnn' (not 'ooo'), and have the process end once the first item is found. Similar to Array.find, I want to avoid processing the rest of the data once a matching item is found.
One boring old way to do it would be something like this, with a loop, but that's... boring, and not as neat as the lovely array functions :)
let found
// loop through all data entries in the outer array
for (const d of data) {
// attempt to find a matching item in the inner array.
// using array.find means we stop at the first match, yay!
const theItem = d.arr.find(item => {
return myPredicate(item)
})
// we also need to break out of the loop. ugh!
if (theItem) {
found = theItem
break
}
}
// return what we found (may be undefined)
return found
Now, I realise that I can do something with find() and some(), say, similar to the answer here ES6 - Finding data in nested arrays, but the problem is that using find on the outer array means that we get back the first item of the outer data array, whereas I want an item from the inner arr array.
const outer = data.find(d => {
return d.arr.some(item => {
return myPredicate(item)
})
})
I would then have to process outer AGAIN to find the item in outer.arr, something like
outer.arr.find(item => myPredicate(item))
This doesn't sit well with me, as the call to some(...) has already gone through and found the matching inner item!
I thought this would be straight forward, and maybe it is, but for one reason or another I got stuck on this little challenge.
I've also looked at the nice traverse library (https://www.npmjs.com/package/traverse), but again that seems to be more about traversing through a whole tree rather than stopping and returning once a particular node is found.
Anyone up for a challenge? ;)
The easiest (though slightly ugly) solution would be to assign the matching item to an outer variable when found:
let foundNested;
data.some(subarr => (
subarr.some((item) => {
if (myPredicate(item)) {
foundNested = item;
return true;
}
});
});
You might use .reduce to avoid assigning to an outer variable:
const myPredicate = ({ val }) => val === 5;
const data = [
{arr: [{val:6,name:'aaa'},{val:4,name:'bbb'},{val:8,name:'ccc'}]},
{arr: [{val:3,name:'mmm'},{val:5,name:'nnn'},{val:9,name:'ppp'},{val:5,name:'ooo'}]}
];
const found = data.reduce((a, { arr }) => (
a ||
arr.find(myPredicate)
), null);
console.log(found);
Problem is, the reduce won't short-circuit - it'll fully iterate over the outer array regardless. For true short-circuiting, I think I'd prefer using a for..of loop:
const data = [
{arr: [{val:6,name:'aaa'},{val:4,name:'bbb'},{val:8,name:'ccc'}]},
{arr: [{val:3,name:'mmm'},{val:5,name:'nnn'},{val:9,name:'ppp'},{val:5,name:'ooo'}]}
];
function findNested(outerArr, myPredicate) {
for (const { arr } of outerArr) {
for (const item of arr) {
if (myPredicate(item)) {
return item;
}
}
}
}
const myPredicate = ({ val }) => val === 5;
console.log(findNested(data, myPredicate));
You'll want to write your own find function that doesn't take a predicate but a result-producing callback:
function find(iterable, callback) {
for (const value of iterable) {
const result = callback(value);
if (result !== undefined)
return result;
}
}
With that, you can write
const data = [
{arr: [{val:6,name:'aaa'},{val:4,name:'bbb'},{val:8,name:'ccc'}]},
{arr: [{val:3,name:'mmm'},{val:5,name:'nnn'},{val:9,name:'ppp'},{val:5,name:'ooo'}]}
];
console.log(find(data, ({arr}) => find(arr, o => o.val == 5 ? o : undefined)));
Alternatively, if you want to get all results, flatMap is the perfect tool:
data.flatMap(({arr}) => arr.filter(({val}) => val == 5));
Sure, why not. I'm up for it. This can probably be improved upon. But this will work. Let's say you are trying to find an object with id of 5 in a multidimensional array.
const arr = [[[{id: 1}], [{id: 2}]], [[{id: 3}]], [[{id: 4}], [{id: 5}], [{id: 6}]]]
function findObject (obj) {
if (Array.isArray(obj)) {
const len = obj.length
for (let i = 0; i < len; i++) {
const found = findObject(obj[i])
if (found) {
return found
}
}
} else if (obj.id === 5) { // Put your search condition here.
return obj
}
}
const obj = findObject(arr)
console.log('obj: ', obj)
This seems to work, but, in my opinion, it's still not clean with that 'found' variable sitting outside the main block and being assigned from inside the nested find block. It's better though. Thoughts?
let found
data.find(d =>
d.arr.find(item => {
found = myPredicate(item) ? item : void 0
return found !== void 0
}) !== void 0
)
return found

Javascript: With an array of indices, splice a nested object using the indices to find the element with a recursive function

Lets say I have an array: [0,1,5].
The object I want to splice is object[0].content[1].content[5].splice()
However, the array can be however long, if it's just length 1,
then object[0].splice()
If it's length 2, object[0].content[1].splice()
I want to create a recursive function that does this, so I don't have to make a lot of if length conditionals. Any ideas on how to do this? Thank you
Recursion is neither necessary nor desirable for solving a problem of this type. All you need is a simple loop:
var array = [0, 1, 5];
var result = object[array.shift()];
while(var index = array.shift()) {
result = result.content[index];
}
var spliceResult = result.splice();
I get that recursiveness is not necessary in this situation. In the grand scheme neither is coding. But sometimes students like myself come to these places for help for specific questions based on constraints or edge cases. Try this one on for size. This specifically eliminates more than one occurrence of 0.
var takeOut = function(array) {
if (array.length === 0) { return array };
if(takeOut(array.slice(1))[0] === 0 && array[0] === 0) {
return takeOut(array.slice(1));
} else {
return [array[0]].concat(takeOut(array.slice(1)));
}
};

best way to replace the first occurrence of an item in an array

Is this the best way to replace the first occurrence of something?
my_list[my_list.indexOf(old_item)] = new_item
Note: If the item is not in the array, I don't want anything to happen. (No errors / broken array)
If you're not sure the item is in the list you should do:
var idx = my_list.indexOf(old_item)
if (idx !== -1) { my_list[idx] = new_item }
But else I think it's the best way to do it.
Setting a value at the index -1 won't raise an error, but will still modify the object as would setting a key in a generic js object:
var my_list = [1, 2, 3];
var old_item = 5;
var new_item = 10;
my_list[my_list.indexOf(old_item)] = new_item;
// my_list is [1, 2, 3, '-1': 10]
// my_list.length is still 3
// Object.keys(my_list) is [ '0', '1', '2', '-1' ]
So you probably don't want to do it.
Not really! There are a few ways you could do it -- one is the way you have, another is using the search method rather than indexOf, which is more versatile in that it can accept regex arguments. Note however that search is not supported in some older JS environments. There are also longer, worse ways, like using filter, or splice, but those definitely wouldn't work better.
The only suggestion I'd make is a conditional that checks whether old_item is still in the list -- indexOf returns -1 if something is not in the list, and in that case, you'll be replacing a non-existent index.
Basically, I think you're fine -- it might not be the prettiest expression, but it's about as succinct as you can get in JS.
Arrays are not useful structures to search values. You may consider a set instead:
my_set.delete(old_item);
my_set.add(new_item);
Note that your code may not behave as expected if old_item does not appear in the array, then indexOf will return -1. Maybe use something like this:
var idx = my_list.indexOf(old_item);
if (idx >= 0) my_list[idx] = new_item;
else my_list.push(new_item);
indexOf is definitely one of the best ways. But you want to make sure indexOf was successful before using it:
var index = my_list.indexOf(old_item);
if (index !== -1) {
my_list[index] = new_item;
}
Also you can use the ~ operator if you are into terse JavaScript:
var index = my_list.indexOf(old_item);
if (~index) {
my_list[index] = new_item;
}
Fast solution using Array.prototype.indexOf and Array.prototype.splice functions:
var myList = [1,2,3,4,5], oldItem = 4, newItem = 7;
~(pos = myList.indexOf(oldItem)) && myList.splice(pos, 1, newItem);
console.log(myList);

Javascript array sort and unique

I have a JavaScript array like this:
var myData=['237','124','255','124','366','255'];
I need the array elements to be unique and sorted:
myData[0]='124';
myData[1]='237';
myData[2]='255';
myData[3]='366';
Even though the members of array look like integers, they're not integers, since I have already converted each to be string:
var myData[0]=num.toString();
//...and so on.
Is there any way to do all of these tasks in JavaScript?
This is actually very simple. It is much easier to find unique values, if the values are sorted first:
function sort_unique(arr) {
if (arr.length === 0) return arr;
arr = arr.sort(function (a, b) { return a*1 - b*1; });
var ret = [arr[0]];
for (var i = 1; i < arr.length; i++) { //Start loop at 1: arr[0] can never be a duplicate
if (arr[i-1] !== arr[i]) {
ret.push(arr[i]);
}
}
return ret;
}
console.log(sort_unique(['237','124','255','124','366','255']));
//["124", "237", "255", "366"]
You can now achieve the result in just one line of code.
Using new Set to reduce the array to unique set of values.
Apply the sort method after to order the string values.
var myData=['237','124','255','124','366','255']
var uniqueAndSorted = [...new Set(myData)].sort()
UPDATED for newer methods introduced in JavaScript since time of question.
This might be adequate in circumstances where you can't define the function in advance (like in a bookmarklet):
myData.sort().filter(function(el,i,a){return i===a.indexOf(el)})
Here's my (more modern) approach using Array.protoype.reduce():
[2, 1, 2, 3].reduce((a, x) => a.includes(x) ? a : [...a, x], []).sort()
// returns [1, 2, 3]
Edit: More performant version as pointed out in the comments:
arr.sort().filter((x, i, a) => !i || x != a[i-1])
function sort_unique(arr) {
return arr.sort().filter(function(el,i,a) {
return (i==a.indexOf(el));
});
}
How about:
array.sort().filter(function(elem, index, arr) {
return index == arr.length - 1 || arr[index + 1] != elem
})
This is similar to #loostro answer but instead of using indexOf which will reiterate the array for each element to verify that is the first found, it just checks that the next element is different than the current.
Try using an external library like underscore
var f = _.compose(_.uniq, function(array) {
return _.sortBy(array, _.identity);
});
var sortedUnique = f(array);
This relies on _.compose, _.uniq, _.sortBy, _.identity
See live example
What is it doing?
We want a function that takes an array and then returns a sorted array with the non-unique entries removed. This function needs to do two things, sorting and making the array unique.
This is a good job for composition, so we compose the unique & sort function together. _.uniq can just be applied on the array with one argument so it's just passed to _.compose
the _.sortBy function needs a sorting conditional functional. it expects a function that returns a value and the array will be sorted on that value. Since the value that we are ordering it by is the value in the array we can just pass the _.identity function.
We now have a composition of a function that (takes an array and returns a unique array) and a function that (takes an array and returns a sorted array, sorted by their values).
We simply apply the composition on the array and we have our uniquely sorted array.
This function doesn't fail for more than two duplicates values:
function unique(arr) {
var a = [];
var l = arr.length;
for(var i=0; i<l; i++) {
for(var j=i+1; j<l; j++) {
// If a[i] is found later in the array
if (arr[i] === arr[j])
j = ++i;
}
a.push(arr[i]);
}
return a;
};
Here is a simple one liner with O(N), no complicated loops necessary.
> Object.keys(['a', 'b', 'a'].reduce((l, r) => l[r] = l, {})).sort()
[ 'a', 'b' ]
Explanation
Original data set, assume its coming in from an external function
const data = ['a', 'b', 'a']
We want to group all the values onto an object as keys as the method of deduplication. So we use reduce with an object as the default value:
[].reduce(fn, {})
The next step is to create a reduce function which will put the values in the array onto the object. The end result is an object with a unique set of keys.
const reduced = data.reduce((l, r) => l[r] = l, {})
We set l[r] = l because in javascript the value of the assignment expression is returned when an assignment statement is used as an expression. l is the accumulator object and r is the key value. You can also use Object.assign(l, { [r]: (l[r] || 0) + 1 }) or something similar to get the count of each value if that was important to you.
Next we want to get the keys of that object
const keys = Object.keys(reduced)
Then simply use the built-in sort
console.log(keys.sort())
Which is the set of unique values of the original array, sorted
['a', 'b']
The solution in a more elegant way.
var myData=['237','124','255','124','366','255'];
console.log(Array.from(new Set(myData)).sort((a,b) => a - b));
I know the question is very old, but maybe someone will come in handy
A way to use a custom sort function
//func has to return 0 in the case in which they are equal
sort_unique = function(arr,func) {
func = func || function (a, b) {
return a*1 - b*1;
};
arr = arr.sort(func);
var ret = [arr[0]];
for (var i = 1; i < arr.length; i++) {
if (func(arr[i-1],arr[i]) != 0)
ret.push(arr[i]);
}
}
return ret;
}
Example: desc order for an array of objects
MyArray = sort_unique(MyArray , function(a,b){
return b.iterator_internal*1 - a.iterator_internal*1;
});
No redundant "return" array, no ECMA5 built-ins (I'm pretty sure!) and simple to read.
function removeDuplicates(target_array) {
target_array.sort();
var i = 0;
while(i < target_array.length) {
if(target_array[i] === target_array[i+1]) {
target_array.splice(i+1,1);
}
else {
i += 1;
}
}
return target_array;
}
I guess I'll post this answer for some variety. This technique for purging duplicates is something I picked up on for a project in Flash I'm currently working on about a month or so ago.
What you do is make an object and fill it with both a key and a value utilizing each array item. Since duplicate keys are discarded, duplicates are removed.
var nums = [1, 1, 2, 3, 3, 4, 5, 5, 6, 7, 7, 8, 9, 9, 10];
var newNums = purgeArray(nums);
function purgeArray(ar)
{
var obj = {};
var temp = [];
for(var i=0;i<ar.length;i++)
{
obj[ar[i]] = ar[i];
}
for (var item in obj)
{
temp.push(obj[item]);
}
return temp;
}
There's already 5 other answers, so I don't see a need to post a sorting function.
// Another way, that does not rearrange the original Array
// and spends a little less time handling duplicates.
function uniqueSort(arr, sortby){
var A1= arr.slice();
A1= typeof sortby== 'function'? A1.sort(sortby): A1.sort();
var last= A1.shift(), next, A2= [last];
while(A1.length){
next= A1.shift();
while(next=== last) next= A1.shift();
if(next!=undefined){
A2[A2.length]= next;
last= next;
}
}
return A2;
}
var myData= ['237','124','255','124','366','255','100','1000'];
uniqueSort(myData,function(a,b){return a-b})
// the ordinary sort() returns the same array as the number sort here,
// but some strings of digits do not sort so nicely numerical.
function sort() only is only good if your number has same digit, example:
var myData = ["3","11","1","2"]
will return;
var myData = ["1","11","2","3"]
and here improvement for function from mrmonkington
myData.sort().sort(function(a,b){return a - b;}).filter(function(el,i,a){if(i==a.indexOf(el) & el.length>0)return 1;return 0;})
the above function will also delete empty array and you can checkout the demo below
http://jsbin.com/ahojip/2/edit
O[N^2] solutions are bad, especially when the data is already sorted, there is no need to do two nested loops for removing duplicates. One loop and comparing to the previous element will work great.
A simple solution with O[] of sort() would suffice. My solution is:
function sortUnique(arr, compareFunction) {
let sorted = arr.sort(compareFunction);
let result = sorted.filter(compareFunction
? function(val, i, a) { return (i == 0 || compareFunction(a[i-1], val) != 0); }
: function(val, i, a) { return (i == 0 || a[i-1] !== val); }
);
return result;
}
BTW, can do something like this to have Array.sortUnique() method:
Array.prototype.sortUnique = function(compareFunction) {return sortUnique(this, compareFunction); }
Furthermore, sort() could be modified to remove second element if compare() function returns 0 (equal elements), though that code can become messy (need to revise loop boundaries in the flight). Besides, I stay away from making my own sort() functions in interpreted languages, since it will most certainly degrade the performance. So this addition is for the ECMA 2019+ consideration.
The fastest and simpleness way to do this task.
const N = Math.pow(8, 8)
let data = Array.from({length: N}, () => Math.floor(Math.random() * N))
let newData = {}
let len = data.length
// the magic
while (len--) {
newData[data[len]] = true
}
var array = [2,5,4,2,5,9,4,2,6,9,0,5,4,7,8];
var unique_array = [...new Set(array)]; // [ 2, 5, 4, 9, 6, 0, 7, 8 ]
var uniqueWithSorted = unique_array.sort();
console.log(uniqueWithSorted);
output = [ 0, 2, 4, 5, 6, 7, 8, 9 ]
Here, we used only Set for removing duplicity from the array and then used sort for sorting array in ascending order.
I'm afraid you can't combine these functions, ie. you gotta do something like this:-
myData.unique().sort();
Alternatively you can implement a kind of sortedset (as available in other languages) - which carries both the notion of sorting and removing duplicates, as you require.
Hope this helps.
References:-
Array.sort
Array.unique

Categories

Resources