Match sequential digits - javascript

I am trying to make a function that can match sequential digits in a string that can also contains letters, such:
ae12 / aeg12345km / mea65 / ab2d43a21 / poe09ac etc.
The string should have sequential digits, which means any digit that are beside and in a sequence (12 is sequential, 1a2 is not sequential, 09 is sequential, a0b0 is not sequential, 00 is not sequential, 11 is not sequential, 112 is sequential)
I went on a couple of topics and it doesn't seem to be possible using regex.
I created this function but I'm not totally satisfied with it, I'd like to improve it:
containsSequentialDigits: function (str) {
var array = str.split('');
var previousC = null;
return array.some(function (c) {
if (isNaN(c)) {
previousC = null;
return false;
}
if (c === previousC+1 || c === previousC-1 ||
c === 0 && previousC === 9 || c === 9 && previousC === 0) {
return true;
}
previousC = c;
return false;
});
}
Do you have any suggestion to simplify it?
Thanks

I think that this regular expresion should work
/(0[19]|1[20]|2[31]|3[42]|4[53]|5[64]|6[75]|7[86]|8[97]|9[08])/

I'm also pretty sure a regex will do. Try this one:
function containsSequentialDigits(str) {
return /01|12|23|34|45|56|67|78|89|90|09|98|87|76|65|54|43|32|21|10/.test(str);
}
var s = [
's', // false, no digits
'1x2', // false, no consecutive digits
'2x1', // false, dito
'13', // false, not "sequential"
'12', // true, increasing
'21', // true, decreasing
];
for( var i = 0; i < s.length; i++ ) {
console.log( s[i], containsSequentialDigits(s[i]) );
}
Outputs:
"s" false
"1x2" false
"2x1" false
"13" false
"12" true
"21" true

The easiest way to do this is with a simple string of consecutive digits and the .indexOf() method. Assuming that you are looking for ONLY whether or not there are consecutive digits in the string and not what they are, the longest group of consecutive digits, or anything like that (that would take more coding :) ), then the following function would work:
function hasConsecutiveDigits(sValue) {
var sConsecutiveDigits = "012345678909876543210";
var regDigitPattern = /\d{2,}/g;
var aDigitGroups = sValue.match(regDigitPattern);
if (aDigitGroups !== null) {
for (j = 0; j < aDigitGroups.length; j++) {
for (x = 0; x < (aDigitGroups[j].length - 1); x++) {
var sCurrDigits = aDigitGroups[j].substring(x, x + 2);
if (sConsecutiveDigits.indexOf(sCurrDigits) !== -1) {
return true;
}
}
}
}
return false;
}
The sConsecutiveDigits string contains all of the possible consecutive digit patterns, so you can simply capture any group of one or more digits (using the regDigitPattern regex value) and then see if any two-digit subgroup of those groups exists as a substring of the sConsecutiveDigits string.
Additionally, since you are only looking for "yes, it has consecutive digits" or "no, it does not have consecutive digits", you can exit from the check as soon as you have found a match, cutting down on the processing a little.
I tested the function using the following code:
var aTestData = ["ae12", "aeg12345km", "mea65", "ab2d43a21", "poe09ac", "adsas", "asd13sad", "asda1357sd", "sd4dfg3dfg5df"];
for (i = 0; i < aTestData.length; i++) {
var result = hasConsecutiveDigits(aTestData[i]);
console.log("'" + aTestData[i] + "' " + ((result) ? "contains" : "does not contain") + " consecutive digits.")
}
. . . and recieved the following results:
'ae12' contains consecutive digits.
'aeg12345km' contains consecutive digits.
'mea65' contains consecutive digits.
'ab2d43a21' contains consecutive digits.
'poe09ac' contains consecutive digits.
'adsas' does not contain consecutive digits.
'asd13sad' does not contain consecutive digits.
'asda1357sd' does not contain consecutive digits.
'sd4dfg3dfg5df' does not contain consecutive digits.
UPDATE
I ran some speed tests on my solution and Daniel Böhmer's here: http://jsperf.com/duplicate-digit-check
Long story, short, they perform pretty similarly across the three prosers that I tested (IE 9, FF 31, and Chrome 40), with a very slight edge to the indexOf approach, most of the time.
The fact that all of my code runs as fas as a single regex check gives you an idea of how heavy regex can be. It's a great tool, but it should be used "gently". :)

Related

Each digit differs from the next one by 1

Script has to work this way:
isJumping(9) === 'JUMPING'
This is a one digit number
isJumping(79) === 'NOT JUMPING'
Neighboring digits do not differ by 1
isJumping(23454) === 'JUMPING'
Neighboring digits differ by 1
I have:
function isJumping(number) {
let str = number.toString();
for (let i = 1; i < str.length; i++) {
if (Math.abs(str[i+1]) - Math.abs(str[i]) == 1){
return 'JUMPING';
}
}
return 'NOT JUMPING';
}
console.log(isJumping(345));
Help please, where is mistake?
Loop over the characters and early return with "NOT JUMPING" if the condition is violated & if the condition is never violated return "JUMPING".
function isJumping(num) {
const strNum = String(Math.abs(num));
for (let i = 0; i < strNum.length - 1; i++) {
if (Math.abs(strNum[i] - strNum[i + 1]) > 1) {
// replace `> 1` with `!== 1`, if diff 0 is not valid!
return "NOT JUMPING";
}
}
return "JUMPING";
}
console.log(isJumping(9));
console.log(isJumping(79));
console.log(isJumping(23454));
There are a couple of issues:
You're not handling single digits.
You're returning too early. You're returning the first time you see a difference of 1 between digits, but you don't know that subsequent differences will also be 1.
You're not checking the difference between the first and second digits, and you're going past the end of the string.
You're using Math.abs as a means of converting digits to numbers.
Instead (see comments):
function isJumping(number) {
let str = number.toString();
for (let i = 1; i < str.length; i++) {
// Convert to number, do the difference, then
// use Math.abs to make -1 into 1 if necessary
if (Math.abs(+str[i] - str[i-1]) !== 1) {
// Found a difference != 1, so we're not jumping
return "NOT JUMPING";
}
}
// Never found a difference != 1, so we're jumping
return "JUMPING";
}
console.log(isJumping(345)); // JUMPING
console.log(isJumping(9)); // JUMPING
console.log(isJumping(79)); // NOT JUMPING
console.log(isJumping(23454)); // JUMPING
In that, I use +str[i] to convert str[i] to number and implicitly convert str[i-1] to number via the - operator, but there are lots of ways to convert strings to numbers (I list them here), pick the one that makes sense for your use case.
You might also need to allow for negative numbers (isJumping(-23)).
A clumsy way would be if (Math.abs(Math.abs(str[i+1]) - Math.abs(str[i])) == 1). Right now you are using Math.abs() to convert digits to numbers. Also, indexing is off, you start from 1, which is good, but then you should compare [i] and [i-1]. And the usual mismatch: you can say "JUMPING", only at the end. So you should check for !==1, and return "NOT JUMPING" inside the loop, and "JUMPING" after. That would handle the 1-digit case too.
It's a more readable practice to use parseInt() for making a number from a digit, otherwise the implementation of the comment:
function isJumping(number) {
let str = number.toString();
for (let i = 1; i < str.length; i++) {
if (Math.abs(parseInt(str[i-1]) - parseInt(str[i])) !== 1){
return 'NOT JUMPING';
}
}
return 'JUMPING';
}
console.log(isJumping(345));
console.log(isJumping(3));
console.log(isJumping(79));
You just need to check your single digit case, and then see if all the digits vary by just 1
function isJumping(number) {
let str = number.toString();
if(str.length == 1)
return 'JUMPING'
const allByOne = str.substring(1).split('').every( (x,i) => {
var prev = str[i];
return Math.abs( +x - +prev) == 1
})
return allByOne ? 'JUMPING' : 'NOT JUMPING';
}
console.log(isJumping(9));
console.log(isJumping(79));
console.log(isJumping(23454));
A vaguely functional approach... The find gets position of the first character pair where the gap is more than one. The .filter deals with negatives (and other extraneous characters) by ignoring them.
// default b to a, so that last digit case, where b===undefined, gives true
const gapIsMoreThanOne = (a,b=a) => ( Math.abs(a-b)>1);
const isDigit = n => /[0-9]/.test(n);
const isJumping = n => n.toString()
.split("")
.filter(isDigit)
.find((x,i,arr)=>gapIsMoreThanOne(x,arr[i+1]))
=== undefined
? "JUMPING" : "NOT JUMPING"
;
console.log(isJumping(1)); // JUMPING
console.log(isJumping(12)); // JUMPING
console.log(isJumping(13)); // NOT JUMPING
console.log(isJumping(21)); // JUMPING
console.log(isJumping(21234565)); // JUPING
console.log(isJumping(-21234568)); // NOT JUMPING
console.log(isJumping("313ADVD")); // NOT JUMPING
PS: To me "JUMPING" implies that there is a gap greater than one, not that there isn't: but I've gone with how it is in the question.

Is there a way to avoid number to string conversion & nested loops for performance?

I just took a coding test online and this one question really bothered me. My solution was correct but was rejected for being unoptimized. The question is as following:
Write a function combineTheGivenNumber taking two arguments:
numArray: number[]
num: a number
The function should check all the concatenation pairs that can result in making a number equal to num and return their count.
E.g. if numArray = [1, 212, 12, 12] & num = 1212 then we will have return value of 3 from combineTheGivenNumber
The pairs are as following:
numArray[0]+numArray[1]
numArray[2]+numArray[3]
numArray[3]+numArray[2]
The function I wrote for this purpose is as following:
function combineTheGivenNumber(numArray, num) {
//convert all numbers to strings for easy concatenation
numArray = numArray.map(e => e+'');
//also convert the `hay` to string for easy comparison
num = num+'';
let pairCounts = 0;
// itereate over the array to get pairs
numArray.forEach((e,i) => {
numArray.forEach((f,j) => {
if(i!==j && num === (e+f)) {
pairCounts++;
}
});
});
return pairCounts;
}
console.log('Test 1: ', combineTheGivenNumber([1,212,12,12],1212));
console.log('Test 2: ', combineTheGivenNumber([4,21,42,1],421));
From my experience, I know conversion of number to string is slow in JS, but I am not sure whether my approach is wrong/lack of knowledge or does the tester is ignorant of this fact. Can anyone suggest further optimization of the code snipped?
Elimination of string to number to string will be a significant speed boost but I am not sure how to check for concatenated numbers otherwise.
Elimination of string to number to string will be a significant speed boost
No, it won't.
Firstly, you're not converting strings to numbers anywhere, but more importantly the exercise asks for concatenation so working with strings is exactly what you should do. No idea why they're even passing numbers. You're doing fine already by doing the conversion only once for each number input, not every time your form a pair. And last but not least, avoiding the conversion will not be a significant improvement.
To get a significant improvement, you should use a better algorithm. #derpirscher is correct in his comment: "[It's] the nested loop checking every possible combination which hits the time limit. For instance for your example, when the outer loop points at 212 you don't need to do any checks, because regardless, whatever you concatenate to 212, it can never result in 1212".
So use
let pairCounts = 0;
numArray.forEach((e,i) => {
if (num.startsWith(e)) {
//^^^^^^^^^^^^^^^^^^^^^^
numArray.forEach((f,j) => {
if (i !== j && num === e+f) {
pairCounts++;
}
});
}
});
You might do the same with suffixes, but it becomes more complicated to rule out concatenation to oneself there.
Optimising further, you can even achieve a linear complexity solution by putting the strings in a lookup structure, then when finding a viable prefix just checking whether the missing part is an available suffix:
function combineTheGivenNumber(numArray, num) {
const strings = new Map();
for (const num of numArray) {
const str = String(num);
strings.set(str, 1 + (strings.get(str) ?? 0));
}
const whole = String(num);
let pairCounts = 0;
for (const [prefix, pCount] of strings) {
if (!whole.startsWith(prefix))
continue;
const suffix = whole.slice(prefix.length);
if (strings.has(suffix)) {
let sCount = strings.get(suffix);
if (suffix == prefix) sCount--; // no self-concatenation
pairCounts += pCount*sCount;
}
}
return pairCounts;
}
(the proper handling of duplicates is a bit difficile)
I like your approach of going to strings early. I can suggest a couple of simple optimizations.
You only need the numbers that are valid "first parts" and those that are valid "second parts"
You can use the javascript .startsWith and .endsWith to test for those conditions. All other strings can be thrown away.
The lengths of the strings must add up to the length of the desired answer
Suppose your target string is 8 digits long. If you have 2 valid 3-digit "first parts", then you only need to know how many valid 5-digit "second parts" you have. Suppose you have 9 of them. Those first parts can only combine with those second parts, and give you 2 * 9 = 18 valid pairs.
You don't actually need to keep the strings!
It struck me that if you know you have 2 valid 3-digit "first parts", you don't need to keep those actual strings. Knowing that they are valid 2-digit first parts is all you need to know.
So let's build an array containing:
How many valid 1-digit first parts do we have?,
How many valid 2-digit first parts do we have?,
How many valid 3-digit first parts do we have?,
etc.
And similarly an array containing the number of valid 1-digit second parts, etc.
X first parts and Y second parts can be combined in X * Y ways
Except if the parts are the same length, in which case we are reusing the same list, and so it is just X * (Y-1).
So not only do we not need to keep the strings, but we only need to do the multiplication of the appropriate elements of the arrays.
5 1-char first parts & 7 3-char second parts = 5 * 7 = 35 pairs
6 2-char first part & 4 2-char second parts = 6 * (4-1) = 18 pairs
etc
So this becomes extremely easy. One pass over the strings, tallying the "first part" and "second part" matches of each length. This can be done with an if and a ++ of the relevant array element.
Then one pass over the lengths, which will be very quick as the array of lengths will be very much shorter than the array of actual strings.
function combineTheGivenNumber(numArray, num) {
const sElements = numArray.map(e => "" + e);
const sTarget = "" + num;
const targetLength = sTarget.length
const startsByLen = (new Array(targetLength)).fill(0);
const endsByLen = (new Array(targetLength)).fill(0);
sElements.forEach(sElement => {
if (sTarget.startsWith(sElement)) {
startsByLen[sElement.length]++
}
if (sTarget.endsWith(sElement)) {
endsByLen[sElement.length]++
}
})
// We can now throw away the strings. We have two separate arrays:
// startsByLen[1] is the count of strings (without attempting to remove duplicates) which are the first character of the required answer
// startsByLen[2] similarly the count of strings which are the first 2 characters of the required answer
// etc.
// and endsByLen[1] is the count of strings which are the last character ...
// and endsByLen[2] is the count of strings which are the last 2 characters, etc.
let pairCounts = 0;
for (let firstElementLength = 1; firstElementLength < targetLength; firstElementLength++) {
const secondElementLength = targetLength - firstElementLength;
if (firstElementLength === secondElementLength) {
pairCounts += startsByLen[firstElementLength] * (endsByLen[secondElementLength] - 1)
} else {
pairCounts += startsByLen[firstElementLength] * endsByLen[secondElementLength]
}
}
return pairCounts;
}
console.log('Test 1: ', combineTheGivenNumber([1, 212, 12, 12], 1212));
console.log('Test 2: ', combineTheGivenNumber([4, 21, 42, 1], 421));
Depending on a setup, the integer slicing can be marginally faster
Although in the end it falls short
Also, when tested on higher N values, the previous answer exploded in jsfiddle. Possibly a memory error.
As far as I have tested with both random and hand-crafted values, my solution holds. It is based on an observation, that if X, Y concantenated == Z, then following must be true:
Z - Y == X * 10^(floor(log10(Y)) + 1)
an example of this:
1212 - 12 = 1200
12 * 10^(floor((log10(12)) + 1) = 12 * 10^(1+1) = 12 * 100 = 1200
Now in theory, this should be faster then manipulating strings. And in many other languages it most likely would be. However in Javascript as I just learned, the situation is a bit more complicated. Javascript does some weird things with casting that I haven't figured out yet. In short - when I tried storing the numbers(and their counts) in a map, the code got significantly slower making any possible gains from this logarithm shenanigans evaporate. Furthermore, storing them in a custom-crafted data structure isn't guaranteed to be faster since you have to build it etc. Also it would be quite a lot of work.
As it stands this log comparison is ~ 8 times faster in a case without(or with just a few) matches since the quadratic factor is yet to kick in. As long as the possible postfix count isn't too high, it will outperform the linear solution. Unfortunately it is still quadratic in nature with the breaking point depending on a total number of strings as well as their length.
So if you are searching for a needle in a haystack - for example you are looking for a few pairs in a huge heap of numbers, this can help. In the other case of searching for many matches, this won't help. Similarly, if the input array was sorted, you could use binary search to push the breaking point further up.
In the end, unless you manage to figure out how to store ints in a map(or some custom implementation of it) in a way that doesn't completely kill the performance, the linear solution of the previous answer will be faster. It can still be useful even with the performance hit if your computation is going to be memory heavy. Storing numbers takes less space then storing strings.
var log10 = Math.log(10)
function log10floored(num) {
return Math.floor(Math.log(num) / log10)
}
function combineTheGivenNumber(numArray, num) {
count = 0
for (var i=0; i!=numArray.length; i++) {
let portion = num - numArray[i]
let removedPart = Math.pow(10, log10floored(numArray[i]))
if (portion % (removedPart * 10) == 0) {
for (var j=0; j!=numArray.length; j++) {
if (j != i && portion / (removedPart * 10) == numArray[j] ) {
count += 1
}
}
}
}
return count
}
//The previous solution, that I used for timing, comparison and check purposes
function combineTheGivenNumber2(numArray, num) {
const strings = new Map();
for (const num of numArray) {
const str = String(num);
strings.set(str, 1 + (strings.get(str) ?? 0));
}
const whole = String(num);
let pairCounts = 0;
for (const [prefix, pCount] of strings) {
if (!whole.startsWith(prefix))
continue;
const suffix = whole.slice(prefix.length);
if (strings.has(suffix)) {
let sCount = strings.get(suffix);
if (suffix == prefix) sCount--; // no self-concatenation
pairCounts += pCount*sCount;
}
}
return pairCounts;
}
var myArray = []
for (let i =0; i!= 10000000; i++) {
myArray.push(Math.floor(Math.random() * 1000000))
}
var a = new Date()
t1 = a.getTime()
console.log('Test 1: ', combineTheGivenNumber(myArray,15285656));
var b = new Date()
t2 = b.getTime()
console.log('Test 2: ', combineTheGivenNumber2(myArray,15285656));
var c = new Date()
t3 = c.getTime()
console.log('Test1 time: ', t2 - t1)
console.log('test2 time: ', t3 - t2)
Small update
As long as you are willing to take a performance hit with the setup and settle for the ~2 times performance, using a simple "hashing" table can help.(Hashing tables are nice and tidy, this is a simple modulo lookup table. The principle is similar though.)
Technically this isn't linear, practicaly it is enough for the most cases - unless you are extremely unlucky and all your numbers fall in the same bucket.
function combineTheGivenNumber(numArray, num) {
count = 0
let size = 1000000
numTable = new Array(size)
for (var i=0; i!=numArray.length; i++) {
let idx = numArray[i] % size
if (numTable[idx] == undefined) {
numTable[idx] = [numArray[i]]
} else {
numTable[idx].push(numArray[i])
}
}
for (var i=0; i!=numArray.length; i++) {
let portion = num - numArray[i]
let removedPart = Math.pow(10, log10floored(numArray[i]))
if (portion % (removedPart * 10) == 0) {
if (numTable[portion / (removedPart * 10) % size] != undefined) {
let a = numTable[portion / (removedPart * 10) % size]
for (var j=0; j!=a.length; j++) {
if (j != i && portion / (removedPart * 10) == a[j] ) {
count += 1
}
}
}
}
}
return count
}
Here's a simplified, and partially optimised approach with 2 loops:
// let's optimise 'combineTheGivenNumber', where
// a=array of numbers AND n=number to match
const ctgn = (a, n) => {
// convert our given number to a string using `toString` for clarity
// this isn't entirely necessary but means we can use strict equality later
const ns = n.toString();
// reduce is an efficient mechanism to return a value based on an array, giving us
// _=[accumulator], na=[array number] and i=[index]
return a.reduce((_, na, i) => {
// convert our 'array number' to an 'array number string' for later concatenation
const nas = na.toString();
// iterate back over our array of numbers ... we're using an optimised/reverse loop
for (let ii = a.length - 1; ii >= 0; ii--) {
// skip the current array number
if (i === ii) continue;
// string + number === string, which lets us strictly compare our 'number to match'
// if there's a match we increment the accumulator
if (a[ii] + nas === ns) ++_;
}
// we're done
return _;
}, 0);
}

Check if a string starts with a number in range

I have a string that can be of random length and I need to check if it satisfies the following conditions:
starts with 4 digits
the first 4 digits form a number in range 2221-2720 (credit card number)
Right now I have the following code:
var isCardNumber = function (myString)
{
var num = parseInt(myString.substr(0, 4));
if(num) {
return num >= 2221 && num <= 2720;
};
return false;
};
Can this be done simpler/shorter? Probably with a regexp?
var isCardNumber = function ( cardNumber ) {
var n = +cardNumber.substr( 0, 4 )
return n && n > 2220 && n < 2721
}
Here is a regex for your general culture. As other said, I would not recommend using it. You could do performance tests but I'd expect it to be slower than the integer comparison test.
^2(2[2-9]\d|[3-6]\d\d|7([0-1]\d|2[0-1]))
To construct this kind of regex you need to break your range into easily reprensentable strings ; 2221-2720 is :
2220 to 2299 (22[2-9]\d)
2300 to 2699 (2[3-6]\d\d)
2700 to 2719 (27[0-1]\d)
2720 to 2721 (272[0-1])
Then you can factorise it as I did or just use it as is, with each fragment separated by a |.

Javascript - String of a Byte with all combinations possible

i have a sting with a byte in it ("00001011") and now id like to get a array with all possible combinations of the 1 (acitve) "bits" in it also as a "byte string"
so from
var bString = "00001011"; //outgoing string
to a array with all string in it with all possible combinations of this "byte string" like - "00000001", "00000011", "00000010" and so on
is that possible?
thank you in advance
function combinations( input ){
var number = parseInt( input, 2 );
var combinations = [];
var zeroes = (new Array(input.length)).join(0);
for(var i=1;i<=number;i++){
if((i&number) == i){ combinations.push( i ) }
}
return combinations.map( function(dec){
return (zeroes + dec.toString(2)).substr( -zeroes.length-1 );
});
}
http://jsfiddle.net/jkf7pfxn/3/
console.log( combinations("00001011") );
// ["00000001", "00000010", "00000011", "00001000", "00001001", "00001010", "00001011"]
The idea goes as follows: iterate all numbers from 1 to the input number. If current number AND input number return the current number then both have 1 bits in the same place.
On a smaller number, "0101" (which is 5) it works as follows:
1 & 5 == 1, (0001 & 0101) push 1 to the matches.
2 & 5 == 0, (0010 & 0101) no match.
3 & 5 == 1, (0011 & 0101) no match.
4 & 5 == 4, (0100 & 0101) push 4 to the matches.
5 & 5 == 5, (0101 & 0101) push 5 to the matches.
So the combinations for 0101 are 1 (0001), 2 (0010), 4 (0100) and 5 (0101).
Then there's this little trick to pad numbers with zeroes:
var zeroes = (new Array(input.length)).join(0); // gives a long enough string of zeroes
then
// convert to base 2, add the zeroas at the beginning,
// then return the last n characters using negative value for substring
return (zeroes + dec.toString(2)).substr( -1 * zeroes.length);
Since 11111111 is 255 so just loop all values and convert them to binary
$(document).ready(function() {
for (var i = 0; i < 256; i++) {
$('#core').append('<div>' + dec2bin(i) + '</div>');
}
function dec2bin(dec) {
return ('00000000' + (dec >>> 0).toString(2)).slice(-8);
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id='core'></div>
If you want to enumerate all combinations of binary numbers where 1 can only be in the place of your pattern, you can write a simple recursive function:
var input = "00010111";
var current = [];
function combinations()
{
if (input.length === current.length)
{
var output = current.join('');
if (parseInt(output, 2) !== 0) // exclude all-zeroes case
document.body.innerHTML += output + "<br/>";
return;
}
current.push('0');
combinations();
current.pop();
if (input[current.length - 1] === '1')
{
current.push('1');
combinations();
current.pop();
}
}
combinations();
This algorithm works well for input of any length.
Although it is a recursion, it has a linear time complexity.

Algorithm for splitting words randomly into defined length groups

I am writing a program (in JavaScript) which needs to randomly split a string (a single word) into groups of letters, with each groups length (character count) being either 2,3 or 4 characters long. For example, australia could return:
aus
tral
ia
or
au
str
alia
Currently I am doing it "manually", with if statements for each string length, eg:
if (word.length == 4){ //split
sections.push(word.substr(0,2));
sections.push(word.substr(2,4));
}
if (word.length == 5){ //either 2/3 or 3/2
if (randomBetween(1,2) == 1){
sections.push(word.substr(0,2));
sections.push(word.substr(2,5));
} else {
sections.push(word.substr(0,3));
sections.push(word.substr(3,5));
}
}
etc...
// randomBetween(x,y) randomly returns one of the arguments
Does anyone have a more algorithmic solution?
Choose a random length from 2 to 4 iteratively to form a list of groups. Handle the edge cases when the remaining string is too small to have all of these options available.
Note that not every possible combination will be chosen with uniform probability. I don't think there's a trivial way to do so efficiently.
I'll leave it to you to choose what happens if a word is passed in with a length less than 2.
function randomlySplit(word) {
var groups = [],
tail = word;
while (tail.length) {
var availableLengths = [2, 3, 4];
if (tail.length <= 3) availableLengths = [tail.length];
if (tail.length === 4) availableLengths = [2];
if (tail.length === 5) availableLengths = [2, 3];
var length = availableLengths[(Math.random() * availableLengths.length) | 0];
groups.push(tail.slice(0, length));
tail = tail.slice(length);
}
return groups;
}
alert(randomlySplit("australia"));
You can see this in action on jsFiddle.
I made a commented function for you, I hope it'll help.
function randomlySplit(word) {
var parts = [];
// Loop as long as word exists
while(word.length) {
// Get an integer which is 2,3 or 4
var partLength = Math.floor(Math.random() * 3 + 2);
// See if only 1 char would be left
if(word.length - partLength === 1) {
// Either add or subtract 1 to partLength
if(partLength < 4) partLength++;
else partLength--;
}
// No issue that partLength > word.length
parts.push(word.substring(0,partLength));
word = word.substring(partLength);
}
return parts;
}
alert(randomlySplit("australia"));

Categories

Resources