Inherited truth check in condition comparison - javascript

I'm looking for a clean way to get true or false based on an order of permissions based on the following rules:
Starts with Company Permissions as a default
Then to Team Permissions if permission defined
Finally to User Permissions if permission is
This would need to also handle undefined. So basically wanting to see if there's some "clean" way to do this without having to conditionally check each value and moving on.
In this example, the result should be false since there are no User Permissions defined and the Team Permissions has false.
const UserPermissions = {}
const TeamPermissions = {
PERMISSION_ONE: false
}
const CompanyPermissions = {
PERMISSION_ONE: true
}
const hasPermissions = UserPermissions.PERMISSION_ONE || TeamPermissions.PERMISSION_ONE || CompanyPermissions.PERMISSION_ONE
console.log(hasPermissions)
Thanks!

From my understanding, the rules are:
ignore undefined
return true of false, whatever comes first
This little function should handle that, not sure how you want to deal with empty (or all undefined) arguments.
let x;
let t = true;
let f = false;
let firstBool = (...a) => Boolean(a.filter(x => typeof x !== 'undefined').shift());
console.log(firstBool(x,t,f));
console.log(firstBool(t,x,f));
console.log(firstBool(x,f,t));
console.log(firstBool());
In your example, that would be
const hasPermissions = firstBool(
UserPermissions.PERMISSION_ONE,
TeamPermissions.PERMISSION_ONE,
CompanyPermissions.PERMISSION_ONE
]

If you are looking for the same property name in multiple objects, you might be able to slightly alter the technique in georg's answer:
const firstBoolProp = (...objs) => (prop) =>
objs.reduce((a, o) => Boolean(a) === a ? a : o[prop], undefined)
const UserPermissions = {}
const TeamPermissions = {PERMISSION_ONE: false}
const CompanyPermissions = {PERMISSION_ONE: true}
console .log (
firstBoolProp
(UserPermissions, TeamPermissions, CompanyPermissions)
('PERMISSION_ONE')
)
You can then use a single function to multiple permissions against that same set of permission objects:
const getPermissions = firstBoolProp(UserPermissions, TeamPermissions, CompanyPermissions)
const perms = {
p1: getPermissions('PERMISSION_ONE'),
p2: getPermissions('PERMISSION_TWO'),
}
//=> {p1: false, p2: undefined}
And if you want to use an array rather than individual parameters, you can simply replace (...obj) => with (obj) =>

One way to do this is to store the permissions in an array and reduce it to a boolean:
/**
*
* #param {{PERMISSION_ONE:boolean}[]} permissions
*/
function anyoneHasPermission(permissions, name = "PERMISSION_ONE") {
for (const perm of permissions) {
if (perm[name]) {
return true;
}
}
}
console.log(anyoneHasPermission([UserPermissions, TeamPermissions, CompanyPermissions]))
You need to be more specific in what you want to accomplish. Choosing the right data structure is usually more important than choosing an algorithm. Indeed, sometimes the right data structure makes the algorithm disappear completely.

Throw the permissions into an array in the order that you want them validated.
Iterate the array and if any of your conditions is not met then return false and break.
This will stop you running down the entire chain if say your top level permission is not set (because nothing beyond that matters anymore and no use validating it)
const UserPermissions = {PERMISSION_ONE: true}
const TeamPermissions = {}
const CompanyPermissions = {PERMISSION_ONE: true}
const permCheck = HasPerms([CompanyPermissions, TeamPermissions, UserPermissions]);
alert(permCheck);
function HasPerms(permArray){
for (var i = 0; i < permArray.length; i++) {
if (!permArray[i] || !permArray[i].PERMISSION_ONE) {return false;}
}
return true;
}
You can expand the function to dynamically take in the permission you would like to check but example shown hardcoded for simplicity sake.

Related

Set of Tuples in JavaScript?

What is the best way to implement a Set of coordinates in JavaScript? I would like to be able to do things like:
let s=new Set();
s.add([1,1]);
if (s.has([1,1])) // false, since these are different arrays
The above doesn't work, since the Set is storing a reference to the array instead of the contents.
You can subclass Set for more flexibility.
class ObjectSet extends Set{
add(elem){
return super.add(typeof elem === 'object' ? JSON.stringify(elem) : elem);
}
has(elem){
return super.has(typeof elem === 'object' ? JSON.stringify(elem) : elem);
}
}
let s=new ObjectSet();
s.add([1,1]);
console.log(s.has([1,1]))
console.log(s.has([1,2,3]));
console.log([...s]);
console.log([...s].map(JSON.parse));//get objects back
This can be done with strings:
let s=new Set();
s.add("1,1");
s.add("2,2");
console.log(s.has("1,1"), s.has("1,2")); // true false
However, I would prefer to do this with some type of numeric tuple to avoid repeated string conversion logic.
If you only plan to store pairs of coords, another possibility is to use a combination of a Map (for the first coord) and a Set (for the second coord).
function TupleSet() {
this.data = new Map();
this.add = function([first, second]) {
if (!this.data.has(first)) {
this.data.set(first, new Set());
}
this.data.get(first).add(second);
return this;
};
this.has = function([first, second]) {
return (
this.data.has(first) &&
this.data.get(first).has(second)
);
};
this.delete = function([first, second]) {
if (!this.data.has(first) ||
!this.data.get(first).has(second)
) return false;
this.data.get(first).delete(second);
if (this.data.get(first).size === 0) {
this.data.delete(first);
}
return true;
};
}
let mySet = new TupleSet();
mySet.add([0,2]);
mySet.add([1,2]);
mySet.add([0,3]);
console.log(mySet.has([1,3]));
console.log(mySet.has([0,2]));
mySet.delete([0,2]);
console.log(mySet.has([0,2]));
Unfortunately, unlike a normal Set for which:
You can iterate through the elements of a set in insertion order.
— MDN Set
This approach will, for the example above, iterate in the order:
[0,2]
[0,3]
[1,2]
I was building a game when I came across this problem.
Here's a typescript class that might be able to help you. It uses a tree to do its magic.
You should be able to easily modify this to use arrays instead of the x and y parameters
// uses an internal tree structure to simulate set-like behaviour
export default class CoordinateSet {
tree: Record<number, Record<number, boolean>> = {}
add(x: number, y: number) {
this.tree[x] ||= {}
this.tree[x][y] = true;
}
remove(x: number, y: number) {
// if the coordinate doesn't exist, don't do anything
if (!this.tree[x] || !this.tree[y]) {
return;
}
// otherwise, delete it
delete this.tree[x][y];
// if the branch has no leaves, delete the branch, too
if (!Object.keys(this.tree[x]).length) {
delete this.tree[x]
}
}
has(x: number, y: number) {
return !!this.tree[x]?.[y];
}
}
And tests, which will also show you how it works:
import CoordinateSet from "./CoordinateSet";
describe("CoordinateSet", () => {
it("Can add a coordinate", () => {
const cs = new CoordinateSet();
expect(cs.has(1,1)).toBeFalsy();
cs.add(1, 1);
expect(cs.has(1,1)).toBeTruthy();
});
it("Can remove a coordinate", () => {
const cs = new CoordinateSet();
cs.add(1, 1);
expect(cs.has(1,1)).toBeTruthy();
cs.remove(1,1);
expect(cs.has(1,1)).toBeFalsy();
})
})
If we can assume that our tuples are finite integers, they could be encoded as a float.
class TupleSet extends Set{
add(elem){
return super.add((typeof elem === 'object'
&& Number.isSafeInteger(elem[0])
&& Number.isSafeInteger(elem[1]))
? elem[0]+elem[1]/10000000 : elem);
}
has(elem){
return super.has((typeof elem === 'object'
&& Number.isSafeInteger(elem[0])
&& Number.isSafeInteger(elem[1]))
? elem[0]+elem[1]/10000000 : elem);
}
}
function TupleSetParse(elem){
return (Number.isFinite(elem)?
[Math.round(elem),Math.round((elem-Math.round(elem))*10000000)]:elem);
}
let s=new TupleSet();
s.add([1,5]);
s.add([1000000,1000000]);
s.add([-1000000,-1000000]);
console.log(s.has([1,5])); // true
console.log(s.has([1,2])); // false
console.log([...s].map(TupleSetParse));
// [ [ 1, 5 ], [ 1000000, 1000000 ], [ -1000000, -1000000 ] ]
Of course, this is limited in range. And it is fragile to some malformed input, so additional error checking should be added. However, after some testing, this method is only 25% better in speed and memory usage than the JSON.stringify approach. So, JSON is the preferred approach.

How to concatenate indefinite number of arrays of Javascript objects

I have an array of Groups s.t. each Group has many Users
I want to return all (unique) Users for a given array of Groups.
So far, I have
let actor = await User.query().findById(req.user.id).eager('groups') // find the actor requesting
let actor_groups = actor.groups // find all groups of actor
if (actor_groups.length > 1)
var actor_groups_users = actor_groups[0].user
for (let i = 0; i < actor_groups.length; i++) {
const actor_groups_users = actor_groups_users.concat(actor_groups[i]);
}
console.log('actor groups users is', actor_groups_users);
else
// return users from the first (only) group
which returns the error: actor_groups_users is not defined
Feels like a roundabout way to do this. Is there a way to just combine actor_groups into a single combined group?
Here we can cycle through, adding users if not already in the array, using .forEach() and .includes().
This is assuming that group.user is an Array of users.
let users = [];
// check that actor_groups has items in it
if (actor_groups && actor_groups.length > 1) {
// cycle through each actor_group
actor_groups.forEach( group => {
// check if we have a 'user' array with items in it
if (group.user && group.user.length > 1) {
// cycle through each user in the group
group.user.forEach( user => {
// check if we already have this user
// if not, add it to users
if (!users.includes(user)) {
users.push(user);
}
}
}
}
}
You can simply do this:
const allGroupsArrs = actor_groups.map(({ user }) => user);
const actor_groups_users = [].concat(...allGroupArrs);
Or, you could simply use the .flat() method, which is not yet officially part of the ES standard, but is on its way there and has browser support outside of IE:
const allGroupsArrs = actor_groups.map(({ user }) => user);
const actor_groups_users = allGroupArrs.flat();
Also, the above would result in duplicate values in actor_groups_users if there are people who are in multiple groups. You can remedy this (assuming the array elements are primitive values) using a Set:
const unique_users = [...new Set(actor_groups_users)];
The most efficient way I can think of is
const users = [...new Set([...actor_groups].flatMap(el => el.user))]
I used this example:
const actor_groups = [{user: ['ok','boom']}, {user: 'er'}]
console.log([...new Set([...actor_groups].flatMap(el => el.user))])
//output: ["ok", "boom", "er"]

How do I use javascript includes to filter out text that does not include specific text

I have a method that gets a list of saved photos and determines the number of photos listed. What I wish to do is return the number of photos that contain the text "Biological Hazards" in the name. Here is my code so far
getPhotoNumber(): void {
this.storage.get(this.formID+"_photos").then((val) => {
this.photoResults = JSON.parse(val);
console.log("photoResults", this.photoResults);
// photoResults returns 3 photos
// Hazardscamera_11576868238023.jpg,
// Biological Hazardscamera_11576868238023.jpg,
// Biological Hazardscamera_11576868351915.jpg
this.photoList = this.photoResults.length;
console.log("photoList", this.photoList); // returns 3
this.photoListTwo = this.photoResults.includes('Biological Hazards').length; // I wish to return 2
}).catch(err => {
this.photoList = 0;
});
}
Any help would be greatly appreciated.
Xcode log
[
One way to do this is to .filter() the array, and then calculate the length of that array.
this.photoListTwo = this.photoResults.filter(photoString => {
return photoString === 'Biological Hazards' //or whatever comparison makes sense for your data
}).length;
Quick solution for this (sorry for the lack of better formating, posting from mobile):
const array = ["Hazardscamera_11576868238023.jpg", "Biological Hazardscamera_11576868238023.jpg", "Biological Hazardscamera_11576868351915.jpg"];
const filterBioHazards = (str) => /Biological Hazards/.test(str);
console.log(array.filter(filterBioHazards).length);
// Prints 2
The method includes returns boolean to indicate whether the array contains a value or not. What you need is to filter your array and return its length after.
You need to replace the line:
this.photoListTwo = this.photoResults.includes('Biological Hazards').length;
By this:
this.photoListTwo = this.photoResults.filter(function(result) {return result.contains("Biological Hazards");}).length;

Javascript's method forEach() creates array with undefined keys

I am building a simple todo app, and I'm trying to get the assigned users for each task. But let's say that in my database, for some reason, the tasks id starts at 80, instead of starting at 1, and I have 5 tasks in total.
I wrote the following code to get the relationship between user and task, so I would expect that at the end it should return an array containing 5 keys, each key containing an array with the assigned users id to the specific task.
Problem is that I get an array with 85 keys in total, and the first 80 keys are undefined.
I've tried using .map() instead of .forEach() but I get the same result.
let assignedUsers = new Array();
this.taskLists.forEach(taskList => {
taskList.tasks.forEach(task => {
let taskId = task.id;
assignedUsers[taskId] = [];
task.users.forEach(user => {
if(taskId == user.pivot.task_id) {
assignedUsers[taskId].push(user.pivot.user_id);
}
});
});
});
return assignedUsers;
I assume the issue is at this line, but I don't understand why...
assignedUsers[taskId] = [];
I managed to filter and remove the empty keys from the array using the line below:
assignedUsers = assignedUsers.filter(e => e);
Still, I want to understand why this is happening and if there's any way I could avoid it from happening.
Looking forward to your comments!
If your taskId is not a Number or autoconvertable to a Number, you have to use a Object. assignedUsers = {};
This should work as you want it to. It also uses more of JS features for the sake of readability.
return this.taskLists.reduce((acc, taskList) => {
taskList.tasks.forEach(task => {
const taskId = task.id;
acc[taskId] = task.users.filter(user => taskId == user.pivot.task_id);
});
return acc;
}, []);
But you would probably want to use an object as the array would have "holes" between 0 and all unused indexes.
Your keys are task.id, so if there are undefined keys they must be from an undefined task id. Just skip if task id is falsey. If you expect the task id to possibly be 0, you can make a more specific check for typeof taskId === undefined
this.taskLists.forEach(taskList => {
taskList.tasks.forEach(task => {
let taskId = task.id;
// Skip this task if it doesn't have a defined id
if(!taskId) return;
assignedUsers[taskId] = [];
task.users.forEach(user => {
if(taskId == user.pivot.task_id) {
assignedUsers[taskId].push(user.pivot.user_id);
}
});
});
});

ReactJS / ES6: Searching Japanese text using includes?

So, I'm writing a client-side search and I need to look through strings of Japanese characters. I'm wondering how to do this properly?... i.e. Do I change the format of the text into utf-8 something and then search the utf-8?
Example:
All my data has japaneseData.title : "フェリーチェ三田"
When I type in my search.value as : "フェ" using japaneseData.title.includes(search.value) I don't get a match...
How do I do this correctly?
Okay, after further inspection, the comments were correct and includes was finding the substring. This is all happening inside of a filter() and I'm trying to return the objects that match...
After changing my code to:
let filteredArrayofObjects = Lists.houseLists.filter(house => house.building_name.includes(query.search));
I was getting back some but not all. Problem cases:
"アーバイルスパシエ芝浦BAY-SIDE".includes("エ芝浦"); // this evaluates to true, but does not get included in my filtered array...
Okay, further digging, it seems the issue is I need to wait for the filter process before returning the results... haven't yet found a solution to that just yet.
async filter(arr, callback) {
return (await Promise.all(
arr.map(async item => {
return (await callback(item)) ? item : undefined;
})
)).filter(i => i !== undefined);
}
handleFilterLists = async (query = {}) => {
const { Lists } = this.props;
let searchResults = await this.filter(Lists.houseLists, async house => {
return house.building_name.includes(query.search);
// the final evaluation to look similar to this:
// var newArray = homes.filter(function (el) {
// return el.price <= 1000 &&
// el.sqft >= 500 &&
// el.num_of_beds >=2 &&
// el.num_of_baths >= 2.5;
// });
});
this.setState({ searchResults });
}
Okay, so, I'm trying to set state.searchResults after the filter method has checked for matching objects in the array Lists.houseLists...
includes returns true or false if the substring is detected or not. If you want the index of where the first detected substring begins, use indexOf.
I used your sample source and search text with includes and it returns true.
Edit:
I used your updated data and this still works. https://codepen.io/anon/pen/RMWpwe
const sourceText = 'アーバイルスパシエ芝浦BAY-SIDE';
const searchText = 'エ芝浦';
const lists = [
'スパシエ',
'芝浦BAY-SIDE',
'エ芝浦',
'パシエ芝浦BAY'
];
console.log(lists.filter(item => item.includes(searchText)));
// ["エ芝浦", "パシエ芝浦BAY"]

Categories