var persons = [
{ Color: "Gold", Location: ["Down"] },
{ Color: "Silver", Location: ["Up", "Down"] },
{ Color: "Silver", Location: ["Up"] }
];
var criteria = [
{ Field: "Color", Values: ["Silver"] },
{ Field: "Location", Values: ["Up", "Down"] }
];
Here field color is of type String, and Location is an array.
I have persons, and then there is a filter criteria. I need an output so that all the values selected in the filter needs to match with the data. So in the data provided, only those records should be visible if Silver, Up and Down are available in a record. (Note the AND parameter, there is no OR condition anywhere).
So the output will be:
{ Color: "Silver", Location: ["Up", "Down"] }
Now if the filter criteria is:
var criteria = [
{ Field: "Color", Values: ["Silver"] },
{ Field: "Location", Values: ["Up"] }
];
the output will be:
{ Color: "Silver", Location: ["Up", "Down"] },
{ Color: "Silver", Location: ["Up"] }
So you see all the values in the filter should match with the records.
I broke down the problem into separate functions. It's way more verbose than your solution but I do think it's more readable.
Also: it does work.
var persons = [
{ Color: "Gold", Location: ["Down"] },
{ Color: "Silver", Location: ["Up", "Down"] },
{ Color: "Silver", Location: ["Up"] }
];
var criteria = [
{ Field: "Color", Values: ["Silver"] },
{ Field: "Location", Values: ["Up", "Down"] }
];
const arraysEqual = (arr1, arr2) => {
// Very simple array comparison.
if (arr1.length !== arr2.length) return false;
arr1 = arr1.sort();
arr2 = arr2.sort();
for(let i=0; i < arr1.length; i++) {
if (arr1[i] !== arr2[i]) return false;
}
return true;
};
let result = persons.filter(person => {
// All criteria must match.
for (let criterium of criteria) {
if (criterium.Field === 'Color') {
if (person.Color !== criterium.Values[0]) return false;
}
if (criterium.Field === 'Location') {
if (!arraysEqual(person.Location, criterium.Values)) return false;
}
}
// We didn't *not* match for anything, so we matched!
return true;
});
console.log(result);
/*
{ Color: "Silver", Location: ["Down", "Up"] }
*/
See https://repl.it/#niels_bom/GoldenIdealMicrobsd for a running example.
After third edit, now I am clear on what you want - to have an array as a subset of the other.
var result = persons.filter(function (person) {
return criteria.every(function (c) {
var value = person[c.Field];
if (typeof value === 'object') {
return c.Values.length<=value.length &&
c.Values.every(function(v,i){return value.includes(v) ;});
}
else
return c.Values.indexOf(value) > -1;
})
})
I also updated your jsfiddle : https://jsfiddle.net/gzL42dna/1/
Also checkout: How to compare arrays in JavaScript?
I believe this works. Using a Set helps a lot with partial matches. Tell me if you would like an explanation of the code.
var persons = [
{ Color: "Gold", Location: ["Down"] },
{ Color: "Silver", Location: ["Up", "Down"] },
{ Color: "Silver", Location: ["Up"] }
];
var criteria = [
{ Field: "Color", Values: ["Silver"] },
{ Field: "Location", Values: ["Up", "Down"] }
];
console.log(match(persons, criteria));
function match(persons, criteria) {
let personMatches = [...persons]
for (let i=0; i < criteria.length; i++) {
let {Field, Values} = criteria[i]
personMatches = personMatches.filter(obj => {
if (Array.isArray(obj[Field])) {
return hasMatches(obj[Field], Values)
} else {
return Values.includes(obj[Field])
}
})
}
return personMatches
}
function hasMatches(arr1, criteria) {
let criteriaSet = new Set(criteria)
let personsSet = new Set(arr1)
for (let el of criteriaSet) {
if (!personsSet.has(el)) return false
}
return true
}
Related
I have this simple array:
const arr = [
{
"id": 2,
"color": "red"
},
{
"id": 1,
"color": "blue"
},
{
"id": 2,
"color": "yellow"
},
];
I want to create a hash map where I want to add new colors on that key.
E.g I want to added color: green on id: 3
Now here you can see there is no id: 3
Now here I am expecting:
{
2: [{color: "red"}]
1: [{color: "blue"}, {color: "yellow"}],
3: [{color: "green"}]
}
Now if I want to add color: brown on id: 2
In that case I am expecting:
{
2: [{color: "red"}, {color: "brown"}]
1: [{color: "blue"}, {color: "yellow"}],
3: [{color: "green"}]
}
I have created a Playground:
const arr = [
{
"id": 2,
"color": "red"
},
{
"id": 1,
"color": "blue"
},
{
"id": 2,
"color": "yellow"
},
];
function addItem(id: number, colors: any) {
let newArr = {[id]: colors};
arr.forEach(function (obj) {
newArr[obj.id].push({id: obj.color});
});
return newArr;
}
console.log(addItem(3, [{color: "green"}]))
console.log(addItem(1, [{color: "brown"}]))
Here I also want to avoid duplicates
const arr = [{
"id": 2,
"color": "red"
},
{
"id": 1,
"color": "blue"
},
{
"id": 2,
"color": "yellow"
},
];
const grouped = arr.reduce((groups, current) => {
if (!(current.id in groups)) {
groups[current.id] = []
}
groups[current.id].push({
color: current.color
})
return groups
}, {})
addItem(3, {
color: "green"
})
addItem(1, {
color: "brown"
})
console.log(grouped)
function addItem(id, item) {
if (!(id in grouped)) {
grouped[id] = []
}
grouped[id].push(item)
}
function test(id, color) {
for (var i = 0; i < arr.length; i++) {
if (arr[i].id == id) {
arr[i].color.push(color)
}
}
}
Basically it will loop through your array, and if its id matches, it will add your color.
Following code can help you to solve this problem
const hashMap = new Map([
[1, [{ color: "red" }]],
[2, [{ color: "blue" }]],
[3, [{ color: "yellow" }]],
]);
function addItem(id, colors) {
hashMap.set(
id,
hashMap.has(id) ? [...hashMap.get(id).concat(colors)] : colors
);
return hashMap;
}
console.log(hashMap);
console.log(addItem(3, [{ color: "green" }]));
console.log(addItem(4, [{ color: "pink" }]));
let arr = [{
"id": 2,
"color": "red"
},
{
"id": 1,
"color": "blue"
},
{
"id": 2,
"color": "yellow"
},
];
const groupBy = (xs, key) => {
return xs.reduce(function(rv, x) {
const y = {...x};
delete y[key];
(rv[x[key]] = rv[x[key]] || []).push(y);
return rv
}, {})
}
const addItem = (id, colors) => {
// const newArr = arr... etc if you don't want to modify the existing array
arr = arr.concat(colors.map(c => {
c.id = id;
return c
}))
const grouped = groupBy(arr, 'id')
return grouped
}
console.log(addItem(3, [{
color: "green"
}]))
console.log(addItem(1, [{
color: "brown"
}]))
You may find a class useful here to encapsulate your data.
On initialisation pass in your array of objects and convert it to a Map with array values. (Note: you can use an object here as an alternative).
Create a method that adds new colours to the appropriate map array if they don't already exist (i.e. testing for duplicates).
const arr=[{id:2,color:"red"},{id:1,color:"blue"},{id:2,color:"yellow"}];
class ColorMap {
// `reduce` over the array to create a `colors` Map.
// If the id doesn't exist on the map as a key,
// create it, and assign an empty array to it.
// Then push in the color to the array if
// it doesn't already exist
constructor(arr) {
this.colors = arr.reduce((acc, obj) => {
const { id, color } = obj;
if (!acc.has(id)) acc.set(id, []);
if (!this.colorExists(id, color)) {
acc.get(id).push({ color });
}
return acc;
}, new Map());
}
// Simple check to see if the color already
// exists in the target array
colorExists(id, color) {
return this.colors?.get(id)?.find(obj => {
return obj.color === color;
});
}
// Similar to the `reduce` function, if the id doesn't have
// a key on the map create one, and initialise an empty array,
// and if the color doesn't already exist add it
addColor(id, color) {
if (!this.colors.has(id)) this.colors.set(id, []);
if (!this.colorExists(id, color)) {
this.colors.get(id).push({ color });
}
}
// Return the colors map as a readable object
showColors() {
return Object.fromEntries(this.colors);
}
}
const colorMap = new ColorMap(arr);
colorMap.addColor(3, 'green');
colorMap.addColor(1, 'brown');
colorMap.addColor(1, 'brown');
console.log(colorMap.showColors());
Additional documentation
Logical nullish assignment
reduce
Spread syntax
I am new to react, I have an object and an array, I want to get details of unmatched items from the object when compared with array values. I tried but shows all the data when consol.log. here is my code
var content:[
0:{id:20, name:'Jack Daniel'}
1:{id:21, name:'Sophie McDonald'}
2:{id:22, name:'Jason Thomas'}
3:{id:23, name:'Chris Williams'}
]
var filter:[Sophie McDonald, Chris Williams]
filterValues = content.filter(item=> {
for(var i = 0;i<filter.length;i++) {
if (item.name === filtered[i])
{
return item
}
}
});
console.log(filteredValues)
// returns 0:{id:21, name:'Sophie McDonald'}
// 1:{id:23, name:'Chris Williams'}
But I need unmatched results,
filterValues = content.filter(item=> {
for(var i = 0;i<filter.length;i++) {
if (item.name !== filtered[i])
{
return item
}
}
});
console.log(filteredValues)
// returns 0:{id:20, name:'Jack Daniel'}
// 1:{id:21, name:'Sophie McDonald'}
// 2:{id:22, name:'Jason Thomas'}
// 3:{id:23, name:'Chris Williams'}
Result must be
0:{id:20, name:'Jack Daniel'}
1:{id:22, name:'Jason Thomas'}
Try using filter, checking if the values of the array are present in your object values:
const content = [{
id: 20,
name: 'Jack Daniel'
},
{
id: 21,
name: 'Sophie McDonald'
},
{
id: 22,
name: 'Jason Thomas'
},
{
id: 23,
name: 'Chris Williams'
}
];
const values = ['Sophie McDonald', 'Chris Williams'];
const filteredValues = content.filter(({
name
}) => !values.includes(name));
console.log(filteredValues);
Seems to work with a few edits to the format:
let content = [
{id:20, name:'Jack Daniel'},
{id:21, name:'Sophie McDonald'},
{id:22, name:'Jason Thomas'},
{id:23, name:'Chris Williams'}
]
let filter = ["Sophie McDonald", "Chris Williams"]
let filterValues = content.filter(item=> {
for(var i = 0;i<filter.length;i++) {
if (item.name !== filter[i]){
return item
}
}
});
console.log(filterValues)
I have this JSON tree view that represents a menu :
var menus = [
{
label : "1",
items: [
{
label : "1.1"
},
{
label : "1.2",
items : [
{
label : "1.2.1"
},
{
label : "1.2.2"
}
]
},
{
label : "1.3"
},
]
},
{
label : "2"
}
]
I want to mutate this JSON by adding for each item a selected property. This property, a boolean, will be set to true if the label is the right one or this is the tricky part if the descendant is the right one.
For instance, if I'm looking for label 1.2, all labels 1 and 1.2 will be selected. So I will get this JSON :
var menus = [
{
label : "1",
selected : true,
items: [
{
label : "1.1"
selected : false
},
{
label : "1.2",
selected : true,
items : [
{
label : "1.2.1"
selected : false
},
{
label : "1.2.2",
selected : false
}
]
},
{
label : "1.3",
selected : false
},
]
},
{
label : "2",
selected : false
}
]
the selected : false is not necessary.
Lodash is OK for me;)!
Any suggestions?
edit : where I am ! --> https://codepen.io/anon/pen/XGoXjM?editors=0010
edit 2 : finding elements must not be based on the way I wrote the labels. The labels can be any string... Sorry...
Thanks
This solution uses a for loop to iterate recursively the menu items and their children. If an item is selected, it adds selected: true to the item and it's parents:
const selectMenuItems = menus => selectedLabel => {
const internal = arr => {
let selected = false
for(let i = 0; i < arr.length; i++) {
const item = arr[i]
const childrenSelected = !!item.items && internal(item.items)
item.selected = childrenSelected || item.label === selectedLabel
selected = selected || item.selected
}
return selected
}
internal(menus)
return menus
}
const menus = [{"label":"1","items":[{"label":"1.1"},{"label":"1.2","items":[{"label":"1.2.1"},{"label":"1.2.2"}]},{"label":"1.3"}]},{"label":"2"}]
const menuSelector = selectMenuItems(menus)
const result = menuSelector('1.2')
console.log(result)
.as-console-wrapper { top: 0; max-height: 100% !important; }
I would simply check labels this way:
var menus = [{
label: "1",
items: [{
label: "1.1"
},
{
label: "1.2",
items: [{
label: "1.2.1"
},
{
label: "1.2.2"
}
]
},
{
label: "1.3"
},
]
},
{
label: "2"
}
];
var checkSelected = function(items, search) {
for (var key in items) {
items[key].selected = search.startsWith(items[key].label) && items[key].label.length<=search.length;
if (items[key].items) {
checkSelected(items[key].items, search);
};
};
};
var test = "1.2";
checkSelected(menus, test);
console.log(menus);
Also on JSFiddle.
The startsWith() method determines whether a string begins with the
characters of a specified string, returning true or false as
appropriate.
quoted from here
You can use some recursive approach to implement this.
let str = '1.2.1';
function checkItem(arr, strArr) {
// iterate over the array
arr.forEach((obj) => {
// set selected property based on matching every digit in label in same order
// if digits would be single then you can use startsWith and no need to split string
obj.selected = obj.label.split('.').every((it, i) => it === strArr[i]);
// if nested item is there then call recursively
obj.items && checkItem(obj.items, strArr);
});
return arr;
}
checkItem(menus, str.split('.'));
var menus = [{
label: "1",
items: [{
label: "1.1"
},
{
label: "1.2",
items: [{
label: "1.2.1"
},
{
label: "1.2.2"
}
]
},
{
label: "1.3"
},
]
},
{
label: "2"
}
];
let str = '1.2.1';
function checkItem(arr, strArr) {
arr.forEach((obj) => {
obj.selected = obj.label.split('.').every((it, i) => it === strArr[i]);
obj.items && checkItem(obj.items, strArr);
});
return arr;
}
checkItem(menus, str.split('.'));
console.log(menus);
UPADATE : Since you want to update selected property completely independent of label you can do something like follows. I assume you want to
update based on position in the array.
let str = '1.2.1';
function checkItem(arr, prefixArray, strArr) {
// iterate over the array
arr.forEach((obj, i) => {
// generate new prefix array for checking
let pa = [...prefixArray, i + 1];
// compare prefix array with the string array to check matches
obj.selected = pa.every((it, i) => it == strArr[i]);
// if items defined do it recursively
obj.items && checkItem(obj.items, pa, strArr);
});
return arr;
}
checkItem(menus,[], str.split('.'));
var menus = [{
label: "1",
items: [{
label: "1.1"
},
{
label: "1.2",
items: [{
label: "1.2.1"
},
{
label: "1.2.2"
}
]
},
{
label: "1.3"
},
]
},
{
label: "2"
}
];
let str = '1.2.1';
function checkItem(arr, prefixArray, strArr) {
arr.forEach((obj, i) => {
let pa = [...prefixArray, i + 1];
obj.selected = pa.every((it, i) => it == strArr[i]);
obj.items && checkItem(obj.items, pa, strArr);
});
return arr;
}
checkItem(menus,[], str.split('.'));
console.log(menus);
My code is as follows :
let filters = [
{name: "MAKE", values:
[
{
Volkswagen: {active: true, make: "Volkswagen"},
Skoda: {active: true, make: "Skoda"}
}
]
}
]
function getFilterValues(){
return filters.filter(f => {
if(f.name == "MAKE"){
return f.values.filter(i => {
Object.keys(i).map(key => {
return key;
});
});
}
});
}
var div = document.getElementById('output');
div.innerHTML = getFilterValues();
I want to loop through filters to get the object keys.
Thus the result that I want is, in this case Volkswagen, Skoda. But my function getFilterValues doesn't return what I want.
Here is jsfiddle.
Any advice?
The main problem is with the filter function. You want map since with filter you return true/false whether the element should be included in the resulting code. Check out this diff: https://www.diffchecker.com/CX6hOoxo
This works: https://jsfiddle.net/o93Lm0rc/101/
let filters = [
{name: "MAKE", values:
[
{
Volkswagen: {active: true, make: "Volkswagen"},
Skoda: {active: true, make: "Skoda"}
}
]
}
]
function getFilterValues(){
return filters.map(f => {
if(f.name == "MAKE"){
return f.values.map(i => {
return Object.keys(i).map(key => {
return key;
});
});
}
});
}
var div = document.getElementById('output');
div.innerHTML = getFilterValues();
You can use Object.keys() to get the list of keys.
var filters = [{
name: "MAKE",
values: [{
Volkswagen: {
active: true,
make: "Volkswagen"
},
Skoda: {
active: true,
make: "Skoda"
}
}]
}];
for (i = 0; i < filters.length; i++) {
if (typeof filters[i].values == "object") {
console.log(Object.keys(filters[i].values[0]));
}
}
I have used the AngularJS filter to search objects and even used the deep search with the $, however partial searches like the below never return the result that I would expect, I can split the search input into an array using .split(" ") though I do not know how to correctly pass this array to the filter.
I have tried to define my own filter with no success.
Data:
[
{
Description: "test Description",
Color: "Blue",
Size: "Large"
}, {
Description: "aest 2",
Color: "orange",
Size: "Large"
}
, {
Description: "test 3",
Color: "black",
Size: "small"
}
]
Search input -> expected behavior
"tes blu" -> return one result
"te desc" -> return one result
"purple" -> return zero results
Any advice on how to proceed would be greatly appreciated.
I am using AngularJS: v1.3.13
code here: http://plnkr.co/edit/j3NqCtO1qHJrMuj6y2nv?p=preview
this is how I solved it: http://plnkr.co/edit/W66NxRlh8deRUh6Uxpor?p=preview
var App = angular.module("App",[]);
App.controller("testCtrl",function($rootScope,$scope){
$scope.testObjects = [
{
Description: "test Description",
Color: "Blue",
Size: "Large"
}, {
Description: "aest 2",
Color: "orange",
Size: "Large"
}
, {
Description: "test 3",
Color: "black",
Size: "small"
}
];
$scope.Search = "";
$rootScope.ScoreThings = function(object , inputArray){
var result = 0;
for (var x = 0; x < inputArray.length; x++)
{
angular.forEach(object, function(value, key) {
if (value.toLowerCase().indexOf(inputArray[x].toLowerCase()) > -1)
{
result ++;
}
});
}
return result;
}
});
App.filter('SearchFilter2', function ($rootScope) {
return function (items, inputString) {
var filtered = [];
var inputArray = inputString.split(" ");
for (var i = 0; i < items.length; i++)
{
if ($rootScope.ScoreThings(items[i], inputArray) > (inputArray.length / 1.2) )
{
filtered.push(items[i]);
}
}
return filtered;
};
});
use it in your view like:
<tr ng-repeat="testObject in testObjects | SearchFilter2 : Search ">