Javascript | Why does the variable equal undefined? - javascript

In this code largestGap is equal to undefined when it is logged. Basically this code is turning the string of 1s and 0s into an array and finding the largest gap between two 1s, as you can see looking through largestGap should equal 4, but as stated earlier it returns undefined.
var gaps = [];
var gapCount = 0;
var largestGap = gaps[0];
var string = '10010001000010001001';
string.split('');
var stringArray = Array.from(string);
stringArray.forEach(function(item, array) {
if (item == '1') {
if (gapCount > 0) {
gaps.push(gapCount);
}
gapCount = 0;
} else {
gapCount++;
}
});
for (i = 0; i < gaps.length; i++) {
if (largestGap < gaps[i]) {
largestGap = arr[i];
}
}
console.log(`The largest gap in the string is ${largestGap}`);

You initialize largestGap like:
var largestGap = gaps[0];
But the gaps array is empty initially. Initialize it only after the gaps array is populated, otherwise the test later (if (largestGap < gaps[i])) will not work.
There's also no arr variable. Use the gaps variable instead:
var gaps = [];
var gapCount = 0;
var string = '10010001000010001001';
var stringArray = Array.from(string);
stringArray.forEach(function(item, array) {
if (item == '1') {
if (gapCount > 0) {
gaps.push(gapCount);
}
gapCount = 0;
} else {
gapCount++;
}
});
var largestGap = 0;
for (i = 0; i < gaps.length; i++) {
if (largestGap < gaps[i]) {
largestGap = gaps[i];
}
}
console.log(`The largest gap in the string is ${largestGap}`);
Note that string.split(''); does nothing - it creates an array, and that array is never used, so you can remove that line.
This could be done much more concisely by matching 0s with a regular expression, then mapping the array of matched substrings to each match's length, then calling Math.max with that array of lengths:
const string = '10010001000010001001';
const matchLengths = (string.match(/0+/g) || []).map(str => str.length);
const largestGap = Math.max(...matchLengths);
console.log(`The largest gap in the string is ${largestGap}`);
The || [] is needed because, if there are no matches, the global regular expression match will return null rather than an empty array. If you're sure there will be at least one 0 in the string, you can remove that part to simplify things.

You are getting value from an empty array thats why first you should fill the gap array the use gap[0] insead of doing it before you even pushed anything to the array

you are assigning variables as
var gaps = [];
var gapCount = 0;
var largestGap = gaps[0];
So largestGap in the end also will have no value as it is assigned at the beginning when gaps variable had no value

Related

Find the index of a sub array that contains a number

var array = [[2,3,4],[4,5,6],[2,3,9]];
var number = 9;
If I have this nested array and this variable how do I return the index
where the sub-array with the number is. So the final result should be 2 or.
So far I have:
var indexOfRemainingArray = array.filter(function(item,i) {
if(item != number) {
return i;
}
});
I would like to know how to use map or filter functions for this.
Use Array#findIndex to find the index, and use Array#indexOf in the callback to check if the sub array contains the number at least once.
var array = [[2,3,4],[4,5,6],[2,3,9]];
var number = 9;
var indexOfRemainingArray = array.findIndex(function(sub) {
return sub.indexOf(number) !== -1;
});
console.log(indexOfRemainingArray);
And if you need both indexes, you can assign the result of the inner indexOf to a variable:
var array = [[2,3,4],[4,5,9],[2,3,1]];
var number = 9;
var innerIndex;
var indexOfRemainingArray = array.findIndex(function(sub) {
innerIndex = sub.indexOf(number);
return innerIndex !== -1;
});
console.log(indexOfRemainingArray, innerIndex);

How can I split this string of numbers with ranges?

I have a string of numbers like this:
var string= "1,2,3,4-8,15,17,18-21,22";
How can I split it into an array that forms: [1,2,3,4,5,6,7,8,15,17,18,19,20,21,22]
UPDATE:Okay, code coming up in just a bit... trying to get a jsfiddle up.
var mystring= "1,2,3,4-8,15,17,18-21,22";
var array1= mystring.split(",");
document.getElementById("output").innerHTML=array1;
var array2 = searchStringInArray ("-", array1);
document.getElementById("output2").innerHTML=array2;
function searchStringInArray (str, strArray) {
for (var j=0; j<strArray.length; j++) {
if (strArray[j].match(str)) return j;
}
return -1;
}
So around here I got stuck and was thinking there should be a better way. I know you have to search the array for hyphen split strings. But I failed to get them into another array that i could then insert into the first array.
https://jsfiddle.net/08au43ka/
var string= "1,2,3,4-8,15,17,18-21,22";
var arr=string.split(",");
var crr=[];
arr.forEach(function(a){
brr= a.split("-");
if(brr.length==2){
var o=parseInt(brr[0]);
var p=parseInt(brr[1]);
for(var i=o;i<=p;i++)
crr.push(i);
}
else
crr.push(parseInt(brr[0]));
})
console.log(crr);
You could split first by comma, then by minus and reduce the whole to a new array with an inner loop for missing values.
var string = "1,2,3,4-8,15,17,18-21,22",
result = string.split(',').reduce(function (r, a) {
var b = a.split('-').map(Number);
do {
r.push(b[0]);
b[0]++;
} while (b[0] <= b[1]);
return r;
}, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can just replace the ranges:
var string = "1,2,3,4-8,15,17,18-21,22"
var regexRanges = /(\d+)-(\d+)/g;
var getRangeValues = function(range, start, end) {
return Array(end - start + 1).fill(+start).map((x, i)=> x + i);
};
var result = JSON.parse('[' + string.replace(regexRanges, getRangeValues) + ']');
console.log(result);
var string= "1,2,3,4-8,15,17,18-21,22";
var chunks = string.split(",");
var numbers = [];
for (var i = 0; i < chunks.length; i++) {
var chunk = chunks[i];
if (chunk.indexOf('-') < 0) {
numbers.push(parseInt(chunk));
}
else {
var pair = chunk.split('-');
for (var j = pair[0]; j <= pair[1]; j++) {
numbers.push(parseInt(j));
}
}
}
console.log(numbers);
Since there is no known method for me to achieve what you want most likely you will need to write your own.
I'd split that string by commas, then i'd iterate through array looking for anything containing dash in it, if it contains dash grab that array item, parse it
get left side, get right side, create loop from i = left to i<right, push items into original array.

Split string into array by several delimiters

Folks,
I have looked at underscore.string and string.js modules and still can't find a good way to do the following:
Suppose I have a query string string:
"!dogs,cats,horses!cows!fish"
I would like to pass it to a function that looks for all words that start with !, and get back an Array:
['dogs','cows','fish']
Similarly, the same function should return an array of words that start with ,:
['cats','horses]
Thanks!!!
You can use RegEx to easily match the split characters.
var string = "!dogs,cats,horses!cows!fish";
var splitString = string.split(/!|,/);
// ["dogs", "cats", "horses", "cows", "fish"]
The only issue with that is that it will possibly add an empty string at the beginning of the array if you start it with !. You could fix that with a function:
splitString.forEach(function(item){
if(item === ""){
splitString.splice(splitString.indexOf(item), 1)
}
});
EDIT:
In response to your clarificaiton, here is a function that does as you ask. It currently returns an object with the values commas and exclaim, each with an array of the corresponding elements.
JSBin showing it working.
function splitString(str){
var exclaimValues = [];
var expandedValues = [];
var commaValues = [];
var needsUnshift = false;
//First split the comma delimited values
var stringFragments = str.split(',');
//Iterate through them and see if they contain !
for(var i = 0; i < stringFragments.length; i++){
var stringValue = stringFragments[i];
// if the value contains an !, its an exclaimValue
if (stringValue.indexOf('!') !== -1){
exclaimValues.push(stringValue);
}
// otherwise, it's a comma value
else {
commaValues.push(stringValue);
}
}
// iterate through each exclaim value
for(var i = 0; i < exclaimValues.length; i++){
var exclaimValue = exclaimValues[i];
var expandedExclaimValues = exclaimValue.split('!');
//we know that if it doesn't start with !, the
// the first value is actually a comma value. So move it
if(exclaimValue.indexOf('!') !== 0) commaValues.unshift(expandedExclaimValues.shift());
for(var j = 0; j < expandedExclaimValues.length; j++){
var expandedExclaimValue = expandedExclaimValues[j];
//If it's not a blank entry, push it to our results list.
if(expandedExclaimValue !== "") expandedValues.push(expandedExclaimValue);
}
}
return {comma: commaValues, exclaim: expandedValues};
}
So if we do:
var str = "!dogs,cats,horses!cows!fish,comma!exclaim,comma2,comma3!exclaim2";
var results = splitString(str)
results would be:
{
comma: ["comma3", "comma", "horses", "cats", "comma2"],
exclaim: ["dogs", "cows", "fish", "exclaim", "exclaim2"]
}

Word frequency for array of key/values on javascript

I'm trying to implement a piece of code on javascript to analyse word/frequency on a given string. My objective is to return a array as the following:
[{text: firstword, size:3 },{text:secondword , size:5 },{text: nword, size: 1},...]
I implemented the following code but I'm running out of memory, so I don't really know if its ok or not.
function wordFrequency(txt){
var wordArray = txt.split(/[ .?!,*'"]/);
var newArray = [];
$.each(wordArray, function (ix, word) {
if (newArray.length >= 1){
newArray.some(function (w){
if (w.text === word){
w.size++;
} else {
newArray.push({text: word, size: 1});
}
});
} else {
newArray.push({text: word, size: 1});
}
});
return newArray;
}
Array.prototype.some expects the given callback to return true or false and returns true as soon as your callback returns true for a given element, otherwise it returns false.
So some iterates over all elements, with your given callback, and your callback checks if the given element text equals the search word and if not adds a new object. Introducing a new element the some function can iterate over.
So to make this clear, for every word thats in the newArray before the word you're searching, you're adding a new object containing your word.
Suppose your newArray looks like this:
[{word:"test"},{word:"another"},{word:"one"},{word:"more"}]
after calling your function for the word even it looks like this:
[{word:"test"},{word:"another"},{word:"one"},{word:"more"},{word:"even"},{word:"even"},{word:"even"},{word:"even"}]
Using Array.prototype.filter would be the better approach here, finding you the matching element, note that I also replaced $.each with Array.prototype.forEach:
function wordFrequency(txt){
var wordArray = txt.split(/[ .?!,*'"]/);
var newArray = [], wordObj;
wordArray.forEach(function (word) {
wordObj = newArray.filter(function (w){
return w.text == word;
});
if (wordObj.length) {
wordObj[0].size += 1;
} else {
newArray.push({text: word, size: 1});
}
});
return newArray;
}
document.write(JSON.stringify(wordFrequency("count everything, count all the words, count all the words!").sort(function(a,b){return a.size<b.size})).split("},").join("}<br/>"));
It would be simpler and far more efficient to create a direct map from word to frequency, and only afterwards convert that to your array structure. Given an array words create a map of the words:
var freq = words.reduce(function(p, c) {
p[c] = (p[c] || 0) + 1;
return p;
}, {});
and the convert that map into your array:
var array = Object.keys(freq).map(function(key) {
return { text: key, size: freq[key] };
});
To tell the frequency all you need is a hash map approach. Your algorithm is quadratic, since the some method is nested in the each method, so you're always looping over the newArray just to find an entry and increment the size.
A map approach is easily achievable using a JavaScript object. It also gives you constant look-up time, which is better performance than the nested loops approach.
Try this approach instead:
function wordFrequency(txt){
var wordArray = txt.split(/[ .?!,*'"]/);
var map = {};
$.each(wordArray, function(ix, word) {
// skip empty results
if (!word.length) {
return;
}
// add word to map
if (!map[word]) {
map[word] = 0;
}
map[word]++;
});
return map;
}
To use the function:
var text = "hello!world*hello foo 'bar'foo";
var result = wordFrequency(text);
// iterate over results
Object.keys(result).forEach(function(w) {
console.log(w + ": " + result[w]);
});
// or use for...in
for (var w in result) {
console.log(w + ": " + result[w]);
}
If you really wanted to, you could then map the result into your desired array format with text and size properties:
var mappedResult = Object.keys(result).map(function(w) {
return { text: w, size: result[w] };
});
console.log(mappedResult);
Also, depending on your target browsers, you might consider using the array forEach instead of the jQuery $.each, similar to what I did with the Object.keys portion.
Here's the JSBin example.
You would probably want to avoid any iterations on duplicate elements and keep your results array unique. Since any of the iterators of Array.prototype will include each of the elements, they might not be the ideal solution for this. Sometimes plain old loops do the job best ...
(You may also want to expressively escape any special characters in your regular expression).
function wordFrequency(txt) {
var words = txt.split(/[ \.\?!,\*'"]+/),
seen = [];
for (var i = 0; i < words.length; i++) {
var w = words[i],
found = false;
for (var j = 0; j < seen.length; j++) {
if (w === seen[j].text) {
seen[j].size++;
found = true;
break;
}
}
if (!found) seen.push( { text: w, size: 1 } );
}
return seen;
}
(Note that the inner for-loop isn't visited for the first word, so the first word will be pushed to the seen-stack and the inner for-loop will start with the second word compared to the first one. Only words that we haven't seen already are added to the seen-stack, making it an array of unique elements.)
And here is the equivalent using Array.prototype.forEach() and Array.prototype.indexOf(), but we have to add another intermediate results stack for the latter one. So we'll have to add another iteration to produce the final result. (We wouldn't have to do this using Array.prototype.findIndex(), but this is not a standard method.)
function wordFrequency2(txt) {
var words = txt.split(/[ \.\?!,\*'"]+/),
seen = [],
freq = [];
// get frequencies
words.forEach(function (w) {
var idx = seen.indexOf(w);
if (idx >= 0) {
freq[idx]++;
}
else {
seen.push(w);
freq.push(1);
}
});
// produce the results array
var r = [];
seen.forEach(function (w, idx) {
r.push( { text: w, size: freq[idx] } );
});
return r;
}
Putting optimization into account, the first version using explicit loops will be probably performing faster ...
var words = (function(){
var sWords = document.body.innerText.toLowerCase().trim().replace(/[,;.]/g,'').split(/[\s\/]+/g).sort();
var iWordsCount = sWords.length; // count w/ duplicates
// array of words to ignore
var ignore = ['and','the','to','a','of','for','as','i','with','it','is','on','that','this','can','in','be','has','if'];
ignore = (function(){
var o = {}; // object prop checking > in array checking
var iCount = ignore.length;
for (var i=0;i<iCount;i++){
o[ignore[i]] = true;
}
return o;
}());
var counts = {}; // object for math
for (var i=0; i<iWordsCount; i++) {
var sWord = sWords[i];
if (!ignore[sWord]) {
counts[sWord] = counts[sWord] || 0;
counts[sWord]++;
}
}
var arr = []; // an array of objects to return
for (sWord in counts) {
arr.push({
text: sWord,
frequency: counts[sWord]
});
}
// sort array by descending frequency | http://stackoverflow.com/a/8837505
return arr.sort(function(a,b){
return (a.frequency > b.frequency) ? -1 : ((a.frequency < b.frequency) ? 1 : 0);
});
}());
(function(){
var iWordsCount = words.length; // count w/o duplicates
for (var i=0; i<iWordsCount; i++) {
var word = words[i];
console.log(word.frequency, word.text);
}
}());

javascript array with numeric index without undefineds

suppose I do..
var arr = Array();
var i = 3333;
arr[i] = "something";
if you do a stringify of this array it will return a string with a whole bunch of undefined numeric entries for those entries whose index is less than 3333...
is there a way to make javascript not do this?
I know that I can use an object {} but I would rather not since I want to do array operations such as shift() etc which are not available for objects
If you create an array per the OP, it only has one member with a property name of "333" and a length of 334 because length is always set to be at least one greater than the highest index. e.g.
var a = new Array(1000);
has a length of 1000 and no members,
var a = [];
var a[999] = 'foo';
has a length of 1000 and one member with a property name of "999".
The speedy way to only get defined members is to use for..in:
function myStringifyArray(a) {
var s = [];
var re = /^\d+$/;
for (var p in a) {
if (a.hasOwnProperty(p) && re.test(p)) {
s.push(a[p]);
}
}
return '' + s;
}
Note that the members may be returned out of order. If that is an issue, you can use a for loop instead, but it will be slower for very sparse arrays:
function myStringifyArray(a) {
var s = [];
var re = /^\d+$/;
for (var i=0, iLen=a.length; i<iLen; i++) {
if (a.hasOwnProperty(i)) {
s.push(a[i]);
}
}
return '' + s;
}
In some older browsers, iterating over the array actually created the missing members, but I don't think that's in issue in modern browsers.
Please test the above thoroughly.
The literal representation of an array has to have all the items of the array, otherwise the 3334th item would not end up at index 3333.
You can replace all undefined values in the array with something else that you want to use as empty items:
for (var i = 0; i < arr.length; i++) {
if (typeof arr[i] == 'undefined') arr[i] = '';
}
Another alternative would be to build your own stringify method, that would create assignments instead of an array literal. I.e. instead of a format like this:
[0,undefined,undefined,undefined,4,undefined,6,7,undefined,9]
your method would create something like:
(function(){
var result = [];
result[0] = 0;
result[4] = 4;
result[6] = 6;
result[7] = 7;
result[9] = 9;
return result;
}())
However, a format like that is of course not compatible with JSON, if that is what you need.

Categories

Resources