I have an array that contains different clothes and the type of the cloth. For example, I may have a specific shirt that belongs to the shirts category. What I want to do is get all the types from an array and ignore any duplicate entries. So if I have 3 shirts and 2 trousers, I will only get 1 shirt and 1 trouser.
array = [
{
name: "test 1",
type: "shirt"
},
{
name: "test 2",
type: "shirt"
},
{
name: "test 3",
type: "trousers"
},
{
name: "test 4",
type: "trousers"
}
];
var categories = {};
for(var i = 0, len = array.length; i < len; i++) {
if(categories.indexOf(array[i].type) > -1) {
console.log('Duplicate type');
}
else {
console.log('New type');
categories.push(array[i].type);
}
}
But I end up getting TypeError: categories.indexOf is not a function.
Pretty short solution using ES6 Set object:
The Set object lets you store unique values of any type, whether
primitive values or object references.
var categories = new Set();
array.forEach((o) => categories.add(o.type));
categories = [...categories]; // Set to Array transformation
console.log(categories); // ["shirt", "trousers"]
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
You need an array for categories, not an object.
var categories = [];
array = [
{
name: "test 1",
type: "shirt"
},
{
name: "test 2",
type: "shirt"
},
{
name: "test 3",
type: "trousers"
},
{
name: "test 4",
type: "trousers"
}
];
var categories = [];
for(var i = 0, len = array.length; i < len; i++) {
if(categories.indexOf(array[i].type) > -1) {
console.log('Duplicate type');
}
else {
console.log('New type');
categories.push(array[i].type);
}
}
console.log(categories);
This happens because you define categories as object literal ({}), rather than an array ([]):
// --------------vv
var categories = {};
Your issue is that you are trying to invoke .push method on an object but the method is available only on Array. You need to make categories an array in order to push to it.
As an alternative, you can use pure function without any mutations using Array.prototype.reduce() to reduce the array of duplicate objects to unique ones:
var array = [
{
name: "test 1",
type: "shirt"
},
{
name: "test 2",
type: "shirt"
},
{
name: "test 3",
type: "trousers"
},
{
name: "test 4",
type: "trousers"
}
];
function unique(input) {
return input.reduce(function (acc, current) {
if (acc.indexOf(current.type) > -1) {
return acc
} else {
return acc.concat([current.type]);
}
}, [])
}
var categories = unique(array);
console.log(categories);
If you want to see the result of every row then I think first implementation could be the answer but if you want just the categories then using map make it simple.
array = [
{ name: "test 1", type: "shirt" },
{ name: "test 2", type: "shirt" },
{ name: "test 3", type: "trousers" },
{ name: "test 4", type: "trousers" }
];
// --------------------------------------
var categories = [];
array.forEach(v => {
if (this[v.type])
return console.log('Duplicate type');
console.log('New type');
this[v.type] = true;
categories.push(v.type);
}, {});
console.log(categories);
// --------------------------------------
var map = new Map;
array.forEach(v => map.set(v.type, v.type));
categories = [...map.keys()];
console.log(categories);
Related
I'm trying to create an array from items inside objects, as well as items inside arrays inside objects in a vue app, by using foreach to loop over them. It works well when I only have one single item, but I can't figure out how to loop over an array inside the object and add all of those items to the array I'm creating.
What I have now
const examples = [
{
name: "Example 1",
type: ["Meat", "Water", "Dairy"],
showDetail: false
},
{
name: "Example 2",
type: Fruit,
showDetail: false
},
{
name: "Example 3",
type: Vegetable,
showDetail: false
}
]
new Vue({
data: {
examplesList: examples,
type: ''
},
methods: {
filterList: function() {
this.type = event.target.value;
}
},
computed: {
uniqueList: function() {
const types = [];
this.examplesList.forEach((example) => {
if (!types.includes(example.type)) {
types.push(example.type);
}
});
return types;
}
}
})
It works fine if I remove the object with the array inside of "type", and adds the Fruit and Vegetable items to the array. Any ideas?
Desired output:
["Meat", "Water", "Dairy", "Fruit", "Vegetable"]
Here is one possible solution. You'll need to translate the solution to vue, of course, but the problem here really doesn't have anything to do with vue specifically so I've shown a vanilla javascript solution just to keep things simple.
const examples = [
{
name: "Example 1",
type: ["Meat", "Water", "Dairy", "Fruit"],
showDetail: false
},
{
name: "Example 2",
type: "Fruit",
showDetail: false
},
{
name: "Example 3",
type: "Vegetable",
showDetail: false
}
];
const types = [];
examples.forEach((example) => {
const exampleTypes = Array.isArray(example.type)
? example.type
: [example.type];
for (let exampleType of exampleTypes) {
if (!types.includes(exampleType)) {
types.push(exampleType);
}
}
});
console.log(types);
Here's an abstract way of doing that using a Set. Sets guarantee unique values meaning there's no need to check if an item is present or not.
Using just an array will become increasingly expensive to check if an item was already added as it will have to scan the entire array for each includes, O(n) time complexity.
const examples = [{
name: "Example 1",
type: ["Meat", "Water", "Dairy"],
showDetail: false
},
{
name: "Example 2",
type: "Fruit",
showDetail: false
},
{
name: "Example 3",
type: "Vegetable",
showDetail: false
}
];
const typeSet = new Set();
let types;
examples.forEach((example) => {
if (Array.isArray(example.type)) {
example.type.forEach(type => {
typeSet.add(type);
});
} else {
typeSet.add(example.type);
}
});
types = [...typeSet];
console.log(types);
Here is one possible solution to achieve the desired result:
computed: {
uniqueList: function() {
return this.examplesList.reduce(
(acc, itm) => (
Array.isArray(itm.type)
? itm.type.filter(t => !acc.includes(t)).length > 0
? [
...acc,
...itm.type.filter(t => !acc.includes(t))
]
: acc
: acc.includes(itm.type)
? acc
: [...acc, itm.type]
), []
)
}
}
Explanation
reduce is used on the array this.examplesList
Each item itm is processed and acc is the accumulator/aggregator (initially set to an empty array [])
if itm.type is an Array, then
if any elements in itm.type array is not already present in acc array, include it (by using the ... spread operator)
otherwise (ie, itm.type is a string)
if it is not already in acc, then include it (again, using ... spread operator)
That's it !
Please comment if any further clarification/s or question/s.
Code snippet
const examples = [{
name: "Example 1",
type: ["Meat", "Water", "Dairy"],
showDetail: false
},
{
name: "Example 2",
type: "Fruit",
showDetail: false
},
{
name: "Example 3",
type: "Vegetable",
showDetail: false
}
];
const getUniqueTypes = (arr = examples) => (
arr.reduce(
(acc, itm) => (
Array.isArray(itm.type)
? itm.type.filter(t => !acc.includes(t)).length > 0
? [
...acc,
...itm.type.filter(t => !acc.includes(t))
]
: acc
: acc.includes(itm.type)
? acc
: [...acc, itm.type]
), []
)
);
console.log(getUniqueTypes());
Working Demo :
const examples = [{
name: "Example 1",
type: ["Meat", "Water", "Dairy"],
showDetail: false
},
{
name: "Example 2",
type: "Fruit",
showDetail: false
},
{
name: "Example 3",
type: "Vegetable",
showDetail: false
}];
let newArray = []
examples.forEach((item) => {
if (typeof(item.type) === 'object') {
item.type.forEach((elem) => {
newArray.push(elem)
})
} else {
newArray.push(item.type)
}
})
console.log(newArray)
I am trying to convert a 2d array into a json object using a key map.
The key map looks like
var keys = ['id', 'title', 'customer.id', 'customer.name', 'customer.phone.home', 'customer.phone.mobile' ];
and the data is
var data = [
[1, 'Task 1', 'C1', 'Customer 1', '999', '8888'],
[2, 'Task 2', 'C2', 'Customer 2', '333', '5555']
];
Output JSON should be
var output = [
{
"id":1,
"title":"Task 1",
"customer":{
"id":"C1",
"name":"Customer 1",
"phone":{
"home":"999",
"mobile":"8888"
}
}
},
{
"id":2,
"title":"Task 2",
"customer":{
"id":"C2",
"name":"Customer 2",
"phone":{
"home":"333",
"mobile":"5555"
}
}
}
];
I am trying to do it something like but I am not good here making smerecursion etc. Could anyone help please?
function arrToJSON(headers, data){
var output = [];
data.forEach(row, index){
var cObj = {};
headers.forEach(header, itemIndex){
var headerParts = header.split('.');
// NOt sure what to do here
}
}
}
You can easily achieve the result using map and reduce in js.
createObj(acc, curr.split("."), 0, o[index]);
is the function that is used in recursion and that is what you're looking for.
Arguments
createObj(
acc, // object in which you want to add value
curr.split("."), // send path as an array
0, // current index in path, initially zero
o[index] // value to be assigned
);
var keys = [
"id",
"title",
"customer.id",
"customer.name",
"customer.phone.home",
"customer.phone.mobile",
];
var data = [
[1, "Task 1", "C1", "Customer 1", "999", "8888"],
[2, "Task 2", "C2", "Customer 2", "333", "5555"],
];
function createObj(obj, arr, index, value) {
if (index === arr.length - 1) obj[arr[index]] = value;
else {
if (!obj[arr[index]]) obj[arr[index]] = {};
createObj(obj[arr[index]], arr, index + 1, value);
}
}
const result = data.map((o) => {
return keys.reduce((acc, curr, index) => {
createObj(acc, curr.split("."), 0, o[index]);
return acc;
}, {});
});
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;
}
You can use simply use destructure and spread operator with reduce.
var data = [
[1, "Task 1", "C1", "Customer 1", "999", "8888"],
[2, "Task 2", "C2", "Customer 2", "333", "5555"],
];
const buildObject = (arr = []) => {
return arr.reduce((acc, [id, title, cid, name, home, mobile]) => {
const row = {
id,
title,
customer: { id: cid, name, phone: { home, mobile } },
};
return acc.concat(row);
}, []);
};
console.log(buildObject(data));
I have an object which I'm trying to filter out elements with a path and map, but I can't get past the first level into the nested children.
My object (with UI components removed):
const items = [
{
path: "/login"
},
{
path: "/help"
},
{
name: "Guidelines",
children: [
{
name: "Section 1",
children: [
{
name: "Chapter 1",
path: "/section-1/chapter-1"
},
{
name: "Chapter 2",
path: "/section-1/chapter-2"
}
]
},
{
name: "Section 2",
children: [
{
name: "Chapter 3",
path: "/section-2/chapter-3"
},
{
name: "Chapter 4",
path: "/section-2/chapter-4"
}
]
}
]
}
];
This filters the elements with a path, but only to the first level:
const filteredRoutes = items.filter((route) => route.path);
Result:
[
{"path":"/login"},
{"path":"/help"}
]
My goal is to have a list of routes with 6 items in this Codesandbox
[
{ "path": "/login" },
{ "path": "/help" },
{ "path": "/section-1/chapter-1" },
{ "path": "/section-1/chapter-2" },
{ "path": "/section-2/chapter-3" },
{ "path": "/section-2/chapter-4" },
]
Thanks
const getPath = (x) => (x.path ? { path: x.path } : x.children?.map(getPath));
const filteredRoutes = items && items.map(getPath).flat(Infinity);
Does this solve your problem?
const filteredRoutes = [];
const arr = items.map((item) => {
if (item.path) {
filteredRoutes.push({"path" : item.path});
} else {
item.children.map((child) => {
if (child.children) {
child.children.map((_child) => {
filteredRoutes.push({"path" : _child.path});
})
}
})
}
});
console.log(filteredRoutes);
Would something like this work?
const findRoutesWithPaths = (routes) => {
if (!routes) {
return [];
}
const filteredRoutes = [];
// Loop over all the routes
routes.forEach((item) => {
// Add `path` from self
if (item.path) {
filteredRoutes.push(item);
}
// Add `path`s from children
if (item.children) {
filteredRoutes.push(...findRoutesWithPaths(item.children));
}
});
return filteredRoutes;
};
const filteredRoutes = findRoutesWithPaths(items);
codesandbox
Although you wanted to use the filter method, I found a way to iterate your array of objects recursively in case you have an unknown depth, your pathArray should have a length of 6 given the example data, but it will work of you have more children in your data as well.
var pathArray = [];
//Loop through all the objects in your items array
for (var k = 0; k < items.length; k++) {
//For each object let's gather all the paths in the object
var route = items[k];
function getPath(obj) {
//If the object has a "children" attribute then we should look inside
if (obj.hasOwnProperty("children")) {
for (var i = 0; i < obj.children.length; i++) {
getPath(obj.children[i]);
}
}
// If not then this is the base level, which means there is a path attribute we need to grab
else {
pathArray.push(obj.path); //Add the path to our array
}
}
getPath(route);
}
Let me know if you need any more clarification
I am trying to get the key from the value of the Object. I have the following array:
["Test 91", "Test 92", "Demo 1", "Demo 2"]
And I have one object:
{
D123_EMG: {
value: "Test 91",
isArchived: true
}
D21: {
value: "Test 92",
isArchived: false
}
Z6200_EMO: {
value: "Demo 1",
isArchived: true
}
G211_GTH: {
value: "Demo 2",
isArchived: false
}
}
So how can I get key as D123_EMG if the value is Test 91?
I tried this, but not getting proper response
var data = Object.keys(objectData);
var keys = []
for(var i = 0; i < array.length; i++){
for(var j = 0; j < data.length; j++){
if(array[i] === objectData[data[j].value) {
keys.push(objectData[data[j])
}
}
}
Also, can it be optimized since I used two loops or one-liner approach?
You can use filter() in this way:
const values = ["Test 91", "Test 92", "Demo 1", "Demo 2"];
const data = {
D123_EMG: {
value: "Test 91",
isArchived: true
},
D21: {
value: "Test 92",
isArchived: false
},
Z6200_EMO: {
value: "Demo 1",
isArchived: true
},
G211_GTH: {
value: "Demo 2",
isArchived: false
}
}
const keysFound = Object.keys(data).filter(key => values.includes(data[key].value));
console.log(keysFound); // ["D123_EMG", "D21", "Z6200_EMO", "G211_GTH"];
This isn't really related to react. Someone else may have a cleaner solution, but here is one that will work if I understand your question correctly:
let data = {
D123_EMG: {
value: "Test 91",
isArchived: true
},
D21: {
value: "Test 92",
isArchived: false
},
Z6200_EMO: {
value: "Demo 1",
isArchived: true
},
G211_GTH: {
value: "Demo 2",
isArchived: false
}
}
let name = '';
Object.entries(data).forEach((v) => {
// if the object value matches, set the name variable to the key
if (v[1].value == 'Test 91') {
name = v[0];
}
})
console.log(name)
I like to use .reduce() which in this case also works. Read from the MDN documentation:
The reduce() method executes a reducer function (that you provide) on each element of the array, resulting in a single output value.
And you can combine it with Object.entries() where the documentation states:
The Object.entries() method returns an array of a given object's own enumerable string-keyed property [key, value] pairs, in the same order as that provided by a for...in loop. (The only important difference is that a for...in loop enumerates properties in the prototype chain as well).
See the working solution what I made:
const data = { D123_EMG: { value: "Test 91", isArchived: true }, D21: { value: "Test 92", isArchived: false }, Z6200_EMO: { value: "Demo 1", isArchived: true }, G211_GTH: { value: "Demo 2", isArchived: false } };
const filterValue = 'Test 91';
const entries = Object.entries(data);
const result = entries.reduce((a, c) => c[1].value === filterValue ? c[0] : a, '');
console.log(result);
I hope this helps!
If you're experiencing this problem in a state management store, then this is a sign that the store is not properly designed. Without more information, I can't really recommend an improvement on how to redesign your state.
So, barring a redesign of your state, you may consider creating a map by value like so:
const byValue = Object.keys(data).reduce((accumulator, currentKey) => {
const currentObject = data[currentKey];
currentObject.key = currentKey;
accumulator[currentObject.value] = currentObject;
return accumulator;
}, {});
This produces a map that looks like this:
{
"Test 91": { "value": "Test 91", "isArchived": true, "key": "D123_EMG" },
"Test 92": { "value": "Test 92", "isArchived": false, "key": "D21" },
"Demo 1": { "value": "Demo 1", "isArchived": true, "key": "Z6200_EMO" },
"Demo 2": { "value": "Demo 2", "isArchived": false, "key": "G211_GTH" }
}
With this, you use the value as the lookup key:
const test91 = byValue["Test 91"]
...
I am trying to create an object from a forEach loop in javascript and in this simple object, I am trying to add up all of the counts for each item in the array.
Currently, I'm using firebase in an ionic (angular/typescript) project and I am returning an array of items from firebase. The array looks something like this:
[
{
id:'uniqueID',
specs: {
count:5,
forSale:false,
group:"qPELnTnwGJEtJexgP2qX",
name:"Foxeer Cam 2",
type:"camera"
}
},
{
id:'uniqueID',
specs: {
count:4,
forSale:false,
group:"qPELnTnwGJEtJexgP2qX",
name:"Furibee 40a",
type:"esc"
}
},
{
id:'uniqueID',
specs: {
count:4,
forSale:false,
group:"qPELnTnwGJEtJexgP2qX",
name:"Runcam Cam 1",
type:"camera"
}
},
{
id:'uniqueID',
specs: {
count:1,
forSale:false,
group:"qPELnTnwGJEtJexgP2qX",
name:"Hobbywing 4 in 1",
type:"esc"
}
}
]
Here's how I'm running through these items (result being the list of items):
let partArr = [];
let typeObj = {};
result.forEach(part => {
//Put all parts in a list
partArr.push({
'id':part.id,
'specs':part.data(),
});
//Put all types in a list
typeObj[part.data().type] = {
'count': 0
};
});
Now, I need to increment the count, adding each part's count to the last depending on their type. The typeObj should look something like this.
{
esc: {
count:5
},
camera: {
count:10
}
}
I tried adding the count to the count like so:
typeObj[part.data().type] = {
'count': typeObj[part.data().type].count + part.data().count
};
but it does not recognize there being a count when I'm first making the object. (I think).
Any advice?
You can use Array#reduce to accomplish this since you want to transform an array into a single object:
const array = [
{
id: 'uniqueID',
specs: {
count: 5,
forSale: false,
group: "qPELnTnwGJEtJexgP2qX",
name: "Foxeer Cam 2",
type: "camera"
}
},
{
id: 'uniqueID',
specs: {
count: 4,
forSale: false,
group: "qPELnTnwGJEtJexgP2qX",
name: "Furibee 40a",
type: "esc"
}
},
{
id: 'uniqueID',
specs: {
count: 4,
forSale: false,
group: "qPELnTnwGJEtJexgP2qX",
name: "Runcam Cam 1",
type: "camera"
}
},
{
id: 'uniqueID',
specs: {
count: 1,
forSale: false,
group: "qPELnTnwGJEtJexgP2qX",
name: "Hobbywing 4 in 1",
type: "esc"
}
}
];
const typeObj = array.reduce((obj, { specs: { count, type } }) => {
obj[type] = obj[type] || { count: 0 };
obj[type].count += count;
return obj;
}, {});
console.log(typeObj);
What this does is assign obj[type] to either itself or if it doesn't yet exist, a new object { count: 0 }. Then it increments the count property of obj[type] by the specified count in the data.
If you have a data method that returns an object with the data (as you seem to have in the question), you can modify it like so:
const typeObj = array.reduce((obj, item) => {
const { type, count } = item.data().specs;
obj[type] = obj[type] || { count: 0 };
obj[type].count += count;
return obj;
}, {});
console.log(typeObj);
It would probably be easier to use reduce to generate the object with the counts all at once, and to call .data() only once on each iteration:
const results = [
{
data() { return { specs: {
count:5,
forSale:false,
group:"qPELnTnwGJEtJexgP2qX",
name:"Foxeer Cam 2",
type:"camera"
}}}},
{
data() { return { specs: {
count:4,
forSale:false,
group:"qPELnTnwGJEtJexgP2qX",
name:"Furibee 40a",
type:"esc"
}}}},
{
data() { return { specs: {
count:4,
forSale:false,
group:"qPELnTnwGJEtJexgP2qX",
name:"Runcam Cam 1",
type:"camera"
}}}},
{
data() { return { specs: {
count:1,
forSale:false,
group:"qPELnTnwGJEtJexgP2qX",
name:"Hobbywing 4 in 1",
type:"esc"
}}}}
];
const typeObj = results.reduce((a, item) => {
const { count, type } = item.data().specs;
if (!a[type]) a[type] = { count: 0 };
a[type].count += count;
return a;
}, {});
console.log(typeObj);