How could I do the following in javascript in efficient way?
I've counter of 7 items(item0, item1, item2...item6) in order..
like in counts = [0,2,0,5,6,0,9];
There are 3 mutually exclusive groups:
group1: item0, item1, item2
group2: item3, 4, 5
group3: item6
A group is considered selected if and only if counters of the group member elements are >= 0.
Now I want to know which group is selected?
After clarification by understack, the OP, a group is selected if [at least] one of its elements is selected.
This in turn makes the "mutually exclusive" part of the question ambiguous, because the example supplied (counts = [0,2,0,5,6,0,9]) all 3 group would be selected...
Never the less...
The problem of identifying which group is selected can be optimally resolved by relying on on JavaScript short-circuit evaluation of boolean expressions.
A tentative solution would look like the following:
counts = [0,2,0,5,6,0,9]; // as stated an bad value for counts,
// if groups are to be mutually exclusive
if (counts[0] || counts[1] || counts[2])
{
GroupSelected = 1;
}
else if (counts[3] || counts[4] || counts[5])
{
GroupSelected = 2;
}
else if (counts[6] > 0)
{
GroupSelected = 3;
}
else
{
GroupSelected = -1; // none of the groups is selected !!!
}
Note: A possible optimization would come from a "prior" knowledge of the probabilities of a given element to be selected (relative to others, in its group), as well as the probability for a given group to be selected.
With such knowledge, the above snippet can be rewritten to first test for the most likely groups first, and within each group to test for the most likely elements first.
Here is a data structure to do what you want.
var DS = {
Items: {},
Groups: {},
setItem: functon(index, item, group){
this.Items[index] = item;
if ( typeof this.Groups[group] === 'undefined' )
this.Groups[group] = [index];
else
this.Groups[group].push(index);
},
isSelected: function(item) {
return item >= 0;
},
isSelectedGroup: function(group) {
var Group = this.Groups[group];
for ( var i in Group ) {
var Item = this.Items[i];
if ( this.isSelected(Item) ) {
return true;
}
}
},
getSelectedGroups: function(){
var selected = [];
for ( var group in this.Groups ) {
var Group = this.Groups[group];
if ( this.isSelectedGroup(Group) ) {
selected.push(group);
}
}
return selected;
}
}
To use with your items: 0,2,0,5,6,0,9. Do the following:
DS.setItem(0,0,1);
DS.setItem(1,2,1);
DS.setItem(2,0,1);
DS.setItem(3,5,2);
DS.setItem(4,6,2);
DS.setItem(5,0,2);
DS.setItem(6,9,3);
To test:
DS.isSelectedGroup(3);
// or to get all selected groups
DS.getSelectedGroups();
Should work, if not you should be able to figure it out :)
Using the lodash library, to determine if all elements of a (sub)array can match an element in another array,
function arrayMatchesSubarray(arr, subarr) {
var filteredSubarray = _.filter(subarr, function(subarrelement) {
return _.any(arr, function(arrelement){
return arrelement === subarrelement;
});
});
return _.isEqual(subarr, filteredSubarray);
};
Result:
arrayContainsSubarray([1,2,3,4],[1,2]); // true
arrayContainsSubarray([1,2,3,4],[5,6,1]); // false
arrayContainsSubarray([1,2,3,4],[1,4,3]); // true
Related
I have a task and I need a function that validate '123456' equals '213456','312456' etc. Maybe, I missing something, but absolutely have no idea how to validate it
I tried to do regexp like if(userInput = [/answer/g] return true but it isn't work
The following checks that one string is a permutation of another as long as all the characters in the set string are different.
let s = new Set('123456');
let input = '213456';
if(input.length === s.size && [...input].every(c => s.has(c)))
return true;
let string = '123457';
let user_input ='623451';
function compare(string, user_input)
{
if(string.length == user_input.length)
{
for (let i = 0; i < string.length; i++) {
if (user_input.includes(string[i])) continue;
else
return false;
}
return true;
}
else
{
console.log('failure');
}
}
console.log(compare(string, user_input));
you might like this solution
Here a solution which runs in O(n) and can also be used if the expected string contains duplicate charaters like e.g. 112233 (see second example within the code), which other solutions posted here are not taking into consideration.
The idea here is that you first create a lookup table (I am using a JavaScript object here) once which holds all the numbers/ characters and their count within the expected word. Then using this lookup table I check for any of the characters in a user supplied word (the word which has to be validated) if it has all the expected characters and (this is the distinction to other answers posted here) if their count matches the expected count of said character/ number. If one string is a permutation of the other the lookup table should have 0 as the count for every single character. If any character is not 0, it is not a permutation.
function validate(userInput, validOptionsMap) {
// create a copy as we are mutating the values within the object
const validOptionsWithCount = { ...validOptionsMap
};
const elementsMatch = userInput.split("").every(input => {
if (validOptionsWithCount[input]) {
const count = validOptionsWithCount[input];
if (count === 0) {
return false;
}
validOptionsWithCount[input] = count - 1;
return true;
}
return false;
});
if (!elementsMatch) return false;
return Object.values(validOptionsWithCount).every(count => count === 0);
};
function buildLookupTableForExpectedPermutation(expectedPalindrome) {
return expectedPalindrome.split("")
.reduce((all, cur) => (all[cur] ?
all[cur] = all[cur] + 1 :
all[cur] = 1, all), {});
}
// Your example
const permutation = "123456";
const validOptionsWithCount = buildLookupTableForExpectedPermutation(permutation);
console.log(`Checking for permutations of ${permutation}`)
console.log("123456:", validate("123456", validOptionsWithCount)); // true
console.log("123457:", validate("123457", validOptionsWithCount)); // false
console.log("265413:", validate("265413", validOptionsWithCount)); // true
console.log("2654131:", validate("2654131", validOptionsWithCount)); // false
console.log("261413:", validate("261413", validOptionsWithCount)); // false
// example with multiple identical values (other solutions posted here will fail at least one of these tests)
const permutation2 = "112233";
const validOptionsWithCount2 = buildLookupTableForExpectedPermutation(permutation2);
console.log(`Checking for permutations of ${permutation2}`)
console.log("112233:", validate("112233", validOptionsWithCount2)); // true
console.log("123123:", validate("123123", validOptionsWithCount2)); // true
console.log("123131:", validate("123131", validOptionsWithCount2)); // false
console.log("1231231:", validate("1231231", validOptionsWithCount2)); // false
console.log("12312:", validate("12312", validOptionsWithCount2)); // false
I have a question about clear way of return true if two booleans are true but also can be empty. I mean there can be one or even non booleans and there it should be also true. So far I use:
var isSelectedM = true;
var isSelectedE = true;
if(this.getModel("info").getProperty("/dep")) {
isSelectedM = this.byId("checkBoxM").getSelected();
}
if(this.getModel("info").getProperty("/sta")) {
isSelectedE = this.byId("checkBoxE").getSelected();
}
return (isSelectedM && isSelectedE);
I see two problems here - I would like to start with both values as false and then probably change them to true and second it take so many lines. How can I do it more clear as I don't like my code now?
I'd use an array of arrays, subarrays containing the getProperty string and its linked byId string, and use an every test:
const items = [
['/dep', 'checkBoxM'],
['/sta', 'checkBoxE']
]
return items.every(([prop, id]) => (
!this.getModel("info").getProperty(prop)
|| this.byId(id).getSelected()
);
Another benefit of this is that it's easily extendable to 3 or more items with very little additional code, just add to the items array.
Or, in ugly ES5:
var items = [
['/dep', 'checkBoxM'],
['/sta', 'checkBoxE']
];
for (var i = 0; i < items.length; i++) {
var item = items[i];
if (this.getModel("info").getProperty(item[0])
&& !this.byId(item[1]).getSelected()) {
return false;
}
}
return true;
As you can see, the code is a lot more verbose and harder to understand - I'd recommend using Babel and polyfills instead.
I have a localStorage file made up of numeric keys. To write a new key, value pair to the file, I want to make sure the key does not already exist. If it does exist, I add one to the key and test again if it's present. It's a small set, so I thought this could be handled with a straightforward iteration of all keys and a check for match, e.g.:
var CheckItAintThere = function() {
for ( var i = 0, len = window.localStorage.length; i < len; ++i )
{
var Foundit = false;
console.log('checking for duplicate to see if '+window.localStorage.key(i)+' is equal to '+SaveNumber);
if (window.localStorage.getItem( window.localStorage.key(i) ) == SaveNumber.toString())
{
console.log('FOUNDIT TRUE');
Foundit = true;
}
}
while (Foundit == true) {
SaveNumber = SaveNumber + 1;
if (localStorage.getItem( localStorage.key( i ) ) !== SaveNumber.toString())
{
console.log('SaveNumber = '+SaveNumber+' LocalStoragekey = '+localStorage.key(i));
Foundit = false;
}
}
}
But Foundit never tests true, even when the console.log reports:
> checking for duplicate to see if 0 is equal to 3
> checking for duplicate to see if 1 is equal to 3
> checking for duplicate to see if 3 is equal to 3
I tried adding .toString() to the key, but as I understand it keys are stored as strings anyway, and this had no impact.
I have a feeling I'm going to have a palmprint on my forehead, but what am I doing wrong?
I'm using multiple checkboxes to filter properties using angularjs. Currently, I am using a custom filter to show all properties of a certain type. I have multiple checkboxes that as you check each new one it filters the results.
At present, each new checkbox you check narrows your search (i.e. which properties are both rural AND coastal) and I would like to widen the search (i.e. which properties are either rural OR coastal). I'm really new to this.
Here is my app:
propertyApp.controller('PropertyListControl', function ($scope) {
$scope.properties = [
{
title: "Sharrow Bay Hotel",
location:['rural', 'coastal']
},
{
title: "The Royal Oak Inn",
location:['rural']
},
{
title: "Scale Hill Cottages",
location:['urban']
},
];
$location = {}
// Currently using this great custom filter:
}).filter('filteredLocation', function() {
return function(properties, location) {
var result = properties.slice(); // copy array
angular.forEach(location, function(value, key) {
if(value) {
for(var index = 0; index < result.length; index++) {
property = result[index];
if(property.location.indexOf(key) == -1) {
result.splice(index--,1);
}
}
}
});
return result;
};
});
And my checkboxes:
<label><input type="checkbox" ng-model="location.rural"/>Rural</label>
<label><input type="checkbox" ng-model="location.urban"/>Urban</label>
<label><input type="checkbox" ng-model="location.coastal"/>Coastal</label>
That filter starts with all your locations:
var result = properties.slice();
and removes any that don't match your test:
result.splice(index--,1);
Thus it's acting like an "and" since, as in your example, anything without "coastal" is removed and then anything without "Rural" is removed. So the only items left are ones that match both conditions.
To turn it into an "or" filter I'd start with an empty array:
var result = [];
and add the results as they match (so any that match either test will be added):
result.push(property);
To avoid duplicates I've also switched the loops so the outer loop now covers the list of properties and the inner loop goes over the list of locations to filter. Then we can abort out of the inner loop once we find that the property matches any of the locations.
Here's the entire function:
.filter('filteredLocation', function() {
return function(properties, location) {
var result = [];
for(var index = 0; index < properties.length; index++) {
var added = false;
angular.forEach(location, function(value, key) {
if(value && !added) {
property = properties[index];
if(property.location.indexOf(key) != -1) {
result.push(property);
added = true; // Mark as added so we don't add duplicates
}
}
})
};
return result;
};
demo fiddle
I've got an in page text search using JS, which is here:
$.fn.eoTextSearch = function(pat) {
var out = []
var textNodes = function(n) {
if (!window['Node']) {
window.Node = new Object();
Node.ELEMENT_NODE = 1;
Node.ATTRIBUTE_NODE = 2;
Node.TEXT_NODE = 3;
Node.CDATA_SECTION_NODE = 4;
Node.ENTITY_REFERENCE_NODE = 5;
Node.ENTITY_NODE = 6;
Node.PROCESSING_INSTRUCTION_NODE = 7;
Node.COMMENT_NODE = 8;
Node.DOCUMENT_NODE = 9;
Node.DOCUMENT_TYPE_NODE = 10;
Node.DOCUMENT_FRAGMENT_NODE = 11;
Node.NOTATION_NODE = 12;
}
if (n.nodeType == Node.TEXT_NODE) {
var t = typeof pat == 'string' ?
n.nodeValue.indexOf(pat) != -1 :
pat.test(n.nodeValue);
if (t) {
out.push(n.parentNode)
}
}
else {
$.each(n.childNodes, function(a, b) {
textNodes(b)
})
}
}
this.each(function() {
textNodes(this)
})
return out
};
And I've got the ability to hide columns and rows in a table. When I submit a search and get the highlighted results, there would be in this case, the array length of the text nodes found would be 6, but there would only be 3 highlighted on the page. When you output the array to the console you get this:
So you get the 3 tags which I was expecting, but you see that the array is actually consisting of a [span,undefined,span,undefined,undefined,span]. Thus giving me the length of 6.
<span>
<span>
<span>
[span, undefined, span, undefined, undefined, span]
I don't know why it's not stripping out all of the undefined text nodes when I do the check for them. Here's what I've got for the function.
performTextSearch = function(currentObj){
if($.trim(currentObj.val()).length > 0){
var n = $("body").eoTextSearch($.trim(currentObj.val())),
recordTitle = "matches",
arrayRecheck = new Array(),
genericElemArray = new Array()
if(n.length == 1){
recordTitle = "match"
}
//check to see if we need to do a recount on the array length.
//if it's more than 0, then they're doing a compare and we need to strip out all of the text nodes that don't have a visible parent.
if($(".rows:checked").length > 0){
$.each(n,function(i,currElem){
if($(currElem).length != 0 && typeof currElem != 'undefined'){
if($(currElem).closest("tr").is(":visible") || $(currElem).is(":visible")){
//remove the element from the array
console.log(currElem)
arrayRecheck[i] = currElem
}
}
})
}
if(arrayRecheck.length > 0){
genericElemArray.push(arrayRecheck)
console.log(arrayRecheck)
}
else{
genericElemArray.push(n)
}
genericElemArray = genericElemArray[0]
$("#recordCount").text(genericElemArray.length + " " +recordTitle)
$(".searchResults").show()
for(var i = 0; i < genericElemArray.length; ++i){
void($(genericElemArray[i]).addClass("yellowBkgd").addClass("highLighted"))
}
}
else{
$(".highLighted").css("background","none")
}
}
If you look at the code below "//check to see if we need to do a recount on the array length. ", you'll see where I'm stripping out the text nodes based off of the display and whether or not the object is defined. I'm checking the length instead of undefined because the typeof == undefined wasn't working at all for some reason. Apparently, things are still slipping by though.
Any idea why I'm still getting undefined objects in the array?
My apologies for such a big post!
Thanks in advance
I've modified your eoTextSearch() function to remove dependencies on global variables in exchange for closures:
$.fn.extend({
// helper function
// recurses into a DOM object and calls a custom function for every descendant
eachDescendant: function (callback) {
for (var i=0, j=this.length; i<j; i++) {
callback.call(this[i]);
$.fn.eachDescendant.call(this[i].childNodes, callback);
}
return this;
},
// your text search function, revised
eoTextSearch: function () {
var text = document.createTextNode("test").textContent
? "textContent" : "innerText";
// the "matches" function uses an out param instead of a return value
var matches = function (pat, outArray) {
var isRe = typeof pat.test == "function";
return function() {
if (this.nodeType != 3) return; // ...text nodes only
if (isRe && pat.test(this[text]) || this[text].indexOf(pat) > -1) {
outArray.push(this.parentNode);
}
}
};
// this is the function that will *actually* become eoTextSearch()
return function (stringOrPattern) {
var result = $(); // start with an empty jQuery object
this.eachDescendant( matches(stringOrPattern, result) );
return result;
}
}() // <- instant calling is important here
});
And then you can do something like this:
$("body").eoTextSearch("foo").filter(function () {
return $(this).closest("tr").is(":visible");
});
To remove unwanted elements from the search result. No "recounting the array length" necessary. Or you use each() directly and decide within what to do.
I cannot entirely get my head around your code, but the most likely issue is that you are removing items from the array, but not shrinking the array afterwards. Simply removing items will return you "undefined", and will not collapse the array.
I would suggest that you do one of the following:
Copy the array to a new array, but only copying those items that are not undefined
Only use those array items that are not undefined.
I hope this is something of a help.
Found the answer in another post.
Remove empty elements from an array in Javascript
Ended up using the answer's second option and it worked alright.