Evaluate dynamically user-added If-Statements - javascript

How can I achieve that users can add multiply custom if-statements?
For example let's say there is a given variable called x with a given value of let's say 8.
The user sees that x = 8 and has a button to add an if-statement. He clicks the button and can insert the condition which triggers an event (let's say it prints "Hello World"). So he enters "x < 100" into the field which is true. Therefore "Hello World" is printed.
After clicking the button once again, he is able to add an other condition, let's say "x < 7" which is also true. Because both conditions are true, "Hello World" is still printed.
I think you got the point of my questions, even though I lack the vocabulary.
So how could I manage to let user add an undefined amount of conditions which will be checked before "Hello World" is printed?
The only solution I know is to limit the possible amount of conditions and check each one if it is empty / what the conditions says.
Thanks a lot!

Unless you want to build an entire language you have to get clear on what exact operations you are going to allow here.
For example the operation of < and > and ==, basically all comparison operations (<= and >= as well) can be implemented via the following:
/* your X variable, might be var if you desire to change */
let x = 12
/* the array of conditions the user entered */
var conditions : [(((Int, Int) -> Bool), Int)] = []
/* some user input - read as e.g. "x > 2"*/
conditions.append((<, 100))
conditions.append((>, 2))
conditions.append((==, 12))
/* you evaluate all conditions in the following way */
let eval = conditions.map { $0(x, $1) }
let allTrue = !eval.contains(false)
/* allTrue would be true in this case because 12 < 100 && 12 > 2 && 12 == 12 */
Your "hard" job is it now to interpret the user input as some condition. But that is not too difficult, you simply need a mapping of the text input of "<" to the actual operator <.
You can adjust the above code to take care of Double instead of Int easily if you fell like you need that. But you have to aware of floating point inaccuracy and the problem that arise when checking for equality (thanks to #dfri for pointing this out).
A little bit more difficult part comes in regards to combining the conditions with or instead of and what above code does and what you currently describe in your question.
Just because I like closures: The following is the entire input reading and parsing:
func getOperator(str: String) -> ((Int, Int) -> Bool)? {
switch str {
case "<":
return (<)
case ">":
return (>)
case "==":
return (==)
case "<=":
return (<=)
case ">=":
return (>=)
default:
return nil
}
}
func parseUserInput(str:String) -> (((Int, Int) -> Bool), Int) {
var input = str as NSString
input = input.stringByReplacingOccurrencesOfString(" ", withString: "")
//let variable = input.substringToIndex(1) // in case you want more than one variable, but that will have to change the entire setup a bit
// this has to be this "ugly" to incorporate both 1 char and 2 char long operators
let operato = input.substringFromIndex(1).stringByTrimmingCharactersInSet(NSCharacterSet.alphanumericCharacterSet())
let number = input.substringFromIndex(operato.lengthOfBytesUsingEncoding(NSASCIIStringEncoding) + 1)
if let number = Int(number), op = getOperator(operato) {
return (op, number)
}
return ((<, 999999)) // need some error handling here
}
conditions.append(parseUserInput("x > 123"))
Instead of resolving the operator using a function you can even use a plain old dictionary mapping from ">" to (>) etc.

First you need a way to switch between operators. A very simple enum is perfect for this. Just add all the operators you want to use.
enum Operator : String {
case biggerThan = ">"
case smallerThan = "<"
case equal = "=="
init?(string:String) {
switch string {
case ">" :
self = .biggerThan
case "<" :
self = .smallerThan
case "==" :
self = .equal
default :
return nil
}
}
}
Each time a user clicks a button and inserts a condition, a corresponding Condition value will be created.
struct Condition {
var value: Int
var operation: Operator
}
This function returns a Bool depending on x, the inputValue and the chosen operator.
func checkCondition(x: Int, condition: Condition) -> Bool {
switch condition.operation {
case .biggerThan :
return condition.value > x
case .smallerThan :
return condition.value < x
case .equal :
return condition.value == x
}
}
This does the same but for a whole bunch of conditions. Here you can implement more logic. If all need to be true for example add : if !result { return false }.
func checkAllConditions(x:Int, conditions: [Condition]) {
for condition in conditions {
let result = checkCondition(x, condition: condition)
print(result)
}
}
Now all you need to do is store conditions in an array as the user creates them
func userCondition(operation:String, input:String) -> Condition? {
guard let op = Operator(string: operation) else {
return nil
}
guard let doubleValue = Double(input) else {
return nil
}
return Condition(value: Int(doubleValue), operation: op)
}
let conditionA = userCondition("<", input: "10")! // use if let instead of !
let conditionB = userCondition(">", input: "10")! // use if let instead of !
let conditionC = userCondition("==", input: "23")! // use if let instead of !
var x : Int = 23
checkAllConditions(x, conditions: [conditionA,conditionB,conditionC])

struct MyConditions {
let myEps: Double = 0.001
var x: Double
var lessThan = [Double]()
var equalTo = [Double]()
var greaterThan = [Double]()
init(x: Double) {
self.x = x
}
mutating func addConstraint(operand: Double, op: String) {
if op == "<" {
lessThan.append(operand)
}
else if op == "==" {
equalTo.append(operand)
}
else if op == ">" {
greaterThan.append(operand)
}
}
func checkConstraints() -> Bool {
for op in lessThan {
if !(x < op) {
return false
}
}
for op in equalTo {
if !(x - myEps < op && x + myEps > op) {
return false
}
}
for op in greaterThan {
if !(x > op) {
return false
}
}
return true
}
}
Tests:
func feasibleHelloWorld(x: MyConditions) {
if x.checkConstraints() {
print("Hello world!")
}
}
var x = MyConditions(x: 8)
x.addConstraint(100, op: "<")
x.checkConstraints() // true
feasibleHelloWorld(x) // Hello world!
x.addConstraint(8, op: "==")
x.checkConstraints() // true
feasibleHelloWorld(x) // Hello world!
x.addConstraint(7, op: "<")
x.checkConstraints() // false
feasibleHelloWorld(x) // ... nothing

Related

How do I prevent the input of a number like 2.2.2 in my calculator?

I have been working on a simple JS calculator using an OOP approach. I am struggling to create a fix that prevents the input of extra decimals. For example, a user can input 3.2.1.5. Ideally, this calculator would display the entire expression on the digital calculator screen before solving it and returning the result. With this in mind, simply preventing a user from adding a second decimal would prevent them from adding(or whatever operator they may choose) multiple decimals together. I have considered using .split() and .join() on operators in the input, but it is beginning to get convoluted as there are multiple operators to consider. Ideally, I want to avoid regex.
const keys = document.querySelector('.calc-buttons');
keys.addEventListener('click', event => {
const {target} = event
const {value} = target
if(!target.matches('button')){
return
}else{
calculator.parseInput(value)
//console.log(value)
}
})
const calculator = {
displayText: '0',
prevTotal: null,
parseInput(value){
//have any of the special buttons(AC, decimal, =) been clicked?
switch(value){
case '=':
//calculate answer
this.calcAnswer(this.displayText)
break
case 'AC':
//clear screen & stored values
this.clearAll()
break
case '.':
//create decimal
if(this.displayText == 0){
//pass'0.'
this.addText('0.')
}else{
//add value to text string
this.addText(value)
}
break
default:
//add value to text string
this.addText(value)
break
}
},
addText(value){
if(this.displayText == '0'){
this.displayText = ''
}else if(this.prevTotal !== null){
this.displayText = this.prevTotal
this.prevTotal = null
}
//check if previous input is a number
if(isNaN(+(value)) && isNaN(+(this.displayText))){
if(isNaN(this.displayText.slice(-1))){
return
}
}else if(value == '.' && this.displayText.slice(-1) == '.'){
return
}
this.displayText += value
//output display text to screen
this.outputText(this.displayText)
},
outputText(text){
document.querySelector('.screen').value = text
},
calcAnswer(equation){
let result = Function("return " + equation)()
this.outputText(result)
//console.log(equation)
//console.log(result)
this.prevTotal = result
},
clearAll(){
this.displayText = '0',
this.prevTotal = null
this.outputText(this.displayText)
}
}
Functions are based on StepUp's answer (which is wrong AFAIK; it should be .length > 2 but I can't comment yet)
const hasManySymbols = (str, symbol) => {
const firstIndex = str.indexOf(symbol) // find the first occurrence of the symbol
if(firstIndex == -1) return false // if there is no first occurrence there are not many symbols
return str.indexOf(symbol, firstIndex + 1) != -1 // whether or not there is a second occurrence
}
const validate = str => hasManySymbols(str, '.') ? 'invalid input' : 'valid input'
console.log(validate('1.23')) // "valid input"
console.log(validate('1.2.3')) // "invalid input"
I'm not sure if this is faster or slower but it should theoretically be faster I guess.
You can create a simple function to avoid repetition of code and hiding unnecessary details in functions. In addition, it helps to reduce convolution.
So create a function which will check eligibility of input and based on result just notify user or remove last incorrect character.
The sketch of function could like this:
const hasManySigns = (str, sign) => str.split(sign).length > 2
An example:
const hasManySigns = (str, sign) => str.split(sign).length > 2
let foo = '1.2.3'
const validate = str => hasManySigns(str, '.') ? 'incorrect input' : 'correct input'
console.log(validate(foo))

javascript string comparison not returning true for equal strings in console [solved]

I'm working with words and their phonemes. I found that in my code (and in the console) what looks like two identical strings " 'b eh1 r z'" for example are not returning true when compared, whether with double or triple equals. I did sanity tests in the chrome console, in the node console and in the file itself, and they all return expected results (i.e. only the 'strinfigied' variable seems corrupted. I'm racking my brains trying to figure what's going on. This is what is not workign as expected:
let stringified = trialPhonemeSequence.join(" ")
if (p == "z"){
console.log(trialPhonemeSequence)
let bearstring = 'b eh1 r z'
console.log("SHould be adding 'z' at ", i, "so we got", trialPhonemeSequence, "and stringified", stringified)
console.log(`String|${dictionary['bears']}| length ${dictionary['bears'].length} should equal |${stringified}| length ${stringified.length}: ${dictionary['bears'] == stringified} and ${bearstring == stringified}`);
}
What the Chrome Console outputs
String|b eh1 r z| length 10 should equal |b eh1 r z| length 10: false and false
Here is the entire function up to that point for context. I don't think you want the entire min reproduable code as it requires large dictionaries and datasets and initialization. The goal of this function was to input bear and look for words that are a phonemic match, allowing for addition of a phoneme (the 'z' sound in this test case).
function findAddedPhonemes(word, dictionary, hashMap)
{
let matches = []
let phonemeSequence = dictionary[word]
let phonemeSequenceList = phonemeSequence.split(" ")
for (let i = 0; i <= phonemeSequenceList.length; i++)
{
phonemeList.forEach((p, ind) => // all the items in the list
{
let trialPhonemeSequence = phonemeSequenceList.slice()
trialPhonemeSequence.splice(i, 0, p) // insert p phoneme into index
let stringified = trialPhonemeSequence.join(" ")
if (p == "z"){
console.log(trialPhonemeSequence)
let bearstring = 'b eh1 r z'
console.log(`String|${dictionary['bears']}| length ${dictionary['bears'].length} should equal |${stringified}| length ${stringified.length}: ${dictionary['bears'] == stringified} and ${bearstring == stringified}`);
}
if (stringified == "b eh1 r z"){ //THIS IS WHERE ITS BROKEN
console.log("Bears stringified searching!!!!!!!!!!!!")
}
let hash = stringified.hashCode(dictKeys.length * 4)
if (hashMap[hash] !== undefined)
{
hashMap[hash].forEach((o) =>
{
let key = getObjectsKey(o)
if (checkIfIdentical(dictionary[key], stringified))
{
matches.push(key)
}
})
}
})
}
console.log("Matches", matches)
return matches
}
EDIT (SOLVED):
There is a char 13 (Carriage Return) in Stringified string but not the others. I think I understand where this is coming from. I was inserting a new phoneme with splice in each syllable of the word, and when splicing it onto the end of the words, it's not automatically stripping the '\n', which results in comparison errors. I now know one has to do this manually and wrong hash values. BTW the phoneme dictionary ishere
Thanks #VLAZ !
stringified.split("").map(c => {
console.log(c.charCodeAt(0))
})
console.log("New word")
bearstring.split("").map(c => {
console.log(c.charCodeAt(0))
})
console.log(stringified==bearstring)

convert number to string returns empty if number 0

Trying to convert string to a number, works fine apart from when the number is zero it returns an empty string;
I understand 0 is false, but I just need a neat way of it returning the string "0"
I'm using:
const num = this.str ? this.str.toString() : '' ;
I even thought of using es6 and simply ${this.str} but that didn't work
Because 0 is "false-y" in JavaScript, as you've already figured out, you can't utilized it in a conditional. Instead, ask yourself what the conditional is really trying to solve.
Are you worried about null / undefined values? Perhaps this is better:
const num = (typeof this.str !== "undefined" && this.str !== null) ? this.str.toString() : "";
Odds are you really only care if this.str is a Number, and in all other cases want to ignore it. What if this.str is a Date, or an Array? Both Date and Array have a .toString() method, which means you may have some weird bugs crop up if one slips into your function unexpectedly.
So a better solution may be:
const num = (typeof this.str === "number") ? this.str.toString() : "";
You can also put your code in a try catch block
const num = ''
try {
num = this.str.toString();
} catch(e) {
// Do something here if you want.
}
Just adding to given answers - if you do:
x >> 0
you will convert anything to a Number
'7' >> 0 // 7
'' >> 0 // 0
true >> 0 // 1
[7] >> 0 // 7
It's a right shift bit operation. You can do magic with this in many real life cases, like described in this article.
In my case, the zero (number) that I wanted to converted to a string (which was the value of an option in a select element) was a value in an enum.
So I did this, since the enum was generated by another process and I could not change it:
let stringValue = '';
if (this.input.enumValue === 0) {
stringValue = '0';
} else {
stringValue = this.input.enumValue.toString();
}

How to check if a variable is empty in JavaScript?

var characterName = document.getElementById("pilotName").value;
var characterID = 0;
var charSecStatus = 0;
var corpName = " ";
var allianceName = " ";
//callback
makeRequest('https://api.eveonline.com/eve/CharacterID.xml.aspx?names=' + characterName, function() {
if (xmlhttp.status == OK_RESPONSE) {
//read character info
characterID = xmlhttp.responseXML.getElementsByTagName("row")[0].getAttribute("characterID");
makeRequest('https://api.eveonline.com/eve/CharacterInfo.xml.aspx?characterID=' + characterID, function() {
if (xmlhttp.status == OK_RESPONSE) {
//read character info
characterID = xmlhttp.responseXML.getElementsByTagName("characterID")[0].innerHTML;
charSecStatus = xmlhttp.responseXML.getElementsByTagName("securityStatus")[0].innerHTML;
corpName = xmlhttp.responseXML.getElementsByTagName("corporation")[0].innerHTML;
allianceName = (xmlhttp.responseXML.getElementsByTagName("alliance")[0] || {
innerHTML: ""
}).innerHTML;
}
});
}
});
(partial code pictured, no bracketspam pls)
I'm trying to check if the "alliance" variable is empty because certain '"corp" are not in "alliances" and it would be critical error on website if it tried to display an empty variable, so is there a way to check if allianceName is empty after retrieving it from the XML tree and setting it to like "No Alliance" if it IS empty?
Thanks
You are declaring true variables here
var corpName = " ";
var allianceName = " "
In JavaScript, the value of a string with whitespace characters is actually truthy. While the above values do look empty, the reality is they're not. Try it out yourself.
Boolean(" ");
=> true
To set these variables empty, you need to declare them without whitespace characters
var corpName = "";
var allianceName = "";
After you have this, there are two ways to check if the variable is empty
if (!corpName || !corpName.length)
Assuming, you did accidentally include the whitespace character, there are several ways to fix this. We can modify these strings in these cases.
Boolean(" ".trim());
=> false
In the above example, the trim() function is used to remove whitespace characters from a string.
You could also search the string for non-whitespace characters
/\S/.test(" ")
=> false
The \S modifier finds all non whitespace characters.
Alternatively, you could also use the replace function
Boolean(" ".replace(/\s/g, ''));
The global flag g finds all references to the previous regular expression.
If you just want to check whether there's any value in a variable, then try this,
if (str) {
//do something
}
or,
//when undefined
if (typeof str == 'undefined'){
//do something
}
If you need to check specifically for an empty string over null, then, try this.
//when defined, but empty
if ((str.length == 0)||(str== ""))
{
//do something
}
You can try something like this:
Note: If you value is null or undefined, !value?true:false should work. Also value.length is also good approach but should be used in case of string. If value is number, value.length will return 0, i.e. true for empty check.
function isEmpty(value) {
switch (typeof(value)) {
case "string": return (value.length === 0);
case "number":
case "boolean": return false;
case "undefined": return true;
case "object": return !value ? true : false; // handling for null.
default: return !value ? true : false
}
}
var a = " ";
console.log(a, "is empty:", isEmpty(a))
console.log(a, "is empty:", isEmpty(a.trim()))
a = 0;
console.log(a, "is empty:", isEmpty(a))
a = undefined;
console.log(a, "is empty:", isEmpty(a))
a = null;
console.log(a, "is empty:", isEmpty(a))
Also, you can change
allianceName = (xmlhttp.responseXML.getElementsByTagName("alliance")[0] || {
innerHTML: "
}).innerHTML;
to
allianceName = xmlhttp.responseXML.getElementsByTagName("alliance")[0].innerHTML || "";
I believe in JavaScript there's a function called isset() which takes one argument and returns true if it is set to a value and false if it is not.

javascript Binary operation don't work for options

Here is what i have...
some array item have option that is binary
like
$item[0] = 0010
$item[1] = 1000
$item[2] = 0110
$item[3] = 1101
each bit represent some option
what i need is to compare it to the customer request option
let's say it's 0010
so i need the logic to show only $item[0] and $item[2] because second byte is 1. BUT, when there is no customer options check : 0000 i must show them all
only when some options are show there must be filter...
i should have listen more in my math class... i am clueless now, please help !
note :
everything come from here : http://jsfiddle.net/yHxue/
but i dont understand this line :
markers[m].setMap(((markers[m].props & props)>>>0===props)? ((props)?map:null): null);, so i have rewritten it, mine don't work !
If your data is actual numbers, then just use the bitwise & operator on each member.
var customerOption = parseInt("0010", 2)
$item.forEach(function(n, i) {
if (customerOption === 0 || ((n & customerOption) == customerOption))
alert(i); // show this item
});
If they're strings, then you'll need to convert each item to a number first.
Same as above, but...
parseInt(n, 2);
var $item = [];
$item[0] = "0010"
$item[1] = "1000"
$item[2] = "0110"
$item[3] = "1101"
var customerOption = parseInt("0010", 2)
$item.forEach(function(n, i) {
if (customerOption === 0 || ((parseInt(n, 2) & customerOption) == customerOption))
document.querySelector("pre").textContent += "\n0010 matched index: " + i;
});
document.querySelector("pre").textContent += "\n\n"
var customerOption = parseInt("0110", 2)
$item.forEach(function(n, i) {
if (customerOption === 0 || ((parseInt(n, 2) & customerOption) == customerOption))
document.querySelector("pre").textContent += "\n0110 matched index: " + i;
});
<pre></pre>
Convert all the strings (items as well as the mask) to a number using parseInt(string, 2).
If mask is 0, just take all the items. If not:
Then use the & operator to see the common 1 bits:
var common = item & mask;
Then test common:
If you want any of the mask bits, then common should just not be 0.
If you want all of the mask bits, then common should equal mask.
EDIT:
markers[m].setMap(
((markers[m].props & props) >>> 0 === props) ?
((props) ? map : null) :
null
);
markers[m].props & props looks for common 1 bits, as I explain above; >>> 0 makes sure the number is positive. The result is tested against props, making sure the common bits are all the bits in props (as in my second option above). If all the props bits are set, and if props is not zero, then markers[m] is setMap-ped to map; otherwise, to null.
var selections = ['0010', '1000', '0110', '1111'];
var getRequiredSelections = function (customerSelection) {
var customerSelectionInt = parseInt(customerSelection, 2);
if (customerSelectionInt) {
return selections.filter(function (binaryString) {
return parseInt(binaryString, 2) & customerSelectionInt;
});
} else {
return selections;
}
};
You can use it like so:
getRequiredSelections('0010');
BUT, when there is no customer options check : 0000 i must show them all
In 2's compliment, -1 is all 1 in binary
if (!props) props = -1;
Testing for something in binary exactly, use both & and ===
needle === (needle & haystack)
This means looking for needle = 1001 in haystack = 0001 is false, even though 1001 & 0001 is the truthy 0001
You may not want to use exact matches in your case
You could write some code that takes callbacks to process your array
function applyByFlag(flag, callbackTrue, callbackFalse) {
if (!flag)
flag = -1;
function nop() {}
if (!callbackTrue) callbackTrue = nop;
if (!callbackFalse) callbackFalse = nop;
boxes.forEach(function (e, i, a) {
if (flag & e) // not exact test
callbackTrue.call(e, e, i, a);
else
callbackFalse.call(e, e, i, a);
});
}
Of course, you'd probably want to adjust the test to work against e.props
The code you have is almost there; just add an additional check for when props is zero:
for(var m=0; m < markers.length; m++) {
var show = (markers[m].props & props)>>>0===props || props == 0;
markers[m].setMap(show ? map : null);
}
Demo
The markers[m].props & props expression will either be 0 (if not all bits of props match) or the value of props itself (if all bits of props match).
The additional >>>0 will turn the expression into an unsigned 32-bit integer value, but it's not necessary if you use the following expression:
var show = (markers[m].props & props) || props == 0;

Categories

Resources