Efficient parsing of a string into float array in JavaScript - javascript

The Collada file format contains lots of data which is stored as whitespace delimited list of float/int/boolean values. These lists can be huge. We parse these lists in JavaScript using roughly the following code:
var floats = input.split(' ').map(parseFloat)
But this generates a lot of garbage because the huge input string is split into thousands of individual small strings. Is there a way to avoid that? Something like a parseFloat() function which I can point at a offset within an existing string, and so avoid generating the garbage.

If you prefix your string with one space, you can use this to get the first number out of the string:
var start = 0 // Initial search offset.
var firstOccurence = input.indexOf(' ', start);
//Get the substring from the first occurrence of ' ' after start, to the next one, and parse it as a float.
parseFloat(input.substring(firstOccurence , input.indexOf(' ', firstOccurence+1)));
Then, you can use the following to get the numbers after that, out of the string:
firstOccurence = t.indexOf(' ', firstOccurence+1);
parseFloat(t.substring(firstOccurence , t.indexOf(' ', firstOccurence+1)));
Using this in a loop, you can search through your list, and you can start searching the list at a specified index. You can't however, ask for the 7th float in the list, for example. The only reliable way to do that is to use .split(' '), or to use the above code in a loop.
To find the nth float in the input, you can use the following:
for(var i = 0, previousIndex = 0; i < n; i++){
previousIndex = input.indexOf(' ', previousIndex + 1);
}
var float = parseFloat(input.substring(previousIndex, input.indexOf(' ', previousIndex + 1)));

This function can handle multiple white spaces as well as input that does not parse as a float.
input = "123 123 123 foo", start = 0, end = 0, result = [], num;
// empty string evaluates to false
while( end < input.length ) {
end = input.indexOf(" ", start );
// -1 => only one element in input || end of the string has been reached
if( end === -1 ) { end = input.length }
num = parseFloat( input.substring( start, end ) );
// Should extracted string fail to parse then skip
if( num ) result.push( num );
// move forward or end will match current empty space
start = end + 1;
}
result[2] // => "123";
Fiddle here
UPDATE
Just had a brainwave. Don't use the map function but just use the array created by splitting the string like so:
var floats = input.split(' ');
for( var i = 0, len = input.length; i < len; i++ ) {
input[i] = parseFloat( input[i] );
}

How about this?
parseFloat(input.substring(5,10));
Where 5,10 are the starting and ending offsets of your desired chunk of string.

Related

vowelFrequency JavaScript function

I need to iterate over an input string and return a string with the count of the frequency of the different vowels in the string. The vowels in the return string should be in the order they appear in the input string. So, if 'hello world' is the function's parameter, the function should return e1o2. The code I have so far is below. It returns e1o2o3. For some reason, it is not stopping the count of o after it hits the o in hello, and seems to be counting the o in world as a separate count. I think it is, finalString += char + sum;, that is causing this. But, I do not know how to create this function in the first place. Thank you for your time.
function vowelFrequency(str) {
let finalString = '';
let sum = 0;
for (let char of str) {
if ('aeiou'.includes(char)) {
sum += 1;
finalString += char + sum;
}
}
return finalString;
};
The main problem is your sum counter. It counts all vowels together.
Better appoach would be to create a dictionary of vowels
where we add +1 every time we met a match.
In short the idea is:
if (char === 'e') {
dic['e'] += 1;
}
const text = 'hello world';
function vowelFrequency(str) {
let finalString = '';
let dic = {};
for (let char of str) {
if ('aeiou'.includes(char)) {
//check if dictionary has no certain vowel
//it happens when we first time meet a vowel
if (!(char in dic)) {
dic[char] = 0;
}
//increment our counter
dic[char]+=1;
}
}
//by the end of the loop
//we have object with { e: 1, o: 2 }
//now we need to gather values into string
//loop through the object
for ([char, count] of Object.entries(dic)) {
finalString += char + count;
}
return finalString;
};
console.log(vowelFrequency(text));
Shorter version of the same solution would be:
function vowelFrequency(str) {
const vowels = 'aeiou';
let dic = [...str].reduce((dic, char) => {
if (vowels.includes(char))
dic[char] = dic[char] + 1 || 1;
return dic;
}, {});
return Object.entries(dic)
.map(([char, count]) => `${char}${count}`)
.join('');
};
One concise approach would be to transform the string via String.prototype.replaceAll (evaluating every character in the string). The following code searches the original string (which you may wish to normalize beforehand with .toLowerCase() for better results) for any character.
"hello world".replaceAll(/./g, ( char, index, str ) =>
!'aeiou'.includes( char ) || str.lastIndexOf( char ) > index
? "" : char + [ ...str ].filter( o => o == char ).length
);
Each character is checked against a list of vowels. We also check to see if the character index is the last index of this character (does it appear multiple times) in the original string. If either of these conditions fail, an empty string is returned in the character's place.
If our character is in our vowel list, and is the last instance of itself, then we split the original string, filter-out non-matching characters, and return the final count of character instances.
The above approach is somewhat of a gimmick. It's concise, but probably not very self-explanatory or maintainable. Realistically, you'd want to take a slightly more verbose approach (see below).
Note that Map is preferred over a standard object to ensure that key-insertion order is preserved.
function charInstanceString ( input, chars = "aeiou" ) {
/**
* Cycle over each character in our string, checking
* if it appears in our `chars` string. If the character
* appears in our `chars` string, we'll update our map
* to reflect the number of instances for the character.
*/
const charMap = new Map();
for ( const char of input ) {
if ( !chars.includes( char ) ) continue;
charMap.set( char, charMap.get( char ) + 1 || 1 );
}
/**
* Cycle over our map, adding each character (and its
* corresponding count) to an output string.
*/
let output = "";
for ( const [ char, count ] of charMap ) {
output += `${ char }${ count }`;
}
return output;
}

Having trouble re-indexing my array after looping from the end of the array while adding characters in place

I am trying to solve this particular algorithm question:
You are given a license key represented as a string S which consists only alphanumeric character and dashes. The string is separated into N+1 groups by N dashes.
Given a number K, we would want to reformat the strings such that each group contains exactly K characters, except for the first group which could be shorter than K, but still must contain at least one character. Furthermore, there must be a dash inserted between two groups and all lowercase letters should be converted to uppercase.
Given a non-empty string S and a number K, format the string according to the rules described above.
Example 1:
Input: S = "5F3Z-2e-9-w", K = 4
Output: "5F3Z-2E9W"
Explanation: The string S has been split into two parts, each part has 4 characters.
Note that the two extra dashes are not needed and can be removed.
Example 2:
Input: S = "2-5g-3-J", K = 2
Output: "2-5G-3J"
Explanation: The string S has been split into three parts, each part has 2 characters except the first part as it could be shorter as mentioned above.
Note:
The length of string S will not exceed 12,000, and K is a positive integer.
String S consists only of alphanumerical characters (a-z and/or A-Z and/or 0-9) and dashes(-).
String S is non-empty.
I have written the following code:
const licenseKeyFormatting = (S, K) => {
//convert to array, remove special characters, and capitalize
let s = [...S.replace(/\W/g, '').toUpperCase()]
let pos = 1
//from end of array add '-' for every K
for (let i = s.length - 1; i > 0; i--) {
if (pos === K) {
s.splice(i, 0, '-')
pos = 1
i-- //re-index bc adding to the array
}
pos++
}
return s
}
console.log(licenseKeyFormatting("5F3Z-2e-9-w", 4)) //5F3Z-2E9W
console.log(licenseKeyFormatting("2-5g-3-J", 2)) //2-5G-3J
console.log(licenseKeyFormatting("a-a-a-a-", 1)) // this test case fails should be A-A-A-A, I am getting AAA-A
I am pretty sure the flaw in my logic is due to the re-index, but I can't figure out how to address it.
You should not alter the index. Using splice to insert an element pushes the other elements back, however since you iterate from back to front that doesn't matter. You've already handled the shifted elements.
Another issue is setting pos = 1 in the loop. This is directly followed by pos++. So when pos reaches K the value of pos will be reset to 2 at the end of the loop. Either set pos = 0 (in the loop) so it ends on 1 or move pos++ into the else section.
const licenseKeyFormatting = (S, K) => {
//convert to array, remove special characters, and capitalize
let s = [...S.replace(/\W/g, '').toUpperCase()]
let pos = 1
//from end of array add '-' for every K
for (let i = s.length - 1; i > 0; i--) {
if (pos === K) {
s.splice(i, 0, '-')
pos = 0
}
pos++
}
return s.join("") // <- added join for cleaner console output
}
console.log(licenseKeyFormatting("5F3Z-2e-9-w", 4)) //5F3Z-2E9W
console.log(licenseKeyFormatting("2-5g-3-J", 2)) //2-5G-3J
console.log(licenseKeyFormatting("a-a-a-a-", 1)) // this test case fails should be A-A-A-A, I am getting AAA-A
my way....
function licenseKeyFormatting( S, K )
{
let arr = [...S.replace(/\W/g, '').toUpperCase()]
, p = 0
;
for (let i=arr.length;i--;)
{
p = ++p % K // p = (p+1) % K
if (!p&&i) arr.splice(i,0,'-') // if p===0 and i>0
}
return arr.join('')
}
console.log(licenseKeyFormatting("5F3Z-2e-9-w", 4)) // 5F3Z-2E9W
console.log(licenseKeyFormatting("2-5g-3-J", 2)) // 2-5G-3J
console.log(licenseKeyFormatting("a-a-a-a-", 1)) // A-A-A-A
OR: (more simple)
function licenseKeyFormatting( S, K )
{
let arr = [...S.replace(/\W/g, '').toUpperCase()];
for (let p=arr.length-K;p>0;p-=K) arr.splice(p,0,'-');
return arr.join('');
}
console.log( licenseKeyFormatting("5F3Z-2e-9-w", 4)) // 5F3Z-2E9W
console.log( licenseKeyFormatting("2-5g-3-J", 2)) // 2-5G-3J
console.log( licenseKeyFormatting("a-a-a-a-", 1)) // A-A-A-A

adding a space to every space in a string, then cycling back around until length is met

I have the following while loop as part of my text justify function. The idea is that I have text strings (str) that need to be justified (spaces added to existing spaces in between words) to equal to a given length (len)
The catch is I can only add one space to an existing space at a time before I iterate over to the next space in the string and add another space there. If that's it for all spaces in the string and it's still not at the required length, I cycle back over to the original space (now two spaces) and add another. Then it goes to the next space between words and so on and so on. The idea is that any spaces between words in the string should not have a differential of more than one space (i.e. Lorem---ipsum--dolor--sit, not Lorem----ipsum--dolor-sit)
From my research, I decided that using a substring method off the original string to add that first extra space, then I will increment the index and move to the next space in the string and repeat the add. Here's my code:
var indexOf = str.indexOf(" ", 0);
if ( indexOf > -1 ) {
while ( indexOf > -1 && str.length < len ) {
//using a regexp to find a space before a character
var space = /\s(?=\b)/.exec(str);
str = str.substring(0, indexOf + 1) + " " + str.substring(indexOf + 1);
//go to next space in string
indexOf = str.indexOf(space, indexOf + 2);
if ( indexOf === -1 ) {
//loops back to beginning of string
indexOf = str.indexOf(space, 0);
}
}
}
finalResults.push(str);
This code works most of the time, but I noticed that there are instances where the cycle of spacing is not correct. For example, it generates the following string:
sit----amet,--blandit
when the correct iteration would be
sit---amet,---blandit
Any assistance in making this code properly iterate over every space (to add one space) in the string once, then cycling back around to the beginning of the string to start over until the desired length is achieved would be most appreciated.
I think it's more efficient to compute the number spaces required in the beginning.
var s = "today is a friday";
var totalLength = 40;
var tokens = s.split(/\s+/);
var noSpaceLength = s.replace(/\s+/g,'').length;
var minSpace = Math.floor((totalLength - noSpaceLength)/(tokens.length-1));
var remainder = (totalLength - noSpaceLength) % (tokens.length-1);
var out = tokens[0];
for (var i = 1; i < tokens.length; i++) {
var spaces = (i <= remainder ? minSpace+1 : minSpace);
out += "-".repeat(spaces) + tokens[i];
}
$('#out').text(out);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="out"></div>
This solution
splits the string (s) into words in an array (a)
finds the number of spaces to be added between all words (add)
finds the remainder of spaces to be added between first words (rem)
then sticks the words with add spaces + one if rem is not exhausted
Code
var s = "Caballo sin Nombre"; // assume one space between words
var len = 21; // desired length
var need = len - s.length;
var a = s.split(/ /); // split s
// need>0 and at least two words
if (need > 0 && a.length>1) {
var add = Math.floor(need / (a.length-1)) + 1; // all spaces need that (+existing)
var rem = need % (a.length-1); // remainder
var sp = '';
while (add-- > 0) sp += ' ';
// replace
var i,res = ''; // result
for (i=0 ; i<a.length-1 ; i++) {
res += a[i] + sp;
if (rem-- > 0) res += ' '; // remainder
}
res += a[i];
s = res;
}
console.log("'" + s + "' is " + s.length + " chars long.");
This function adds the spaces using a global replace, carefully limiting the text size.
function expand (txt, colwidth) {
txt = txt.replace (/\s\s+/, ' '); // Ensure no multiple spaces in txt
for (var spaces = ' ', // Spaces to check for
limit = colwidth - txt.length; // number of additional spaces required
limit > 0; // do while limit is positive
spaces += ' ') // add 1 to spaces to search for
txt = txt.replace (RegExp (spaces, 'g'),
function (r) {
// If limit > 0 then add a space else do not.
return limit > 0 && --limit ? r + ' ' : r
});
return txt;
}
for (var w = 21; w--;) console.log (expand ('this is a test.', w));
Shows this on console:
this is a test.
this is a test.
this is a test.
this is a test.
14 this is a test.

put dash after every n character during input from keyboard

$('.creditCardText').keyup(function() {
var foo = $(this).val().split("-").join(""); // remove hyphens
if (foo.length > 0) {
foo = foo.match(new RegExp('.{1,4}', 'g')).join("-");
}
$(this).val(foo);
});
I found this tutorial on putting dash after every 4 character from here my question is what if the character interval is not constant like in this example it is only after every 4 what if the interval is 3 characters "-" 2 characters "-" 4 characters "-" 3 characters "-" so it would appear like this 123-12-1234-123-123.
In this case, it is more convenient to just write normal code to solve the problem:
function format(input, format, sep) {
var output = "";
var idx = 0;
for (var i = 0; i < format.length && idx < input.length; i++) {
output += input.substr(idx, format[i]);
if (idx + format[i] < input.length) output += sep;
idx += format[i];
}
output += input.substr(idx);
return output;
}
Sample usage:
function format(input, format, sep) {
var output = "";
var idx = 0;
for (var i = 0; i < format.length && idx < input.length; i++) {
output += input.substr(idx, format[i]);
if (idx + format[i] < input.length) output += sep;
idx += format[i];
}
output += input.substr(idx);
return output;
}
$('.creditCardText').keyup(function() {
var foo = $(this).val().replace(/-/g, ""); // remove hyphens
// You may want to remove all non-digits here
// var foo = $(this).val().replace(/\D/g, "");
if (foo.length > 0) {
foo = format(foo, [3, 2, 4, 3, 3], "-");
}
$(this).val(foo);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<input class="creditCardText" />
While it is possible to do partial matching and capturing with regex, the replacement has to be done with a replacement function. In the replacment function, we need to determine how many capturing group actually captures some text. Since there is no clean solution with regex, I write a more general function as shown above.
You can split it using a regular expression. In this case, I'm using a expression to check for non-spaces with interval 3-2-4-3.
The RegExp.exec will return with a "match" array, with the first element containing the actual string. After removing the first element of the match, you can then join them up with dashes.
var mystring = "123121234123"
var myRegexp = /^([^\s]{3})([^\s]{2})([^\s]{4})([^\s]{3})$/g
var match = myRegexp.exec(mystring);
if (match)
{
match.shift();
mystring = match.join("-")
console.log(mystring)
}
Per further comments, the op clarified they need a fixed interval for when to insert dashes. In that case, there are several ways to implement it; I think regular expression would probably be the worst, in other words, overkill and overly complication solution.
Some simpler options would be to create a new character array, and in a loop append character by character, adding a dash too every time you get to the index you want. This would probably be the easiest to write and grok after the fact, but a little more verbose.
Or you could convert to a character array and use an 'insert into array at index'-type function like splice() (see Insert Item into Array at a Specific Index or Inserting string at position x of another string for some examples).
Pass the input value and the indexes to append the separator, first, it will remove the existing separators then just append separators on positions indexes.
export function addSeparators(
input: string,
positions: number[],
separator: string
): string {
const inputValue = input.replace(/-/g, '').split(''); // remove existing separators and split characters into array
for (let i = 0; i < inputValue.length; i++) {
if (positions.includes(i)) inputValue.splice(i, 0, separator);
}
return inputValue.join('');
}

JQuery/JavaScript increment number

I am trying to increment a number by a given value each second and retain the formatting using JavaScript or JQuery
I am struggling to do it.
Say I have a number like so:
1412015
the number which this can be incremented by each second is variable it could be anything beween 0.1 and 2.
Is it possible, if the value which it has to be incremented by each second is 0.54 to incremenet the number and have the following output:
1,412,016
1,412,017
1,412,018
Thanks
Eef
I'm not quite sure I understand your incrementation case and what you want to show.
However, I decided to chime in on a solution to format a number.
I've got two versions of a number format routine, one which parses an array, and one which formats with a regular expression. I'll admit they aren't the easiest to read, but I had fun coming up with the approach.
I've tried to describe the lines with comments in case you're curious
Array parsing version:
function formatNum(num) {
//Convert a formatted number to a normal number and split off any
//decimal places if they exist
var parts = String( num ).replace(/[^\d.]-/g,'').split('.');
//turn the string into a character array and reverse
var arr = parts[0].split('').reverse();
//initialize the return value
var str = '';
//As long as the array still has data to process (arr.length is
//anything but 0)
//Use a for loop so that it keeps count of the characters for me
for( var i = 0; arr.length; i++ ) {
//every 4th character that isn't a minus sign add a comma before
//we add the character
if( i && i%3 == 0 && arr[0] != '-' ) {
str = ',' + str ;
}
//add the character to the result
str = arr.shift() + str ;
}
//return the final result appending the previously split decimal place
//if necessary
return str + ( parts[1] ? '.'+parts[1] : '' );
}
Regular Expression version:
function formatNum(num) {
//Turn a formatted number into a normal number and separate the
//decimal places
var parts = String( num ).replace(/[^\d.]-/g,'').split('.');
//reverse the string
var str = parts[0].split('').reverse().join('');
//initialize the return value
var retVal = '';
//This gets complicated. As long as the previous result of the regular
//expression replace is NOT the same as the current replacement,
//keep replacing and adding commas.
while( retVal != (str = str.replace(/(\d{3})(\d{1,3})/,'$1,$2')) ) {
retVal = str;
}
//If there were decimal points return them back with the reversed string
if( parts[1] ) {
return retVal.split('').reverse().join('') + '.' + parts[1];
}
//return the reversed string
return retVal.split('').reverse().join('');
}
Assuming you want to output a formatted number every second incremented by 0.54 you could use an interval to do your incrementation and outputting.
Super Short Firefox with Firebug only example:
var num = 1412015;
setInterval(function(){
//Your 0.54 value... why? I don't know... but I'll run with it.
num += 0.54;
console.log( formatNum( num ) );
},1000);
You can see it all in action here: http://jsbin.com/opoze
To increment a value on every second use this structure:
var number = 0; // put your initial value here
function incrementNumber () {
number += 1; // you can increment by anything you like here
}
// this will run incrementNumber() every second (interval is in ms)
setInterval(incrementNumber, 1000);
This will format numbers for you:
function formatNumber(num) {
num = String(num);
if (num.length <= 3) {
return num;
} else {
var last3nums = num.substring(num.length - 3, num.length);
var remindingPart = num.substring(0, num.length - 3);
return formatNumber(remindingPart) + ',' + last3nums;
}
}
function rounded_inc(x, n) {
return x + Math.ceil(n);
}
var x = 1412015;
x = rounded_inc(x, 0.54);

Categories

Resources