How to create a search language? - javascript

I would like to create a simple filtering language in Javascript.
I want to search inside a collection of products, say:
product=[
{price:10,name:"T-Shirt",category:"Clothing",published_at:"07-08-2014",size:"10x30",color:"#0000FF"},
{price:20,name:"Chair",category:"Furniture",published_at:"09-03-2013",size:"30x30",color:"#00FF00"},
{price:30,name:"iPhone",category:"Phones",published_at:"17-03-2014",size:"40x30",color:"#FF00FF"},
{price:40,name:"Samsung Galaxy",category:"Phones",published_at:"12-01-2012",size:"10x60",color:"#00BBBB"},
];
With only one input of text, I would like to be able to query inside this array, for example:
cat:Clothing => gives back the tshirt
price:>15 => gives back Chair, iPhone, and Samsung Galaxy
name:iP => Filters trough the names and gives back iPhone
price:>15&&size:>35x25 => Filters trough the names and gives back iPhone
I know they are some language parsers like
https://github.com/tolmasky/language
https://github.com/zaach/jison (used by coffeescript)
but I don't know which one to choose (and why) and if it is a good idea to use one ? Any ideas ?

I think this is simple enough to do yourself without a library.
Here's my crack at it:
http://jsfiddle.net/qrz48/2/
// polyfill Array.prototype.forEach if you need to support older browsers...
// there's one at: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
var product = [
{price:10,name:"T-Shirt",category:"Clothing",published_at:"07-08-2014",size:"10x30",color:"#0000FF"},
{price:20,name:"Chair",category:"Furniture",published_at:"09-03-2013",size:"30x30",color:"#00FF00"},
{price:30,name:"iPhone",category:"Phones",published_at:"17-03-2014",size:"40x30",color:"#FF00FF"},
{price:40,name:"Samsung Galaxy",category:"Phones",published_at:"12-01-2012",size:"10x60",color:"#00BBBB"}
];
// extend this as you require new search operators.
var operators = {
"==": function(a, b) {return a == b;},
"===": function(a, b) {return a === b;},
">": function(a, b) {return a > b;},
">=": function(a, b) {return a >= b;},
"<": function(a, b) {return a < b;},
"<=": function(a, b) {return a <= b;},
"*": function(a, b) {return a.indexOf(b) > -1;}
};
// usage: find("category", "===", "Clothing")
function find(key, operator, condition, searchIn) {
if( ! searchIn) {
searchIn = product;
}
var result = [];
searchIn.forEach(function(item) {
if(operators[operator](item[key], condition)) {
result.push(item);
}
});
return result;
}
// usage: query("category:===:Clothing");
function query(str) {
var conditions = str.split("&&");
var result = [];
conditions.forEach(function(condition, index) {
var parts = condition.split(":");
var key = parts[0];
var operator = parts[1];
var condition = parts[2];
var searchIn = (conditions.length > 1 && index > 0) ? result : null;
result = find(key, operator, condition, searchIn);
});
return result;
}
// usage
console.log(query("category:===:Clothing"));
console.log(query("price:>:20"));
console.log(query("name:*:iP"));
console.log(query("price:>:20&&name:*:Galaxy"));
You can pass a search string to the query function above, as requested, with the exception that a colon is required both before and after the condition operator. For example, to find all products where the price is greater than 20, run:
query("price:>:20");
You can also combine search conditions using the format in your question:
query("price:>:20&&category:===:Clothing");
I couldn't think how to do your size comparison without splitting the size data out into separate values, e.g. sizeX and sizeY, which would be easier to compare using something like
query("sizeX:>:30&&sizeY:>:20");
If anything this was just quite fun to write. What do you think?

I have decided to test with JISON (Similar to BISON)
/* description: Parses end executes mathematical expressions. */
/* lexical grammar */
%lex
%%
\s+ /* skip whitespace */
[0-9]+("."[0-9]+)?\b return 'NUMBER'
">" return '>'
"price:" return 'PRICE'
<<EOF>> return 'EOF'
"name:" return 'NAME'
[a-z]+ return 'STRING'
"," return 'COMMA'
/lex
/* operator associations and precedence */
%left 'COMMA'
%left 'PRICE' 'NAME'
%start expressions
%% /* language grammar */
expressions
: e EOF
{ typeof console !== 'undefined' ? console.log($1) : print($1);
return $1; }
;
e
: 'PRICE' '>' e
{$$ = {price:{gt:$3}};}
| 'NAME' e
{$$ = {name:{contains:$2}};}
| NUMBER
{$$ = Number(yytext);}
| STRING
{$$ = String(yytext);}
| e 'COMMA' e
{ for (var attrname in $1) { $3[attrname]=$1[attrname]; $$ = $3; }}
;
The result of the parser is the following:
price:>30 => { price: { gt: 30 } }
name:blabla,price:>10 => { price: { gt: 10 }, name: { contains: 'blabla' } }
name:test => { name: { contains: 'test' } }
This solution seems a bit more portable to me, because it doesn't directly deal with the parsing.

Related

JavaScript - trying to loop through an object to only return an array of String values [duplicate]

How can I determine whether a variable is a string or something else in JavaScript?
This is what works for me:
if (typeof myVar === 'string' || myVar instanceof String)
// it's a string
else
// it's something else
You can use typeof operator:
var booleanValue = true;
var numericalValue = 354;
var stringValue = "This is a String";
var stringObject = new String( "This is a String Object" );
alert(typeof booleanValue) // displays "boolean"
alert(typeof numericalValue) // displays "number"
alert(typeof stringValue) // displays "string"
alert(typeof stringObject) // displays "object"
Example from this webpage. (Example was slightly modified though).
This won't work as expected in the case of strings created with new String(), but this is seldom used and recommended against[1][2]. See the other answers for how to handle these, if you so desire.
The Google JavaScript Style Guide says to never use primitive object wrappers.
Douglas Crockford recommended that primitive object wrappers be deprecated.
Since 580+ people have voted for an incorrect answer, and 800+ have voted for a working but shotgun-style answer, I thought it might be worth redoing my answer in a simpler form that everybody can understand.
function isString(x) {
return Object.prototype.toString.call(x) === "[object String]"
}
Or, inline (I have an UltiSnip setup for this):
Object.prototype.toString.call(myVar) === "[object String]"
FYI, Pablo Santa Cruz's answer is wrong, because typeof new String("string") is object
DRAX's answer is accurate and functional and should be the correct answer (since Pablo Santa Cruz is most definitely incorrect, and I won't argue against the popular vote.)
However, this answer is also definitely correct, and actually the best answer (except, perhaps, for the suggestion of using lodash/underscore). disclaimer: I contributed to the lodash 4 codebase.
My original answer (which obviously flew right over a lot of heads) follows:
I transcoded this from underscore.js:
['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'].forEach(
function(name) {
window['is' + name] = function(obj) {
return toString.call(obj) == '[object ' + name + ']';
};
});
That will define isString, isNumber, etc.
In Node.js, this can be implemented as a module:
module.exports = [
'Arguments',
'Function',
'String',
'Number',
'Date',
'RegExp'
].reduce( (obj, name) => {
obj[ 'is' + name ] = x => toString.call(x) == '[object ' + name + ']';
return obj;
}, {});
[edit]: Object.prototype.toString.call(x) works to delineate between functions and async functions as well:
const fn1 = () => new Promise((resolve, reject) => setTimeout(() => resolve({}), 1000))
const fn2 = async () => ({})
console.log('fn1', Object.prototype.toString.call(fn1))
console.log('fn2', Object.prototype.toString.call(fn2))
I recommend using the built-in functions from jQuery or lodash/Underscore. They're simpler to use and easier to read.
Either function will handle the case DRAX mentioned... that is, they both check if (A) the variable is a string literal or (B) it's an instance of the String object. In either case, these functions correctly identify the value as being a string.
lodash / Underscore.js
if(_.isString(myVar))
//it's a string
else
//it's something else
jQuery
if($.type(myVar) === "string")
//it's a string
else
//it's something else
See lodash Documentation for _.isString() for more details.
See jQuery Documentation for $.type() for more details.
Edit: The current way to do it is typeof value === 'string'. For example:
const str = 'hello';
if (typeof str === 'string') { ... }
Below has been deprecated since node v4.
If you work on the node.js environment, you can simply use the built-in function isString in utils.
const util = require('util');
if (util.isString(myVar)) {}
function isString (obj) {
return (Object.prototype.toString.call(obj) === '[object String]');
}
I saw that here:
http://perfectionkills.com/instanceof-considered-harmful-or-how-to-write-a-robust-isarray/
Best way:
var s = 'String';
var a = [1,2,3];
var o = {key: 'val'};
(s.constructor === String) && console.log('its a string');
(a.constructor === Array) && console.log('its an array');
(o.constructor === Object) && console.log('its an object');
(o.constructor === Number || s.constructor === Boolean) && console.log('this won\'t run');
Each of these has been constructed by its appropriate class function, like "new Object()" etc.
Also, Duck-Typing:
"If it looks like a duck, walks like a duck, and smells like a duck - it must be an Array"
Meaning, check its properties.
Hope this helps.
Edit; 12/05/2016
Remember, you can always use combinations of approaches too. Here's an example of using an inline map of actions with typeof:
var type = { 'number': Math.sqrt.bind(Math), ... }[ typeof datum ];
Here's a more 'real world' example of using inline-maps:
function is(datum) {
var isnt = !{ null: true, undefined: true, '': true, false: false, 0: false }[ datum ];
return !isnt;
}
console.log( is(0), is(false), is(undefined), ... ); // >> true true false
This function would use [ custom ] "type-casting" -- rather, "type-/-value-mapping" -- to figure out if a variable actually "exists". Now you can split that nasty hair between null & 0!
Many times you don't even care about its type. Another way to circumvent typing is combining Duck-Type sets:
this.id = "998"; // use a number or a string-equivalent
function get(id) {
if (!id || !id.toString) return;
if (id.toString() === this.id.toString()) http( id || +this.id );
// if (+id === +this.id) ...;
}
Both Number.prototype and String.prototype have a .toString() method. You just made sure that the string-equivalent of the number was the same, and then you made sure that you passed it into the http function as a Number. In other words, we didn't even care what its type was.
Hope that gives you more to work with :)
I can't honestly see why one would not simply use typeof in this case:
if (typeof str === 'string') {
return 42;
}
Yes it will fail against object-wrapped strings (e.g. new String('foo')) but these are widely regarded as a bad practice and most modern development tools are likely to discourage their use. (If you see one, just fix it!)
The Object.prototype.toString trick is something that all front-end developers have been found guilty of doing one day in their careers but don't let it fool you by its polish of clever: it will break as soon as something monkey-patch the Object prototype:
const isString = thing => Object.prototype.toString.call(thing) === '[object String]';
console.log(isString('foo'));
Object.prototype.toString = () => 42;
console.log(isString('foo'));
Performance
Today 2020.09.17 I perform tests on MacOs HighSierra 10.13.6 on Chrome v85, Safari v13.1.2 and Firefox v80 for chosen solutions.
Results
For all browsers (and both test cases)
solutions typeof||instanceof (A, I) and x===x+'' (H) are fast/fastest
solution _.isString (lodash lib) is medium/fast
solutions B and K are slowest
Update: 2020.11.28 I update results for x=123 Chrome column - for solution I there was probably an error value before (=69M too low) - I use Chrome 86.0 to repeat tests.
Details
I perform 2 tests cases for solutions
A
B
C
D
E
F
G
H
I
J
K
L
when variable is string - you can run it HERE
when variable is NOT string - you can run it HERE
Below snippet presents differences between solutions
// https://stackoverflow.com/a/9436948/860099
function A(x) {
return (typeof x == 'string') || (x instanceof String)
}
// https://stackoverflow.com/a/17772086/860099
function B(x) {
return Object.prototype.toString.call(x) === "[object String]"
}
// https://stackoverflow.com/a/20958909/860099
function C(x) {
return _.isString(x);
}
// https://stackoverflow.com/a/20958909/860099
function D(x) {
return $.type(x) === "string";
}
// https://stackoverflow.com/a/16215800/860099
function E(x) {
return x?.constructor === String;
}
// https://stackoverflow.com/a/42493631/860099
function F(x){
return x?.charAt != null
}
// https://stackoverflow.com/a/57443488/860099
function G(x){
return String(x) === x
}
// https://stackoverflow.com/a/19057360/860099
function H(x){
return x === x + ''
}
// https://stackoverflow.com/a/4059166/860099
function I(x) {
return typeof x == 'string'
}
// https://stackoverflow.com/a/28722301/860099
function J(x){
return x === x?.toString()
}
// https://stackoverflow.com/a/58892465/860099
function K(x){
return x && typeof x.valueOf() === "string"
}
// https://stackoverflow.com/a/9436948/860099
function L(x) {
return x instanceof String
}
// ------------------
// PRESENTATION
// ------------------
console.log('Solutions results for different inputs \n\n');
console.log("'abc' Str '' ' ' '1' '0' 1 0 {} [] true false null undef");
let tests = [ 'abc', new String("abc"),'',' ','1','0',1,0,{},[],true,false,null,undefined];
[A,B,C,D,E,F,G,H,I,J,K,L].map(f=> {
console.log(
`${f.name} ` + tests.map(v=> (1*!!f(v)) ).join` `
)})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js" integrity="sha512-90vH1Z83AJY9DmlWa8WkjkV79yfS2n2Oxhsi2dZbIv0nC4E6m5AbH8Nh156kkM7JePmqD6tcZsfad1ueoaovww==" crossorigin="anonymous"></script>
This shippet only presents functions used in performance tests - it not perform tests itself!
And here are example results for chrome
This is a great example of why performance matters:
Doing something as simple as a test for a string can be expensive if not done correctly.
For example, if I wanted to write a function to test if something is a string, I could do it in one of two ways:
1) const isString = str => (Object.prototype.toString.call(str) === '[object String]');
2) const isString = str => ((typeof str === 'string') || (str instanceof String));
Both of these are pretty straight forward, so what could possibly impact performance? Generally speaking, function calls can be expensive, especially if you don't know what's happening inside. In the first example, there is a function call to Object's toString method. In the second example, there are no function calls, as typeof and instanceof are operators. Operators are significantly faster than function calls.
When the performance is tested, example 1 is 79% slower than example 2!
See the tests: https://jsperf.com/isstringtype
I like to use this simple solution:
var myString = "test";
if(myString.constructor === String)
{
//It's a string
}
if (s && typeof s.valueOf() === "string") {
// s is a string
}
Works for both string literals let s = 'blah' and for Object Strings let s = new String('blah')
I find this simple technique useful to type-check for String -
String(x) === x // true, if x is a string
// false in every other case
const test = x =>
console.assert
( String(x) === x
, `not a string: ${x}`
)
test("some string")
test(123) // assertion failed
test(0) // assertion failed
test(/some regex/) // assertion failed
test([ 5, 6 ]) // assertion failed
test({ a: 1 }) // assertion failed
test(x => x + 1) // assertion failed
The same technique works for Number too -
Number(x) === x // true, if x is a number
// false in every other case
const test = x =>
console.assert
( Number(x) === x
, `not a number: ${x}`
)
test("some string") // assertion failed
test(123)
test(0)
test(/some regex/) // assertion failed
test([ 5, 6 ]) // assertion failed
test({ a: 1 }) // assertion failed
test(x => x + 1) // assertion failed
And for RegExp -
RegExp(x) === x // true, if x is a regexp
// false in every other case
const test = x =>
console.assert
( RegExp(x) === x
, `not a regexp: ${x}`
)
test("some string") // assertion failed
test(123) // assertion failed
test(0) // assertion failed
test(/some regex/)
test([ 5, 6 ]) // assertion failed
test({ a: 1 }) // assertion failed
test(x => x + 1) // assertion failed
Same for Object -
Object(x) === x // true, if x is an object
// false in every other case
NB, regexps, arrays, and functions are considered objects too.
const test = x =>
console.assert
( Object(x) === x
, `not an object: ${x}`
)
test("some string") // assertion failed
test(123) // assertion failed
test(0) // assertion failed
test(/some regex/)
test([ 5, 6 ])
test({ a: 1 })
test(x => x + 1)
But, checking for Array is a bit different -
Array.isArray(x) === x // true, if x is an array
// false in every other case
const test = x =>
console.assert
( Array.isArray(x)
, `not an array: ${x}`
)
test("some string") // assertion failed
test(123) // assertion failed
test(0) // assertion failed
test(/some regex/) // assertion failed
test([ 5, 6 ])
test({ a: 1 }) // assertion failed
test(x => x + 1) // assertion failed
This technique does not work for Functions however -
Function(x) === x // always false
For #Faither -
const fmt = JSON.stringify
function test1() {
const a = "1"
const b = 1
console.log(`Number(${fmt(a)}) === ${fmt(b)}`, Number(a) === b) // true
}
function test2() {
const a = "1"
const b = 1
console.log(`Number.isInteger(${fmt(a)})`, Number.isInteger(a)) // false
console.log(`Number.isInteger(${fmt(b)})`, Number.isInteger(b)) // true
}
function test3() {
name = 1 // global name will always be a string
console.log(fmt(name)) // "1"
console.log(`String(${fmt(name)}) === ${fmt(name)}`, String(name) === name) // true
}
function test4() {
const name = 1 // local name
console.log(fmt(name)) // 1
console.log(`String(${fmt(name)}) === ${fmt(name)}`, String(name) === name) // false
}
test1(); test2(); test3(); test4()
Taken from lodash:
function isString(val) {
return typeof val === 'string' || ((!!val && typeof val === 'object') && Object.prototype.toString.call(val) === '[object String]');
}
console.log(isString('hello world!')); // true
console.log(isString(new String('hello world'))); // true
You can use this function to determine the type of anything:
var type = function(obj) {
return Object.prototype.toString.apply(obj).replace(/\[object (.+)\]/i, '$1').toLowerCase();
};
To check if a variable is a string:
type('my string') === 'string' //true
type(new String('my string')) === 'string' //true
type(`my string`) === 'string' //true
type(12345) === 'string' //false
type({}) === 'string' // false
https://codepen.io/patodiblasi/pen/NQXPwY?editors=0012
To check for other types:
type(null) //null
type(undefined) //undefined
type([]) //array
type({}) //object
type(function() {}) //function
type(123) //number
type(new Number(123)) //number
type(/some_regex/) //regexp
type(Symbol("foo")) //symbol
A simple and fast way to test can be using the constructor name attribute.
let x = "abc";
console.log(x.constructor.name === "String"); // true
let y = new String('abc');
console.log(y.constructor.name === "String"); // true
Performance
I also found that this works fine too, and its a lot shorter than the other examples.
if (myVar === myVar + '') {
//its string
} else {
//its something else
}
By concatenating on empty quotes it turns the value into a string. If myVar is already a string then the if statement is successful.
var a = new String('')
var b = ''
var c = []
function isString(x) {
return x !== null && x !== undefined && x.constructor === String
}
console.log(isString(a))
console.log(isString(b))
console.log(isString(c))
The following method will check if any variable is a string (including variables that do not exist).
const is_string = value => {
try {
return typeof value() === 'string';
} catch (error) {
return false;
}
};
let example = 'Hello, world!';
console.log(is_string(() => example)); // true
console.log(is_string(() => variable_doesnt_exist)); // false
This is good enough for me.
WARNING: This is not a perfect solution.
See the bottom of my post.
Object.prototype.isString = function() { return false; };
String.prototype.isString = function() { return true; };
var isString = function(a) {
return (a !== null) && (a !== undefined) && a.isString();
};
And you can use this like below.
//return false
isString(null);
isString(void 0);
isString(-123);
isString(0);
isString(true);
isString(false);
isString([]);
isString({});
isString(function() {});
isString(0/0);
//return true
isString("");
isString(new String("ABC"));
WARNING: This works incorrectly in the case:
//this is not a string
var obj = {
//but returns true lol
isString: function(){ return true; }
}
isString(obj) //should be false, but true
A simple solution would be:
var x = "hello"
if(x === x.toString()){
// it's a string
}else{
// it isn't
}
A Typechecker helper:
function isFromType(variable, type){
if (typeof type == 'string') res = (typeof variable == type.toLowerCase())
else res = (variable.constructor == type)
return res
}
usage:
isFromType('cs', 'string') //true
isFromType('cs', String) //true
isFromType(['cs'], Array) //true
isFromType(['cs'], 'object') //false
Also if you want it to be recursive(like Array that is an Object), you can use instanceof.
(['cs'] instanceof Object //true)
I'm going to go a different route to the rest here, which try to tell if a variable is a specific, or a member of a specific set, of types.
JS is built on ducktyping; if something quacks like a string, we can and should use it like a string.
Is 7 a string? Then why does /\d/.test(7) work?
Is {toString:()=>('hello there')} a string? Then why does ({toString:()=>('hello there')}) + '\ngeneral kenobi!' work?
These aren't questions about should the above work, the point is they do.
So I made a duckyString() function
Below I test many cases not catered for by other answers. For each the code:
sets a string-like variable
runs an identical string operation on it and a real string to compare outputs (proving they can be treated like strings)
converts the string-like to a real string to show you duckyString() to normalise inputs for code that expects real strings
text = 'hello there';
out(text.replace(/e/g, 'E') + ' ' + 'hello there'.replace(/e/g, 'E'));
out('Is string? ' + duckyString(text) + '\t"' + duckyString(text, true) + '"\n');
text = new String('oh my');
out(text.toUpperCase() + ' ' + 'oh my'.toUpperCase());
out('Is string? ' + duckyString(text) + '\t"' + duckyString(text, true) + '"\n');
text = 368;
out((text + ' is a big number') + ' ' + ('368' + ' is a big number'));
out('Is string? ' + duckyString(text) + '\t"' + duckyString(text, true) + '"\n');
text = ['\uD83D', '\uDE07'];
out(text[1].charCodeAt(0) + ' ' + '😇'[1].charCodeAt(0));
out('Is string? ' + duckyString(text) + '\t"' + duckyString(text, true) + '"\n');
function Text() { this.math = 7; }; Text.prototype = {toString:function() { return this.math + 3 + ''; }}
text = new Text();
out(String.prototype.match.call(text, '0') + ' ' + text.toString().match('0'));
out('Is string? ' + duckyString(text) + '\t"' + duckyString(text, true) + '"\n');
This is in the same vein as !!x as opposed to x===true and testing if something is array-like instead of necessitating an actual array.
jQuery objects; are they arrays? No. Are they good enough? Yeah, you can run them through Array.prototype functions just fine.
It's this flexibility that gives JS its power, and testing for strings specifically makes your code less interoperable.
The output of the above is:
hEllo thErE hEllo thErE
Is string? true "hello there"
OH MY OH MY
Is string? true "oh my"
368 is a big number 368 is a big number
Is string? true "368"
56839 56839
Is string? true "😇"
0 0
Is string? true "10"
So, it's all about why you want to know if something's a string.
If, like me, you arrived here from google and wanted to see if something was string-like, here's an answer.
It isn't even expensive unless you're working with really long or deeply nested char arrays.
This is because it is all if statements, no function calls like .toString().
Except if you're trying to see if a char array with objects that only have toString()'s or multi-byte characters, in which case there's no other way to check except to make the string, and count characters the bytes make up, respectively
function duckyString(string, normalise, unacceptable) {
var type = null;
if (!unacceptable)
unacceptable = {};
if (string && !unacceptable.chars && unacceptable.to == null)
unacceptable.to = string.toString == Array.prototype.toString;
if (string == null)
;
//tests if `string` just is a string
else if (
!unacceptable.is &&
(typeof string == 'string' || string instanceof String)
)
type = 'is';
//tests if `string + ''` or `/./.test(string)` is valid
else if (
!unacceptable.to &&
string.toString && typeof string.toString == 'function' && string.toString != Object.prototype.toString
)
type = 'to';
//tests if `[...string]` is valid
else if (
!unacceptable.chars &&
(string.length > 0 || string.length == 0)
) {
type = 'chars';
//for each char
for (var index = 0; type && index < string.length; ++index) {
var char = string[index];
//efficiently get its length
var length = ((duckyString(char, false, {to:true})) ?
char :
duckyString(char, true) || {}
).length;
if (length == 1)
continue;
//unicode surrogate-pair support
char = duckyString(char, true);
length = String.prototype[Symbol && Symbol.iterator];
if (!(length = length && length.call(char)) || length.next().done || !length.next().done)
type = null;
}
}
//return true or false if they dont want to auto-convert to real string
if (!(type && normalise))
//return truthy or falsy with <type>/null if they want why it's true
return (normalise == null) ? type != null : type;
//perform conversion
switch (type) {
case 'is':
return string;
case 'to':
return string.toString();
case 'chars':
return Array.from(string).join('');
}
}
Included are options to
ask which method deemed it string-y
exclude methods of string-detection (eg if you dont like .toString())
Here are more tests because I'm a completionist:
out('Edge-case testing')
function test(text, options) {
var result = duckyString(text, false, options);
text = duckyString(text, true, options);
out(result + ' ' + ((result) ? '"' + text + '"' : text));
}
test('');
test(null);
test(undefined);
test(0);
test({length:0});
test({'0':'!', length:'1'});
test({});
test(window);
test(false);
test(['hi']);
test(['\uD83D\uDE07']);
test([['1'], 2, new String(3)]);
test([['1'], 2, new String(3)], {chars:true});
All negative cases seem to be accounted for
This should run on browsers >= IE8
Char arrays with multiple bytes supported on browsers with string iterator support
Output:
Edge-case testing
is ""
null null
null null
to "0"
chars ""
chars "!"
null null
chars ""
to "false"
null null
chars "😇"
chars "123"
to "1,2,3"
Implementation from lodash library v4.0.0
// getTag.js
const toString = Object.prototype.toString;
/**
* Gets the `toStringTag` of `value`.
*
* #private
* #param {*} value The value to query.
* #returns {string} Returns the `toStringTag`.
*/
function getTag(value) {
if (value == null) {
return value === undefined
? "[object Undefined]"
: "[object Null]";
}
return toString.call(value);
}
// isString.js
import getTag from "./getTag.js";
/**
* Checks if `value` is classified as a `String` primitive or object.
*
* #since 0.1.0
* #category Lang
* #param {*} value The value to check.
* #returns {boolean} Returns `true` if `value` is a string, else `false`.
* #example
*
* isString('abc')
* // => true
*
* isString(1)
* // => false
*/
function isString(value) {
const type = typeof value;
return (
type === "string" || (type === "object" &&
value != null &&
!Array.isArray(value) &&
getTag(value) == "[object String]")
);
}
export default isString;
I have a technique that's stupid. But straightforward.
if(maybeAString.toUpperCase)
weHaveAString(maybeAString)
Yeah, it's far from perfect. But it is straightforward.
Just to expand on #DRAX's answer, I'd do this:
function isWhitespaceEmptyString(str)
{
//RETURN:
// = 'true' if 'str' is empty string, null, undefined, or consists of white-spaces only
return str ? !(/\S/.test(str)) : (str === "" || str === null || str === undefined);
}
It will account also for nulls and undefined types, and it will take care of non-string types, such as 0.
A code to have only string without any numbers
isNaN("A") = true;
parseInt("A") = NaN;
isNaN(NaN) = true;
Than we can use isNaN(parseInt()) to have only the string
let ignoreNumbers = "ad123a4m";
let ign = ignoreNumbers.split("").map((ele) => isNaN(parseInt(ele)) ? ele : "").join("");
console.log(ign);
also we can use isFinite() rather than typeof or isNAN()
check this:
var name="somename",trickyName="123", invalidName="123abc";
typeof name == typeof trickyName == typeof invalidName == "string" 🤷‍♀️
isNAN(name)==true
isNAN(trickyName)==false
isNAN(invalidName)==true 👀
where:
isFinite(name) == false
isFinite(trickyName)== true
isFinite(invalidName)== true
so we can do:
if(!isFinite(/*any string*/))
console.log("it is string type for sure")
notice that:
isFinite("asd123")==false
isNAN("asd123")==true
isString() checks whether passed argument is a string or not, using optional chaining and latest standards:
const isString = (value) => {
return value?.constructor === String;
}
I'm not sure if you mean knowing if it's a type string regardless of its contents, or whether it's contents is a number or string, regardless of its type.
So to know if its type is a string, that's already been answered.
But to know based on its contents if its a string or a number, I would use this:
function isNumber(item) {
return (parseInt(item) + '') === item;
}
And for some examples:
isNumber(123); //true
isNumber('123'); //true
isNumber('123a');//false
isNumber(''); //false

How to Sort Alphabets on right-side ascending and number on left side descending (string from user input) ? im new to learning js

I'm working on a function to accept input from a user. When user submit, it will sort ascending for alphabet and number on the right descending. For example if user input is :
a98 ##$64b,ce>75d l3kj gh and the output should be : abcdeghjkl9876543
So here is what I've done so far :
let sortNumButton = document.getElementById('sortNumButton');
let sortOutputContainer = document.getElementById('sortOutputContainer');
let inputField = document.getElementById('inputField');
sortNumButton.addEventListener('click', function(){
let string = document.createElement('p')
string.innerText = inputField.value
.split('')
.sort((a,b) => isFinite(a) - isFinite(b) || a > b || -(a > b))
.join('')
.replace(/[^a-zA-Z-0-9]/g, "")
sortOutputContainer.appendChild(string)
inputField.value = ""
})
One way of doing this is just to create a score system for the comparison.
For example, if we are a character, we can just use the charCodeAt to get it's charcode, just return this as positive value.
If we are then a number, we could just return it as a number say 1000 then subtract it's value.. ps just doing 1000-somechar, will also convert to number so no parseInt etc needed.
eg..
const txt = 'a98 ##$64b,ce>75d l3kj gh';
const isNumeric = /[0-9]/;
const isAlpha = /[a-z]/;
const isAlphaNumeric = /[a-z0-9]/;
function score(v) {
if (isAlpha.test(v)) return v.charCodeAt(0);
else return 1000-v;
}
console.log(
txt.split('')
.filter(t => isAlphaNumeric.test(t))
.sort((a,b) => score(a) - score(b))
.join('')
)
Another option, if we have limited chars we could just create a simple lookup table and then sort on this..
eg..
const txt = 'a98 ##$64b,ce>75d l3kj gh';
const isAlphaNumeric = /[a-z0-9]/;
const lookup = 'abcdefghijklmnopqrstuvwxyz9876543210';
function score(v) {
return lookup.indexOf(v);
}
console.log(
txt.split('')
.filter(t => isAlphaNumeric.test(t))
.sort((a,b) => score(a) - score(b))
.join('')
)
You can use regex to pull out the numbers and letters separately and then you can perform the sorting.
Working Demo :
// Input string
const str = 'a98 ##$64b,ce>75d l3kj gh';
// letters string with sorting in ascending order.
const lettersString = str.match(/[a-z]/g).sort().join('');
// Numbers string with sorting in descending order.
const numbersString = str.match(/[0-9]/g).sort().reverse().join('');
// Result
console.log(`${lettersString}${numbersString}`);
You can use a regular expression to pull out the numbers and letters with match. This returns an array that you can then join into a string for output.
This example is a little more functional that is probably needed but it shows a different approach.
const str = 'a98 ##$64b,ce>75d l3kj gh';
// `comparator` gets called with a `type`
// argument and returns a function that gets used
// as the actual sort comparator.
// We use `localCompare` to return a sorted array
// based on the type.
function comparator(type) {
return function (a, b) {
return (
type === 'str'
? a.localeCompare(b) > b.localeCompare(a)
: a.localeCompare(b) < b.localeCompare(a)
);
}
}
// `sort` accepts a type argument and
// returns a string based on calling
// the sort method on the array which calls the
// comparator function with the type, and once that
// process is complete returns a string
function sort(data, type) {
return data.sort(comparator(type)).join('');
}
// `match` returns an array, so we pass that to
// the sort function with a type parameter
const letters = sort(str.match(/[a-z]/g), 'str');
const numbers = sort(str.match(/[0-9]/g), 'num');
console.log(`${letters}${numbers}`);
Additional information
Template strings
localeCompare
Conditional (ternary) operator

Convert camel case to sentence case in javascript

I found myself needing to do camel case to sentence case string conversion with sane acronym support, a google search for ideas led me to the following SO post:
Convert camelCaseText to Sentence Case Text
Which is actually asking about title case not sentence case so I came up with the following solution which maybe others will find helpful or can offer improvements to, it is using ES6 which is acceptable for me and can easily be polyfilled if there's some horrible IE requirement.
The below uses capitalised notation for acronyms; I don't agree with Microsoft's recommendation of capitalising when more than two characters so this expects the whole acronym to be capitalised even if it's at the start of the string (which technically means it's not camel case but it gives sane controllable output), multiple consecutive acronyms can be escaped with _ (e.g. parseDBM_MXL -> Parse DBM XML).
function camelToSentenceCase(str) {
return str.split(/([A-Z]|\d)/).map((v, i, arr) => {
// If first block then capitalise 1st letter regardless
if (!i) return v.charAt(0).toUpperCase() + v.slice(1);
// Skip empty blocks
if (!v) return v;
// Underscore substitution
if (v === '_') return " ";
// We have a capital or number
if (v.length === 1 && v === v.toUpperCase()) {
const previousCapital = !arr[i-1] || arr[i-1] === '_';
const nextWord = i+1 < arr.length && arr[i+1] && arr[i+1] !== '_';
const nextTwoCapitalsOrEndOfString = i+3 > arr.length || !arr[i+1] && !arr[i+3];
// Insert space
if (!previousCapital || nextWord) v = " " + v;
// Start of word or single letter word
if (nextWord || (!previousCapital && !nextTwoCapitalsOrEndOfString)) v = v.toLowerCase();
}
return v;
}).join("");
}
// ----------------------------------------------------- //
var testSet = [
'camelCase',
'camelTOPCase',
'aP2PConnection',
'JSONIsGreat',
'thisIsALoadOfJSON',
'parseDBM_XML',
'superSimpleExample',
'aGoodIPAddress'
];
testSet.forEach(function(item) {
console.log(item, '->', camelToSentenceCase(item));
});

Evaluate dynamically user-added If-Statements

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

How to code a calculator in javascript without eval

So, I've searched high and low, and I can't find an answer to this. I've attempted it about three times and gotten a basic one cranked out by basically storing the input in an array as a string, parsing the numbers, then switching on the operator, in order to evaluate the integers, but I'm having a really hard time figuring out the chaining logic. Does anyone have any suggestions? Of maybe even just the psuedocode? I really don't want to use eval. Thanks a lot
For a simple calculator with only 5 operators (^, *, /, +, -) and no parentheses, you can do something like this. First, it is convenient to turn the string into an array of numbers and operators. Then, we go through the array looking for each operator in order of precedence, and applying the operator to the numbers preceding and following the it.
function tokenize(s) {
// --- Parse a calculation string into an array of numbers and operators
const r = [];
let token = '';
for (const character of s) {
if ('^*/+-'.includes(character)) {
if (token === '' && character === '-') {
token = '-';
} else {
r.push(parseFloat(token), character);
token = '';
}
} else {
token += character;
}
}
if (token !== '') {
r.push(parseFloat(token));
}
return r;
}
function calculate(tokens) {
// --- Perform a calculation expressed as an array of operators and numbers
const operatorPrecedence = [{'^': (a, b) => Math.pow(a, b)},
{'*': (a, b) => a * b, '/': (a, b) => a / b},
{'+': (a, b) => a + b, '-': (a, b) => a - b}];
let operator;
for (const operators of operatorPrecedence) {
const newTokens = [];
for (const token of tokens) {
if (token in operators) {
operator = operators[token];
} else if (operator) {
newTokens[newTokens.length - 1] =
operator(newTokens[newTokens.length - 1], token);
operator = null;
} else {
newTokens.push(token);
}
}
tokens = newTokens;
}
if (tokens.length > 1) {
console.log('Error: unable to resolve calculation');
return tokens;
} else {
return tokens[0];
}
}
const userInput = document.getElementById('userInput');
userInput.focus();
userInput.addEventListener('input', function() {
document.getElementById('result').innerHTML = "The answer is " + calculate(tokenize(userInput.value));
});
<input type="text" id="userInput" />
<div id="result"></div>
(Alternative version here). To allow parentheses, you could tell the calculate function to check for parentheses before it starts looking for any of the other operators, then recursively call itself on the expression within each set of parentheses. The parsing function can also be improved e.g. removing any white space and dealing with errors.

Categories

Resources