For the example, SomeItem is the model for an object (would be modeled as an interface in Typescript or you can just imagine that there is an item with the form of SomeItem if we are in untyped land.
Say I have a Set: mySet = new Set([{item: SomeItem, selected: true}, ...]).
And I want to check if itemA: SomeItem is selected or not.
What is the cleanest way to do this?
This did not work:
const isSelected = mySet.has({item: itemA, selected: true});
Nor did this:
const isSelected = Array.from(mySet).includes({item: itemA, selected: true});
I'm assuming the above two did not work because it is trying to compare the objects by reference, rather than value.
This does work:
let isSelected: boolean;
mySet.forEach(state => {
if (state.item === itemA) {
isSelected = state.selected;
}
});
But my gut tells me there is a correct way to do this.
So,
How do I extract the value of a property of an object in a Set?
Comparing two objects with the same properties returns true only if they have the same reference, I would suggest to compare their properties as the properties are primitive values..
The array some method can be used to filter if the set contains an specific object
let mySet = new Set([{item: 'SomeItem', selected: true}]);
let itemA = "SomeItem";
let isSelected = Array.from(mySet).some(element => element.item === itemA);
console.log(isSelected);
Let's take a look at it this way, Sets mainly return an iterable. Sure, they're hashed in order, as is a Map, but from the looks of your data structure, a Map would benefit you more here.
const x = new Set([
{ "foo": 1, selected: true },
{ "bar": 1, selected: false },
{ "baz": 1, selected: false },
{ "barf": 1, selected: false },
]);
Now, to get what you're looking for, you'll need as you did, convert to an array using Array.from (or [...x] spread it) and iterate, finding they key.
Now, as a Map:
const y = new Map();
y.set("foo", { selected: true });
y.set("bar", { selected: false });
y.set("baz", { selected: false });
y.set("barf", { selected: false });
With this, you simply change the structure slightly to give item1 or whatever you use the Map key, and set whatever elements you want.
y.has("foo"); // true
y.get("foo").selected; //true
So if you wanted here, it's much easier to grab the iterable key name and get which Map index has the property you want
Related
I have an object that looks like this:
const query = {
item: {
'available': false
},
status: {
'locked': true
}
}
I want to fetch the nested keys available and locked and create a new object with their values, resulting in
{
'available': false,
'locked': true
}
Whats the most precise way to do this? Bear in mind that the keys and the nested keys are dynamic, i.e. can change in future depending on another object.
You could get the values and create a new object.
const
query = { item: { available: false }, status: { locked: true } },
joined = Object.assign({}, ...Object.values(query));
console.log(joined);
Use spread operator:
const query = {
item: {
'available': false
},
status: {
'locked': true
}
}
const value = {...query.item, ...query.status};
console.log(value);
I'm having a bit of trouble understanding the placement of the "length" property, in a to-do-list app that I'm making as part of a JavaScript course.
The To-Do-List:
const todos = [{
text: 'First task',
completed: true
}, {
text: 'Second task',
completed: false
}, {
text: 'Third task',
completed: true
}]
My question:
The code below gets me a total # of tasks that still need to be completed. What I don't understand is: why does the .length property come after the closing "})"? I would have assumed that the code is !todo.completed.length, rather than !todo.completed}).length. I can see that only the latter works - but I'm trying to grasp why. Thank you!!
let remaining = todos.filter(function(todo){
return !todo.completed
}).length
This is because Array.prototype.filter returns an array. MDN reference for Array.prototype.filter.
For example, these two statements are equivalent:
[1, 2, 3].filter(function(){
return false
}).length
and
[].length
This is because this:
[1, 2, 3].filter(function(){
return false
})
returns [].
var one = [1, 2, 3].filter(function() {
return false
}) // Removes all elements from array
var two = []
console.log(one, one.length)
console.log(two, two.length)
First of all: the filter function iterates over each todo object and returns a new list of todo objects.
Each todo object is passed to the function you provide as parameter (in this case an anonymous function), only when this function returns true the todo object is added to the new list.
Since you want to know the amount of todo's that still need to be completed you return !todo.completed because this will return true when todo.completed is false.
When the filter function iterated over each object it returns the new list and that's when you can access it's length.
returning !todo.completed.lenght doesn't really make any sense because you're trying to get the length of a boolean and the filter function expects true or false.
Okay, so I am trying to create a function that allows you to input an array of Objects and it will return an array that removed any duplicate objects that reference the same object in memory. There can be objects with the same properties, but they must be different in-memory objects. I know that objects are stored by reference in JS and this is what I have so far:
const unique = array => {
let set = new Set();
return array.map((v, index) => {
if(set.has(v.id)) {
return false
} else {
set.add(v.id);
return index;
}
}).filter(e=>e).map(e=>array[e]);
}
Any advice is appreciated, I am trying to make this with a very efficient Big-O. Cheers!
EDIT: So many awesome responses. Right now when I run the script with arbitrary object properties (similar to the answers) and I get an empty array. I am still trying to wrap my head around filtering everything out but on for objects that are referenced in memory. I am not positive how JS handles objects with the same exact key/values. Thanks again!
Simple Set will do the trick
let a = {'a':1}
let b = {'a': 1,'b': 2, }
let c = {'a':1}
let arr = [a,b,c,a,a,b,b,c];
function filterSameMemoryObject(input){
return new Set([...input])
}
console.log(...filterSameMemoryObject(arr))
I don't think you need so much of code as you're just comparing memory references you can use === --> equality and sameness .
let a = {'a':1}
console.log(a === a ) // return true for same reference
console.log( {} === {}) // return false for not same reference
I don't see a good reason to do this map-filter-map combination. You can use only filter right away:
const unique = array => {
const set = new Set();
return array.filter(v => {
if (set.has(v.id)) {
return false
} else {
set.add(v.id);
return true;
}
});
};
Also if your array contains the objects that you want to compare by reference, not by their .id, you don't even need to the filtering yourself. You could just write:
const unique = array => Array.from(new Set(array));
The idea of using a Set is nice, but a Map will work even better as then you can do it all in the constructor callback:
const unique = array => [...new Map(array.map(v => [v.id, v])).values()]
// Demo:
var data = [
{ id: 1, name: "obj1" },
{ id: 3, name: "obj3" },
{ id: 1, name: "obj1" }, // dupe
{ id: 2, name: "obj2" },
{ id: 3, name: "obj3" }, // another dupe
];
console.log(unique(data));
Addendum
You speak of items that reference the same object in memory. Such a thing does not happen when your array is initialised as a plain literal, but if you assign the same object to several array entries, then you get duplicate references, like so:
const obj = { id: 1, name: "" };
const data = [obj, obj];
This is not the same thing as:
const data = [{ id: 1, name: "" }, { id: 1, name: "" }];
In the second version you have two different references in your array.
I have assumed that you want to "catch" such duplicates as well. If you only consider duplicate what is presented in the first version (shared references), then this was asked before.
I have an object:
var props = {//values here};
this.options: {
cssSelectorAncestor: props.cssSelectorAncestor,
media: {
autoPlay: props.autoPlay,
muted: props.muted
}
}
this.options = _.merge(
{
defaultPlaybackRate: 1.0,
minPlaybackRate: 0.5
}, props);
How do I merge all of the properties in props apart from the ones that have already been assigned to options.
Consider props immutable.
I'm just guessing here, because I'm having trouble following your sample code, but I think you want defaults():
const result = _.defaults({ foo: true }, { foo: false, bar: true });
// ➜ { foo: true, bar: true }
This will only merge in properties that don't already exist.
Merge all three objects into an empty object. Make sure you do it in the right order. The keys with highest priority go on the right.
Let's say we have an immutable object that is created using Facebook's great Immutable.js. I want to compare two lists that were produced using .map or .filter out of single source and make sure they are equal. It seems to me, that when using map/filter you are creating a new object that has nothing to do with a previous object. How can I make triple equality === work? Does it make sense at all?
var list = Immutable.List([ 1, 2, 3 ]);
var list1 = list.map(function(item) { return item; })
var list2 = list.map(function(item) { return item; })
console.log("LIST1 =", list1.toJS()) // [1, 2, 3]
console.log("LIST2 =", list2.toJS()) // [1, 2, 3]
console.log("EQUAL ===?", list1===list2); // false! Why? How?
You can play with it here: http://jsfiddle.net/eo4v1npf/1/
Context
I am building application using React + Redux. My state has one list that contains items, that have attribute selected:
items: [
{id: 1, selected: true},
{id: 2, selected: false},
{id: 3, selected: false},
{id: 4, selected: true}
]
I want to pass only selected ids to another container, so I tried it using simple connect:
function getSelectedIds(items) {
return items
.filter((item) => item.get("selected"))
.map((item) => item.get("id"));
}
export default connect(
(state: any) => ({
ids: getSelectedIds(state.get("items"))
})
)(SomePlainComponent);
Problem is, if I set additional attributes:
{id: 1, selected: true, note: "The force is strong with this one"}
This causes state to change and SomePlainComponent to rerender, although the list of selected Ids is exactly the same. How do I make sure pure renderer works?
Edit with some additional info
For react pure rendering I was using mixin from react-pure-render:
export default function shouldPureComponentUpdate(nextProps, nextState) {
return !shallowEqual(this.props, nextProps) ||
!shallowEqual(this.state, nextState);
}
As it is not aware of props that could be immutable, they are treated as changed, i.e.
this.props = {
ids: ImmutableList1
}
nextProps = {
ids: ImmutableList2
}
Although both attributes ids are equal by content, they are completely different objects and do not pass ImmutableList1 === ImmutableList2 test and shouldComponentUpdate returns true. #Gavriel correctly pointed that deep equal would help, but that should be the last resort.
Anyway, I'll just apply accepted solution and problem will be solved, thanks guys! ;)
You can never have strict equality of immutable structures since an Immutable.js object, inherently, is unique.
You can use the .is function which takes two immutable objects and compares the values within them. This works because Immutable structures implement equals and hashCode.
var map1 = Immutable.Map({a:1, b:1, c:1});
var map2 = Immutable.Map({a:1, b:1, c:1});
console.log(Immutable.is(map1, map2));
// true
If you want to keep your component pure and working with === then you can also denormalize your Redux state and store the selectedIds as a property in the store. Only update this list when an action occurs that adds/removes a selected item or toggles an item selection, but not when other arbitrary properties of the item are updated.