Related
I have a requirement to check if a string is formed with any combination of strings given in a array
example : we have array of ["for","car","keys","forth"] and a string of "forthcarkeys", the result should be true.If the string is "forthcarxykeys", the result should be false as xy is not in the array. Order of words in array doesnt matter.
its not necessary that all strings from array should be matched but the teststring should only be made of any of the strings in array. If it contain any string other than the ones in array, return false
my approach:
var str = "forthcarkeys";
var arr = ["for","car","keys","forth"];
for(var i=0;i<arr.length;i++)
{
if(str.indexOf(arr[i]) !== -1)
{
str.replace(arr[i],"");
}
}
if(str !== "")
{
console.log("yes");
} else
{
console.log("no");
}
but this approach is not efficient and failing.
One possible approach is to check for each prefix whether it can be expressed using the input strings.
The idea is that if we can easily compute whether the prefix with length i can be expressed with the input strings, if we already have this information for shorter prefixes (this can be done by checking whether any of the allowed strings lead to a shorter expressible prefix) -- see code below.
var str = "forthcarkeys";
var arr = ["for","car","keys","forth"];
// isPossible[i] indicates whether we can express the
// prefix str.substring(0, i) using strings in arr.
var isPossible = Array(str.length + 1).fill(false);
// it is always possible to construct the empty string
isPossible[0] = true;
// consider each prefix of str, from shortest to longest
for (var i = 1; i <= str.length; i++) {
// try to reach this prefix using an allowed string s_allowed,
// by appending s_allowed to a shorter prefix
for (var j = 0; j < arr.length; j++) {
// "start" is the position where the current string
// would need to be appended
start = i - arr[j].length;
if (start >= 0 && isPossible[start]) {
if (str.substring(start, i) == arr[j]) {
isPossible[i] = true;
// we break the loop over j, because we already found a
// solution to express the current prefix str.substring(0,i)
break;
}
}
}
}
for (var i = 1; i <= str.length; i++) {
console.log(str.substring(0, i) + " - " + isPossible[i] + "\n")
}
if (isPossible[str.length]) {
console.log("yes");
} else {
console.log("no");
}
To further detail how this works, consider a smaller example:
str = "abcd"
arr = ["ab", "a", "cd"]
The approach described here tests all prefixes of str, in increasing order of their length:
Step 0: empty prefix -- this is considered as always fine (it can be expressed using 0 strings).
Step 1: prefix "a":
We try reaching this prefix using a shorter prefix + one allowed string. To do so, we iterate through the allowed strings:
"ab" can not be appended to a shorter prefix in order to get "a" (because the start position would need to be -1).
"a" can be appended to the empty prefix, which is always fine -- thus we get that prefix "a" is fine (can be expressed using the allowed strings).
Step 2: prefix "ab":
We try reaching this prefix using a shorter prefix + one allowed string. To do so, we iterate through the allowed strings:
"ab" can be appended to the empty prefix, which is always fine -- thus we get that prefix "ab" is fine (can be expressed using the allowed strings).
Step 3: prefix "abc":
We try reaching this prefix using a shorter prefix + one allowed string. To do so, we iterate through the allowed strings:
"ab" -- to append this one to a shorter prefix and get the current "abc" prefix, we would need to start at position 1, but the substring from that start position is "bc", thus we can not append the string "ab" to get prefix "abc".
"a" -- similar to above, we can not append "a" to get prefix "abc".
"cd" -- similar to above, we can not append "cd" to get prefix "abc".
We exhausted all allowed strings, thus prefix "abc" can not be expressed using the allowed strings.
Step 4: prefix "abcd" (entire string):
We try reaching this prefix using a shorter prefix + one allowed string. To do so, we iterate through the allowed strings:
"ab" -- to append this one to a shorter prefix and get the current "abcd" prefix, we would need to start at position 2, but the substring from that start position is "cd", thus we can not append the string "ab" to get prefix "abcd".
"a" -- similar to above, we can not append "a" to get prefix "abcd".
"cd" -- we can append this allowed string to the "ab" prefix. In a previous step we found out that the "ab" prefix is fine (can be expressed with given strings), thus it is fine to append "cd" from there.
Thus, we get that the prefix "abcd" (which corresponds to the entire string) can be expressed using the input strings.
you need sort array by length Dec order and change if condition for yes is str==""
function check(str){
var arr = ["for", "car", "keys", "forth"];
arr= arr.sort((a,b)=> b.length-a.length) // sort with length dec order
console.log(arr) //longest length string is first then to lower length
for (var i = 0; i < arr.length; i++) {
str = str.replace(arr[i], "");
}
if (str.trim()== "") { //empty
console.log("yes");
} else {
console.log("no");
}
}
check("forthcarkeys")
check("forthcarxykeys")
If all words are matched first, non occurring words can be omitted at the start. Then if the matches are in order of occurrence in the string, recursion can be used to find all matches after the match:
function eval(str, wordList = ["for","car","keys","forth", "the"]){ //note, added 'the' for testing
if(!str)return false; //empty string -> false
const words = wordList.map(w=> ({word:w, index: str.indexOf(w)})) //map all words with their occurence index inside the string
.filter(w=>w.index !== -1) //get rid of non occuring words alltogether
.sort((w1,w2) => w1.index - w2.index); //sort by index of occurence
const check = (arr,ind) => {
if(ind>=str.length)return ind === str.length; //end of string reached -> match if exactly at end (false if greater than)
let w;
while(arr.length){
[w,...arr] = arr; //destructure: w = next word (index 0), arr is set to the remaining elements
if(w.index > ind) return false; //gap since last match -> no match
if(w.index===ind && check(arr,ind + w.word.length)) //if match is at the expected index, check the next indices
return true; //word started at the 'current' index and remaining words match as well
//if code arrives here, try further with next word (while)
}
return false;
};
return check(words,0); //start recursive function with all words at string index 0
}
//test
function test(str, words){
console.log(str,':', eval(str, words));
}
test("forthcarkeys");
test("forthcarxykeys");
test("forthecar");
test("abcdef",[ "abc", "def", "abcd" ]);
You could make an extended try and search for every words and use the temporary result set for filtering out if the words are in the string.
function check(string, array) {
function fork(i, t) {
var s = t.slice(), j;
if (i === possibilities.length) {
result.push(t.join(''));
return;
}
if (possibilities[i].word.split('').every(function (c, j) { return s[j + possibilities[i].position] !== ''; })) {
for (j = 0; j < possibilities[i].word.length; j++) {
s[j + possibilities[i].position] = ''
}
}
fork(i + 1, s);
fork(i + 1, t);
}
var possibilities = array.reduce(function (r, a) {
var p = string.indexOf(a);
while (p !== -1) {
r.push({ word: a, position: p });
p = string.indexOf(a, p + 1);
}
return r;
}, []),
result = [];
console.log(possibilities);
fork(0, string.split(''));
console.log(result);
return result.some(function (a) { return !a; });
}
console.log(check("forthcarkeyboardingfor", ["for", "car", "key", "forth", "keyboard", "boarding"])); // true
console.log(check("forthxycarkeyboardingfor", ["for", "car", "key", "forth", "keyboard", "boarding"])); // false
.as-console-wrapper { max-height: 100% !important; top: 0; }
Version as above with early exit.
function check(string, array) {
function fork(i, t) {
var s = t.slice(), j;
if (i === possibilities.length) {
return !t.join('');
}
if (possibilities[i].word.split('').every(function (c, j) { return s[j + possibilities[i].position] !== ''; })) {
for (j = 0; j < possibilities[i].word.length; j++) {
s[j + possibilities[i].position] = '';
}
}
return fork(i + 1, s) || fork(i + 1, t);
}
var possibilities = array.reduce(function (r, a) {
var p = string.indexOf(a);
while (p !== -1) {
r.push({ word: a, position: p });
p = string.indexOf(a, p + 1);
}
return r;
}, []);
return fork(0, string.split(''));
}
console.log(check("forthcarkeyboardingfor", ["for", "car", "key", "forth", "keyboard", "boarding"])); // true
console.log(check("forthxycarkeyboardingfor", ["for", "car", "key", "forth", "keyboard", "boarding"])); // false
Here is a more robust function that finds all the possible elements and ways that were used to compose it. If the length of the result is zero, then the original text cannot be made from the pool.
function decompose(orignal, pool) { // recurisve function to find combinations of text
var results = [];
for (var element of pool) { // for each element in pool
if (orignal == element) { // resursive base case, stop when orignal == element
results.push([element]); // * add solution
} else {
if (orignal.indexOf(element) == 0) { // if original text starts with element
var remaining = orignal.slice(element.length); // ready remaining text to be scanned
var subresults = decompose(remaining, pool); // recursive call: findCombinationsOf remaining
for (subresult of subresults) {
results.push([element].concat(subresult)); // * add solution
}
}
}
}
return results;
}
console.log(JSON.stringify(decompose("forthcarkeys", ["for","car","keys","forth"])));
console.log(JSON.stringify(decompose("forthcarkeys", ["for","car","keys","forth", "th"])));
console.log(JSON.stringify(decompose("nowaydude!", ["for","car","keys","forth", "th"])));
Here's my solution
Details:
transform the given string "forthcarxykeys" into an array and assign it to a variable chars
iterate over the given array ["for","car","keys","forth"]
per every iteration, check if the word ( i.e. "for") exists in the array
if it exists, obtain the index for each letter found and mark it as true in the char array
return true if all values in chars are true, false if not.
JS:
// arr = ["for", "car", "keys", "forth"];
// str = "forthcarxykeys";
function check(arr, str) {
let chars = str.split('');
for (let i = 0; i < arr.length; i++) {
let word = arr[i];
let index = str.indexOf(word);
let wordExists = index !== -1;
if (wordExists) {
let endIndex = index + word.length;
for (index; index < endIndex; index++) {
chars[index] = true;
}
}
}
return chars.every(i => i === true);
}
Here is updated code. String replace wasn't working, hence used regex to achieve that. var re = new RegExp(arr[i], 'g');
function check(str, arr) {
var flag = true;
for (var i = 0; i < arr.length; i++) {
if (str.indexOf(arr[i]) === -1) {
flag = false;
}
}
if (!flag) {
console.log("Some keys didn't matched.");
} else {
console.log("Nothing remains. All matched.");
}
}
var str = "forcarxy";
var arr = ["for", "car", "keys", "forth"];
check(str, arr);
var arr = ["abcd", "def", "abc"];
var str = "abcdef";
check(str, arr);
var arr = [ "abcd", "cdef" ];
var str = "abcdef";
check(str, arr);
var str = "aabc";
var arr = ["a", "bc"];
check(str, arr);
Code has been updated to consider the commented case by #inetphantom
I have string like the following:
11222233344444445666
What I would like to do is output the number followed the times it was displayed:
112433475163
Question is, I want this to be efficient. I can store this in an object as the following:
1: { id: 1, displayed: 2},
2: { id: 2, displayed: 1},
3: { id: 3, displayed: 2},
etc.
I can access this object and increment displayed.
My issues is, there is no guarantee in the order. I would like to store the keys in the order they are in the string. How do I accomplish the importance of the order in the object?
This is a proposal for run length coding with an array which holds infomation about one charcter and the count of it:
{
"char": "1",
"count": 2
},
var string = "11222233344444445666",
array = function () {
var r = [], o = {};
string.split('').forEach(function (a, i, aa) {
if (a !== aa[i - 1]) {
o[a] = { char: a, count: 0 };
r.push(o[a]);
}
o[a].count++;
});
return r;
}(string);
document.write('<pre>' + JSON.stringify(array, 0, 4) + '</pre>');
Quick solution with for loop:
var str = "7771122229933344444445666",
obj = {},
len = str.length,
val = null,
count_str = "",
key = "";
for (var i = 0; i < len; i++) {
val = str[i], key = 'k' + val;
if (!obj[key]) {
obj[key] = {'id': val, 'displayed': 1};
} else {
obj[key].displayed++;
}
}
for (var p in obj) {
count_str += obj[p]['id'] + obj[p]['displayed'];
}
console.log(count_str); // "7312249233475163"
because you have such a small set of distinct numbers, I seen no reason why you can't use a array (yeah it's not super ideal memorywise if you skip values and it becomes sparse, but for such a small subset it won't affect you enough to worry of it). Then you can use (number-1) as the index and increment that number as needed.
var counts = [];
var str = "11222233344444445666";
for(var i in str){
var index = parseInt(str[i])-1
counts[index] = (counts[index]||0)+1;
}
for(var i in counts){
var which = 1+parseInt(i);
var count = counts[i];
console.log("# of " + which +"'s: "+count);
}
https://jsfiddle.net/ga0fqpqn/
note: You shouldn't need the parseInt(i)... just +i should work but I think jsfiddle has a bug with it about it defaulting i to handle like a string.
You could store an additional array with the order of the numbers, which you only append to if the object doesn't yet contain the given number. Then once you're done counting, iterate through that array and output the number and the count from the lookup dictionary.
var chars = "1234576123452345".split("");
var order = [];
var hash = {};
chars.forEach(function(char) {
if (!hash[char]) {
hash[char] = 1;
order.push(char);
} else {
hash[char]++;
}
});
console.log(order.map(function(char) {
return char + hash[char];
}).join(""));
// "12233343537161"
Just wondering if there is some other way than this.
var hashStringArray = function(array) {
array.sort();
return array.join('|');
};
I don't like sorting much and using that delimiter is not safe either if it's contained in one of the strings. In overall I need to produce same hash no matter the order of strings. It will be rather short arrays (up to 10 items), but it will be required very often so it shouldn't be too slow.
I intend to use it with ES6 Map object and I need to easily find same array collection.
Updated example of use
var theMap = new Map();
var lookup = function(arr) {
var item = null;
var hashed = hashStringArray(arr);
if (item = theMap.get( hashed )) {
return item;
}
theMap.set( hashed, itemBasedOnInput );
return itemBasedOnInput;
}
var arr1 = ['alpha','beta','gama'];
var arr2 = ['beta','alpha','gama'];
lookup(arr1) === lookup(arr2)
Performance tests
http://jsperf.com/hashing-array-of-strings/5
Two things occurred to me as the basis of a solution:
summing doesn't depend on order, which is actually a flaw in simple checksums (they don't catch changes in block order within a word), and
we can convert strings to summable numbers using their charcodes
Here's a function to do (2) :
charsum = function(s) {
var i, sum = 0;
for (i = 0; i < s.length; i++) {
sum += (s.charCodeAt(i) * (i+1));
}
return sum
}
Here's a version of (1) that computes an array hash by summing the charsum values:
array_hash = function(a) {
var i, sum = 0
for (i = 0; i < a.length; i++) {
var cs = charsum(a[i])
sum = sum + (65027 / cs)
}
return ("" + sum).slice(0,16)
}
Fiddle here: http://jsfiddle.net/WS9dC/11/
If we did a straight sum of the charsum values, then the array ["a", "d"] would have the same hash as the array ["b", "c"] - leading to undesired collisions. So based on using non-UTF strings, where charcodes go up to 255, and allowing for 255 characters in each string, then the max return value of charsum is 255 * 255 = 65025. So I picked the next prime number up, 65027, and used (65027 / cs) to compute the hash. I am not 100% convinced this removes collisions... perhaps more thought needed... but it certainly fixes the [a, d] versus [b, c] case.
Testing:
var arr1 = ['alpha','beta','gama'];
var arr2 = ['beta','alpha','gama'];
console.log(array_hash(arr1))
console.log(array_hash(arr2))
console.log(array_hash(arr1) == array_hash(arr2))
Outputs:
443.5322979371356
443.5322979371356
true
And testing a case that shows different hashes:
var arr3 = ['a', 'd'];
var arr4 = ['b', 'c'];
console.log(array_hash(arr3))
console.log(array_hash(arr4))
console.log(array_hash(arr3) == array_hash(arr4))
outputs:
1320.651443298969
1320.3792001649144
false
Edit:
Here's a revised version, which ignore duplicates from the arrays as it goes, and return the hash based on unique items only:
http://jsfiddle.net/WS9dC/7/
array_hash = function(a) {
var i, sum = 0, product = 1
for (i = 0; i < a.length; i++) {
var cs = charsum(a[i])
if (product % cs > 0) {
product = product * cs
sum = sum + (65027 / cs)
}
}
return ("" + sum).slice(0, 16)
}
testing:
var arr1 = ['alpha', 'beta', 'gama', 'delta', 'theta', 'alpha', 'gama'];
var arr2 = ["beta", "gama", "alpha", "theta", "delta", "beta"];
console.log(array_hash(arr1))
console.log(array_hash(arr2))
console.log(array_hash(arr1) === array_hash(arr2))
returns:
689.878503111701
689.878503111701
true
Edit
I've revised the answer above to account for arrays of words that have the same letters. We need these to return different hashes, which they now do:
var arr1 = ['alpha', 'beta']
var arr2 = ['alhpa', 'ateb']
The fix was to add a multiplier to the charsum func based on the char index:
sum += (s.charCodeAt(i) * (i+1));
If you calculate a numeric hash code for each string, then you can combine them with an operator where the order doesn't matter, like the ^ XOR operator, then you don't need to sort the array:
function hashStringArray(array) {
var code = 0;
for (var i = 0; i < array.length; i++) {
var n = 0;
for (var j = 0; j < array[i].length; j++) {
n = n * 251 ^ array[i].charCodeAt(j);
}
code ^= n;
}
return code
};
You can do this:
var hashStringArray = function(array) {
return array.sort().join('\u200b');
};
The \u200b character is an unicode character that also means null, but is not the same as the \0 character, which is most widely used.
'\u200b' == '\0'
> false
An idea to have very fast hash if your set of possible string is less than 32 items long : hash the string with a built-in hash function that will return power-of two as hash :
function getStringHash(aString) {
var currentPO2 = 0;
var hashSet = [];
getStringHash = function ( aString) {
var aHash = hashSet[aString];
if (aHash) return aHash;
aHash = 1 << currentPO2++;
hashSet[aString] = aHash;
return aHash;
}
return getStringHash(aString);
}
Then use this hash on your string array, ORing the hashes ( | ) :
function getStringArrayHash( aStringArray) {
var aHash = 0;
for (var i=0; i<aStringArray.length; i++) {
aHash |= getStringHash(aStringArray[i]);
}
return aHash;
}
So to test a bit :
console.log(getStringHash('alpha')); // 1
console.log(getStringHash('beta')); // 2
console.log(getStringHash('gamma')); // 4
console.log(getStringHash('alpha')); // 1 again
var arr1 = ['alpha','beta','gama'];
var arr2 = ['beta','alpha','gama'];
var arr3 = ['alpha', 'teta'];
console.log(getStringArrayHash(arr1)); // 11
console.log(getStringArrayHash(arr2)); // 11 also, like for arr1
var arr3 = ['alpha', 'teta'];
console.log(getStringArrayHash(arr3)); // 17 : a different array has != hashset
jsbin is here : http://jsbin.com/rozanufa/1/edit?js,console
RQ !!! with this method, arrays are considered as set, meaning that a repeated item won't change the hash of an array !!!
This HAS to be faster since it uses only 1) function call 2) lookup 3) integer arithmetic.
So no sort, no (long) string, no concat.
jsperf confirms that :
http://jsperf.com/hashing-array-of-strings/4
EDIT :
version with prime numbers, here : http://jsbin.com/rozanufa/3/edit?js,console
// return the unique prime associated with the string.
function getPrimeStringHash(aString) {
var hashSet = [];
var currentPrimeIndex = 0;
var primes = [ 2, 3, 5, 7, 11, 13, 17 ];
getPrimeStringHash = function ( aString) {
var aPrime = hashSet[aString];
if (aPrime) return aPrime;
if (currentPrimeIndex == primes.length) aPrime = getNextPrime();
else aPrime = primes[currentPrimeIndex];
currentPrimeIndex++
hashSet[aString] = aPrime;
return aPrime;
};
return getPrimeStringHash(aString);
// compute next prime number, store it and returns it.
function getNextPrime() {
var pr = primes[primes.length-1];
do {
pr+=2;
var divides = false;
// discard the number if it divides by one earlier prime.
for (var i=0; i<primes.length; i++) {
if ( ( pr % primes[i] ) == 0 ) {
divides = true;
break;
}
}
} while (divides == true)
primes.push(pr);
return pr;
}
}
function getStringPrimeArrayHash( aStringArray) {
var primeMul = 1;
for (var i=0; i<aStringArray.length; i++) {
primeMul *= getPrimeStringHash(aStringArray[i]);
}
return primeMul;
}
function compareByPrimeHash( aStringArray, anotherStringArray) {
var mul1 = getStringPrimeArrayHash ( aStringArray ) ;
var mul2 = getStringPrimeArrayHash ( anotherStringArray ) ;
return ( mul1 > mul2 ) ?
! ( mul1 % mul2 )
: ! ( mul2 % mul1 );
// Rq : just test for mul1 == mul2 if you are sure there's no duplicates
}
Tests :
console.log(getPrimeStringHash('alpha')); // 2
console.log(getPrimeStringHash('beta')); // 3
console.log(getPrimeStringHash('gamma')); // 5
console.log(getPrimeStringHash('alpha')); // 2 again
console.log(getPrimeStringHash('a1')); // 7
console.log(getPrimeStringHash('a2')); // 11
var arr1 = ['alpha','beta','gamma'];
var arr2 = ['beta','alpha','gamma'];
var arr3 = ['alpha', 'teta'];
var arr4 = ['alpha','beta','gamma', 'alpha']; // == arr1 + duplicate 'alpha'
console.log(getStringPrimeArrayHash(arr1)); // 30
console.log(getStringPrimeArrayHash(arr2)); // 30 also, like for arr1
var arr3 = ['alpha', 'teta'];
console.log(getStringPrimeArrayHash(arr3)); // 26 : a different array has != hashset
console.log(compareByPrimeHash(arr1, arr2) ); // true
console.log(compareByPrimeHash(arr1, arr3) ); // false
console.log(compareByPrimeHash(arr1, arr4) ); // true despite duplicate
As the title says, I've got a string and I want to split into segments of n characters.
For example:
var str = 'abcdefghijkl';
after some magic with n=3, it will become
var arr = ['abc','def','ghi','jkl'];
Is there a way to do this?
var str = 'abcdefghijkl';
console.log(str.match(/.{1,3}/g));
Note: Use {1,3} instead of just {3} to include the remainder for string lengths that aren't a multiple of 3, e.g:
console.log("abcd".match(/.{1,3}/g)); // ["abc", "d"]
A couple more subtleties:
If your string may contain newlines (which you want to count as a character rather than splitting the string), then the . won't capture those. Use /[\s\S]{1,3}/ instead. (Thanks #Mike).
If your string is empty, then match() will return null when you may be expecting an empty array. Protect against this by appending || [].
So you may end up with:
var str = 'abcdef \t\r\nghijkl';
var parts = str.match(/[\s\S]{1,3}/g) || [];
console.log(parts);
console.log(''.match(/[\s\S]{1,3}/g) || []);
If you didn't want to use a regular expression...
var chunks = [];
for (var i = 0, charsLength = str.length; i < charsLength; i += 3) {
chunks.push(str.substring(i, i + 3));
}
jsFiddle.
...otherwise the regex solution is pretty good :)
str.match(/.{3}/g); // => ['abc', 'def', 'ghi', 'jkl']
Building on the previous answers to this question; the following function will split a string (str) n-number (size) of characters.
function chunk(str, size) {
return str.match(new RegExp('.{1,' + size + '}', 'g'));
}
Demo
(function() {
function chunk(str, size) {
return str.match(new RegExp('.{1,' + size + '}', 'g'));
}
var str = 'HELLO WORLD';
println('Simple binary representation:');
println(chunk(textToBin(str), 8).join('\n'));
println('\nNow for something crazy:');
println(chunk(textToHex(str, 4), 8).map(function(h) { return '0x' + h }).join(' '));
// Utiliy functions, you can ignore these.
function textToBin(text) { return textToBase(text, 2, 8); }
function textToHex(t, w) { return pad(textToBase(t,16,2), roundUp(t.length, w)*2, '00'); }
function pad(val, len, chr) { return (repeat(chr, len) + val).slice(-len); }
function print(text) { document.getElementById('out').innerHTML += (text || ''); }
function println(text) { print((text || '') + '\n'); }
function repeat(chr, n) { return new Array(n + 1).join(chr); }
function textToBase(text, radix, n) {
return text.split('').reduce(function(result, chr) {
return result + pad(chr.charCodeAt(0).toString(radix), n, '0');
}, '');
}
function roundUp(numToRound, multiple) {
if (multiple === 0) return numToRound;
var remainder = numToRound % multiple;
return remainder === 0 ? numToRound : numToRound + multiple - remainder;
}
}());
#out {
white-space: pre;
font-size: 0.8em;
}
<div id="out"></div>
If you really need to stick to .split and/or .raplace, then use /(?<=^(?:.{3})+)(?!$)/g
For .split:
var arr = str.split( /(?<=^(?:.{3})+)(?!$)/ )
// [ 'abc', 'def', 'ghi', 'jkl' ]
For .replace:
var replaced = str.replace( /(?<=^(?:.{3})+)(?!$)/g, ' || ' )
// 'abc || def || ghi || jkl'
/(?!$)/ is to not stop at end of the string. Without it's:
var arr = str.split( /(?<=^(?:.{3})+)/ )
// [ 'abc', 'def', 'ghi', 'jkl' ] // is fine
var replaced = str.replace( /(?<=^(.{3})+)/g, ' || ')
// 'abc || def || ghi || jkl || ' // not fine
Ignoring group /(?:...)/ is to prevent duplicating entries in the array. Without it's:
var arr = str.split( /(?<=^(.{3})+)(?!$)/ )
// [ 'abc', 'abc', 'def', 'abc', 'ghi', 'abc', 'jkl' ] // not fine
var replaced = str.replace( /(?<=^(.{3})+)(?!$)/g, ' || ' )
// 'abc || def || ghi || jkl' // is fine
My solution (ES6 syntax):
const source = "8d7f66a9273fc766cd66d1d";
const target = [];
for (
const array = Array.from(source);
array.length;
target.push(array.splice(0,2).join(''), 2));
We could even create a function with this:
function splitStringBySegmentLength(source, segmentLength) {
if (!segmentLength || segmentLength < 1) throw Error('Segment length must be defined and greater than/equal to 1');
const target = [];
for (
const array = Array.from(source);
array.length;
target.push(array.splice(0,segmentLength).join('')));
return target;
}
Then you can call the function easily in a reusable manner:
const source = "8d7f66a9273fc766cd66d1d";
const target = splitStringBySegmentLength(source, 2);
Cheers
const chunkStr = (str, n, acc) => {
if (str.length === 0) {
return acc
} else {
acc.push(str.substring(0, n));
return chunkStr(str.substring(n), n, acc);
}
}
const str = 'abcdefghijkl';
const splittedString = chunkStr(str, 3, []);
Clean solution without REGEX
My favorite answer is gouder hicham's. But I revised it a little so that it makes more sense to me.
let myString = "Able was I ere I saw elba";
let splitString = [];
for (let i = 0; i < myString.length; i = i + 3) {
splitString.push(myString.slice(i, i + 3));
}
console.log(splitString);
Here is a functionalized version of the code.
function stringSplitter(myString, chunkSize) {
let splitString = [];
for (let i = 0; i < myString.length; i = i + chunkSize) {
splitString.push(myString.slice(i, i + chunkSize));
}
return splitString;
}
And the function's use:
let myString = "Able was I ere I saw elba";
let mySplitString = stringSplitter(myString, 3);
console.log(mySplitString);
And it's result:
>(9) ['Abl', 'e w', 'as ', 'I e', 're ', 'I s', 'aw ', 'elb', 'a']
try this simple code and it will work like magic !
let letters = "abcabcabcabcabc";
// we defined our variable or the name whatever
let a = -3;
let finalArray = [];
for (let i = 0; i <= letters.length; i += 3) {
finalArray.push(letters.slice(a, i));
a += 3;
}
// we did the shift method cause the first element in the array will be just a string "" so we removed it
finalArray.shift();
// here the final result
console.log(finalArray);
var str = 'abcdefghijkl';
var res = str.match(/.../g)
console.log(res)
here number of dots determines how many text you want in each word.
function chunk(er){
return er.match(/.{1,75}/g).join('\n');
}
Above function is what I use for Base64 chunking. It will create a line break ever 75 characters.
Here we intersperse a string with another string every n characters:
export const intersperseString = (n: number, intersperseWith: string, str: string): string => {
let ret = str.slice(0,n), remaining = str;
while (remaining) {
let v = remaining.slice(0, n);
remaining = remaining.slice(v.length);
ret += intersperseWith + v;
}
return ret;
};
if we use the above like so:
console.log(splitString(3,'|', 'aagaegeage'));
we get:
aag|aag|aeg|eag|e
and here we do the same, but push to an array:
export const sperseString = (n: number, str: string): Array<string> => {
let ret = [], remaining = str;
while (remaining) {
let v = remaining.slice(0, n);
remaining = remaining.slice(v.length);
ret.push(v);
}
return ret;
};
and then run it:
console.log(sperseString(5, 'foobarbaztruck'));
we get:
[ 'fooba', 'rbazt', 'ruck' ]
if someone knows of a way to simplify the above code, lmk, but it should work fine for strings.
Coming a little later to the discussion but here a variation that's a little faster than the substring + array push one.
// substring + array push + end precalc
var chunks = [];
for (var i = 0, e = 3, charsLength = str.length; i < charsLength; i += 3, e += 3) {
chunks.push(str.substring(i, e));
}
Pre-calculating the end value as part of the for loop is faster than doing the inline math inside substring. I've tested it in both Firefox and Chrome and they both show speedup.
You can try it here
Here's a way to do it without regular expressions or explicit loops, although it's stretching the definition of a one liner a bit:
const input = 'abcdefghijlkm';
// Change `3` to the desired split length.
const output = input.split('').reduce((s, c) => {
let l = s.length-1;
(s[l] && s[l].length < 3) ? s[l] += c : s.push(c);
return s;
}, []);
console.log(output); // output: [ 'abc', 'def', 'ghi', 'jlk', 'm' ]
It works by splitting the string into an array of individual characters, then using Array.reduce to iterate over each character. Normally reduce would return a single value, but in this case the single value happens to be an array, and as we pass over each character we append it to the last item in that array. Once the last item in the array reaches the target length, we append a new array item.
Some clean solution without using regular expressions:
/**
* Create array with maximum chunk length = maxPartSize
* It work safe also for shorter strings than part size
**/
function convertStringToArray(str, maxPartSize){
const chunkArr = [];
let leftStr = str;
do {
chunkArr.push(leftStr.substring(0, maxPartSize));
leftStr = leftStr.substring(maxPartSize, leftStr.length);
} while (leftStr.length > 0);
return chunkArr;
};
Usage example - https://jsfiddle.net/maciejsikora/b6xppj4q/.
I also tried to compare my solution to regexp one which was chosen as right answer. Some test can be found on jsfiddle - https://jsfiddle.net/maciejsikora/2envahrk/. Tests are showing that both methods have similar performance, maybe on first look regexp solution is little bit faster, but judge it Yourself.
var b1 = "";
function myFunction(n) {
if(str.length>=3){
var a = str.substring(0,n);
b1 += a+ "\n"
str = str.substring(n,str.length)
myFunction(n)
}
else{
if(str.length>0){
b1 += str
}
console.log(b1)
}
}
myFunction(4)
function str_split(string, length = 1) {
if (0 >= length)
length = 1;
if (length == 1)
return string.split('');
var string_size = string.length;
var result = [];
for (let i = 0; i < string_size / length; i++)
result[i] = string.substr(i * length, length);
return result;
}
str_split(str, 3)
Benchmark: http://jsben.ch/HkjlU (results differ per browser)
Results (Chrome 104)
This question already has answers here:
Counting the occurrences / frequency of array elements
(39 answers)
Closed 4 months ago.
I need to write some kind of loop that can count the frequency of each letter in a string.
For example: "aabsssd"
output: a:2, b:1, s:3, d:1
Also want to map same character as property name in object. Any good idea how to do this?
I am not sure how to do it.
This is where I am so far:
var arr = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"];
function counter(x) {
var count = 0,
temp = [];
x = x.split('');
console.log(x);
for (var i = 0, len = x.length; i < len; i++) {
if (x[i] == "a") {
count++;
}
}
return count;
}
var a = "aabbddd";
console.log(counter(a));
Here you go:
function getFrequency(string) {
var freq = {};
for (var i=0; i<string.length;i++) {
var character = string.charAt(i);
if (freq[character]) {
freq[character]++;
} else {
freq[character] = 1;
}
}
return freq;
};
some ES6 syntax with reduce:
let counter = str => {
return str.split('').reduce((total, letter) => {
total[letter] ? total[letter]++ : total[letter] = 1;
return total;
}, {});
};
counter("aabsssd"); // => { a: 2, b: 1, s: 3, d: 1 }
Another solution:
function count (string) {
var count = {};
string.split('').forEach(function(s) {
count[s] ? count[s]++ : count[s] = 1;
});
return count;
}
With some ES6 features and short-circuiting:
const counter = s => [...s].reduce((a, c) => (a[c] = ++a[c] || 1) && a, {})
console.log(
counter("hello") // {h: 1, e: 1, l: 2, o: 1}
)
Here's another way:
const freqMap = s => [...s].reduce((freq,c) => {freq[c] = -~freq[c]; return freq} ,{})
Or, if you prefer a "for" loop:
function freqMap(s) {
freq={};
for (let c of s)
freq[c]=-~freq[c];
return freq;
}
e.g. freqMap("MaMaMia") returns Object{M : 3, a : 3, i : 1}
This method leverages the fact that in javascript, bitwise not on "undefined" gives -1, (whereas "undefined+1" gives NaN).
So, -~undefined is 1, -~1 is 2, -~2 is 3 etc.
We can thus iterate over the characters of the string, and simply increment freq[c] without any "if". The first time we encounter a character c, freq[c] will be undefined, so we set it to -~freq[c] which is 1. If we subsequently encounter c again, we again set freq[c] to -~freq[c], which will now be 2, etc.
Simple, elegant, concise.
More declarative way to get a word histogram will be to utilise reduce to iterate through letters and come up with a new object that contains letters as keys and frequencies as values.
function getFrequency(str) {
return str.split('').reduce( (prev, curr) => {
prev[curr] = prev[curr] ? prev[curr] + 1 : 1;
return prev;
}, {});
};
console.log(getFrequency('test')); // => {t: 2, e: 1, s: 1}
a leaner, functional solution:
using ES6 Arrows && Logical Operators:
const buildFreqDict = string =>
string.split('').reduce((freqDict, char) => {
freqDict[char] = (freqDict[char] || 0) + 1;
return freqDict;
}, {})
console.log(buildFreqDict("banana"))
Explained
split string into array of characters.
and then feed it into a reduce method (using method.chaining()).
if char is already logged in countDict then add 1 to it.
or if character not found in countDict then set it to 1.
return new values back up to reduce's accumulator object
NB: don't forget about including the third argument of .reduce(): in this case it is a {} (object literal) that serves to initialize the freqDict object.
for more info see Counting instances of values in an object half way down the page here: MDN Reduce
and for more info about using logical operators please see here: MDN Logical Operators
An easy way. In addition, its get you an alphabetically sorted list. It loops throught an arrray and evaluate if the character is already in the object: if false, the character is added to the object, if true, its frequency increase a unit.
const text= "Lorem ipsum dolor sit amet consectetur adipiscing"
const textAsArray = text.split('').sort()
let charactersList = {}
for (char of textAsArray) {
if (!charactersList[char]) {
charactersList[char]=1;
}
else {
charactersList[char]++
}
}
console.log(charactersList)
I have reviewed and I think that this adapts very well to the need they pose. I would like it to be in a single line but I don't know how to generate the object dynamically.
const uniqueCount=(arr)=>{
let rs ={};
arr.sort().join("").match(/(.)(\1*)/g).map(i=>rs[i[0]]=i.length);
return rs;
};
console.log(uniqueCount(["a","b","c","d","d","e","a","b","c","f","g","h","h","h","e","a"]));
//{ a: 3, b: 2, c: 2, d: 2, e: 2, f: 1, g: 1, h: 3 }
I find it very successful to use .match() and regex /(.)(\1*)/g as explained above.
If it is just a string, you just need to add a .split("") before and that's it.
One more version with sorting by alphabetically. This function works for both.
Frequency of characters by alphabetically sorted
Frequency of characters in order of occurrence
Caveat: Only works if whole string is in lowercase
function freqWithAlphabetTable(str, doNeedToSort) {
let cnt = new Array(26).fill(0), firstLowerCase = 97, output = {}
for (let i = 0; i < str.length; i++)
cnt[str[i].charCodeAt(0) - firstLowerCase]++ // filling the array with count at it's index
if (doNeedToSort) {
for (let i = 0; i < cnt.length; i++) {
if (cnt[i] !== 0)
output[String.fromCharCode(firstLowerCase)] = cnt[i]
firstLowerCase++;
}
} else {
for (let i = 0; i < str.length; i++) {
let letterIndexVal = cnt[str[i].charCodeAt(0) - firstLowerCase];
if (letterIndexVal != 0 ) {
output[str[i]] = letterIndexVal
letterIndexVal = 0 // replacing it with zero to avoid repetition
}
}
}
console.log(output);
return output;
}
for(i = strlen(string)var string = 'aabsssd';
var chars = new Array();
for(var i = 0; i < string.length; i++){
var char = string.charAt(i);
if(chars[char] == undefined){
chars[char] = 0;
}
chars[char]++;
}
console.log(chars);
Here's another option using underscore.js:
function charCount(str) {
return _(str.split('')).countBy(function(char) {
return char.toLowerCase();
});
}
charCount('aaabbbbdd') outputs Object {a: 3, b: 4, d: 2}
const recorrences = ['a', 'b', 'c', 'a', 'b','a']
.map(i => !!~i.indexOf('a'))
.filter(i => i)
.length;
console.log(`recorrences ${recorrences}`)
//recorrences 3
// Count frequency of characters in a string
// input: 'Hello, I'm Paul!'
// result: {
// H: 1,
// E: 1,
// L: 3,
// ... and so on ...
// }
const countChars = (string) => {
let charStats = {};
string = string.replace(' ', '').toUpperCase().split('');
string.forEach((char) => {
if (charStats[char]) {
charStats[char]++;
} else {
charStats[char] = 1;
}
});
return charStats;
};
Another Solution
function maxChar(str) {
const charMap = {};
let max = 0;
let maxChar = '';
for(let char of str){
if(charMap[char]){
charMap[char]++;
}else{
charMap[char] = 1;
}
}
for(let char in charMap){
if(charMap[char] > max){
max = charMap[char];
maxChar = char;
}
}
return maxChar;
}
===>
maxChar('355385')
"5"
var str = 'abcccdddd';
function maxCharCount(target) {
const chars = {};
let maxChar = '';
let maxValue = 1;
for (let char of target) {
chars[char] = chars[char] + 1 || 1;
}
return chars;
}
console.log(maxCharCount(str));
The same solution but refactored. So cool how we can solve this problem with so many different answers :)
function getFrequency(string) {
var freq = {};
for (let character in string) {
let char = string[character];
(freq[char]) ? freq[char]++ : freq[char] = 1
}
return freq;
};
You can use this. Just pass the string and it will return object with all the character frequency.
function buildCharMap(string) {
const charMap = {};
string.replace(/[^\w]/g, '').toLowerCase();
for (let char of string) {
charMap[char] = charMap[char] + 1 || 1;
}
return charMap;
}
[...str].map( char => map.get(char) ? map.set( char, map.get(char) + 1) : map.set(char,1) )
cheat code to count frequency of a char in a string is
let target = "e";
let string = " i want to see that person that came in here last";
let frequency = string.split(target).length - 1;
or all in one line
console.log(string.split("e").length - 1)
Everyone using split and reduce are over-complicating things.
string is an iterator so you can use a for/of loop to go over each letter - there's no need to split it into an array so reduce can use it. reduce is very useful for lots of things but it often seems like: "when all you have is a hammer everything looks like a nail". I think its used unnecessarily in many places.
Anyway...
Create a new object.
Loop over the string.
If there is no key in the object that corresponds to the current letter, add it and set it it to zero.
Increment it.
function counter(str) {
// Create an object
const obj = {};
// Loop through the string
for (const letter of str) {
// If the object doesn't have a `letter`
// property create one and set it to 0;
obj[letter] ??= 0;
// Increment the value
++obj[letter];
}
// Finally return the object
return obj;
}
const str = 'aabbddd';
console.log(counter(str));
Additional documentation
for/of
Logical nullish assignment