Array with subarray loop Javascript - javascript

Is it possible to create an array and loop through it in JavaScript like below?
<?php
$options = array(
'color' => array('blue', 'yellow', 'white'),
'size' => array('39', '40', '41'),
);
foreach($options as $option => $values){
echo $option.'<br>';
foreach($values as $value){
echo $value.' ';
}
echo '<br>';
}
?>
I checked the internet but I can not find a good example.
Thanks for helping!

Exactly
let options = {
color: ['blue', 'yellow', 'white'],
size: [39, 40, 41]
};
You have three ways to do this:
FOR
for (option in options) {
console.log(option);
var values = options[option];
for (let i = 0, len = values.length, value = values[i]; i < len; value = values[++i]) {
console.log(value);
}
}
ARRAY FOREACH
for (option in options) {
console.log(option);
options[option].forEach(value => {
console.log(value);
});
}
Easiest way: jQuery
$.each(options, (option, values) => {
console.log(option);
$.each(values, (key, value) => {
console.log(value);
});
});

If I understand you correctly you want a javascript equivalent to the php code you've given?
If so, it could be achieved the following way:
let options = {
color: ['blue', 'yellow', 'white'],
size: [39, 40, 41]
};
for(option of Object.keys(options)) {
console.log(option);
for(value of options[option]) {
console.log(value);
}
}
this is not exactly the same as your php code, but the loop works in a similar way.

Arrays in javascript have integer keys (indexes). You can use an object as you need to store string keys on the parent level and the values can be arrays as they don't have any string keys
const options = {
color: ['blue', 'yellow', 'white'],
size: ['39', '40', '41'],
}
Object.entries(options).forEach(([key, value]) => {
console.log(key)
value.forEach(el => console.log(el))
})

Related

How to group javascript array based on some property?

I have a JS array of objects like this:
var myArray = [
{ line: 20, text: [31, 80] },
{ line: 10, text: [80, 22] }
]
lines are unique in entire myArray , each line has some texts (which are not unique).
How to match each text to its corresponding lines?
The final result should be like this:
var myNewArray = [
{ text: 31, line: [20] },
{ text: 80, line: [20, 10] },
{ text: 22, line: [10] }
]
Some approaches with Map.
As result you get a temporary map which collects all text, grouped by line. To get an array of objects, map the key/values pairs as eanted properties.
Because of having nested array of the data, you need eiter to normalize the data to get single line/text values and then add a grouping by text,
const
data = [{ line: 20, text: [31, 80] }, { line: 10, text: [80, 22] }],
result = Array.from(
data
.flatMap(({ line, text }) => text.map(text => ({ text, line })))
.reduce((m, { text, line }) => m.set(text, [...(m.get(text) || []), line]), new Map),
([text, line]) => ({ text, line })
);
console.log(result);
Or do it in a single step but with a nested approach of reducing the outer (line) and inner arrays (text arrays).
const
data = [
{ line: 20, text: [31, 80] },
{ line: 10, text: [80, 22] }
],
result = Array.from(
data.reduce(
(m, { line, text }) =>
text.reduce(
(n, text) => n.set(text, [...(n.get(text) || []), line]),
m
),
new Map
),
([text, line]) => ({ text, line })
);
console.log(result);
Here's how:
var myArray = [
{ line: 20, text: [31, 80] },
{ line: 10, text: [80, 22] }
]
var newArray = myArray.reduce((acc, {line, text}) => {
for( let t of text ){
const match = acc.find(({text}) => text == t) // check if the text already exists in newArray
if( match ) match.lines.push(line) // if exists, add the line to that text
else acc.push({text:t, lines:[line]}) // it not, create a new object with that line
}
return acc
}, [])
console.log( newArray )
Or by first generating an Object instead of an Array, which is faster if your dataset is huge, and then convert that to an Array at the end:
var myArray = [
{ line: 20, text: [31, 80] },
{ line: 10, text: [80, 22] }
]
// generate a key/value pairs for text/lines
var newArray = myArray.reduce((acc, {line, text}) => {
for( let t of text )
acc[t] = [...(acc[t] || []), line]
return acc
}, {})
// convert the above Object to an Array of Objects (AKA Collection)
newArray = Object.entries(newArray).map(([text,lines]) => ({text, lines}))
console.log( newArray )
Probably easiest by first building an intermediate Map that indexes lines by text:
const data = [
{line: 20, text: [31,80]},
{line: 10, text: [80,22]}
];
const result = [...data.reduce((map, {line, text}) => {
text.forEach(t => {
map.has(t) || map.set(t, []);
map.get(t).push(line);
});
return map;
}, new Map()).entries()].map(([text, line]) => ({text, line}));
console.log(result);
Here is a simple solution to your problem.
var myArray = [
{ line: 20, text: [31, 80] },
{ line: 10, text: [80, 22] }
]
var myNewArray = []
myArray.forEach((item) => {
item.text.forEach((t) => {
const index = myNewArray.findIndex((e => e.text === t))
index === -1
? myNewArray.push({text: t, line: [item.line]})
: myNewArray[index].line.push(item.line)
})
})
console.log(myNewArray)

Reduce an array to groups

I have this array:
const vals = ['blue', 'blue', 'green', 'blue', 'yellow', 'yellow', 'green']
I would like to reduce it to this:
['blueblue', 'green', 'blue', 'yellowyellow', 'green']
Where it concats the values if they are the same. Once the value changes it starts again.
Trying to use reduce but not sure how to make it work as the acc needs to be a string and an array depending on if the value is the same or not!
let lastType = vals[0]
const groups = vals.reduce((acc, value) => {
if (lastType === value) {
acc += value // string
}
lastType = value
return acc.push(value)
}, [])
The final result is an array, so that is what acc should be.
Instead of appending value to acc, append it to the last element of the array:
const vals = ['blue', 'blue', 'green', 'blue', 'yellow', 'yellow', 'green']
let lastType = null;
const groups = vals.reduce((acc, value) => {
if (lastType === value) {
acc[acc.length - 1] += value;
} else {
lastType = value
acc.push(value)
}
return acc;
}, [])
console.log(groups);
The use of lastType in a closure is a bit of a code smell, as is the use of mutability in the reducer.
A preferable, though slightly more verbose approach:
const vals = ['blue', 'blue', 'green', 'blue', 'yellow', 'yellow', 'green']
const { groups } = vals.reduce(({ lastType, groups }, value) => {
if (lastType === value) {
return {
lastType,
groups: [
...groups.slice(0, groups.length - 2),
groups[groups.length - 1] + value
],
};
}
return {
lastType: value,
groups: [...groups, value],
};
}, { groups: [], lastType: null })
console.log(groups);
You need to check the element at the index in front of the actual index and add a new string to the accumulator.
const
values = ['blue', 'blue', 'green', 'blue', 'yellow', 'yellow', 'green'],
result = values.reduce((accumulator, value, index, array) => {
if (value === array[index - 1]) accumulator[accumulator.length - 1] += value;
else accumulator.push(value);
return accumulator;
}, []);
console.log(result);
Push an empty string to the accumulator if the type is new. Remember that .push returns the new length of the array, so don't return it at the bottom of the function - instead, return the whole accumulator:
const vals = ['blue', 'blue', 'green', 'blue', 'yellow', 'yellow', 'green']
let lastType;
const groups = vals.reduce((acc, value) => {
if (lastType !== value) {
acc.push('');
lastType = value;
}
acc[acc.length - 1] += value;
return acc;
}, [])
console.log(groups);
You need to keep track of a bit more. Basic way of doing it is using an bject to hold the state.
const vals = ['blue', 'blue', 'green', 'blue', 'yellow', 'yellow', 'green']
const groups = vals.reduce((acc, value, index, array) => {
// is it the same, duplicate it
if (acc.lastType === value) {
acc.current += value;
} else {
// did we have a previous value? Add it to the array
if (acc.lastType) {
acc.result.push(acc.current);
}
// set the current type
acc.current = value;
acc.lastType = value;
}
// if we are at the end, add what we have to the array
if (index+1===array.length) {
acc.result.push(acc.current);
}
return acc;
}, { result: [], current: '', lastType: null }).result;
console.log(groups);

How to push not empty key value object into an array using jquery

i have an object with some key:value pairs. I would like to push the object into an array but without the empty object value.
my fiddle: https://jsfiddle.net/howw1fj7/
var myData = [];
var myObj = {
product: "phone",
quantity: 100,
color: "red",
secondColor: '',
imei: "43904325"
};
myData.push(myObj); //push only not empty key:values
$('pre').html(JSON.stringify(myData, null, 4));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<pre></pre>
i have tried something like this but this is not correct:
$.each(myObj, function(key, value) {
if(value.length !=0) {
var myNewObj = {key:value};
myData.push(myNewObj);
}
});
You could define a utility method Object.filter as described in my answer here.
Then you get this:
Object.from = arr => Object.assign(...arr.map( ([k, v]) => ({[k]: v}) ));
Object.filter = (obj, predicate) => Object.from(Object.entries(obj).filter(predicate));
var myData = [];
var myObj = { product: "phone", quantity: 100, color: "red", secondColor:'', imei: "43904325" };
myData.push(Object.filter(myObj, ([k, v]) => v !== ''));
console.log(myData);
You can use for in loop to iterate through the myObj and check for the empty value. Then you can push the new object created into the array.
var myData = [];
var myObj = { product: "phone", quantity: 100, color: "red", secondColor:'', imei: "43904325" };
var obj = {};
for(var key in myObj) {
if(myObj[key] !== '') {
obj[key] = myObj[key];
}
}
myData.push(obj);
$('pre').html(JSON.stringify(myData,null,4));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<pre></pre>
value will not have a .length as it is not an array but a property in the array that you are looping over. You would need to check for null,'',undefined

Javascript - Index Each Element in an Array/List

> var lst = ['red', 'blue', 'yellow'];
> someFunc(lst);
[('red', 0), ('blue', 1), ('yellow', 2)]
Is there any way to do this in Javascript/JQuery? I know that I can simply just make a for loop and convert each of the original list's element to what I want, but I was wondering if there was a built in way of doing this.
Thanks!
You could use Array#map and return for each item an array with the value and the index.
var lst = ['red', 'blue', 'yellow'],
array = lst.map(function (a, i) { return [a, i]; });
console.log(array);
You can leverage the map() function that is providing you both the value and and index of each item in the array:
lst.map(function(v, i) { return { value: v, index: i }; } );
See MDN
With map method in ES6 :
var lst = ['red', 'blue', 'yellow'];
var array = lst.map((item, index) => [item, index])
console.log(array); // [ [ 'red', 0 ], [ 'blue', 1 ], [ 'yellow', 2 ] ]

Filter Array based on another array

I have a list with that contains a list of objects. Each object has 4 properties on it. There is a checkbox list with the unique values of two of the properties, this helps build my filter array.
the Filter might end up looking like this:
[
{
prop: 'username',
val: ['max', 'sam']
},
{
prop: 'color',
val: ['blue', 'green']
}
]
The list of objects would look something like this:
[
{
username: 'sam',
color: 'blue'
},
{
username: 'jimmy',
color: 'blue'
},
{
username: 'sam',
color: 'black'
},
{
username: 'max',
color: 'green'
},
{
username: 'max',
color: 'blue'
}
]
The Desired Result
[
{
username: 'sam',
color: 'blue'
},
{
username: 'max',
color: 'green'
},
{
username: 'max',
color: 'blue'
}
]
I feel like I'm going down a never ending forEach rabbit hole. I'm guessing I need some sort of recursion. Currently here is what I have:
var temporary = scope.transactions;
function getFilteredTransactions() {
var filter = deviceFilterService.get();
if (filter.length > 0) {
var temp2 = [];
angular.forEach(filter, function (fil) {
//object
angular.forEach(fil.val, function (filterValue) {
//list on each object
angular.forEach(temporary, function (transaction) {
if (transaction[fil.prop] === filterValue) {
if (temp2.indexOf(transaction) === -1) {
temp2.push(transaction);
}
}
});
temporary = temp2;
});
});
$log.debug(temporary);
scope.transactions = temporary;
} else {
initialize();
}
}
This is starting to work, the second time it goes through the property for color it ends up just wanting to add the exact same transaction to the temp2 array. There has to be a better way to set this up, possibly through recursion.
If you convert the format of the first list to a dictionary, i think if should get easier.
var dict = {};
angular.forEach(source1, function(ob){
dict[ob.prop] = ob.val;
});
function getFiltered(ob){
for(var prop in ob){
if(dict[prop] && dict[prop].indexOf(ob[prop]) === -1){
return false;
}
}
return true;
};
and just call it as:
var temporary = scope.transactions.filter(getFiltered);
Demo
Basically the first part converts:
[
{
prop: 'username',
val: ['max', 'sam']
},
{
prop: 'color',
val: ['blue', 'green']
}
];
to:
{
username:['max', 'sam'],
color:['blue', 'green']
}
so that it makes the look up much easier.
You might want to change the variable names here for clarity, but this will do what you're asking for:
var values = {};
angular.forEach(startingData, function(rawData) {
angular.forEach(rawData, function(value, key) {
if (angular.isUndefined(values[key])) {
values[key] = [];
}
if (values[key].indexOf(value) === -1) {
values[key].push(value);
}
})
});
var result = [];
angular.forEach(values, function(value, key) {
result.push({prop: key, val: value})
});
You can simply iterate each key of the data the needs filtering, find the appropriate filter per that key, and check the value against the filter values:
$scope.transactions = $scope.transactions.filter(isItemValidFilter);
function isItemValidFilter(item) {
var filters = deviceFilterService.get();
//For each property in the data, get the correct filter from the list of filters
var totalConditions = Object.keys(item).length;
var correctConditions = 0;
for (var filterKey in item) {
var correctFilters = filters.filter(function(dataFilter) {
return dataFilter.prop == filterKey
});
if (correctFilters.length) {
//Ill assume only 1 filter, so just use the 0 index
var correctFilter = correctFilters[0];
var conditions = correctFilter.val;
if (conditions && conditions.length) {
//check the values!
if (conditions.indexOf(item[filterKey]) > -1) {
correctConditions++;
}
}
}
}
return correctConditions === totalConditions;
}
Demo: http://jsfiddle.net/Lz32hka5/1/
Try:
var temp2 = [], matched;
angular.forEach(temporary, function(item){
matched = true;
angular.forEach(Object.keys(item), function(key){
angular.forEach(filter, function(filter){
filter.prop == key && filter.val.indexOf(item[key]) == -1 && (matched = false);
});
});
matched && temp2.push(item);
});
console.log(temp2)
temporary is the list of objects, filter: your filters
Demo: http://jsfiddle.net/wZVanG/7wnae850/

Categories