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);
}
Related
The following code uses the reduce method. It outputs the number of times an element appears in the array. If element appears once then it only outputs 1, otherwise if it is a repeated item then it it is added..
let a = ["a", "b", "c", "a", "b"]
const t = a.reduce((aa, ll) => {
const count = aa[ll];
count
?
aa[ll] = count + 1 :
aa[ll] = 1
return aa
}, {})
console.log(JSON.stringify(t))
// output
// { "a":2, "b":2, "c":1 }
Question is regarding the condition in the ternary operation, specifically the count variable. How is the count variable able to resolve true or false.
The concept is called "Truthy" and "Falsy" respectively. Ie anything that is different from false, 0, -0, 0n, NaN, null, undefined and "" (empty string) can be evaluated to true in Javascript
So your assign var counter = aa[ll] to be the value of the key ll in object aa. That's either a number or undefined. If it's a number !== 0 it's a truthy, if it's 0 or undefined it's falsy. Thus it can be used in a ternary operatator. Depending on the value of counter either the value of the first or the second assignment will be returned (but ignored). The return value of an assignment is always the value of the right hand side ...
While you can use also assignments in the expressions of a ternary operator, I personally wouldn't use it that way but write this assignment as follows
const t = a.reduce((aa, ll) => {
aa[ll] = (aa[ll] || 0) + 1;
return aa
}, {})
(aa[ll] || 0) will return the value of aa[ll] if it's a truthy or 0 otherwise. Thus, in your case, the result of this expression will always be a number >= 0. Then you increase the result by 1 (for the currenct occurence of ll) and assign it back to aa[ll]. This is much shorter than your original code and IMHO much more readable
Heres the answer I found.
"A javascript object consists of key-value pairs where keys are unique. If you try to add a duplicate key with a different value, then the older value for that key is overwritten by the new value."
basically the count variable was checking to see if the new property already exists.
I came across this code when I checked how to find the number that occurs odd number of times.
I tried to understand everything but I ca figure this out.
Please tell what is happening step by step and what each variable means.
Thank you!
function findOdd(A) {
let counts = A.reduce((p, n) => (p[n] = ++p[n] || 1, p), {});
return +Object.keys(counts).find(k => counts[k] % 2) || undefined;
reduce is an Array method which should reduce an array to a single value, which could be an array itself, an object or any flat variable type
reduce will take a function and an initial accumulating object as parameters
The function is called for each element in the array and be passed the accumulating object as first parameter (p) and the single array item as second parameter (n)
The function is in this case an arrow function expression
The function body consists of two expressions connected by the comma operator. The comma operator will return the rightmost expression as a result, in this case p.
The first expression of the comma operator (p[n] = ++p[n] || 1) will return 1 if p[n] is not set, otherwise will increment p[n] and return the result. Thanks to the short-circuit evaluation of the logical OR (||)
This could be written a little bit more detailed as
A.reduce(function (p, n) {
if (p[n]) {
p[n] = p[n] + 1
} else {
p[n] = 1
}
return p
}, {});
As a result you receive an object which counts the appearance of every value in the array
Then you have the return statement which will return the first key of counts which has an odd value
It does this by first creating an Array of the keys of counts with Object.keys.
It then iterates over those keys and checks, whether the modulo division by 2 (% 2) of the value corresponding to every key is a truthy value (in this case not zero) and then return that key.
It will always return the first key with that property
At the end this found value is converted to a number with the unary plus operator
If no value was found, undefined is returned
2 methods are used are used here that you need to understand.
reduce : read about it here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
find read about it here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
the below statement makes a map of a number vs its occurence
let counts = A.reduce((p, n) => (p[n] = ++p[n] || 1, p), {})
and then the second statement searches the map for an element which occurs odd number of times and if no such element it found it returns undefined
return +Object.keys(counts).find(k => counts[k] % 2) || undefined
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(" ").
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], '');
In my project I often find myself checking if a value is an array.
If the value is not an array I create a single element array from it.
If the value is undefined or null, I create an empty array.
The value itself is usually either an array of objects or a single object or undefined
const array = value ? (Array.isArray(value) ? value: [value]) : [];
Is there a more succinct way of doing this (perhaps with lodash or underscore), before I decide to factor this into a separate util function?
You could do
var eventsArray = events ? [].concat(events) : [];
The .concat() function accepts both arrays and individual arguments, so either way you end up with what you want.
since you are using const in your code I assume you are using ES2015 / ES6. ES1015's
Default function parameters allow formal parameters to be initialized with default values if no value or undefined is passed.
function abc(value = []) {
const array = Array.isArray(value) ? value: [value];
}
If you use ES6, you can do the following, it's cleaner than .concat();
function method(value = []) {
const array = [...value];
}
Here's a solution using lodash's castArray and isNil all wrapped up in a mixin:
_.mixin( {'asArray' : function(value){
return _.isNil(value) ? [] : _.castArray(value);
}});
Usage:
_.asArray(null) -> []
_.asArray(undefined) -> []
_.asArray(0) -> [0]
_.asArray(false) -> [false]
_.asArray([1,2,3]) -> [1,2,3]
_.asArray('wibble') -> ['wibble']
A condensed ES2020 way to ensure a variable is an array:
value = [].concat(value ?? [])
Explanation
As #VoteyDisciple explained, the concat function will concatenate single values or an arrays of values. The nullish coalescing operator (??) will use the value on the right (the second empty array) if the value on the left is null or undefined. So if value is null, it will concatenate an empty array to an empty array, which returns an empty array.
Examples
// Different inputs and outputs
values = [].concat([1,2,3] ?? []) // [1,2,3]
values = [].concat(1 ?? []) // [1]
values = [].concat(null ?? []) // []
// Wrap it in a function if you like
function array(value) {
return [].concat(value ?? [])
}
// Use it like this
values = array(values)
// Or even like this
for (let value of array(values)) {
console.log(value)
}
Even shorter solution using .flat()
There is an even slicker way to do it if you don't have to check for the undefined or null value. This basically converts the value to a singleton array if it was not an array before.
[value].flat()
Example usage
[12].flat() // Output: [12]
[[[12]]].flat() // Output: [[12]]
Removing nullish values
If you want to remove nullish values - just add a filter on top of that. This will remove all the values that are falsy.
[value].filter(x => x).flat()
Pitfalls
Due to the fact that 0 (and false) is a falsy value, if you are storing numbers in an array it is advised to explicitly compare array values with null or undefined.
[value].filter(x => x !== null && x !== undefined).flat()
In order to shorten it a bit we could use Lodash's isNil method.
[value].filter(_.isNil).flat()