I failed Javascript tech interview but I dont know why - javascript

I was only allowed to use google document for writing.
Could you please tell me what I did wrong? The recruiter wont get back to me when I asked her why I failed
Task 1:
Implement function verify(text) which verifies whether parentheses within text are
correctly nested. You need to consider three kinds: (), [], <> and only these kinds.
My Answer:
const verify = (text) => {
   const parenthesesStack = []; 
   
  for( let i = 0; i<text.length; i++ ) {
const closingParentheses = parenthesesStack[parenthesesStack.length - 1]
if(text[i] === “(”  || text[i] === “[” || text[i] === “<”  ) {
parenthesisStack.push(text[i]);
} else if ((closingParentheses === “(” && text[i] === “)”) || (closingParentheses === “[” && text[i] === “]”) || (closingParentheses === “<” && text[i] === “>”) ) {
   parenthesisStack.pop();
} 
  };
return parenthesesStack.length ? 0 : 1;  
}
Task 2:
Simplify the implementation below as much as you can.
Even better if you can also improve performance as part of the simplification!
FYI: This code is over 35 lines and over 300 tokens, but it can be written in
5 lines and in less than 60 tokens.
Function on the next page.
// ‘a’ and ‘b’ are single character strings
function func2(s, a, b) {
var match_empty=/^$/ ;
if (s.match(match_empty)) {
return -1;
}
var i=s.length-1;
var aIndex=-1;
var bIndex=-1;
while ((aIndex==-1) && (bIndex==-1) && (i>=0)) {
if (s.substring(i, i+1) == a)
aIndex=i;
if (s.substring(i, i+1) == b)
bIndex=i;
i--;
}
if (aIndex != -1) {
if (bIndex == -1)
return aIndex;
return Math.max(aIndex, bIndex);
} else {
if (bIndex != -1)
return bIndex;
return -1;
}
};
My Answer:
const funcSimplified = (s,a,b) => {
if(s.match(/^$/)) {
return -1;
} else {
return Math.max(s.indexOf(a),s.indexOf(b))
}
}

For starters, I'd be clear about exactly what the recruiter asked. Bold and bullet point it and be explicit.
Secondly, I would have failed you from your first 'for' statement.
See my notes:
// Bonus - add jsdoc description, example, expected variables for added intention.
const verify = (text) => {
// verify what? be specific.
const parenthesesStack = [];
for( let i = 0; i<text.length; i++ ) {
// this could have been a map method or reduce method depending on what you were getting out of it. Rarely is a for loop like this used now unless you need to break out of it for performance reasons.
const closingParentheses = parenthesesStack[parenthesesStack.length - 1]
// parenthesesStack.length - 1 === -1.
// parenthesesStack[-1] = undefined
if(text[i] === “(” || text[i] === “[” || text[i] === “<” ) {
parenthesisStack.push(text[i]);
// “ will break. Use "
// would have been more performant and maintainable to create a variable like this:
// const textOutput = text[i]
// if (textOutput === "(" || textOutput === "[" || textOutput === "<") {
parenthesisStack.push(textOutput)
} else if ((closingParentheses === “(” && text[i] === “)”) || (closingParentheses === “[” && text[i] === “]”) || (closingParentheses === “<” && text[i] === “>”) ) {
parenthesisStack.pop();
// There is nothing in parenthesisStack to pop
}
};
return parenthesesStack.length ? 0 : 1;
// Will always be 0.
}
Not exactly what the intention of your function or logic is doing, but It would fail based on what I can see.
Test it in a browser or use typescript playground. You can write javascript in there too.

Hard to tell without the recruiter feedback. But i can tell that you missundertood the second function.
func2("mystrs", 's', 'm') // returns 5
funcSimplified("mystrs", 's', 'm') // returns 3
You are returning Math.max(s.indexOf(a),s.indexOf(b)) instead of Math.max(s.lastIndexOf(a), s.lastIndexOf(b))
The original code start at i=len(str) - 1 and decrease up to 0. They are reading the string backward.
A possible implementation could have been
const lastOccurenceOf = (s,a,b) => {
// Check for falsyness (undefined, null, or empty string)
if (!s) return -1;
// ensure -1 value if search term is empty
const lastIndexOfA = a ? s.lastIndexOf(a) : -1
const lastIndexOfB = b ? s.lastIndexOf(b) : -1
return Math.max(lastIndexOfA, lastIndexOfB)
}
or a more concise example, which is arguably worse (because less readable)
const lastOccurenceOf = (s,a,b) => {
const safeStr = s || '';
return Math.max(safeStr.lastIndexOf(a || undefined), safeStr.lastIndexOf(b || undefined))
}
I'm using a || undefined to force a to be undefined if it is an empty string, because:
"canal".lastIndexOf("") = 5
"canal".lastIndexOf(undefined) = -1
original function would have returned -1 if case of an empty a or b
Also, have you ask if you were allowed to use ES6+ syntax ? You've been given a vanilla JS and you implemented the equivalent using ES6+. Some recruiters have vicious POV.

Related

Only evaluate parts of a condition when the corresponding flag is set

For example, I have flag1, flag2, flag3,..., flagN as boolean values for flags.
I need to write an if statement like this: If flagK is false, then turn off the "K" part of the condition:
if (condition0 && (flag1 && condition1) && (flag2 && condition2) ... && (flagN && conditionN))
{
// do something
}
// For example, if flag 2 is false then the condition should only be:
if (condition0 && (flag1 && condition1) && ... && (flagN && conditionN))
{
//do something}
}
Particularly, given an example like this (for demo only not my real problem):
const divby2 = false; //if this is false, then ignore the **i%2 === 0** below
const divby3 = true;
const divby4 = true;
const divby5 = true;
//......const divbyN....
const array = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,42,45,241,526]
array.forEach((i) => {
if(i >= 0 && (divby2 && i%2 === 0) && (divby3 && i%3 === 0)) {
console.log(i) // output here should be 3,6,9,12 instead of nothing
}
}
)
Example Result:
The term you are looking for is "short-circuit" similar to the way in real electronic circuit if some part is not working you just short circuit it and by pass flow to rest
if(i >= 0 && ( divby2 && i%2 === 0 || !divby2) && (divby3 && i%3 === 0))
In this case if you are wanting that filtered number should be divisible by 2 that time you set divby2 = true
And when you just want to ignore and don't care about the divisibility by 2 you set divby2 = false
In pseudo
(is-checking-for-divby-2? AND is-current-number-divby-2?) OR (NOT is-checking-for-divby-2?)
As soon as you are not checking for divby 2 you make this logic fragment true so it won't affect evaulation of the follolwing logic fragments
And..Why should you bother making this fragments TRUE?
Because you ANDing them
Similarly you can go for divby3, divby4 ...
I would have an object with your conditions, and then filter out the functions you don't want to run, and then just reduce the object to a single function which runs all of the enabled functions:
const conditions = {
divby2: i => i % 2 === 0,
divby3: i => i % 3 === 0,
};
const enabled = {
divby2: false, //if this is false, then need to ignore the **i%2 === 0** below
divby3: true
}
const enabledConditions = (i) => {
return Object.entries(conditions).filter(
([key, value]) => enabled[key]
).reduce((carry, [_, condition]) => {
return condition(i) && carry
}, i !== false);
}
//......const divbyN....
const array = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,42,45,241,526]
array.forEach((i) => {
if(i >= 0 && enabledConditions(i)){
console.log(i) //output here should be 3,6,9,12 instead of nothing
}
}
)

How to match PHP's explode(';',$s,3) to s.split(';',3) in JavaScript?

If you run an explode in PHP with the resulting array length limited, it will append the remainder of the string to the last element. This is how exploding a string should behave, since nowhere in the split am I saying that I want to discard my data, just split it. This is how it works in PHP:
# Name;Date;Quote
$s = 'Mark Twain;1879-11-14;"We haven\'t all had the good fortune to be ladies; we haven\'t all been generals, or poets, or statesmen; but when the toast works down to the babies, we stand on common ground."';
$a = explode(';',$s,3);
var_dump($a);
array(3) {
[0]=>
string(10) "Mark Twain"
[1]=>
string(10) "1879-11-14"
[2]=>
string(177) ""We haven't all had the good fortune to be ladies; we haven't all been generals, or poets, or statesmen; but when the toast works down to the babies, we stand on common ground.""
}
However, if you run the same code in JavaScript:
> var s = 'Mark Twain;1879-11-14;"We haven\'t all had the good fortune to be ladies; we haven\'t all been generals, or poets, or statesmen; but when the toast works down to the babies, we stand on common ground."'
undefined
> var a = s.split(';',3);
undefined
> a
[ 'Mark Twain',
'1879-11-14',
'"We haven\'t all had the good fortune to be ladies' ]
This makes absolutely no sense, because the whole point of splitting a string is to treat the final portion of the string as a literal, instead of delimited. JavaScript's split with a limit is the exact same as:
# In PHP
$a = array_slice(explode(';',$s), 0, 3);
# Or in JavaScript
var a = s.split(';').slice(0, 3);
If the user in JavaScript only wanted to make use of the first two elements in this array, whether the array is split or not doesn't matter. The first two elements will always have the same value no matter what. The only element that changes, is the last element of the split array.
If the native split with limit method in JavaScript can be replicated using a slice, then what value does it provide?
But I digress, what is the most efficient way to replicate the explode functionality in PHP? Removing each element as a substring until the last element is reached, splitting the entire string and then concatenating the remaining elements, getting the location of the n - 1 delimiter and getting a substring of that, or any other solution I haven't thought of?
According documentation the split function accepts two arguments:
string.split(separator, limit)
However this still gives not the result you want because:
The second parameter is an integer that specifies the number of
splits, items after the split limit will not be included in the array
However, I noticed that the ';' in the text has a space behind it. So you could use a regex.
var s = 'Mark Twain;1879-11-14;"We haven\'t all had the good fortune to be ladies; we haven\'t all been generals, or poets, or statesmen; but when the toast works down to the babies, we stand on common ground."'
var a = s.split(/;(?! )/,3)
console.log(a);
The Regex (/;(?! ) splits all ';' except if there is a space behind it.
Hope this helps!
Loctus.io got you covered, they ported php's explode, and a great number of other php functions to javascript
usage:
$s = 'Mark Twain;1879-11-14;"We haven\'t all had the good fortune to be ladies; we haven\'t all been generals, or poets, or statesmen; but when the toast works down to the babies, we stand on common ground."';
"Mark Twain;1879-11-14;"We haven't all had the good fortune to be ladies; we haven't all been generals, or poets, or statesmen; but when the toast works down to the babies, we stand on common ground.""
$a = explode(';',$s,3);
content of $a as reported by Chrome's javascript console:
0: "Mark Twain"
1: "1879-11-14"
2: ""We haven't all had the good fortune to be ladies; we haven't all been generals, or poets, or statesmen; but when the toast works down to the babies, we stand on common ground.""
length: 3
, source: http://locutus.io/php/strings/explode/
function explode (delimiter, string, limit) {
// discuss at: http://locutus.io/php/explode/
// original by: Kevin van Zonneveld (http://kvz.io)
// example 1: explode(' ', 'Kevin van Zonneveld')
// returns 1: [ 'Kevin', 'van', 'Zonneveld' ]
if (arguments.length < 2 ||
typeof delimiter === 'undefined' ||
typeof string === 'undefined') {
return null
}
if (delimiter === '' ||
delimiter === false ||
delimiter === null) {
return false
}
if (typeof delimiter === 'function' ||
typeof delimiter === 'object' ||
typeof string === 'function' ||
typeof string === 'object') {
return {
0: ''
}
}
if (delimiter === true) {
delimiter = '1'
}
// Here we go...
delimiter += ''
string += ''
var s = string.split(delimiter)
if (typeof limit === 'undefined') return s
// Support for limit
if (limit === 0) limit = 1
// Positive limit
if (limit > 0) {
if (limit >= s.length) {
return s
}
return s
.slice(0, limit - 1)
.concat([s.slice(limit - 1)
.join(delimiter)
])
}
// Negative limit
if (-limit >= s.length) {
return []
}
s.splice(s.length + limit)
return s
}
edit: if you for some reason need/want a smaller implementation, here's 1 i made in response to the comments:
function explode(delimiter, string, limit) {
var spl = string.split(delimiter);
if (spl.length <= limit) {
return spl;
}
var ret = [],i=0;
for (; i < limit; ++i) {
ret.push(spl[i]);
}
for (; i < spl.length; ++i) {
ret[limit - 1] += delimiter+spl[i];
}
return ret;
}
Alright, I created 4 alternative versions of the PHP split string algorithm, along with the two provided by #hanshenrik, and did a basic benchmark on them:
function explode1(delimiter, str, limit) {
if (limit == null) {
return s.split(delimiter);
}
var a = [];
var lastIndex = -1;
var index = 0;
for (var i = 0; i < limit; i++) {
index = str.indexOf(delimiter, lastIndex + 1);
if (i == limit - 1) {
a.push(str.substring(lastIndex + 1));
} else {
a.push(str.substring(lastIndex + 1, index));
}
lastIndex = index;
}
return a;
}
function explode2(delimiter, str, limit) {
if (limit == null) {
return s.split(delimiter);
}
var a = str.split(delimiter);
var ret = a.slice(0, limit - 1);
ret.push(a.slice(limit - 1).join(delimiter));
return ret;
}
function explode3(delimiter, str, limit) {
if (limit == null) {
return s.split(delimiter);
}
var a = s.split(delimiter, limit - 1);
var index = 0;
for (var i = 0; i < limit - 1; i++) {
index = s.indexOf(delimiter, index + 1);
}
a.push(str.substring(index + 1));
return a;
}
function explode4(delimiter, str, limit) {
if (limit == null) {
return s.split(delimiter);
}
var a = str.split(delimiter, limit - 1);
a.push(str.substring(a.join(delimiter).length + 1));
return a;
}
function explode5(delimiter, string, limit) {
// discuss at: http://locutus.io/php/explode/
// original by: Kevin van Zonneveld (http://kvz.io)
// example 1: explode(' ', 'Kevin van Zonneveld')
// returns 1: [ 'Kevin', 'van', 'Zonneveld' ]
if (arguments.length < 2 ||
typeof delimiter === 'undefined' ||
typeof string === 'undefined') {
return null
}
if (delimiter === '' ||
delimiter === false ||
delimiter === null) {
return false
}
if (typeof delimiter === 'function' ||
typeof delimiter === 'object' ||
typeof string === 'function' ||
typeof string === 'object') {
return {
0: ''
}
}
if (delimiter === true) {
delimiter = '1'
}
// Here we go...
delimiter += ''
string += ''
var s = string.split(delimiter)
if (typeof limit === 'undefined') return s
// Support for limit
if (limit === 0) limit = 1
// Positive limit
if (limit > 0) {
if (limit >= s.length) {
return s
}
return s
.slice(0, limit - 1)
.concat([s.slice(limit - 1)
.join(delimiter)
])
}
// Negative limit
if (-limit >= s.length) {
return []
}
s.splice(s.length + limit)
return s
}
function explode6(delimiter, string, limit) {
var spl = string.split(delimiter);
if (spl.length <= limit) {
return spl;
}
var ret = [],i=0;
for (; i < limit; ++i) {
ret.push(spl[i]);
}
for (; i < spl.length; ++i) {
ret[limit - 1] += delimiter+spl[i];
}
return ret;
}
var s = 'Mark Twain,1879-11-14,"We haven\'t all had the good fortune to be ladies; we haven\'t all been generals, or poets, or statesmen; but when the toast works down to the babies, we stand on common ground."'
console.log(s);
console.time('explode1');
var a1 = explode1(',', s, 3);
//console.log(a1);
console.timeEnd('explode1');
console.time('explode2');
var a2 = explode2(',', s, 3);
//console.log(a2);
console.timeEnd('explode2');
console.time('explode3');
var a3 = explode3(',', s, 3);
//console.log(a3);
console.timeEnd('explode3');
console.time('explode4');
var a4 = explode4(',', s, 3);
//console.log(a4);
console.timeEnd('explode4');
console.time('explode5');
var a5 = explode5(',', s, 3);
//console.log(a5);
console.timeEnd('explode5');
console.time('explode6');
var a6 = explode6(',', s, 3);
//console.log(a6);
console.timeEnd('explode6');
The two best-performing algorithms was explode4 principally, with explode3 a close second in multiple iterations of the benchmark:
$ node explode1.js && node explode2.js && node explode3.js && node
explode4.js && node explode5.js && node explode6.js
explode1: 0.200ms
explode2: 0.194ms
explode3: 0.147ms
explode4: 0.183ms
explode5: 0.341ms
explode6: 0.162ms
You can run your own benchmarks, but with my tests I can confirm that splitting an array by n - 1 and then getting an index from joining the resulting array is the fastest algorithm matching explode in PHP.
EDIT: It turns out that the garbage collector biased how each successive function was measured, so I split them off into their own individual files and re-ran the benchmarking a few times. It seems explode3 is the best performing, not explode4, but I won't make a decision that I'm not completely sure of.

Test multiple values in javascript which may not exist

What is the correct way to test this condition when 'members' and 'departments' may not exist? Is there a way to check for their existence while testing the value without causing an error?
if (state.staff.members.length < 5 || state.staff.departments.length < 5) {}
The cleanest (and in my opinion, best) way to do this is with a try...catch block. That way, you can safely and gracefully fail if the properties you are looking for don't exist, like so:
var state;
function myFunction() {
try {
if (state.staff.members.length < 5 || state.staff.departments.length < 5) {
return "PASS";
}
} catch (e) {
console.log(e.toString());
return "PASS"
}
return "FAIL";
}
console.log(myFunction());
Lots and lots of falsy checks
if (
(
state &&
state.staff &&
state.staff.members &&
state.staff.members.length < 5
) ||
(
state &&
state.staff &&
state.staff.departments &&
state.staff.departments.length < 5
)
) {
console.log("foo");
}
I would suggest taking the lodash approach with _.get() instead to encapsulate the checks.
if (
_.get(state, "staff.members.length") < 5 ||
_.get(state, "staff.departments.length") < 5
){
console.log("foo");
}
Try this :)
if (
(state.staff.members && state.staff.members.length < 5) ||
(state.staff.departments && state.staff.departments.length < 5)
) {
// do something
}
if ((state.staff.members && state.staff.members.length < 5) || (state.staff.departments && state.staff.departments.length < 5)) {}
This assumes that you only want the condition to pass when at least one of the properties is set.
Edit: In response to your comment I would then write it the following way
if ((state.staff.members && state.staff.members.length < 5)
|| (state.staff.departments && state.staff.departments.length < 5)
|| state.staff.hasOwnProperty('members') === false
|| state.staff.hasOwnProperty('departments') === false
) {}
For those scenarios, I like to use a little helper like this:
/**
* Accesses nested properties and returns a default value if it encounters null or undefined along the way.
* #param fallback A fallback value if any property or its value is null or undefined.
* #param path The path to access a nested property
*/
export const propOrDefault = <T, R>(fallback: R) => (...path: string[]) => (obj: T | null | undefined): R =>
path.reduce((partialObj, propertyName) => {
if (partialObj && propertyName && partialObj.hasOwnProperty(propertyName)) {
return partialObj[propertyName] || fallback;
}
return fallback;
}, obj);
This is TypeScript, but it should get the point across. And the additional type info might help with understanding.
Example usage:
const isValid = propOrDefault(0)('staff', 'members', 'length')(state) < 5;
As you can see, this helper uses currying so you could pre-configure it for later use like so:
const propOrTen = propOrDefault(0);
const countStaffMembers = propOrTen('staff', 'members', 'length');
const result = countStaffMembers(state);
There are loads of libraries out there with similar tools like lodash and ramda.

handle empty strings at array.sort(?) callback in chrome is very slow

in js i have to sort a lot of array elements(100k-1kk).
İn production its possible to have many blank ('') strings.
in my sort function i handle empty values - so that this values always come last .its ok.. until i have many null or undefined or blank('') values in data
if data have many nulls for example or blank strings performance is veeery slow.
And the main thing is that this fragment very slow at Chrome (at least last version for now 49.0.2623.110 m)
firefox(45.0.1) works very well (even with standart case without empty data my test x10 faster ??)
just test.with chrome and firefox
P.S. i know jsperf is more preferable for that.anyway
https://jsfiddle.net/3h0gtLu2/18/
data = []
var i = 0;
while (i++ <1000){
data.push('' + i)
}
while (i++ < 20000){
data.push(''+i )
}
var start = window.performance.now()
data.sort(function(a,b){
if (a == null || a == undefined)
return 1;
else if ( b == null || b == undefined)
return -1;
return (parseInt(a) - parseInt(b));
})
$('#time0').html($('#time0').html() + (window.performance.now() - start))
data = []
var i = 0;
while (i++ <1000){
data.push('' + i)
}
while (i++ < 20000){
data.push(null )
}
var start = window.performance.now()
data.sort(function(a,b){
if (a == '' || a === null || a == undefined)
return 1;
else if ( a == '' || b === null || b == undefined)
return -1;
return (parseInt(a) - parseInt(b));
})
$('#time1').html($('#time1').html() + (window.performance.now() - start))
data = []
var i = 0;
while (i++ <1000){
data.push('' + i)
}
while (i++ < 20000){
data.push('' )
}
var start = window.performance.now()
data.sort(function(a,b){
if ( a == null || a == undefined)
return 1;
else if ( b == null || b == undefined)
return -1;
return (parseInt(a) - parseInt(b));
})
$('#time2').html($('#time2').html() +( window.performance.now() - start))
data = []
var i = 0;
while (i++ <1000){
data.push('' + i)
}
while (i++ < 20000){
data.push('' )
}
var start = window.performance.now()
data.sort(function(a,b){
if (a == '' || a == null || a == undefined)
return 1;
else if (b == '' || b == null || b == undefined)
return -1;
return (parseInt(a) - parseInt(b));
})
$('#time3').html($('#time3').html() +( window.performance.now() - start))
In order to ensure that your comparator will always return a logical answer for every pair of values, you'll have to add the case for when both values are empty:
data.sort(function(a,b){
var anull = (a == '' || a == null), bnull = (b == '' || b == null);
if (anull && bnull)
return 0;
if (anull)
return 1;
if (bnull)
return -1;
return (parseInt(a) - parseInt(b));
})
Note that you don't need an explicit compare to both null and undefined; comparing == null is exactly the same as comparing === null and === undefined.
My making sure you tell the sort algorithm that when both values are empty they can be left alone (by returning 0), you avoid it thrashing back and forth in some weird cases.
Another thing that might speed things up would be to make a single pass through the array to convert all the empty entries to some single value (maybe null; doesn't matter) and all the non-empty entries to actual numbers. That way your sort won't be paying the price of converting the strings to numbers over and over again (that is, all the calls to parseInt()). If you want the array to be strings, you can always convert it back in a subsequent single pass.

Alternative to nested ternary operator in JS [closed]

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 8 months ago.
Improve this question
I personally love ternary operators, and in my humble opinion, they make complicated expressions very easy to digest. Take this one:
const word = (distance === 0) ? 'a'
: (distance === 1 && diff > 3) ? 'b'
: (distance === 2 && diff > 5 && key.length > 5) ? 'c'
: 'd';
However in our project's ESLINT rules nested ternary operators are forbidden, so I have to get rid of the above.
I'm trying to find out alternatives to this approach. I really don't want to turn it into a huge if / else statement, but don't know if there's any other options.
Your alternatives here are basically:
That if/else you don't want to do
A switch combined with if/else
I tried to come up with a reasonable lookup map option, but it got unreasonable fairly quickly.
I'd go for #1, it's not that big:
if (res.distance == 0) {
word = 'a';
} else if (res.distance == 1 && res.difference > 3) {
word = 'b';
} else if (res.distance == 2 && res.difference > 5 && String(res.key).length > 5) {
word = 'c';
} else {
word = 'd';
}
If all the braces and vertical size bother you, without them it's almost as concise as the conditional operator version:
if (res.distance == 0) word = 'a';
else if (res.distance == 1 && res.difference > 3) word = 'b';
else if (res.distance == 2 && res.difference > 5 && String(res.key).length > 5) word = 'c';
else word = 'd';
(I'm not advocating that, I never advocate leaving off braces or putting the statement following an if on the same line, but others have different style perspectives.)
#2 is, to my mind, more clunky but that's probably more a style comment than anything else:
word = 'd';
switch (res.distance) {
case 0:
word = 'a';
break;
case 1:
if (res.difference > 3) {
word = 'b';
}
break;
case 2:
if (res.difference > 5 && String(res.key).length > 5) {
word = 'c';
}
break;
}
And finally, and I am not advocating this, you can take advantage of the fact that JavaScript's switch is unusual in the B-syntax language family: The case statements can be expressions, and are matched against the switch value in source code order:
switch (true) {
case res.distance == 0:
word = 'a';
break;
case res.distance == 1 && res.difference > 3:
word = 'b';
break;
case res.distance == 2 && res.difference > 5 && String(res.key).length > 5:
word = 'c';
break;
default:
word = 'd';
break;
}
How ugly is that? :-)
To my taste, a carefully structured nested ternary beats all those messy ifs and switches:
const isFoo = res.distance === 0;
const isBar = res.distance === 1 && res.difference > 3;
const isBaz = res.distance === 2 && res.difference > 5 && String(res.key).length > 5;
const word =
isFoo ? 'a' :
isBar ? 'b' :
isBaz ? 'c' :
'd' ;
You could write an immediately invoked function expression to make it a little more readable:
const word = (() => {
if (res.distance === 0) return 'a';
if (res.distance === 1 && res.difference > 3) return 'b';
if (res.distance === 2 && res.difference > 5 && String(res.key).length > 5) return 'c';
return 'd';
})();
Link to repl
We can simplify it using basic operators like && and ||
let obj = {}
function checkWord (res) {
return (res.distance === 0) && 'a'
|| (res.distance === 1 && res.difference > 3) && 'b'
|| (res.distance === 2 && res.difference > 5 && String(res.key).length > 5) && 'c'
|| 'd';
}
// case 1 pass
obj.distance = 0
console.log(checkWord(obj))
// case 2 pass
obj.distance = 1
obj.difference = 4
console.log(checkWord(obj))
// case 3 pass
obj.distance = 2
obj.difference = 6
obj.key = [1,2,3,4,5,6]
console.log(checkWord(obj))
// case 4 fail all cases
obj.distance = -1
console.log(checkWord(obj))
If you are looking to use const with a nested ternary expression, you can replace the ternary with a function expression.
const res = { distance: 1, difference: 5 };
const branch = (condition, ifTrue, ifFalse) => condition?ifTrue:ifFalse;
const word = branch(
res.distance === 0, // if
'a', // then
branch( // else
res.distance === 1 && res.difference > 3, // if
'b', // then
branch( // else
res.distance === 2 && res.difference > 5, // if
'c', // then
'd' // else
)
)
);
console.log(word);
or using named parameters via destructuring...
const branch2 = function(branch) {
return branch.if ? branch.then : branch.else;
}
const fizzbuzz = function(num) {
return branch2({
if: num % 3 === 0 && num % 5 === 0,
then: 'fizzbuzz',
else: branch2({
if: num % 3 === 0,
then: 'fizz',
else: branch2({
if: num % 5 === 0,
then: 'buzz',
else: num
})
})
});
}
console.log(
[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16].map(
cv => fizzbuzz(cv)
)
);
edit
It may be clearer to model it after the python if expression like this:
const res = { distance: 1, difference: 5 };
const maybe = def => ({
if: expr => {
if (expr) {
return { else: () => def };
} else {
return { else: els => els };
}
}
});
const word = maybe('a').if(res.distance === 0).else(
maybe('b').if(res.distance === 1 && res.difference > 3).else(
maybe('c').if(res.distance === 2 && res.difference > 5).else('d')
)
);
console.log(word);
edit
Another edit to remove the nested if/else branches:
const res = { distance: 1, difference: 5 };
const makeResolvedValue = def => {
const elseProp = () => def;
return function value() {
return {
if: () => ({ else: elseProp, value })
};
}
};
const value = def => ({
if: expr => {
if (expr) {
return { else: () => def, value: makeResolvedValue(def) };
} else {
return { else: els => els, value };
}
}
});
// with branching if needed
const word = value('a').if(res.distance === 0)
.else(value('b').if(res.distance === 1 && res.difference > 3)
.else(value('c').if(res.distance === 2 && res.difference > 5)
.else('d')
)
);
console.log(word)
// implicit else option for clarity
const word2 = value('a').if(res.distance === 0)
.value('b').if(res.distance === 1 && res.difference > 3)
.value('c').if(res.distance === 2 && res.difference > 5)
.else('d');
console.log(word2);
If all your truthy conditions evaluate to truthy values (so the value between the question mark and the semicolon evaluates to true if coerced to boolean...) you could make your ternary expressions return false as the falsy expression. Then you could chain them with the bitwise or (||) operator to test the next condition, until the last one where you return the default value.
In the example below, the "condsXXX" array represent the result of evaluating the conditions. "conds3rd" simulates the 3rd condition is true and "condsNone" simulates no condition is true. In a real life code, you'd have the conditions "inlined" in the assignment expression:
var conds3rd = [false, false, true];
var condsNone = [false, false, false];
var val3rd = (conds3rd[0] ? 1 : false) ||
(conds3rd[1] ? 2 : false) ||
(conds3rd[2] ? 3 : 4);
var valNone = (condsNone[0] ? 1 : false) ||
(condsNone[1] ? 2 : false) ||
(condsNone[2] ? 3 : 4);
alert(val3rd);
alert(valNone);
Your example could end up like below:
word = ((res.distance === 0) ? 'a' : false) ||
((res.distance === 1 && res.difference > 3) ? 'b' : false) ||
((res.distance === 2 && res.difference > 5 && String(res.key).length > 5) ? 'c' : 'd';
As a side note, I don't feel it's a good looking code, but it is quite close to using the pure ternary operator like you aspire to do...
word = (res.distance === 0) ? 'a'
: (res.distance === 1 && res.difference > 3) ? 'b'
: (res.distance === 2 && res.difference > 5 && String(res.key).length > 5) ? 'c'
: 'd';
This is an older question, but this is how I would do it... I would start with the default case and then change the variable or pass it unchanged as desired.
var word = 'd';
word = (res.distance === 0) ? 'a' : word;
word = (res.distance === 1 && res.difference > 3) ? 'b' : word
word = (res.distance === 2 && res.difference > 5 && String(res.key).length > 5) ? 'c' : word;
Sometimes we have (or just love) to use one-line expressions or variable definitions.
So, we can use a combination of destructive assignments with the ternary operator. For example,
was:
const a = props.a ? props.a : cond2 ? 'val2.0' : 'val2.1' ;
let's update to:
const { a = cond2 ? 'val2.0' : 'val2.1' } = props;
It even remains relatively well readable.
I personally love using ternary expressions for one liners.
Although, I have to agree that nesting ternary expressions can lead to sketchy code.
I started playing with the Object constructor recently to write clearer code:
let param: "one" | "two" | "three";
// Before
let before: number = param === "one" ? 1 : param === "two" ? 2 : 3;
// After
let after: number = Object({
one: 1,
two: 2,
three: 3
})[param];
Real life example:
const opacity =
Platform.OS === "android"
? 1
: Object({
disabled: 0.3,
pressed: 0.7,
default: 1,
})[(disabled && "disabled") || (pressed && "pressed") || "default"];
If you use lodash you can use _.cond
Point free version with lodash/fp:
const getWord = _.cond([
[_.flow(_.get('distance'), _.eq(0)), _.constant('a')],
[_.flow(_.get('distance'), _.eq(1)) && _.flow(_.get('difference'), _.gt(3)), _.constant('b')],
[
_.flow(_.get('distance'), _.eq(2))
&& _.flow(_.get('difference'), _.gt(5))
&& _.flow(_.get('key'), _.toString, _.gt(5)),
_.constant('c'),
],
[_.stubTrue, _.constant('d')],
]);
If you're in the mood for something a little less readable... this might be for you. Write a general function to take an array of conditions (in the order you'd write your if/else) and an array of assignment values. Use .indexOf() to find the first truth in your conditions, and return the assignment array value at that index. Order is critical, conditions need to match up by index to the assignment you want:
const conditionalAssignment = (conditions, assignmentValues) => assignmentValues[conditions.indexOf(true)];
You can modify to handle truthy instead of struct true, and beware the undefined return if indexOf is -1
I faced this too recently and a google search led me here, and I want to share something I discovered recently regarding this:
a && b || c
is almost the same thing as
a ? b : c
as long as b is truthy. If b isn't truthy, you can work around it by using
!a && c || b
if c is truthy.
The first expression is evaluated as (a && b) || c as && has more priority than ||.
If a is truthy then a && b would evaluate to b if b is truthy, so the expression becomes b || c which evaluates to b if it is truthy, just like a ? b : c would if a is truthy, and if a is not truthy then the expression would evaluate to c as required.
Alternating between the && and || trick and ? and || in the layers of the statement tricks the no-nested-ternary eslint rule, which is pretty neat (although I would not recommend doing so unless there is no other way out).
A quick demonstration:
true ? false ? true : true ? false : true ? true ? true : false : true : true
// which is interpreted as
true ? (false ? true : (true ? false : (true ? (true ? true : false) : true))) : true
// now with the trick in alternate levels
true ? (false && true || (true ? false : (true && (true ? true : false) || true))) : true
// all of these evaluate to false btw
I actually cheated a bit by choosing an example where b is always truthy, but if you are just setting strings then this should work fine as even '0' is ironically truthy.
I've been using a switch(true) statement for these cases. In my opinion this syntax feels slightly more elegant than nested if/else operators
switch (true) {
case condition === true :
//do it
break;
case otherCondition === true && soOn < 100 :
// do that
break;
}
ES6 opens the door to this, a different take on a switch statement.
Object.entries({
['a']: res.distance === 0,
['b']: res.distance === 1 && res.difference > 3,
['c']: (res.distance === 2 && res.difference > 5 && String(res.key).length > 5) ? 'c'
}).filter(n => n[1] === true)[0][0]
I prefer inlined if-else in ternary expression
This..
let assignment;
if (loggedUser === onboard.Employee)
assignment = AssignmentEnum.Employee;
else if (loggedUser === onboard.CreatedBy)
assignment = AssignmentEnum.Manager;
else
assignment = 0;
..can be shortened to:
const assignment =
loggedUser === onboard.Employee ?
AssignmentEnum.Employee
:loggedUser === onboard.CreatedBy ?
AssignmentEnum.Manager
:
0;
Just add the following ES6 lint rules to allow the above code structure
On "indent", add this rule:
"ignoredNodes": ["ConditionalExpression"]
On "operator-linebreak", add this rule:
{ "overrides": { "?": "after", ":": "ignore" } }
But if you don't want to put the assigned value in their own line, aside from the settings above, add the multiline-ternary to off
"multiline-ternary": "off"
Here's an option that probably accomplishes the same thing, but might be verging on unreadable and liable to break. It's probably slightly less efficient than the ternary, too.
const word = [
{ value: 'a', when: () => distance === 0 },
{ value: 'b', when: () => distance === 1 && diff > 3 },
{ value: 'c', when: () => distance === 2 && diff > 5 && key.length > 5 },
{ value: 'd', when: () => 'otherwise' }, // just has to return something truthy
].find(({ when }) => when()).value
Array.prototype.find evaluates each element in the array until its callback returns a truthy value. Then it stops and returns, so we wouldn't evaluate any more cases than necessary.
We could also do it without a bunch of tiny functions. Slightly easier to read, but potentially less efficient if your calculations are slow or prone to throwing errors.
const word = [
{ value: 'a', when: distance === 0 },
{ value: 'b', when: distance === 1 && diff > 3 },
{ value: 'c', when: distance === 2 && diff > 5 && key.length > 5 },
{ value: 'd', when: 'otherwise' }, // just has to be something truthy
].find(({ when }) => when).value
But honestly, for shorter ternaries, I'd probably just go with an if/else (or remove the lint rule if you can get the rest of your team to agree).
I think some of the answers here kinda missed the point why the OP want to use ternary operator. Many prefers ternary expression as it allows them to DRY the assignment operations, not just because they can inline the returned value on same line as the condition and make the code have a lookup-like operation
If you deemed the switch(true) { is not ugly, I wish passing true is optional in javascript, either switch() { or switch {, then you can certainly DRY your assignment operations with switch:
const word =
(() => {
switch (true) {
case res.distance == 0:
return 'a';
case res.distance == 1 && res.difference > 3:
return 'b';
case res.distance == 2 && res.difference > 5 && String(res.key).length > 5:
return 'c';
default:
return 'd';
}
})();
Beside if-else and switch, you can also try square brackets.
const testName = cond1 ? [cond2 : a : b] : [cond3 ? c : d]

Categories