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], '');
Related
This question already has answers here:
How do I check if an array includes a value in JavaScript?
(60 answers)
Closed 4 years ago.
I'm using Javascript to find if something is in an array or not, but when I'm using this code it doesn't alert anything at all, which means it's not working correctly. What am I doing wrong?
var theArray = new Array("one","two","three");
if (theArray.indexOf("one")) { alert("Found it!"); }
if (!theArray.indexOf("four")) { alert("Did not find it!"); }
you should use the includes function as:
if (theArray.includes('one')) { alert("Found it!"); }
Remember that the starting index of an array is 0.
From the docs.
The indexOf() method returns the first index at which a given element
can be found in the array, or -1 if it is not present.
The index of one is 0, which is falsy so the first check will fail. indexOf will return -1 if there is no match so you should explicitly check for that.
var theArray = new Array("one","two","three");
if (theArray.indexOf("one") !== -1) { alert("Found it!"); }
if (theArray.indexOf("four") === -1) { alert("Did not find it!"); }
That's because indexOf returns a number which is the index of the matched string, and -1 if the string does not exist in the array. You need to compare the return value to numbers like these:
if (thisArray.indexOf('one') > -1) { alert('Found it!') }
if (thisArray.indexOf('four') > -1) { alert('Did not find it') }
You can use includes to return a boolean instead, if you'd like.
if (thisArray.includes('one')) { alert('Found it!') }
You'd think that might work, wouldn't you? But there are two gotchas here.
.indexOf() returns the index of the first matching element it finds, or -1 if it doesn't find anything. And remember that JavaScript array's are zero-indexed, meaning the first array element has the index of zero. So if the match is the first element, then zero is the returned value, and like most languages, zero means false when you'd want it to be true. However this bring us to point #2.
.indexOf() performs a comparison using strict equality, or in other words ===. The returned value won't be coerced like if you used true == 1. This is highly relevant here because if it didn't use strict equality, then any element it found (other than the first) would have an index of one or higher, and then your comparison would succeed. For example if (theArray.indexOf("two")) would work since the index of that element is 1. However, single indexOf() does a strict equality check, it fails. Which is why you need to explicitly compare the returned value of indexOf() to something, normally > -1.
Linear search. It is a 'language agnostic' approach to solving the problem of searching an unordered list. Yes, you can use array.includes(), which is a neat one-linear specific to JavaScript. But, it appears as through you are new to programming, at least with JavaScript, and before you take advantage of some of those fancy tools that make life easier, it's worth implementing them yourself so you truly understand what's going on under the hood and more importantly, why it works.
function contains(array, value) {
// Loop through the entire array
for (let i = 0; i < array.length; i++) {
// Return true on first match
if (array[i] === value)
return true
}
// Return false on no match
return false
}
// Make an array
let a = ['one', 'two', 'three']
// If it has an element, notify
if (contains(a, 'one'))
alert('found it')
You could use the bitwise NOT ~ operator and check the value of the returned index or -1 if not found.
~ is a bitwise not operator. It is perfect for use with indexOf(), because indexOf returns if found the index 0 ... n and if not -1:
value ~value boolean
-1 => 0 => false
0 => -1 => true
1 => -2 => true
2 => -3 => true
and so on
var theArray = new Array("one", "two", "three");
if (~theArray.indexOf("one")) {
console.log("Found it!");
}
if (!~theArray.indexOf("four")) {
console.log("Did not find it!");
}
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);
}
Say you have an array of unique values and want to push new elements from another array, that meet a condition, without creating duplicates. E.g.
newArray.forEach(function(element){
if (condition) {
oldArray.push(element);
}
})
With regards to performance in Javascript, is it better to check, in every iteration of the loop, if the element exists already before pushing to the array, or to add all the elements that meet the condition, and then run _.uniq from underscore.js?
newArray.forEach(function(element){
if (condition && !oldArray.includes(element)) {
oldArray.push(element);
}
})
versus:
newArray.forEach(function(element){
if (condition) {
oldArray.push(element);
}
})
oldArray = _.uniq(oldArray);
Maybe it doesn't really make a difference for small projects (and arrays), but I want to know what's best for a large scale project.
_.uniq(oldArray);
will do an other loop of the array, so assuming the arrays are made of thousands elements surely the first solution is better.
Probably more usefull is to use indexOf instead of includes, infact, inside the includes funcion an indexOf is made:
newArray.forEach(function(element){
if (condition && oldArray.indexOf(element)===-1) {
oldArray.push(element);
}
})
How you can see, the includes prototipe is:
String.prototype.includes = function(search, start) {
'use strict';
if (typeof start !== 'number') {
start = 0;
}
if (start + search.length > this.length) {
return false;
} else {
return this.indexOf(search, start) !== -1;
}
};
An elegant solution would be to not use an Array but an object. Use key and value the same or your value as key and "true" as value:
{
"value":"value"
}
or
{
"value": true
}
while you're working with it. When you need the array of keys, convert with "for (p in obj)" to an array.
That way, operations in the array are unique by default without additional effort and only returning the array uses some calculations.
If your're using underscore or lodash, you can use _.keys(obj) as well.
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 think I really just need a second pair of eyes here. I am having some issues with returning the value of a substring. I have a tweet that I have split into an array of words and then I am using array filter to find the Twitter handle. When I do find the handle, I want to make sure that there is no ":" on the end of the tweet.
When I console log the value that I am trying to return, I get the Twitter handle with no colon on the end. The returned value seems to still have the colon. Take a look below. The Twitter handle has to make it through all the logic in order to be returned.
getTweetedBy: function(keywords) {
// Assume keywords is equal to ['#AP:', 'this', 'is', 'a', 'tweet']
return keywords.filter(function(el){
if(el.substring(0, 1) === '#') {
if(el.slice(-1) === ':') {
// the value logged here is "#AP" as it should be
console.log(el.substring(0, el.length - 1));
return el.substring(0, el.length - 1);
}
}
});
}
When I run the code below, the console is logging ["#AP:"]. I need to remove the colon.
filterKeywords = commonFilters.filterKeywords(keywords);
tweetedBy = commonFilters.getTweetedBy(keywords);
storyLink = commonFilters.getTweetLink(keywords);
// The console is logging ["#AP:"]
console.log(tweetedBy);
Any help would be greatly appreciated.
Thanks!
EDIT:
As noted below by David, filter is expecting a truthy or falsey statement to be returned. Can anyone think of a method that is better than filter? Only want to return one value. I know I can do this with a loop, but a method would be better.
Thanks!
You want to separate your filtering and mapping functions. The first filter removes elements that don't match, and the second map transforms those matched values to whatever substring you want.
getTweetedBy: function(keywords) {
// Assume keywords is equal to ['#AP:', 'this', 'is', 'a', 'tweet']
return keywords
.filter(function(el){
return (el.substring(0, 1) === '#' && el.slice(-1) === ':');
})
.map(function(el){
// the value logged here is "#AP" as it should be
console.log(el.substring(0, el.length - 1));
return el.substring(0, el.length - 1);
});
}
Edit: Want it in one function? Here you go:
getTweetedBy: function(keywords) {
// Assume keywords is equal to ['#AP:', 'this', 'is', 'a', 'tweet']
return keywords
.reduce(function(matched, el){
if (el.substring(0, 1) === '#' && el.slice(-1) === ':') {
return matched.concat([ el.substring(0, el.length - 1) ]);
}
return matched;
}, [])
}
filter expects a function that returns truthy/falsey value.
It doesn't collect the values returned by the supplied function, it collects the elements for which the function is truthy. There are a bunch of options, including collecting the matched elements with the additional processing your requirements dictate.