Can destructuring assignment be used to effect a projection in CoffeeScript? - javascript

I'm having some trouble understanding destructuring assignment in CoffeeScript. The documentation contains a couple of examples which together seem to imply that renaming objects during assignment can be used to project (i.e. map, translate, transform) a source object.
I am trying to project a = [ { Id: 1, Name: 'Foo' }, { Id: 2, Name: 'Bar' } ] into b = [ { x: 1 }, { x: 2 } ]. I've tried the following without success; I've clearly misunderstood something. Can anyone explain whether this is possible?
My Poor Attempts That Don't Return [ { x: 1 }, { x: 2 } ]
a = [ { Id: 1, Name: 'Foo' }, { Id: 2, Name: 'Bar' } ]
# Huh? This returns 1.
x = [ { Id } ] = a
# Boo! This returns [ { Id: 1, Name: 'Foo' }, { Id: 2, Name: 'Bar' } ]
y = [ { x: Id } ] = a
# Boo! This returns [ { Id: 1, Name: 'Foo' }, { Id: 2, Name: 'Bar' } ]
z = [ { Id: x } ] = a
CoffeeScript's Parallel Assignment Example
theBait = 1000
theSwitch = 0
[theBait, theSwitch] = [theSwitch, theBait]
I understand this example as implying that variables can be renamed which in this case is used to perform a swap.
CoffeeScript's Arbitrary Nesting Example
futurists =
sculptor: "Umberto Boccioni"
painter: "Vladimir Burliuk"
poet:
name: "F.T. Marinetti"
address: [
"Via Roma 42R"
"Bellagio, Italy 22021"
]
{poet: {name, address: [street, city]}} = futurists
I understand this example as defining a selection of properties from an arbitrary object which includes assigning the elements of an array to variables.
Update: Using thejh's Solution to Flatten an Array of Nested Objects
a = [
{ Id: 0, Name: { First: 'George', Last: 'Clinton' } },
{ Id: 1, Name: { First: 'Bill', Last: 'Bush' } },
]
# The old way I was doing it.
old_way = _.map a, x ->
{ Id: id, Name: { First: first, Last: last } } = x
{ id, first, last }
# Using thejh's solution...
new_way = ({id, first, last} for {Id: id, Name: {First: first, Last: last}} in a)
console.log new_way

b = ({x} for {Id: x} in a) works:
coffee> a = [ { Id: 1, Name: 'Foo' }, { Id: 2, Name: 'Bar' } ]
[ { Id: 1, Name: 'Foo' },
{ Id: 2, Name: 'Bar' } ]
coffee> b = ({x} for {Id: x} in a)
[ { x: 1 }, { x: 2 } ]
coffee>

CoffeeScript Cookbook solves exactly the same problem as yours - solution is map:
b = a.map (hash) -> { x: hash.id }
or list comprehension:
c = ({ x: hash.id } for hash in a)
You can check this fiddle online on CoffeScript homepage (uses console.info to show results).
EDIT:
To make it destrutive just assign mapped variable a to itself:
a = a1 = [ { Id: 1, Name: 'Foo' }, { Id: 2, Name: 'Bar' } ]
a = a.map (hash) -> { x: hash.Id }
console.info a;
a1 = ({ x: hash.Id } for hash in a1)
console.info a1;

Related

How to groupBy the array of object and concat into single array? [duplicate]

This question already has answers here:
group array of objects by id
(8 answers)
Closed 1 year ago.
I want to group the array of objects based on the key and concat all the grouped objects into a single array. GroupBy based on the id
example,
payload
[
{
id: 1,
name: 'a'
},
{
id: 1,
name: 'b'
},
{
id: 1,
name: 'c'
},
{
id: 2,
name: 'b'
},
{
id: 2,
name: 'c'
}
]
expected response
[
[
{
id: 1,
name: 'a'
},
{
id: 1,
name: 'b'
},
{
id: 1,
name: 'c'
}
],
[
{
id: 2,
name: 'b'
},
{
id: 2,
name: 'c'
}
]
]
All the matched elements are in the same array and all the arrays should be in a single array.
Array.redue will help
const input = [
{ id: 1, name: 'a' },
{ id: 1, name: 'b' },
{ id: 1, name: 'c' },
{ id: 2, name: 'b' },
{ id: 2, name: 'c' }
];
const output = input.reduce((acc, curr) => {
const node = acc.find(item => item.find(x => x.id === curr.id));
node ? node.push(curr) : acc.push([curr]);
return acc;
}, []);
console.log(output)
Extract the ids using Set so you have a unique set of them,
then loop over those ids and filter the original array based on it.
let objects = [
{
id: 1,
name: 'a'
},
{
id: 1,
name: 'b'
},
{
id: 1,
name: 'c'
},
{
id: 2,
name: 'b'
},
{
id: 2,
name: 'c'
}
]
let ids = [...new Set(objects.map(i => i.id))]
let result = ids.map(id => objects.filter(n => id === n.id))
console.log(result)
you can create a object with ids array by using Array.reduce method, and get the object values by Object.values
var s = [{
id: 1,
name: 'a'
},
{
id: 1,
name: 'b'
},
{
id: 1,
name: 'c'
},
{
id: 2,
name: 'b'
},
{
id: 2,
name: 'c'
}
];
//go through the input array and create a object with id's, group the values to gather
var ids = s.reduce((a, c) => {
//check object has the `id` property, if not create a property and assign empty array
if (!a[c.id])
a[c.id] = [];
//push the value into desidred object property
a[c.id].push(c)
//return the accumulator
return a;
}, {});
//get the grouped array as values
var outPut = Object.values(ids);
console.log(outPut);
1) You can easily achieve the result using Map and forEach easily
const arr = [
{
id: 1,
name: "a",
},
{
id: 1,
name: "b",
},
{
id: 1,
name: "c",
},
{
id: 2,
name: "b",
},
{
id: 2,
name: "c",
},
];
const map = new Map();
arr.forEach((o) => !map.has(o.id) ? map.set(o.id, [o]) : map.get(o.id).push(o));
const result = [...map.values()];
console.log(result);
/* This is not a part of answer. It is just to give the output full height. So IGNORE IT */
.as-console-wrapper { max-height: 100% !important; top: 0; }
2) You can also achieve the result using reduce
const arr = [
{
id: 1,
name: "a",
},
{
id: 1,
name: "b",
},
{
id: 1,
name: "c",
},
{
id: 2,
name: "b",
},
{
id: 2,
name: "c",
},
];
const result = [...arr.reduce((map, curr) => {
!map.has(curr.id) ? map.set(curr.id, [curr]) : map.get(curr.id).push(curr);
return map;
}, new Map()).values()];
console.log(result);
/* This is not a part of answer. It is just to give the output full height. So IGNORE IT */
.as-console-wrapper { max-height: 100% !important; top: 0; }

Remove a specific object from an object array, filtering by a key and value pair

Is there any quick way to remove a specific object from an object array, filtering by a key and value pair, without specifying an index number?
For example, if there was an object array like so:
const arr = [
{ id: 1, name: 'apple' },
{ id: 2, name: 'banana' },
{ id: 3, name: 'cherry' },
...,
{ id: 30, name: 'grape' },
...,
{ id: 50, name: 'pineapple' }
]
How can you remove only the fruit which has the id: 30 without using its index number?
I have already figured out one way like the following code, but it looks like a roundabout way:
for ( let i = 0; i < arr.length; i++) {
if ( arr[i].id === 30 ) {
arr.splice(i, 1);
}
}
with es6 standard, you can use the filter method
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
const arr = [
{ id: 1, name: 'apple' },
{ id: 2, name: 'banana' },
{ id: 3, name: 'cherry' },
{ id: 30, name: 'grape' },
{ id: 50, name: 'pineapple' }
];
// !== for strict checking
console.log(arr.filter(e => e.id !== 30))

Get the object with more properties from an array of objects

Assuming I have the following array in a JSON file:
[
{ id: 1 },
{ name: 'foo' },
{ id: 3, name: 'foo', nick: 'bar' },
{ id: 4, nick: 'next' },
{ nick: 'nextnext' }
]
How to get the object with more properties? In this example I should get the third item: { id: 3, name: 'foo', nick: 'bar' }
If there is another object with 3 properties, I can get two results or the last found, it doesn't matter, my purpose is to know all properties an object can have.
To cope with multiple results, you could use filter.
var data = [
{ id: 1 },
{ name: 'foo' },
{ id: 3, name: 'foo', nick: 'bar' },
{ id: 4, nick: 'next' },
{ nick: 'nextnext' },
{ id: 6, name: 'another 3', nick: '3'}
]
const mx = Math.max(...data.map(m => Object.keys(m).length));
const res = data.filter(f => Object.keys(f).length === mx)
console.log(res);
You can create an array and put values based on key length.
Since you want objects with most keys, you can get the last item.
var data = [
{ id: 1 },
{ name: 'foo' },
{ id: 3, name: 'foo', nick: 'bar' },
{ id: 4, nick: 'next' },
{ nick: 'nextnext' }
];
var res = data.reduce((a, c) => {
const len = Object.keys(c).length;
a[len] = a[len] || [];
a[len].push(c);
return a;
}, []).pop();
console.log(res);
You can use reduce and Object.keys() to return the object which has more length.
Try the following way:
var data = [
{ id: 1 },
{ name: 'foo' },
{ id: 3, name: 'foo', nick: 'bar' },
{ id: 4, nick: 'next' },
{ nick: 'nextnext' }
]
var res = data.reduce((a, c) => {
return Object.keys(a).length > Object.keys(c).length ? a : c;
})
console.log(res);
let biggestObj = {};
for(let el of array){
if(Object.keys(el).length > Object.keys(biggestObj).length){
biggestObj = el;
}
}
This should do the job!

filter objects from array that have the same value at given key

Let's say we have an array that looks like this:
[
{
id: 0,
name: 'A'
},
{
id: 1,
name:'A'
},
{
id: 2,
name: 'C'
},
{
id: 3,
name: 'B'
},
{
id: 4,
name: 'B'
}
]
I want to keep only this objects that have the same value at 'name' key. So the output looks like this:
[
{
id: 0,
name: 'A'
},
{
id: 1,
name:'A'
},
{
id: 3,
name: 'B'
},
{
id: 4,
name: 'B'
}
]
I wanted to use lodash but I don't see any method for this case.
You can try something like this:
Idea:
Loop over the data and create a list of names with their count.
Loop over data again and filter out any object that has count < 2
var data = [{ id: 0, name: 'A' }, { id: 1, name: 'A' }, { id: 2, name: 'C' }, { id: 3, name: 'B' }, { id: 4, name: 'B' }];
var countList = data.reduce(function(p, c){
p[c.name] = (p[c.name] || 0) + 1;
return p;
}, {});
var result = data.filter(function(obj){
return countList[obj.name] > 1;
});
console.log(result)
A lodash approach that may (or may not) be easier to follow the steps of:
const originalArray = [{ id: 0, name: 'A' }, { id: 1, name: 'A' }, { id: 2, name: 'C' }, { id: 3, name: 'B' }, { id: 4, name: 'B' }];
const newArray =
_(originalArray)
.groupBy('name') // when names are the same => same group. this gets us an array of groups (arrays)
.filter(group => group.length == 2) // keep only the groups with two items in them
.flatten() // flatten array of arrays down to just one array
.value();
console.log(newArray)
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.11/lodash.min.js"></script>
A shorter solution with array.filter and array.some:
var data = [ { ... }, ... ]; // Your array
var newData = data.filter((elt, eltIndex) => data.some((sameNameElt, sameNameEltIndex) => sameNameElt.name === elt.name && sameNameEltIndex !== eltIndex));
console.log("new table: ", newTable);
You could use a hash table and a single loop for mapping the objects or just an empty array, then concat the result with an empty array.
var data = [{ id: 0, name: 'A' }, { id: 1, name: 'A' }, { id: 2, name: 'C' }, { id: 3, name: 'B' }, { id: 4, name: 'B' }],
hash = Object.create(null),
result = Array.prototype.concat.apply([], data.map(function (o, i) {
if (hash[o.name]) {
hash[o.name].update && hash[o.name].temp.push(hash[o.name].object);
hash[o.name].update = false;
return o;
}
hash[o.name] = { object: o, temp: [], update: true };
return hash[o.name].temp;
}));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Efficiently merging fields from one array into two other arrays

Let's say you've got three arrays of objects:
let a1 = [
{ id: 1, name: 'foo' },
{ id: 2, name: 'bar' },
{ id: 3, name: 'baz' }
]
let a2 = [
{ name: 'foo' },
{ name: 'bar' }
]
let a3 = [
{ name: 'bar' },
{ name: 'baz' }
]
The goal is to use a1 as a source, and add an id field to the elements of a2 and a3 with corresponding name fields in a1. What is an efficient way of accomplishing this? (Note: 'efficient' here meaning 'something more elegant than loops-within-loops-within-loops'.)
The result should look like this:
a2: [
{ id: 1, name: 'foo' },
{ id: 2, name: 'bar' }
]
a3: [
{ id: 2, name: 'bar' },
{ id: 3, name: 'baz' }
]
You could use a Map for referencing the id of a given name. Then assign.
var a1 = [{ id: 1, name: 'foo' }, { id: 2, name: 'bar' }, { id: 3, name: 'baz' }],
a2 = [{ name: 'foo' }, { name: 'bar' }],
a3 = [{ name: 'bar' }, { name: 'baz' }],
map = new Map(a1.map(o => [o.name, o.id]));
[a2, a3].forEach(a => a.forEach(o => o.id = map.get(o.name)));
console.log(a2);
console.log(a3);
.as-console-wrapper { max-height: 100% !important; top: 0; }
For an alternative answer, it could be like this.
It doesn't include loops and may be the shortest code in the answers.
const a1 = [{ id: 1, name: 'foo' }, { id: 2, name: 'bar' }, { id: 3, name: 'baz' }];
const a2 = [{ name: 'foo' }, { name: 'bar' }];
const a3 = [{ name: 'bar' }, { name: 'baz' }];
let f = x => a1.filter(a => x.some(y => y.name === a.name));
console.log(f(a2));
console.log(f(a3));
.as-console-wrapper { max-height: 100% !important; top: 0; }
a2.forEach((a2Elem) => a2Elem.id = a1.filter((a1Elem) => a1Elem.name === a2Elem.name)[0].id)
I'd first take the indexes of the given names, then just map the array to be merged into:
function combine(mergeInto, base) {
let indexes = base.map(e => e.name);
return mergeInto.map(e => ({
name: e.name,
id: base[indexes.indexOf(e.name)].id
}));
}
let a1 = [
{ id: 1, name: 'foo' },
{ id: 2, name: 'bar' },
{ id: 3, name: 'baz' }
]
let a2 = [
{ name: 'foo' },
{ name: 'bar' }
]
let a3 = [
{ name: 'bar' },
{ name: 'baz' }
]
function combine(mergeInto, base) {
let indexes = base.map(e => e.name);
return mergeInto.map(e => ({
name: e.name,
id: base[indexes.indexOf(e.name)].id
}));
}
console.log(combine(a3, a1));
A single loop proposal - create a hash table and then merge fields into the arrays - demo below:
let a1=[{id:1,name:'foo'},{id:2,name:'bar'},{id:3,name:'baz'}], a2=[{name:'foo'},{name:'bar'}], a3=[{name:'bar'},{name:'baz'}];
// create a hash table
let hash = a1.reduce(function(p,c){
p[c.name] = c;
return p;
},Object.create(null))
// merge the results
function merge(arr) {
Object.keys(arr).map(function(e){
arr[e]['id'] = hash[arr[e].name]['id'];
});
return arr;
}
console.log(merge(a2), merge(a3));
.as-console-wrapper{top:0;max-height:100%!important;}

Categories