Find object in collection with fuzzy matching - javascript

I have a collection that looks something like this:
const collection = [
{
name: 'THIS_ITEM',
conditions: {
oneCondition: false,
anotherCondition: false,
yourCondition: false,
myCondition: false
}
}, {
name: 'THAT_ITEM',
conditions: {
oneCondition: false,
anotherCondition: false,
yourCondition: true,
myCondition: false
}
}, {
name: 'THOSE_ITEMS',
conditions: {
oneCondition: true,
anotherCondition: false,
yourCondition: null,
myCondition: false
}
}
];
… and later an object that looks like this:
const condition = {
oneCondition: true,
anotherCondition: false,
yourCondition: true,
myCondition: false
};
I’m trying to match the condition object against the nested conditions objects in collection to find the one that matches so I can retrieve the name property from the matching entry.
The thing that’s throwing me for a loop is the fact that the conditions properties can have “fuzzy” values. By that I mean that if any properties in the source collection are set to true or false they MUST match the values in condition exactly. But if the property in the source collection has a value of null it can match either true or false.
Example:
These would match:
const condition = {
oneCondition: true,
anotherCondition: false,
yourCondition: true,
myCondition: false
};
const collection = [
…
}, {
name: 'THOSE_ITEMS',
conditions: {
oneCondition: true,
anotherCondition: false,
yourCondition: null,
myCondition: false
}
}
];
These would not:
const condition = {
oneCondition: true,
anotherCondition: false,
yourCondition: true,
myCondition: false
};
const collection = [
…
}, {
name: 'THAT_ITEM',
conditions: {
oneCondition: false,
anotherCondition: false,
yourCondition: true,
myCondition: false
}
}, {
…
];
Any suggestions? I’m using Lodash but can’t seem to imagine any solution without an overly-verbose and nested concoction.

You could use Array#filter with Array#every for the conditions and test against null value as wildcard.
var collection = [{ name: 'THIS_ITEM', conditions: { oneCondition: false, anotherCondition: false, yourCondition: false, myCondition: false } }, { name: 'THAT_ITEM', conditions: { oneCondition: false, anotherCondition: false, yourCondition: true, myCondition: false } }, { name: 'THOSE_ITEMS', conditions: { oneCondition: true, anotherCondition: false, yourCondition: null, myCondition: false } }],
condition = { oneCondition: true, anotherCondition: false, yourCondition: true, myCondition: false },
result = collection.filter(o =>
Object.keys(condition).every(k =>
o.conditions[k] === null || o.conditions[k] === condition[k]
)
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

You can use lodash#filter with lodash#isMatch and lodash#omitBy to match the condition against a collection object's condition that doesn't contain any null values.
const result = _.filter(collection, v =>
_.isMatch(condition, _.omitBy(v.conditions, _.isNull))
);
const collection = [
{
name: 'THIS_ITEM',
conditions: {
oneCondition: false,
anotherCondition: false,
yourCondition: false,
myCondition: false
}
}, {
name: 'THAT_ITEM',
conditions: {
oneCondition: false,
anotherCondition: false,
yourCondition: true,
myCondition: false
}
}, {
name: 'THOSE_ITEMS',
conditions: {
oneCondition: true,
anotherCondition: false,
yourCondition: null,
myCondition: false
}
}
];
const condition = {
oneCondition: true,
anotherCondition: false,
yourCondition: true,
myCondition: false
};
const result = _.filter(collection, v =>
_.isMatch(condition, _.omitBy(v.conditions, _.isNull))
);
console.log(result);
body > div { min-height: 100%; top: 0; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>

You can filter the collecting using lodash's _.isMatchWith():
const condition = {"oneCondition":true,"anotherCondition":false,"yourCondition":true,"myCondition":false};
const collection = [{"name":"1","conditions":{"oneCondition":true,"anotherCondition":false,"yourCondition":true,"myCondition":false}},{"name":"2","conditions":{"oneCondition":true,"anotherCondition":false,"yourCondition":false,"myCondition":false}},{"name":"3","conditions":{"oneCondition":true,"anotherCondition":false,"yourCondition":null,"myCondition":false}}];
const result = collection.filter(({ conditions }) =>
_.isMatchWith(condition, conditions, (objValue, othValue) =>
objValue === null || othValue === null || objValue === othValue) // if at least one of the values is null or they are equal
);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>

You can use a combination of lodash
.filter(), .every() and .isEqual() methods to loop over the collection and filter only items which have the same conditions as your object:
_.filter(collection, function(c) {
return _.every(_.keys(condition), function(currentKey) {
return c.conditions[currentKey] === null ||
_.isEqual(c.conditions[currentKey], condition[currentKey]);
});
});
Demo:
const collection = [{
name: 'THIS_ITEM',
conditions: {
oneCondition: false,
anotherCondition: false,
yourCondition: false,
myCondition: false
}
}, {
name: 'THAT_ITEM',
conditions: {
oneCondition: false,
anotherCondition: false,
yourCondition: true,
myCondition: false
}
}, {
name: 'THOSE_ITEMS',
conditions: {
oneCondition: true,
anotherCondition: false,
yourCondition: null,
myCondition: false
}
}];
const condition = {
oneCondition: true,
anotherCondition: false,
yourCondition: true,
myCondition: false
};
var result = _.filter(collection, function(c) {
return _.every(_.keys(condition), function(currentKey) {
return c.conditions[currentKey] === null ||
_.isEqual(c.conditions[currentKey], condition[currentKey]);
});
});
console.log(result);
.as-console-wrapper {
max-height: 100% !important;
top: 0;
}
<script src="http://underscorejs.org/underscore-min.js"></script>

Related

Invert boolean Values in JS object array [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 3 days ago.
This post was edited and submitted for review 3 days ago.
Improve this question
I'm dealing with object array
var data = [
{
"rdd": "Transducer Failure",
"performance": true,
"agc": true,
"snr": true,
"sos": true,
"flowvel": true
},
{
"rdd": "Detection Problem",
"performance": false,
"agc": false,
"snr": false,
"sos": false,
"flowvel": false
},
{
"rdd": "Ultrasonic Noise",
"performance": false,
"agc": false,
"snr": false,
"sos": false,
"flowvel": false
},
{
"rdd": "Process Condition Pressure",
"performance": false,
"agc": false,
"snr": false,
"sos": false,
"flowvel": false
},
{
"rdd": "Process Condition Temperature",
"performance": false,
"agc": true,
"snr": false,
"sos": true,
"flowvel": false
},
{
"rdd": "Fouling",
"performance": false,
"agc": false,
"snr": false,
"sos": false,
"flowvel": false
},
{
"rdd": "Changes in flow profile",
"performance": false,
"agc": false,
"snr": false,
"sos": false,
"flowvel": false
},
{
"rdd": "High Velocity",
"performance": true,
"agc": true,
"snr": true,
"sos": false,
"flowvel": false
}
]
Now I want to invert value of object, whichever is false make true and vice verse. also, need to extract key's whose value is True after inversion .. I tried couple of things but no luck.
any idea ??
EDIT :
I Tried using
console.log(data);
for (var key in data) {
var obj = data[key];
Object.entries(obj).forEach(([key, value]) => {
if(value == false){
value = true;
}
})
}
console.log(data)
result remains same
You could check the type of value and get the negated value or value of not boolean.
const
data = [{ rdd: "Transducer Failure", performance: true, agc: true, snr: true, sos: true, flowvel: true }, { rdd: "Detection Problem", performance: false, agc: false, snr: false, sos: false, flowvel: false }, { rdd: "Ultrasonic Noise", performance: false, agc: false, snr: false, sos: false, flowvel: false }, { rdd: "Process Condition Pressure", performance: false, agc: false, snr: false, sos: false, flowvel: false }, { rdd: "Process Condition Temperature", performance: false, agc: true, snr: false, sos: true, flowvel: false }, { rdd: "Fouling", performance: false, agc: false, snr: false, sos: false, flowvel: false }, { rdd: "Changes in flow profile", performance: false, agc: false, snr: false, sos: false, flowvel: false }, { rdd: "High Velocity", performance: true, agc: true, snr: true, sos: false, flowvel: false }],
result = data.map(o => Object.fromEntries(Object
.entries(o)
.map(([k, v]) => [k, typeof v === 'boolean' ? !v : v])
));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Convert arrays of active days to readable string

I need to convert an array of boolean values indicating whether the store is open on a given day.
For example:
Case 1:
Input data: [true, true, true, true, true, true, true]
Expected output: Every day
Case 2:
Input data: [true, true, true, true, true, false, false]
Expected output: Mon-Fri
Case 3:
Input data: [true, true, false, false, true, true, true]
Expected output: Mon-Tue, Fri-Sun
Case 4:
Input data: [true, false, false, true, false, false, true]
Expected output: Mon, Thu, Sun
Case 5:
Input data: [true, true, false, true, true, true, false]
Expected output: Mon-Tue, Thu-Sat
Case 6:
Input data: [true, false, false, false, false, false, false]
Expected output: Only Monday
I came up with this, but need help with cases 2-5
const daysLabels = [
{ label: "Monday", short: "Mon" },
{ label: "Tuesday", short: "Tue" },
{ label: "Wednesday", short: "Wed" },
{ label: "Thursday", short: "Thu" },
{ label: "Friday", short: "Fri" },
{ label: "Saturday", short: "Sat" },
{ label: "Sunday", short: "Sun" }
];
const getSchedule = ({ case: days }) => {
let activeDays = [];
for (let i = 0; i < [...days].length; i++) {
const day = [...days][i];
if (day) {
activeDays.push({ ...daysLabels[i], value: day });
}
}
if (activeDays.length === 7) {
return "Every day";
}
if (activeDays.length === 1) {
return `Only ${activeDays[0].label}`;
}
return "#TODO";
};
Sandbox - link
Here's my answer, This is not an optimized version.
Add below function:-
function getWeekDuration(cases) {
let index = 0;
let weekDurationArray = [];
for (let i = 0; i < cases.length; i++) {
const day = cases[i];
if (i === 0) {
weekDurationArray[index] = [];
}
if (day) {
weekDurationArray[index].push({ ...daysLabels[i],
value: day
});
} else {
if (weekDurationArray[index].length > 0) {
index += 1;
weekDurationArray[index] = [];
}
}
}
// remove empty arrays
weekDurationArray = weekDurationArray.filter(item => item.length > 0);
// get only first and last day of each week duration
weekDurationArray = weekDurationArray.map(weekDuration => {
// concate inner array into string
if (weekDuration.length > 1) {
return `${weekDuration[0].short}-${weekDuration[weekDuration.length - 1].short}`;
}
return weekDuration[0].short;
});
return weekDurationArray.join(', ');
}
Add return the function from getSchedule
return getWeekDuration(days);
You can use Array.reduce() to create groups of day ranges, along with the correct labels.
We then use a Array.map() call to return only the label for each range.
I've added the 6 test cases mentioned, they should all pass.
const daysLabels = [
{ label: "Monday", short: "Mon" },
{ label: "Tuesday", short: "Tue" },
{ label: "Wednesday", short: "Wed" },
{ label: "Thursday", short: "Thu" },
{ label: "Friday", short: "Fri" },
{ label: "Saturday", short: "Sat" },
{ label: "Sunday", short: "Sun" }
];
function getDayRange(input) {
// Deal with 7 days and 1 day only first...
if (input.filter(active => active).length === 7) {
return 'Every day';
} else if (input.filter(active => active).length === 1) {
return `Only ${daysLabels[input.findIndex(active => active)].label}`;
}
// 2 - 6 days active
return input.reduce((acc, active, idx) => {
if (active) {
if (!acc.length || acc[acc.length - 1].end < (idx - 1) ) {
acc.push({ start: idx, end: idx, label: daysLabels[idx].short, startLabel: daysLabels[idx].short });
} else {
acc[acc.length - 1].end = idx;
acc[acc.length - 1].label = acc[acc.length - 1].startLabel + '-' + daysLabels[idx].short;
}
}
return acc;
}, []).map(r => r.label).join(', ');
}
const cases = [
{ input: [true, true, true, true, true, true, true], expected: 'Every day' },
{ input: [true, true, true, true, true, false, false], expected: 'Mon-Fri' },
{ input: [true, true, false, false, true, true, true], expected: 'Mon-Tue, Fri-Sun' },
{ input: [true, false, false, true, false, false, true], expected: 'Mon, Thu, Sun' },
{ input: [true, true, false, true, true, true, false], expected: 'Mon-Tue, Thu-Sat' },
{ input: [true, false, false, false, false, false, false], expected: 'Only Monday' },
]
console.log(`Case`, '\t', 'Pass', '\t', 'Output')
cases.forEach(({ input, expected }, idx) => {
let output = getDayRange(input);
console.log(`${idx + 1}`, '\t', output === expected, '\t', output)
})
.as-console-wrapper { max-height: 100% !important; }
I declare a mapping array for the text, below is the example code,and you can modify the text when function return only one day or everyday.
// for index mapping
let indexMapping = [
"Mon",
"Tue",
"Wed",
"Thu",
"Fri",
"Sat",
"Sun"
];
function getWeeks(arr){
let list = [];
let indexes = [];
let item = [];
// get all indexes
arr.forEach((result,index) => {
if(result) indexes.push(index);
});
// push each text to list
indexes.map(i => {
if(!indexes.includes(i-1)){
if(item.length == 1){
list.push(item[0]);
item = [];
}
item.push(indexMapping[i]);
}
else if(!indexes.includes(i+1)){
item.push(indexMapping[i]);
list.push(item.join("-"));
item = [];
}
});
// if indexes only has one item
if(item.length == 1){
list.push(item[0]);
}
return list;
}
// for test
let testArr2 = [true, true, true, true, true, false, false];
let testArr3 = [true, true, false, false, true, true, true];
let testArr4 = [true, false, false, true, false, false, true];
let testArr5 = [true, true, false, true, true, true, false];
getWeeks(testArr2); // output will be like ['Mon-Fri']
Use of Array#map with regular expressions may help have less hard-coding as follows:
const daysLabels = [
{ label: "Monday", short: "Mon" },
{ label: "Tuesday", short: "Tue" },
{ label: "Wednesday", short: "Wed" },
{ label: "Thursday", short: "Thu" },
{ label: "Friday", short: "Fri" },
{ label: "Saturday", short: "Sat" },
{ label: "Sunday", short: "Sun" }
],
getSchedule = ({ case: days }) => {
const out = days.map((b,i) => b ? daysLabels[i].short : ":")
.join(",").replace(/(?<![:\b]),[,A-za-z]*,(?![:\b])/g,"-")
.replace(/(?<=[a-z]),(?=[A-Z])/g,"-")
.replace(/[,\:]+/g,",").replace(/^[^A-Z]+|[^a-z]+$/g,"")
.replace(/,/g,", ");
return out === 'Mon-Sun' ? 'Every day' :
!out.match(/[\-,]/) ?
`Only ${daysLabels.find(({short}) => short === out).label}` :
out;
};
const cases = [
{ input: [true, true, true, true, true, true, true], expected: 'Every day' },
{ input: [true, true, true, true, true, false, false], expected: 'Mon-Fri' },
{ input: [true, true, false, false, true, true, true], expected: 'Mon-Tue, Fri-Sun' },
{ input: [true, false, false, true, false, false, true], expected: 'Mon, Thu, Sun' },
{ input: [true, true, false, true, true, true, false], expected: 'Mon-Tue, Thu-Sat' },
{ input: [true, false, false, false, false, false, false], expected: 'Only Monday' },
{ input: [false, false, false, true, false, false, false], expected: 'Only Thursday' },
{ input: [false, false, false, false, false, false, true], expected: 'Only Sunday' }
]
console.log(`Case`, '\t', 'Pass', '\t', 'Output')
cases.forEach(({ input, expected }, idx) => {
let output = getSchedule({case:input});
console.log(`${idx + 1}`, '\t', output === expected, '\t', output)
});
.as-console-wrapper { max-height: 100% !important; }

What is the better way to check in array of nested object

I'd like to check at least one checked field is true and not disabled:
const test =
[ { Title:
{ Article: { checked: true, disabled: true }
, Desc: { checked: false, disabled: false }
} }
, { News:
{ Dashboard: { checked: false, disabled: false}
} }
]
I tried like this:
const checkedItems = () => {
let value = false;
test.forEach(el => Object.entries(el).forEach(([title, checkboxProps]) => {
Object.entries(checkboxProps).forEach(([name, config]) => {
if (config["checked"] && !config["disabled"]) {
value = true
}
})
}))
return value;
};
A couple of flatMaps with Object.values can do it pretty cleanly.
const test = [{
Title: {
Article: {
checked: true,
disabled: true
},
Desc: {
checked: false,
disabled: false
}
}
}, {
News: {
Dashboard: {
checked: false,
disabled: false
}
}
}];
const atLeastOneCheckedAndNotDisabled = test
.flatMap(Object.values)
.flatMap(Object.values) // now we have an array of [{checked, disabled}]
.some(innerObj => innerObj.checked && !innerObj.disabled);
console.log(atLeastOneCheckedAndNotDisabled);
You don't care about the keys, only the values, so Object.values will make things easier to work with than Object.entries.

filter array of objects if properties are in another array and values are true

The following is a sample that yields the desired output.
const data = [
{ itemID: '300', status: 'active', inventoryFlag: true, certifiedFlag: false, donateFlag: true },
{ itemID: '400', status: 'inactive', inventoryFlag: true, certifiedFlag: true, donateFlag: false },
{ itemID: '500', status: 'active', inventoryFlag: false, certifiedFlag: false, donateFlag: false },
{ itemID: '600', status: 'active', inventoryFlag: false, certifiedFlag: true, donateFlag: true },
{ itemID: '700', status: 'inactive', inventoryFlag: false, certifiedFlag: true, donateFlag: false }
];
document.getElementById("data").innerText = JSON.stringify(data.filter(o => o.inventoryFlag || o.donateFlag));
<textarea id="data" rows="50" cols="100"></textarea>
I'm trying to find a way to list the filter-by flags in a separate array and then use that in the .filter() somehow to get the same result set as the above code.
For example, here are the flags to use for filtering AND at least one of the values must be true in the object to be included in the final dataset -
const flags = ['inventoryFlag', 'donateFlag'];
As a start, I tried this but it didn't do anything:
const filteredData = data.filter(o => flags.includes(Object.keys(o)));
Inside of the filter, use the some method to detect if any of the property keys of an entry, that are contained in the flags array, have the a value of true.
const data = [
{ itemID: '300', status: 'active', inventoryFlag: true, certifiedFlag: false, donateFlag: true },
{ itemID: '400', status: 'inactive', inventoryFlag: true, certifiedFlag: true, donateFlag: false },
{ itemID: '500', status: 'active', inventoryFlag: false, certifiedFlag: false, donateFlag: false },
{ itemID: '600', status: 'active', inventoryFlag: false, certifiedFlag: true, donateFlag: true },
{ itemID: '700', status: 'inactive', inventoryFlag: false, certifiedFlag: true, donateFlag: false }
];
const flags = ['inventoryFlag', 'donateFlag'];
const result = data.filter(entry =>
flags.some(flag => entry[flag] === true)
);
console.log(result);

How to sort array of objects by its boolean properties

I'm trying to sort this array of objects by its boolean properties however, I'm struggling to find a solution with javascripts 'sort' method
I'm trying to sort it, so the top item in the array would be 'offer = true', then 'shortlisted = true', and finally 'rejected = true'.
var toSort = [{
offer: false,
shortlisted: true,
rejected: false,
stage: 2
}, {
offer: false,
shortlisted: false,
rejected: true,
stage: null
}, {
offer: true,
shortlisted: true,
rejected: false,
stage: null
}, {
offer: false,
shortlisted: true,
rejected: false,
stage: 1
}];
This is the final result I would like to achieve
[{
offer: true,
shortlisted: true,
rejected: false,
stage: null
}, {
offer: false,
shortlisted: true,
rejected: false,
stage: 1
}, {
offer: false,
shortlisted: true,
rejected: false,
stage: 2
}, {
offer: false,
shortlisted: false,
rejected: true,
stage: null
}]
What is the best method to sort this array?
You can use sort() like this.
var toSort = [{
offer: false,
shortlisted: true,
rejected: false,
stage: 2
}, {
offer: false,
shortlisted: false,
rejected: true,
stage: null
}, {
offer: true,
shortlisted: true,
rejected: false,
stage: null
}, {
offer: false,
shortlisted: true,
rejected: false,
stage: 1
}];
var result = toSort.sort(function(a, b) {
return b.offer - a.offer ||
b.shortlisted - a.shortlisted ||
b.rejected - a.rejected
})
console.log(result)
One way is to assign a numerical score to each object, setting a higher score for values of higher precedence. For example:
var scoreObj = function(o) {
var score = 0;
if(o.offer) {
score+=100;
}
if(o.shortlisted) {
score+=10;
}
if(o.rejected) {
score+=1;
}
return score;
};
var sorted = toSort.sort(function(a,b){
return scoreObj(b) - scoreObj(a);
});
A simple way :
toSort.sort(function(a, b){
var numberA = a.offer * 5 + a.shortlisted * 3 + a.rejected;
var numberB = b.offer * 5 + b.shortlisted * 3 + b.rejected;
return numberA < numberB
});
Why this works :
1) we can treat boolean as 0 or 1, so if a.offer is true we are adding 5 to numberA, if not 0
2) If offer property is true, even if all other are true, offer will still appear first (because 5 > 1 + 3 = 4)
For more than 3 properties : This way we give a priority to the boolean properties you want first by giving it a numeric value that is greater than the sum of all the less priority properties

Categories

Resources