Changing one item in a list In immutable.js - javascript

I'm using immutable.js, my data structure is like:
class ItemList extends Record({
items: new List()
})
I want to write function that change one item in this list and keep other the same. For example, a list of {1, 2, 3, 4}, I need a function if an item equals 2, change it to 5.
I'm using something like
updateInfo(updatedInfo) {
return this.withMutations(itemList => {
itemList.set('items', list);
});
}
My question is in this function, how can I just update one item? where should I put the if judgment?
Thanks!

NB: As mentioned by another answer, there is also the undocumented indexOf method which may be easier to use in some cases, taking only the value to find as parameter.
Using findIndex to find the index of the value you need to change and set with the index to change:
list = Immutable.List.of(1, 2, 3, 4);
list = list.set(list.findIndex(function(item) {
return item === 2;
}), 5);
ES6:
list = list.set(list.findIndex((item) => item === 2), 5);
If you need the old value to change it, you can use update instead of set like:
list = list.update(list.findIndex(function(item) {
return item === 2;
}), function(oldValue) {
return 5;
});
ES6:
list = list.update(list.findIndex((item) => item === 2), (oldValue) => 5);

It is easy.
list = Immutable.List.of(1, 2, 3, 4);
list = list.set(list.indexOf(2), 5);
console.log(list.get(1)); //5

A much cleaner version, based on forEach. Its a sideeffect (mutates an otherwise immutable list), so the syntax is similar to using a mutable list -
var list = Immutable.List.of(1, 2, 3, 4);
// Notice no LHS assignment is required as
// forEach is a side-effect method.
list.forEach((value, index, theList) => {
// You can check either value, or index
if (index === soAndSo
|| value.something === something){
// Just change the value!
value.prop = someNewValue;
// Or, in the above case where value
// is not a reference
theList.set(index) = newValue;
// As we found and changed the value
// of interest, lets exit forEach
return false;
}
});
And yes, there's a version for Map too.

Related

Javascript Array.some() and Array.every() equivalent for Set?

In JavaScript, is there an equivalent of Array.some() and Array.every() for the Set built-in object?
No, the only built-in methods on Set.prototype are:
Set.prototype​.add()
Set.prototype​.clear()
Set.prototype​.delete()
Set.prototype​.entries()
Set.prototype​.for​Each()
Set.prototype​.has()
Set.prototype​.values()
Set.prototype​[##iterator]()
It'd probably be easiest to just convert the set to an array, and then use the array methods.
const set1 = new Set([1, 2]);
const set2 = new Set([-1, 2]);
const allPositive = set => [...set].every(num => num > 0);
console.log(
allPositive(set1),
allPositive(set2)
);
It's not natively available on the Set prototype, but if you found yourself needing this frequently, you could easily extent Set to add it.
class extendedSet extends Set{
every(f){
return Array.prototype.every.call([...this], f)
}
some(f){
return Array.prototype.some.call([...this], f)
}
}
let a_set = new extendedSet([1, 2, 3, 4]);
console.log(a_set.every(n => n < 2))
console.log(a_set.some(n => n < 2))
// still works as a Set
console.log(a_set.has(4))
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set#Methods is the documentation for the list avaiable methods for the Set
Methods:
Set.prototype​.add()
Set.prototype​.clear()
Set.prototype​.delete()
Set.prototype​.entries()
Set.prototype​.for​Each()
Set.prototype​.has()
Set.prototype​.values()
Set.prototype​##iterator
In your context you could do something like below:
Array.from(set).some() or Array.from(set).every()
For more info regarding Array vs Set
Other answers suggest first converting the set to an array and then using some array method. This is completely unnecessary and even sub-optimal.
You can use a for loop on the set instance itself, iterate over it, find an element that matches a given condition, then break the loop:
function some(set, predicate) {
for (const item of set)
if (predicate(item))
return true;
return false;
}
function every(set, predicate) {
for (const item of set)
if (!predicate(item))
return false;
return true;
}
const set = new Set([ 42, 17, -1, 8.3 ]);
every(set, (item) => typeof item === "number"); // true
every(set, (item) => item > 0); // false
some(set, (item) => item < 0); // true
some(set, (item) => item === 0); // false

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 }));

Javascript - find latest object depending of value

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]);

How to display only the latest data received?

Hey I am trying to show only the latest message of the day,
Thing is I am not sure how to do that as my code only picks up the one I first wrote..
it's not even the first object in the array.. but it still takes it.
*Note that this code runs every few seconds in intervals to check for new data received.
Below is the response I am using my logic on and the code with the logic
isMOTD = false;
var i = 0;
var MOTD = "";
while (messages[i].text && isMOTD == false) {
i++;
isMOTD = messages[i].text.includes("MOTD");
if (isMOTD)
MOTD = messages[i].text;
}
if (isMOTD) {
console.log(MOTD+' ' +'THIS IS MSG OF THE DAY')
$('.content', el).html(MOTD);
}
}
};
I would do something like this:
var MOTD = messages.filter(message => message.text.includes("MOTD"))
.reduce((a, b) => a.ts > b.ts ? a : b, {ts: -1, text: ''})
.text;
$('.content', el).html(MOTD);
The .filter() creates a new array which only includes messages with MOTD in them.
the .reduce() is going through that filtered array and keeping only the message who's timestamp is highest. I also have it default to an empty string if there are no strings that contain MOTD
And then .text, to deal with just the string, not the timestamps.
EDIT: i've been requested to add some more explanation.
First: arrow functions. EcmaScript 2015 (one of the newer versions of javascript) gave a new way to write functions. When you see an =>, think "function". Rather than doing this:
function (a, b) {
return a + b;
}
you can do this:
(a, b) => {
return a + b;
}
Or if there's just one statement as in this case, you can leave off the curly brackets, the semicolon, and the return:
(a, b) => a + b
Second: .filter. All arrays have a function on them called .filter. As the name suggests, the purpose is to filter the array. You pass in a function that describes how you want it to be filtered, and then it creates a new array with just the matching elements.
So consider the following:
var myArray = [1, 2, 3, 15, 18, 200];
var evenNumbers = myArray.filter(function (num) {
return num % 2 == 0
});
It will loop through the array, and for each element of the array, it calls the function i specified. If the function returns true, then that element is included. If the function returns false, then the element is not included. So for this sample code, evenNumbers will equal [2, 18, 200]
For your case, the filter that i'm doing is:
messages.filter(function (message) {
return message.text.includes("MOTD");
});
So the array that's returned by this will contain all messages who's text includes "MOTD". messages that lack "MOTD" are excluded.
Third: .reduce. All arrays have this function as well. Reduce is quite a versatile function, but the general purpose is to take your array, and in some way boil it down (or "reduce" it) to a single value. You pass in some function that you want to be called for every element of the array. When your function returns a value, that value gets carried forward and used the next time the function is run. Here's an example where i want to sum up all the numbers in an array:
var myArray = [1, 2, 3, 4];
var total = myArray.reduce(function (sumSoFar, current) {
var newSum = sumSoFar + current;
return newSum;
}, 0); //<--- this 0 is the initial value
So here's how it works: It's going to start with the initial value of 0, and then it calls the function. SumSoFar will be 0 at this point (because of the intial value), and current is 1 (because we're starting with the first element of the array, which has a value of 1). The function adds them together, and then returns a new sum of 1. Then the function gets called again, but now sumSoFar is 1 (because that's what was returned the last time), and current is 2 (because we're looking at the second element). It adds 1 + 2, and returns a new sum of 3. And so it continues, adding 3 + 3 to get 6, then adding 6 + 4 to get 10. We're done going the the array, so the final value is 10.
In your case, i want to step through the array and find only the message with the most recent timestamp. I start with an initial value of {ts: -1, text: ''}, because in case there are no messages with "MOTD" in them, i want to have the empty string be the result.
.reduce(function (mostRecentMessageSoFar, currentMessage) {
if (mostRecentMessageSoFar.ts > currentMessage.ts) {
return mostRecentMessageSoFar;
} else {
return currentMessage;
}
}, {ts: -1, text: ''});
So this will walk its way through the array (and remember, we're walking through the filtered array, so they all have "MOTD" in them), it is looking for the most recent message. Any time currentMessage is more recent than the best we've found so far, it switches to that one being the best so far. And in the end, we get the most recent message from the entire array.
That final message is an object that looks something like this:
{
type: 'message',
user: 'U0GL3BR52',
text: 'SOLVE MORE CASES #MOTD',
ts: 1505236695.000533
}
Only the text needs to be put into the dom, so i access that with .text
So, in long form, my solution is this:
var MOTDMessages = messages.filter(function (message) {
return message.text.includes("MOTD");
});
var MostRecentMOTDMessage = MOTDMessages.reduce(
function (mostRecentMessageSoFar, currentMessage) {
if (mostRecentMessageSoFar.ts > currentMessage.ts) {
return mostRecentMessageSoFar;
} else {
return currentMessage;
}
}, {ts: -1, text: ''});
var MOTDText = MostRecentMOTDMessage.text;
$('.content', el).html(MOTDText);
Just sort your message after time descending:
messages.sort((a,b) => b.ts - a.ts );
Then just take the first one:
messages[0]

How to remove an object from an array in Immutable?

Given a state like this:
state = {
things: [
{ id: 'a1', name: 'thing 1' },
{ id: 'a2', name: 'thing 2' },
],
};
How can I create a new state where ID "a1" is removed? It's easy enough to push new items:
return state.set(state.get('things').push(newThing));
But I can't figure out how to search for and remove an object by its id property. I tried this:
return state.set('tracks',
state.get('tracks').delete(
state.get('tracks').findIndex(x => x.get('id') === 'a2')
)
)
But it seems messy, plus it only works if the item is found, because if findIndex returns -1, that's a valid value for delete.
You can use Array#filter.
return state.set('things', state.get('things').filter(o => o.get('id') !== 'a1'));
When you are using filter it iterates all cycle -> one effective way is finding index => slice and using splitter ...
const index = state.findIndex(data => data.id === action.id);
return [...state.slice(0, index), ...state.slice(index + 1)];
Alternatively, as you are "searching and then deleting"...
var itemIndex = this.state.get("tracks").findIndex(x => x.get('id') === 'a2');
return itemIndex > -1 ? this.state.deleteIn(["tracks", itemIndex]) : this.state;
This will ensure the state is not mutated when there are no changes.
Found this thread while looking for a solution to a similar task.
Solved it with update method:
return state.update('things', (things) => things.filter((t) => t.id !== action.things.id))
any idea/comment which one is better/preferred?
You can do that even without immutable.js with following function.
function arrayFilter(array, filter) {
let ret = array
let removed = 0
for (let index = 0; index < array.length; index++) {
const passed = filter(array[index], index, array)
if (!passed) {
ret = [...ret.slice(0, index - removed), ...ret.slice(index - removed + 1)]
removed++
}
}
return ret
}
ImmutableJS working with nested arrays
Immutablejs is great but at the same time makes things more complicated in some edge cases, particularly when working with nested arrays.
Sometimes it is easier to take it back to JS in a general sense for this particular issue.
// 1. get a copy of the list into normal JavaScript
const myList = state.getIn(['root', 'someMap', 'myList']).toJS()
// 2. remove item in list using normal JavaScript and/or anything else
myList.splice(deleteIndex, 1)
// 3. return the new state based on mutated myList
return state
.mergeDeep({ root: { someMap: { myList: undefined } }})
.mergeDeep({ root: { someMap: { myList } }})
Unfortunately, step 3 is necessary to specifically set to undefined because if you simply set myList directly as an array value, ImmutableJS will do a comparison of values between the current list and only modify them creating strange behavior.
The justification for this is to simplify the mental overhead. I do not recommend doing this in a loop, rather manipulate the pure JS array in a loop if you must but should be prior to step 3.

Categories

Resources