I've written a basic function to count word frequency within a string. First, I split the string into an array, then I iterate through the array of words via for loop. That said, if initial string is empty (""), my function will regard "" as a word and thus the result is {"": 1} rather than an empty object. I initially thought that the empty string wouldn't register at all, due to the for-loop.
I've fixed this issue through an if conditional at the start of the function, but I was wondering if there was a better way to go about it.
function countWords(str) {
if (str === "") {
return {};
}
var counts = {};
var wordArray = str.split(" ");
for (i=0;i < wordArray.length; i++) {
word = wordArray[i];
if (!counts[word]) {
counts[word] = 1;
} else {
counts[word] += 1;
}
}
return counts;
}
My Ruby code equivalent did not require anything to not pick up the empty string as a viable word target, so this bothers me somewhat.
Edit: Thank you to all the responses. I appreciate all the help.
Depends on the situation, but if in your case only strings are passed as parameter that check is good enough.
If there's possibility of undefined, null or other falsy value being passed as a parameter as well, you could do this check instead:
if (!str) {
return {};
}
If you want a check even stricter you could do:
if (!str || typeof str !== 'string') {
return {};
}
In this case you are checking falsy values(as we saw above empty string is a falsy value) and making sure you are getting as parameter a string.
This is expected behavior. .split() does not guarantee non-empty strings in the results. Unless you filter out empty strings explicitly, all empty strings that .split() returns will be counted.
Since .split does not coalesce sequential split strings, you may have a lot of empty strings after split: try "a b c".split(" ").
Related
I've written a function that takes a string containing parentheses, loops through the string and counts the number of open and closed parentheses using a for loop. A while loop is then supposed to loop over the string while the number of open parentheses is greater than the number of closed parentheses and append a closed parenthesis to the string. My example code :
function closeThoseParens(string) {
let openTicker = 0;
let closedTicker=0;
for (let i = 0; i < string.length; i++) {
if (string[i] === '(') {
openTicker++;
console.log(openTicker);
} else if (string[i] === ')') {
closedTicker++;
}
}
while (openTicker > closedTicker){
string += ")"
closedTicker++;
console.log(closedTicker);
}}
calling the function on:
let string = '((( )';
closeThoseParens(string)
Should result in a value for string of '((( )))'
However the value returned from the function seems to just be the input string. The tickers show the expected values when logged to the console.
I'm not sure what is happening here, there seems to be something wrong with the way I'm trying to append to the end of the string variable, but I'm not sure what it could be.
You're not modifying the original string, you're modifying the local variable called string. That's why you have to reassign the result of the addition back to string each iteration.
If you're not familiar with the concept of passing by reference vs passing by value, you can skip this paragraph. In JS, strings are passed by value. What this effectively means is you're getting a copy of the string, so you can't modify the original value. If JS passed strings by reference, you would be able to modify the original string and wouldn't have to return a new one. (Disclaimer: This is very simplified, I'm trying to explain this in terms that those new to these concepts can hopefully understand.)
Instead of trying to mutate a parameter, it's usually better to return a new parameter anyways. This is true even for objects where you can modify the original object (to an extent). This is a paradigm of Functional programming that attempts to avoid difficult to understand (and debug) side effects.
To get this to work, you can do something like
function closeThoseParens(string) {
let openTicker = 0;
let closedTicker=0;
let closedParens = string;
for (let i = 0; i < string.length; i++) {
if (string[i] === '(') {
openTicker++;
console.log(openTicker);
} else if (string[i] === ')') {
closedTicker++;
}
}
while (openTicker > closedTicker){
closedParens += ")"
closedTicker++;
console.log(closedTicker);
}
return closedParens;
}
console.log(closeThoseParens('((( )'));
I am new to JavaScript. I have created a indexof function in but it is not giving the correct output:
Question is:
/*
Implement a function called indexOf that accepts two parameters: a string and a character, and returns the first index of character in the string.
*/
This is my code:
function indexOf(string, character) {
let result = string;
let i = 0;
let output = 1;
while (i < result.length) {
if (result[i] === character) {
output = output + indexOf[i];
}
}
return output;
}
I want to know what i am doing wrong. Please Help.
You are making things a little harder than you need to. If you want to do this without calling the built-in indexOf(), which I assume is the point of the exercise, you just need to return from the function as soon as your condition matches. The instructions say "return the first index" — that's the i in your loop.
If you make it through the loop without finding something it's traditional to return -1:
function indexOf(string, character) {
let i=0;
while(i < string.length){
if(string[i] == character){ // yes? just return the index i
return i
}
i++ // no? increase i and move on to next loop iteration
}
return -1; // made it through the loop and without returning. This means no match was found.
}
console.log(indexOf("Mark Was Here", "M"))
console.log(indexOf("Mark Was Here", "W"))
console.log(indexOf("Mark Was Here", "X"))
Assuming from your question that the exercise is to only match the first occurrence of a character and not a substring (multiple characters in a row), then the most direct way to do it is the following:
const indexOf = (word, character) => {
for (let i = 0; i < word.length; i++) {
if (word[i] === character) {
return i;
}
}
return -1;
}
If you also need to match substrings, leave a comment on this answer if you can't figure it out and I'll help you along.
indexOf() is a built in method for strings that tells you the index of a particular character in a word. Note that this will always return the index of the FIRST matching character.-
You can write something like:
function indexOf(string, character){
return string.indexOf(character)
}
So if I were to use my function and pass in the two required arguments:
indexOf("woof", "o") //this would return 1
I have an application that can turns a tex file into a JavaScript object, with key-value pairs. The key being the word and the value being the number of times it has appeared in the text file. Let's go through it together:
FormatText.prototype.toDowncase = function() {
return this._data = this._data.toLowerCase();
};
This turns the words to lowercase
FormatText.prototype.deleteWords = function() {
return this._data = this._data.replace(/\W/g, " ");
};
This replaces all non-words with a space
FormatText.prototype.splitWords = function() {
return this._data = this._data.split(/\s+/);
};
This turns the string in an array and splits at each delimiter
FormatText.prototype.filterEntries = function() {
return this._data = this._data.filter(v => !!v);
};
This one above I have no clue what it does.
FormatText.prototype.countWords = function() {
return this._data = this._data.reduce((dict, v) => {dict[v] = v in dict ? dict[v] + 1 : 1; return dict}, {});
}
Could someone explain this one, however I will get it a try:
This one takes the array and passed the method 'reduce' with two arguments. It counts how many times each individual word has appeared and returns an object with the 'key-value' pairs described at the beginning of this question.
v => !!v means take v, and coerce it to a Boolean type by applying NOT twice. So the filter function is basically removing any falsey values (0, null, undefined) from this._data.
countWords is counting the number of times each word occurs in this._data - it is going through the array and adding 1 to the count if the word has been encountered before, or returning 1 if the word has not been encountered before. It returns an object with the words as keys and the counts as values.
As a note, these functions change the type of this._data, from a string, to an array, to an object. That may cause bugs to appear if e.g. you run the same method twice
Why not just return the value, without NOT NOT, like
v => v
because for filtering the value coerces to a boolean value.
From Array#filter:
Description
filter() calls a provided callback function once for each element in an array, and constructs a new array of all the values for which callback returns a value that coerces to true. callback is invoked only for indexes of the array which have assigned values; it is not invoked for indexes which have been deleted or which have never been assigned values. Array elements which do not pass the callback test are simply skipped, and are not included in the new array.
In this case the double exclamation mark is useless: the value returned from the callback in filter(callback) is then coerced to a boolean automatically, so no need to do it using double exclamation mark. The following lines are equivalent:
.filter(v => !!v)
.filter(v => v)
.filter(Boolean)
This one above I have no clue what it does.
The javascript operator ! (logical not) performs a type coercion (to boolean) on its argument. So applied twice you somehow convert any type to a boolean value which gives you whether it is falsy or truthy.
This is interesting when you want to apply a condition to different types whose semantic is more or less "no value". For example:
!!('') //false
!!(0) //false
!!null //false
!!undefined //false
Could someone explain this one, however I will get it a try
reduce is method of the array prototype which allows to iterate over a collection while aggregating value.
In your specific example the aggregator is a dictionary which maps a word to a count (number of appearance). So if the word is not present in the dictionary it creates a key for this word with a counter initialized to 1 otherwise it increments the counter (if word already present).
A equivalent could be
const countWords = function (words = [], dictionary = {}) {
if(words.length === 0) {
return dictionary;
}
const word = words.pop(); //remove and read the word at the end of the array
if(word in dictionary) {//if key is present in the dictionary
dictionary[word] += 1; // increment
else {
dictionary[word] = 1; // start a counter for new keyword
}
return countWords(words, dictionary);
}
Note: I changed the title of the question, as well as all reference to JSON so that the question better reflects my problem. I got several times the advice of "better iterate the object than work on a serialized version" but I believe (and am certainly mistaken and wrong) that searching for a well-defined pattern in a string is easier than go for iterative or recursive code to iterate an object
I need to extract the pattern "something":"thestring" from a string.
The source string will have many other combinations such as "something":[{"thestring":{"key":18,"anotherkey":"astring"}}], from which only the pair "anotherkey":"astring" is sought for.
I am specifically interested in getting the content of the value, that is thestring in the first example (and astring in the second one).
I tried to match ".*?","(.*?)" but I get more than just the pair, matching the comma after the quote (and it goes downhill form there).
An example of a test string and my failed test is on Regex101
Here is how I would write this:
function extractStrings(obj) {
var stringSet = [];
function extractStringsHelper(obj) {
if (typeof obj === 'string' && stringSet.indexOf(obj) === -1) {
stringSet.push(obj);
} else if (typeof obj === 'array') {
for (var i=0; i<obj.length; i++) {
extractStringsHelper(obj);
}
} else if (typeof obj === 'object' && obj !== null) {
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
extractStringsHelper(obj[key]);
}
}
}
}
extractStringsHelper(obj);
return stringSet;
}
> extractStrings({'a': "strA", 'b': ["strB1", 1, "strB2", {'n': "strC"}]})
> (4) ["strA", "strB1", "strB2", "strC"]
You can also go the regex route and look for:
"[^"]+":"([^"]+)"
Here is your example with the modified regex: https://regex101.com/r/uxS9k0/2
But this path is dark and full of terrors. For example, it breaks if the string contains an escaped double quote. Once you start accounting for all the possible cases, you are basically rewriting a JSON tokenizer.
Try this regex :
If you really want to work on a string, this regex will do what you want, as long as what you look for is always between quotes and preceded by a key between quotes :
"\w+":"(\w+)"
Demo here
The value will be captured in group 1
Here is how to get your value :
var regex = /"\w+":"(\w+)"/g;
var json = "\"something\":\"thestring\"\n\"something\":[{\n\t\"thestring\":{\n\t\t\"key\":18,\n\t\t\"anotherkey\":\"astring\"\n\t}\n}]";
console.log(json);
var match = regex.exec(json);
for (i = 1; match != null; i++) { // You need to loop until you match every value
// Full match is in match[0]
// Your value is in match[1]
console.log("Val"+i+": "+match[1])
match = regex.exec(json);
}
If you are working with valid json you shouldn't need to use regex, and can deserialize it with the following:
var data = JSON.parse(json_string);
I get the first two variables loaded from the backend, then I want to match the brand name I get back and return a two letter code. I put the associated brands in an array of arrays.
It doesn't seem match() is an option, cuz I can't put a variable in regExp().
This didn't work:
if (brand.indexOf(brand_code[i])) {
bc = brand_code[i][1];
}
This didn't work.
if (brand_code[i][0]===brand)
bc = brand_code[i][1];
}
This is my latest attempt.
$(document).ready(function() {
var phone_model='$request.getHeader("x-wurfl-model-name")',
brand='$request.getHeader("x-wurfl-brand-name")',
brand_code=[
['Alcatel','AL'],
['Huawei','HU'],
['LG','LG'],
['Motorola','MT'],
['Samsung','SA'],
['Unimax','UX'],
['ZTE','ZE']];
for (var i = brand_code.length - 1; i >= 0; i--) {
if ($.inArray(brand,brand_code[i])) {
bc = brand_code[i][1];
}
}
$('.faq .mobile_tutorial a').append(bc+phone_model);
});
This gives me an error of Cannot read property '3' of undefined
Where phone_model='Z990g' & brand='ZTE'
Where am I going wrong?
If you would structure your data differently in the variable brand_code, it would become a bit easier:
brand_code={
'Alcatel':'AL',
'Huawei':'HU',
'LG':'LG',
'Motorola':'MT',
'Samsung':'SA',
'Unimax':'UX',
'ZTE':'ZE'
};
bc = brand_code[brand];
}
This will not need to go through an array. Most JavaScript engines find the match in constant time if you use the object-based lookup above. In ES you can use a Map for the same purpose and with the same efficiency.
About your attempt
$.inArray returns 0 when ZTE matches the first element of an array, so the if condition would be false in that case. But worse, when ZTE is not found, the method returns -1, which makes the if condition true.
So, you would have had better results if you had put:
if ($.inArray(brand,brand_code[i])>-1) {
From the jQuery documentation:
The $.inArray() method is similar to JavaScript's native .indexOf() method in that it returns -1 when it doesn't find a match. If the first element within the array matches value, $.inArray() returns 0.
Use Array.filter to find your match, then you can either check that the result's length > 0 and get result[0][1] from it, or use Array.reduce to return only the code:
// filter down to match and reduce the match to it's code value
brand_code.filter(function(pair) {
return pair[0] === brand
}).reduce(function(out, match) {
return match[1];
}, '');
OR ES6:
brand_code.filter(pair => pair[0] === brand)
.reduce((_, match) => match[1], '');