How to perform case-insensitve lookup in javascript's set?
I have a situation where I have a set of allowed strings which doesn't ensure what case they would be in. I need to validate a user input against that set. How can I achieve this?
const countries = new Set();
countries.add("USA");
countries.add("japan");
// returns false, but is there any way I could get
//`my set to ignore case and return true?`
console.log(countries.has("usa"));
console.log(countries.has("USA"));
Just always call .toLowerCase on the string before you add it or before performing a .has check. For sure you can also abstract that into a class (if thats really necessary):
class CaseInsensitiveSet extends Set {
constructor(values) {
super(Array.from(values, it => it.toLowerCase()));
}
add(str) {
return super.add(str.toLowerCase());
}
has(str) {
return super.has(str.toLowerCase());
}
delete(str) {
return super.delete(str.toLowerCase());
}
}
const countries = new CaseInsensitiveSet([
"Usa",
]);
console.log(countries.has("usa")); // true
The short answer is "no". has uses SameValueZero algorithm to seek for a value's existence. See the comparison table here.
If performance is not a concern, you can try two searches, one with uppercased value, and one with lowercased value, and decide whether the value actually exists.
And the better approach would be to always insert the values by converting them to uppercase/lowercase and match accordingly for existence.
Sets check the exact data you have provided. The simplest solution is to save the data in lowercase or UPPERCASE and then search over the set using the .toLoserCase() String method.
Example:
// Save data in lowecase
const set1 = new Set(['test', 'other']);
console.log(set1.has('Test'));
// expected output: false
console.log(set1.has('Other'.toLowerCase()));
// expected output: false
You can add a hasIgnoreCase() prototype on Set.
Set.prototype.hasIgnoreCase = function(str) {
return this.has(str) || this.has(str.toUpperCase());
}
const countries = new Set();
countries.add("USA");
countries.add("japan");
// returns false, but is there any way I could get
//`my set to ignore case and return true?`
console.log(countries.hasIgnoreCase("usa"));
console.log(countries.hasIgnoreCase("USA"));
Related
Let say we have a map in JS as below.
const someMap = new Map();
someMap.set(["Jack", "Mathematics"], "Fee=₹120000");
someMap.set(["Alyne", "Science"], "Fee=₹90000");
// Going to be undefined because the lists are compared by ===
console.log(someMap.get(["Jack", "Mathematics"]));
This prevents us from checking for keys in a map dynamically because of === comparison. One way I can think of is to convert it to a string like "Jack, Mathematics" and then use it as a key. But this doesn't look perfect.
What is the best way to represent keys which are lists?
By best way, I mean a way where we preserve the list structure of the key. Let say we are converting the list ["adog", "isananimal"] to a string "adog,isananimal" and there is another list ["adog,isananimal"] which converts to the same string "adog,isananimal" which would create a confusion while retrieving the values.
Stringification is still the most straightforward approach. If you don't like the implications of that, you can hide the implementation details in a custom class that extends Map:
class ArrayKeyedMap extends Map {
get(array) {
return super.get(this.#toKey(array));
}
set(array, value) {
return super.set(this.#toKey(array), value);
}
has(array) {
return super.has(this.#toKey(array));
}
delete(array) {
return super.delete(this.#toKey(array));
}
#toKey(array) {
return JSON.stringify(array);
}
}
const someMap = new ArrayKeyedMap();
someMap.set(["Jack", "Mathematics"], "Fee=₹120000");
someMap.set(["Alyne", "Science"], "Fee=₹90000");
console.log(someMap.get(["Jack", "Mathematics"]));
If you want, you can take it further and override other Map functions like keys() and entries() as well using a custom #fromKey() function to map the strings back to arrays.
I'm trying to split out the values from props.location.search in React/Redux. I've successfully obtained the mixOne split however I can't seem to return the value of quantity. Here's my code:
const mixOne = props.location.search
? String(props.location.search.split("mixOne=")[1])
: "None";
const quantity = props.location.search
? Number(props.location.search.split("=")[1])
: 1;
And here's the URL that gets generated:
const addToCartHandler = () => {
props.history.push(
`/Cart/${productId}?quantity=${quantity}?mixOne=${mixOne}`
);
};
As you can see quantity returns null, when I need the value selected
props.location.search.split("=") on "?quantity=1?mixOne=Grape" would return [ '?quantity', '1?mixOne', 'Grape' ] since the next = is not until after mixOne.
There's a few different fixes here.
Your query string is invalid– a ? denotes the start of the query string. Separate parameters should be split up using & ampersand characters. It should look like this: ?quantity=1&mixOne=Grape
If you follow the standard here, you can then split it two ways: by = and then by & to get the different parameters. However, there is an easier way.
Using the new-ish URLSearchParams API, you can parse your parameters in a predictable way:
// Use the constructor with your `props.location.search`
const queryParams = new URLSearchParams(props.location.search);
// Use the getters to grab a specific value
const quantity = queryParams.get("quantity");
// Ensure it's a number for safety
const quantityNum = Number(quantity);
// ... the rest of your code here
The query is wrong. You're using double question marks. The second ? should be replaced with &.
?quantity=1&mixOne=Grape
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;
I built a custom component that filters an array of objects. The filter uses buttons, sets from active to non-active and allows more than one option on/off at the same time.
StackBlitz of my attempt - https://stackblitz.com/edit/timeline-angular-7-ut6fxu
In my demo you will see 3 buttons/options of north, south and east. By clicking on one you make it active and the result should include or exclude a matching "location" either north, south and east.
I have created my methods and structure to do the filtering, I'm struggling with the final piece of logic.
So far I have created a method to create an array of filtered locations depending on what the user clicks from the 3 buttons.
Next this passes to my "filter array" that gets the logic that should compare this filtered array against the original to bring back the array of results that are still remaining.
Its not quite working and not sure why - I originally got this piece of functionality working by using a pipe, but fore reasons do not want to go in that direction.
//the action
toggle(location) {
let indexLocation = this.filteredLocations.indexOf(location);
if (indexLocation >= 0) {
this.filteredLocations = this.filteredLocations.filter(
i => i !== location
);
} else {
this.filteredLocations.push({ location });
}
this.filterTimeLine();
}
// the filter
filterTimeLine() {
this.filteredTimeline = this.timeLine.filter(x =>
this.contactMethodFilter(x)
);
}
//the logic
private contactMethodFilter(entry) {
const myArrayFiltered = this.timeLine.filter(el => {
return this.filteredLocations.some(f => {
return f.location === el.location;
});
});
}
https://stackblitz.com/edit/timeline-angular-7-ut6fxu
Sorry for my expression but u have a disaster in your code. jajaja!. maybe u lost that what u need but the logic in your functions in so wrong. comparing string with objects. filter a array that filter the same array inside... soo u need make a few changes.
One:
this.filteredLocations.push({location});
Your are pushing object. u need push only the string.
this.filteredLocations.push(location);
Two:
filterTimeLine() {
this.filteredTimeline = this.timeLine.filter(x =>
this.contactMethodFilter(x)
);
}
in this function you filter the timeLine array. and inside of contactMethodFilter you call filter method to timeLine again....
See a functional solution:
https://stackblitz.com/edit/timeline-angular-7-rg7k3j
private contactMethodFilter(entry) {
const myArrayFiltered = this.timeLine.filter(el => {
return this.filteredLocations.some(f => {
return f.location === el.location;
});
});
}
This function is not returning any value and is passed to the .filter
Consider returning a boolean based on your logic. Currently the filter gets undefined(falsy) and everything would be filtered out
I'm using the immutable.js and redux in project, and I found an quite strange issue.
here is the code used in selector:
{
dealDetail : dealDetails.get(id.toString()).toJS(),
dealTrackLog : dealTrackLogs.get(id).toJS()
}
First, the id is Number, in detail, I must pass string of id, and in trackLogs, on the contrary, it must be Number, otherwise will cause error, "cannot read property toJS() of undefined"
and I think the problem maybe in reducer, here is the code:
// dealDetailReducer
// const initialStateOfDealDetail = fromJS({})
let details = {}
action.data.details.map((detail) => {
details[detail.id] = detail
})
return state.merge(fromJS(details))
...
// dealTrackLogsReducer
// initialStateOfDealTrackLogs = fromJS({})
if (state.get(action.data.id)) {
// has id in state, update
return state.withMutations(s =>
s.update(
action.data.id,
trackLog => trackLog.merge(fromJS(action.data.trackLogs))
)
)
}
// no id in state, just set, id : data
return state.set(action.data.id, fromJS(action.data)
so, I'm hard to understand why and when to pass a Number/String ?
First line
let details = {}
You are using regular object for details state. Objects coerce to string keys.
The second case you are using immutablejs operation that preserve the key type.