Calculate the number of statements in a javascript function - javascript

I want to work out the number of statements inside a block of javascript code. This is to evaluate how short the code is for a programming challenge (if there's a better/easier way to evaluate code, I'm interested in hearing that too).
For the purposes of this evaluation, I would like to assume a statement is anything that is capable of performing an operation within it, for example:
let values = ['test 1', 'test 2'];
for(let i in values) {
let object = {
a: i%3,
b: Math.floor(i/3),
c: i*2
};
let another = {test: 0 || 4};
let something = values[i];
let otherSomething = getSomeValues(object[a], object[b]);
setSomeValues(object[a], object[c]);
for(let j = 0; j < 5; i++) if(i < j) break;
}
There's quite a lot of syntax to cover so ideally I would like to do this with a library if one is available - my Googling was unable to find anything very suitable.
I tried writing a regex to match all possible breaks between statements, but this is getting messy quickly:
[\n;]|\)[ \w]|[{,][\s\w]+:\s*|[{}]
Here's a link to the regexr I've been using. Breaking this down:
[\n;] - matches a newline or semicolon, the normal ways to start a new statement
\)[ \w] - matches statements following a closing bracket, e.g. if (something) return;
[{,][\s\w]+:\s* - matches the key of a key-value pair in an object
[{}] - matches opening and closing brackets of blocks
I also remove any zero-length matches as statements cannot be empty.
As I've said, ideally I would prefer a library but I wanted to ask for some opinions on my approach and if I've got most of the edge cases.

Since you are trying to understand a particular chunk of code and not building a library, you should check out astexplorer.net
Here is a link that displays a nicely parsed tree and if desired, you can configure the example to use alternative parsers (babel, acorn, eslint, etc).

Related

Taking a string and reversing certain words -- Javascript

Write a function that takes in a string of one or more words, and returns the same string, but with all five or more letter words reversed. Strings passed in will consist of only letters and spaces. Spaces will be included only when more than one word is present.
The code I wrote reverses the whole string. I believe it is something with the if statement, it is not catching words greater than 5. My thinking when writing the code is to first convert the string into an array of words, use a for loop to run through the arrays and find the words that are greater than 5, then reverse the words greater than 5. I have found similar problems and this is how far I got searching for help. I'm stuck, any help would be greatly appreciated! I am new to coding and learning Javascript.
function spinWords(backward){
var sentence = "";
var separate = backward.split("");
for (var i = separate.length - 1; i >= 0; i--){
if (separate[i].length >= 1){
sentence += separate[i].split("").reverse().join("");
}
else {
sentence += "" + separate[i];
}
}
return sentence;
}
spinWords("Hey fellow warriors");
You might find the logic a lot easier if you used a regular expression to match 5 or more word characters in a row, and use a replacer function to reverse them:
function spinWords(backward) {
return backward.replace(/\w{5,}/g, word => word.split('').reverse().join(''));
}
console.log(spinWords("Hey fellow warriors"));
To fix your code, you should split the input string (the variabla named backward) by a space, not by the empty string, to get an array of words, then iterate through that array (starting from the beginning, not at the end), checking for each word's length and concatenating with sentence:
function spinWords(backward) {
var sentence = "";
var separate = backward.split(" ");
for (var i = 0; i < separate.length; i++) {
if (sentence) sentence += ' ';
if (separate[i].length >= 5) {
sentence += separate[i].split("").reverse().join("");
} else {
sentence += separate[i];
}
}
return sentence;
}
console.log(spinWords("Hey fellow warriors"));
Below is a discussion about a possible way to approach this problem. It is not what I would likely do for a question as simple as this, but it demonstrates a mindset that makes it easier to solve many problems, and to reuse your work as you do so.
Breaking the problem down
You want to do several things here, so it might help to break the problem down into steps.
At the core, you want to reverse certain words. So why not write a function to do only that? It should be simple. Here is one version:
const reverseWord = word => word.split('').reverse().join('')
reverseWord('word') //=> 'drow'
But you only want to do this to words whose length is at least five. Rather than rewriting our existing function, we can use it to write the more complex one:
const reverseIfGte5 = word => word.length >= 5 ? reverseWord(word) : word
reverseIfGte5('word') //=> 'word'
reverseIfGte5('supercalifragilisticexpialidocious')
//=> 'suoicodilaipxecitsiligarfilacrepus')
Note that here, we write reverseIfGte5 using reverseWord. So our reverseWord function is also available to be reused if we choose, but also so that each function is doing something simple.
We could now write spinWord in terms of this function, via
const spinWords = sentence => sentence.split(/\s+/).map(reverseIfGte5).join(' ')
spinWords('Hey fellow warriors') //=> 'Hey wollef sroirraw'
This does what's expected. And it might be a good place to stop. However...
Do only one thing per function
Our spinWords function above is responsible for two things:
finding the words in the sentence
applying our reversing function to each
Ideally, a function should be responsible for one thing an one thing only. (There is some judgment to be made in deciding how such responsibilities break down, of course.)
It might be useful to break this apart. We can do that in different ways. Here's a simple one:
const updateWords = wordFn => sentence => sentence.split(/\s+/).map(wordFn).join(' ')
const spinWords = updateWords(reverseIfGte5)
Now we have a reusable function, which we could use, for instance, with an obvious capitalizeFirstLetter function like this:
const titleCase = updateWords(capitalizeFirstLetter)
titleCase('Hey fellow warriors') //=> 'Hey Fellow Warriors')
Fixing bugs
Our code now looks like this:
const updateWords = wordFn => sentence => sentence.split(/\s+/).map(wordFn).join(' ')
const reverseWord = word => word.split('').reverse().join('')
const reverseIfGte5 = word => word.length >= 5 ? reverseWord(word) : word
const spinWords = updateWords(reverseIfGte5)
These functions are ordered from the most likely to the least likely to be reusable.
Note that the total code here, when using such reusable functions, is a bit longer than this plain version:
const spinWords = sentence => sentence.split(/\s+/).map(
word => word.length >= 5 ? word.split('').reverse().join('') : word
).join(' ')
But our version has several advantages. Obviously, reusability is one. But another is that with the problem broken down into small pieces, if we find a problem, we know where look to for a solution.
And guess what, there is in fact a potential bug in this solution.
If we call this function with 'Hey fellow warriors', we return 'Hey wollef oirraw' instead of the expected 'Hey wollef sroirraw'. Our spacing is off.
Because this problem is broken down into distinct functions, there is no question about which function we need to change in order to fix this. Clearly it is the function responsible for pulling apart and putting back together the sentence, updateWords. There is one simple fix to this, changing from
const updateWords = wordFn => sentence => sentence.split(/\s+/).map(wordFn).join(' ')
to
const updateWords = wordFn => sentence => sentence.split(' ').map(wordFn).join(' ')
but we might be better off using a variant of the answer from CertainPerformance, and rewriting it like this:
const updateWords = wordFn => sentence => sentence.replace(/\w+/g, wordFn)
This fixes the bug and actually handles more cases than we could have originally. (Note that it now deals with punctuation characters as well):
spinWords('Hey, fellow warriors!') //=> 'Hey, wollef sroirraw!'
The important point is that in order to fix our bug, we were able to isolate the function responsible and change it without changing anything else.
Working code
This is what we have arrived at:
const updateWords = wordFn => sentence => sentence.replace(/\w+/g, wordFn)
const reverseWord = word => word.split('').reverse().join('')
const reverseIfGte5 = word => word.length >= 5 ? reverseWord(word) : word
const spinWords = updateWords(reverseIfGte5)
console.log(spinWords('Hey, fellow warriors!'))
We have broken the problem down into two fairly reusable functions (reverseWord and updateWords), and two very specific to our problem (reverseIfGte5 and spinWords.) Each has a well-defined responsibility, and they are easy to test in isolation.
Extending further
This is as far as I would likely go with such a function. But because my personal library often includes a function, when, which is already a generalization of one of these, sometimes I might prefer to build on that:
const when = (cond, then) => val => cond(val) ? then(val) : val
const reverseIfGte5 = when(word => word.length >= 5, reverseWord)
I created when because there are times when I want to use an altered version of my input if some condition is true about it and use it unaltered if the condition is false. That is exactly what reverseIfGte5 needs to do, so it's useful to build it on top of when.
This is how utility libraries are built. Several specific problems have obvious connections and more general solutions to them are written to handle them. If these such solutions are generic enough, they are candidates for inclusion in your personal library or your team's library. And if they are useful to a wider audience, they might get included in a general-purpose utility library.
I'm one of the authors of Ramda, a utility library for functional programming in JS, and this is exactly how it has been built.

How to use lastIndexOf() function with symbols

Just started using indexOf() and lastIndexOf() functions and I know why they are used, however, the result doesn't make me feel happy :)
let str = $('#info').html();
// WORKS
//alert(str.lastIndexOf('√'));
// DOESN'T WORK
alert(str.lastIndexOf('√'));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="info">√</div>
The problem is I get the alert result as "-1", which means the √ couldn't be found in the str variable. Using simple symbol √ it works, however, I'm not sure if it's a good practice using this symbol here.
In my opinion, another approach about this problem would be encoding √ symbol in the HTML to √, so using "Inspect element" feature you would see √.
What do you think?
There is no direct way to achieve this. But if you still want to do this way then you simply need to create a HEX value of the ASCII value:
let str = ascii_to_hexa($('#info').html());
str = '&#x0'+str.toUpperCase()+';';
alert(str.lastIndexOf('√'));
function ascii_to_hexa(str)
{
var arr1 = [];
for (var n = 0, l = str.length; n < l; n ++){
var hex = Number(str.charCodeAt(n)).toString(16);
arr1.push(hex);
}
return arr1.join('');
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="info">√</div>
When the browser reads and parses your HTML, it builds up a DOM, without retaining the exact HTML you provided. Later, if you ask for HTML, it builds a new HTML string using its own rules for doing that.
That's why str.lastIndexOf('√') doesn't work: The browser isn't under any obligation to give you back the character in the same form you used when you supplied it. It could give it back as just a character (√) or a named character reference (√ in this case) or a decimal numeric character reference (√), rather than the hex numeric character reference you're looking for.
You'll have to test on your target browsers to see what they give you, and then look for that. I suspect most if not all will return the actual character, and so your str.lastIndexOf('√') (or str.lastIndexOf('\u221A')) will be the way to go.
<div>√</div>

How to return multiple tokens with Jison lexer

I'm new to lexing and parsing so sorry if the title isn't clear enough.
Basically, I'm using Jison to parse some text and I am trying to get the lexer to comprehend indentation. Here's the bit in question:
(\r\n|\r|\n)+\s* %{
parser.indentCount = parser.indentCount || [0];
var indentation = yytext.replace(/^(\r\n|\r|\n)+/, '').length;
if (indentation > parser.indentCount[0]) {
parser.indentCount.unshift(indentation);
return 'INDENT';
}
var tokens = [];
while (indentation < parser.indentCount[0]) {
tokens.push('DEDENT');
parser.indentCount.shift();
}
if (tokens.length) {
return tokens;
}
if (!indentation.length) {
return 'NEWLINE';
}
%}
So far, almost all of that works as expected. The one problem is the line where I attempt to return an array of DEDENT tokens. It appears that Jison is just converting that array into a string which causes me to get a parse error like Expecting ........, got DEDENT,DEDENT.
What I'm hoping I can do to get around this is manually push some DEDENT tokens onto the stack. Maybe with a function like this.pushToken('DEDENT') or something along those lines. But the Jison documentation is not so great and I could use some help.
Any thoughts?
EDIT:
I seem to have been able to hack my way around this after looking at the generated parser code. Here's what seems to work...
if (tokens.length) {
var args = arguments;
tokens.slice(1).forEach(function () {
lexer.performAction.apply(this, args);
}.bind(this));
return 'DEDENT';
}
This tricks the lexer into performing another action using the exact same input for each DEDENT we have in the stack, thus allowing it to add in the proper dedents. However, it feels gross and I'm worried there could be unforeseen problems.
I would still love it if anyone had any ideas on a better way to do this.
After a couple of days I ended up figuring out a better answer. Here's what it looks like:
(\r\n|\r|\n)+[ \t]* %{
parser.indentCount = parser.indentCount || [0];
parser.forceDedent = parser.forceDedent || 0;
if (parser.forceDedent) {
parser.forceDedent -= 1;
this.unput(yytext);
return 'DEDENT';
}
var indentation = yytext.replace(/^(\r\n|\r|\n)+/, '').length;
if (indentation > parser.indentCount[0]) {
parser.indentCount.unshift(indentation);
return 'INDENT';
}
var dedents = [];
while (indentation < parser.indentCount[0]) {
dedents.push('DEDENT');
parser.indentCount.shift();
}
if (dedents.length) {
parser.forceDedent = dedents.length - 1;
this.unput(yytext);
return 'DEDENT';
}
return `NEWLINE`;
%}
Firstly, I modified my capture regex to make sure I wasn't inadvertently capturing extra newlines after a series of non-newline spaces.
Next, we make sure there are 2 "global" variables. indentCount will track our current indentation length. forceDedent will force us to return a DEDENT if it has a value above 0.
Next, we have a condition to test for a truthy value on forceDedent. If we have one, we'll decrement it by 1 and use the unput function to make sure we iterate on this same pattern at least one more time, but for this iteration, we'll return a DEDENT.
If we haven't returned, we get the length of our current indentation.
If the current indentation is greater than our most recent indentation, we'll track that on our indentCount variable and return an INDENT.
If we haven't returned, it's time to prepare to possible dedents. We'll make an array to track them.
When we detect a dedent, the user could be attempting to close 1 or more blocks all at once. So we need to include a DEDENT for as many blocks as the user is closing. We set up a loop and say that for as long as the current indentation is less than our most recent indentation, we'll add a DEDENT to our list and shift an item off of our indentCount.
If we tracked any dedents, we need to make sure all of them get returned by the lexer. Because the lexer can only return 1 token at a time, we'll return 1 here, but we'll also set our forceDedent variable to make sure we return the rest of them as well. To make sure we iterate on this pattern again and those dedents can be inserted, we'll use the unput function.
In any other case, we'll just return a NEWLINE.

Ternary Operator use to increase variable

Is it a good practice to use the ternary operator for this:
answersCounter = answer.length != 0 ? ++answersCounter : answersCounter;
This is a question that I always asked myself as it happens quite often. Or, is it better to use a normal if statement? For me, this looks much cleaner in one line.
This is just opinion, but I think that writing the increment like you have it is somewhat poor style.
Assigning a variable to a pre-incremented version of itself is a little bit confusing. To me, the best code is the clearest (excepting nods to optimization where necessary), and sometimes brevity leads to clarity and sometimes it does not (see anything written in Perl... I kid, sorta).
Have you ever had the programming trick question of:
int i = 5;
i += i++ + i;
Or something similar? And you think to yourself who would ever need to know how that works out since when would you ever assign a variable to the pre/post increment version of itself? I mean, you would never ever see that in real code, right?
Well, you just provided an example. And while it is parseable, it is not idiomatic and not clearer than a straight forward if.
E.g.
if (answer.length != 0) answersCounter++;
Of course, some people don't like if statements with out braces, and don't like braces without newlines, which is probably how you ended up with the ternary. Something with the coding style needs to be re-evaluated though if it is resulting in (subjectively) worse code to avoid a few carriage returns.
Again, this is opinion only, and certainly not a rule.
For Javascript
As it's unclear whether OP is asking about Java, JavaScript or genuinely both.
Also know this is an old question but I've been playing with this and ended up here so thought it worth sharing.
The following does nothing, as incrementers within ternary operators don't work as expected.
let i = 0;
const test = true;
i = test ? i++ : i--;
console.log(i) // 0
Switching ++ to +1 and -- to -1 does work.
However it conceptually is a little strange. We are creating an increment of the variable, then assigning that incremented variable back to the original. Rather than incrementing the variable directly.
let i = 0;
const test = true;
i = test ? i+1 : i-1;
console.log(i) // 1
You can also use the logical operators && and ||.
However I personally find this harder to read and know for sure what will be output without testing it.
let i = 0;
const test = true;
i = test && i+1 || i-1;
console.log(i) // 1
But at the end of the day as commented above, an if else statement seems to be the clearest representation.
This increments the variable directly, and if brevity is the aim then it can still all go on one line.
let i = 0;
const test = true;
if (test) { i++ } else { i-- }
console.log(i) // 1

Programming optional ignorance

In Javascript what is the best way to handle scenarios when you have a set of arrays to perform tasks on sets of data and sometimes you do not want to include all of the arrays but instead a combination.
My arrays are labeled in this small snippet L,C,H,V,B,A,S and to put things into perspective the code is around 2500 lines like this. (I have removed code notes from this post)
if(C[0].length>0){
L=L[1].concat(+(MIN.apply(this,L[0])).toFixed(7));
C=C[1].concat(C[0][0]);
H=H[1].concat(+(MAX.apply(this,H[0])).toFixed(7));
V=V[1].concat((V[0].reduce(function(a,b){return a+b}))/(V[0].length));
B=B[1].concat((MAX.apply(this,B[0])-MIN.apply(this,B[0]))/2);
A=A[1].concat((MAX.apply(this,A[0])-MIN.apply(this,A[0]))/2);
D=D[1].concat((D[0].reduce(function(a,b){return a+b}))/(D[0].length));
S=S[1].concat((S[0].reduce(function(a,b){return a+b}))/(S[0].length));
}
It would seem counter-productive in this case to litter the code with tones of bool conditions asking on each loop or code section if an array was included in the task and even more silly to ask inside each loop iteration with say an inline condition as these would also slow down the processing and also make the code look like a maze or rabbit hole.
Is there a logical method / library to ignore instruction or skip if an option was set to false
All I have come up with so far is kind of pointless inline thing
var op=[0,1,1,0,0,0,0,0]; //options
var L=[],C=[],H=[],V=[],B=[],A=[],D=[],S=[];
op[0]&&[L[0]=1];
op[1]&&[C[0]=1,console.log('test, do more than one thing')];
op[2]&&[H[0]=1];
op[3]&&[V[0]=1];
op[4]&&[B[0]=1];
op[5]&&[A[0]=1];
op[6]&&[A[0]=1];
It works in that it sets only C[0] and H[0] to 1 as the options require, but it fails as it needs to ask seven questions per iteration of a loop as it may be done inside a loop. Rather than make seven versions of the the loop or code section, and rather than asking questions inside each loop is there another style / method?
I have also noticed that if I create an array then at some point make it equal to NaN rather than undefined or null the console does not complain
var L=[],C=[],H=[],V=[],B=[],A=[],D=[],S=[];
L=NaN;
L[0]=1;
//1
console.log(L); //NaN
L=undefined;
L[0]=1
//TypeError: Cannot set property '0' of undefined
L=null
L[0]=1
//TypeError: Cannot set property '0' of null
Am I getting warmer? I would assume that if I performed some math on L[0] when isNaN(L)===true that the math is being done but not stored so the line isn't being ignored really..
If I understand what you want I would do something like this.
var op = [...],
opchoice = {
//these can return nothing, no operation, or a new value.
'true': function(val){ /*operation do if true*/ },
'false': function(val){ /*operation do if false*/ },
//add more operations here.
//keys must be strings, or transformed into strings with operation method.
operation: function(val){
//make the boolean a string key.
return this[''+(val == 'something')](val);
}
};
var endop = [];//need this to prevent infinite recursion(loop).
var val;
while(val = op.shift()){
//a queue operation.
endop.push(opchoice.operation(val));
}
I'm sure this is not exactly what you want, but it's close to fulfilling the want of not having a ton of conditions every where.
Your other option is on every line do this.
A = isNaN(A) ? A.concat(...) : A;
Personally I prefer the other method.
It looks like you repeat many of the operations. These operations should be functions so at least you do not redefine the same function over and over again (it is also an optimization to do so).
function get_min(x)
{
return +(MIN.apply(this, a[0])).toFixed(7);
}
function get_max(x)
{
return +(MAX.apply(this, a[0])).toFixed(7);
}
function get_average(x)
{
return (x[0].reduce(function(a, b) {return a + b})) / (x[0].length);
}
function get_mean(x)
{
return (MAX.apply(this, x[0]) - MIN.apply(this, x[0])) / 2;
}
if(C[0].length > 0)
{
L = L[1].concat(get_min(L));
C = C[1].concat(C[0][0]);
H = H[1].concat(get_max(H));
V = V[1].concat(get_average(V));
B = B[1].concat(get_mean(B));
A = A[1].concat(get_mean(A);
D = D[1].concat(get_average(D));
S = S[1].concat(get_average(S));
}
You could also define an object with prototype functions, but it is not clear whether it would be useful (outside of putting those functions in a namespace).
In regard to the idea/concept of having a test, what you've found is probably the best way in JavaScript.
op[0] && S = S[1].concat(get_average(S));
And if you want to apply multiple operators when op[0] is true, use parenthesis and commas:
op[3] && (V = V[1].concat(get_average(V)),
B = B[1].concat(get_mean(B)),
A = A[1].concat(get_mean(A));
op[0] && (D = D[1].concat(get_average(D)),
S = S[1].concat(get_average(S)));
However, this is not any clearer, to a programmer, than an if() block as shown in your question. (Actually, many programmers may have to read it 2 or 3 times before getting it.)
Yet, there is another solution which is to use another function layer. In that last example, you would do something like this:
function VBA()
{
V = V[1].concat(get_average(V));
B = B[1].concat(get_mean(B));
A = A[1].concat(get_mean(A));
}
function DS()
{
D = D[1].concat(get_average(D));
S = S[1].concat(get_average(S));
}
op = [DS,null,null,VBA,null,null,...];
for(key in op)
{
// optional: if(op[key].hasOwnProperty(key)) ... -- verify that we defined that key
if(op[key])
{
op[key](); // call function
}
}
So in other words you have an array of functions and can use a for() loop to go through the various items and if defined, call the function.
All of that will very much depend on the number of combinations you have. You mentioned 2,500 lines of code, but the number of permutations may be such that writing it one way or the other will possibly not reduce the total number of lines, but it will make it easier to maintain because many lines are moved to much smaller code snippet making the overall program easier to understand.
P.S. To make it easier to read and debug later, I strongly suggest you put more spaces everywhere, as shown above. If you want to save space, use a compressor (minimizer), Google or Yahoo! both have one that do a really good job. No need to write your code pre-compressed.

Categories

Resources