Algorithm for splitting words randomly into defined length groups - javascript

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"));

Related

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);
}

Generate random & unique 4 digit codes without brute force

I'm building an app and in one of my functions I need to generate random & unique 4 digit codes. Obviously there is a finite range from 0000 to 9999 but each day the entire list will be wiped and each day I will not need more than the available amount of codes which means it's possible to have unique codes for each day. Realistically I will probably only need a few hundred codes a day.
The way I've coded it for now is the simple brute force way which would be to generate a random 4 digit number, check if the number exists in an array and if it does, generate another number while if it doesn't, return the generated number.
Since it's 4 digits, the runtime isn't anything too crazy and I'm mostly generating a few hundred codes a day so there won't be some scenario where I've generated 9999 codes and I keep randomly generating numbers to find the last remaining one.
It would also be fine to have letters in there as well instead of just numbers if it would make the problem easier.
Other than my brute force method, what would be a more efficient way of doing this?
Thank you!
Since you have a constrained number of values that will easily fit in memory, the simplest way I know of is to create a list of the possible values and select one randomly, then remove it from the list so it can't be selected again. This will never have a collision with a previously used number:
function initValues(numValues) {
const values = new Array(numValues);
// fill the array with each value
for (let i = 0; i < values.length; i++) {
values[i] = i;
}
return values;
}
function getValue(array) {
if (!array.length) {
throw new Error("array is empty, no more random values");
}
const i = Math.floor(Math.random() * array.length);
const returnVal = array[i];
array.splice(i, 1);
return returnVal;
}
// sample code to use it
const rands = initValues(10000);
console.log(getValue(rands));
console.log(getValue(rands));
console.log(getValue(rands));
console.log(getValue(rands));
This works by doing the following:
Generate an array of all possible values.
When you need a value, select one from the array with a random index.
After selecting the value, remove it from the array.
Return the selected value.
Items are never repeated because they are removed from the array when used.
There are no collisions with used values because you're always just selecting a random value from the remaining unused values.
This relies on the fact that an array of integers is pretty well optimized in Javascript so doing a .splice() on a 10,000 element array is still pretty fast (as it can probably just be memmove instructions).
FYI, this could be made more memory efficient by using a typed array since your numbers can be represented in 16-bit values (instead of the default 64 bits for doubles). But, you'd have to implement your own version of .splice() and keep track of the length yourself since typed arrays don't have these capabilities built in.
For even larger problems like this where memory usage becomes a problem, I've used a BitArray to keep track of previous usage of values.
Here's a class implementation of the same functionality:
class Randoms {
constructor(numValues) {
this.values = new Array(numValues);
for (let i = 0; i < this.values.length; i++) {
this.values[i] = i;
}
}
getRandomValue() {
if (!this.values.length) {
throw new Error("no more random values");
}
const i = Math.floor(Math.random() * this.values.length);
const returnVal = this.values[i];
this.values.splice(i, 1);
return returnVal;
}
}
const rands = new Randoms(10000);
console.log(rands.getRandomValue());
console.log(rands.getRandomValue());
console.log(rands.getRandomValue());
console.log(rands.getRandomValue());
Knuth's multiplicative method looks to work pretty well: it'll map numbers 0 to 9999 to a random-looking other number 0 to 9999, with no overlap:
const hash = i => i*2654435761 % (10000);
const s = new Set();
for (let i = 0; i < 10000; i++) {
const n = hash(i);
if (s.has(n)) { console.log(i, n); break; }
s.add(n);
}
To implement it, simply keep track of an index that gets incremented each time a new one is generated:
const hash = i => i*2654435761 % (10000);
let i = 1;
console.log(
hash(i++),
hash(i++),
hash(i++),
hash(i++),
hash(i++),
);
These results aren't actually random, but they probably do the job well enough for most purposes.
Disclaimer:
This is copy-paste from my answer to another question here. The code was in turn ported from yet another question here.
Utilities:
function isPrime(n) {
if (n <= 1) return false;
if (n <= 3) return true;
if (n % 2 == 0 || n % 3 == 0) return false;
for (let i = 5; i * i <= n; i = i + 6) {
if (n % i == 0 || n % (i + 2) == 0) return false;
}
return true;
}
function findNextPrime(n) {
if (n <= 1) return 2;
let prime = n;
while (true) {
prime++;
if (isPrime(prime)) return prime;
}
}
function getIndexGeneratorParams(spaceSize) {
const N = spaceSize;
const Q = findNextPrime(Math.floor(2 * N / (1 + Math.sqrt(5))))
const firstIndex = Math.floor(Math.random() * spaceSize);
return [firstIndex, N, Q]
}
function getNextIndex(prevIndex, N, Q) {
return (prevIndex + Q) % N
}
Usage
// Each day you bootstrap to get a tuple of these parameters and persist them throughout the day.
const [firstIndex, N, Q] = getIndexGeneratorParams(10000)
// need to keep track of previous index generated.
// it’s a seed to generate next one.
let prevIndex = firstIndex
// calling this function gives you the unique code
function getHashCode() {
prevIndex = getNextIndex(prevIndex, N, Q)
return prevIndex.toString().padStart(4, "0")
}
console.log(getHashCode());
Explanation
For simplicity let’s say you want generate non-repeat numbers from 0 to 35 in random order. We get pseudo-randomness by polling a "full cycle iterator"†. The idea is simple:
have the indexes 0..35 layout in a circle, denote upperbound as N=36
decide a step size, denoted as Q (Q=23 in this case) given by this formula‡
Q = findNextPrime(Math.floor(2 * N / (1 + Math.sqrt(5))))
randomly decide a starting point, e.g. number 5
start generating seemingly random nextIndex from prevIndex, by
nextIndex = (prevIndex + Q) % N
So if we put 5 in we get (5 + 23) % 36 == 28. Put 28 in we get (28 + 23) % 36 == 15.
This process will go through every number in circle (jump back and forth among points on the circle), it will pick each number only once, without repeating. When we get back to our starting point 5, we know we've reach the end.
†: I'm not sure about this term, just quoting from this answer
‡: This formula only gives a nice step size that will make things look more "random", the only requirement for Q is it must be coprime to N
This problem is so small I think a simple solution is best. Build an ordered array of the 10k possible values & permute it at the start of each day. Give the k'th value to the k'th request that day.
It avoids the possible problem with your solution of having multiple collisions.

Codewars division Kata using Javascript is producing results that are not divisble by 6

I am trying to solve this Kata from Codewars: https://www.codewars.com/kata/simple-fun-number-258-is-divisible-by-6/train/javascript
The idea is that a number (expressed as a string) with one digit replaced with *, such as "1047*66", will be inserted into a function. You must return an array in which the values are the original number with the * replaced with any digit that will produce a number divisive by 6. So given "1*0", the correct resulting array should be [120, 150, 180].
I have some code that is producing some correct results but erroring for others, and I can't figure out why. Here's the code:
function isDivisibleBy6(s) {
var results = [];
for(i=0;i<10;i++) {
var string = i.toString(); // Convert i to string, ready to be inserted into s
var array = Array.from(s); // Make an array from s
var index = array.indexOf("*"); // Find where * is in the array of s
array[index] = string; // Replace * with the string of i
var number = array.join(""); // Join all indexes of the s array back together. Now we should have
// a single number expressed as a string, with * replaced with i
parseInt(number, 10); // Convert the string to an integer
if((number % 6) == 0) {
results.push(number);
} // If the integer is divisible by 6, add the integer into the results array
}
return(results);
};
This code works with the above example and generally with all smaller numbers. But it is producing errors for larger numbers. For example, when s is "29070521868839*57", the output should be []. However, I am getting ['29070521868839257', '29070521868839557', '29070521868839857']. I can't figure out where this would be going wrong. Is anyone able to help?
The problem is that these numbers are larger than the Number.MAX_SAFE_INTEGER - the point when JavaScript numbers break down in terms of reliability:
var num = 29070521868839257;
console.log(num > Number.MAX_SAFE_INTEGER);
console.log(num % 6);
console.log(num)
The last log shows that the num actually has a different value than what we gave it. This is because 29070521868839257 simply cannot be represented by a JavaScript number, hence you get the closest possible value that can be represented and that's 29070521868839256.
So, after some point in numbers, all mathematical operations become unreliable as the very numbers are imprecise.
What you can do instead is ignore treating this whole as a number - treat it as a string and only apply the principles of divisibility. This makes the task vastly easier.
For a number to be divisible by 6 it has to cover two criteria:
it has to be divisible by 2.
to verify this, you can just get the very smallest digit and check if it's divisible by 2. For example in 29070521868839257 if we take 7, and check 7 % 2, we get 1 which means that it's odd. We don't need to consider the whole number.
it has to be divisible by 3.
to verify this, you can sum each of the digits and see if that sum is divisible by 3. If we sum all the digits in 29070521868839257 we get 2 + 9 + 0 + 7 + 0 + 5 + 2 + 1 + 8 + 6 + 8 + 8 + 3 + 9 + 2 + 5 + 7 = 82 which is not divisible by 3. If in doubt, we can sum the digits again, since the rule can be applied to any number with more than two digits: 8 + 2 = 10 and 1 + 0 = 1. That is still not divisible by 3.
So, if we apply these we can get something like:
function isDivisibleBy6(s) {
return isDivisibleBy2(s) && isDivisibleBy3(s);
};
function isDivisibleBy2(s) {
var lastDigit = Number(s.slice(-1));
return (lastDigit % 2) === 0;
}
function isDivisibleBy3(s) {
var digits = s.split("")
.map(Number);
var sum = digits.reduce(function(a, b) {
return a + b
});
return (sum % 3) === 0;
}
console.log(isDivisibleBy6("29070521868839257"));
console.log(isDivisibleBy6("29070521868839256"));
These can even be recursively defined true to the nature of these rules:
function isDivisibleBy6(s) {
return isDivisibleBy2(s) && isDivisibleBy3(s);
};
function isDivisibleBy2(s) {
if (s.length === 0) {
return false;
}
if (s.length > 1) {
return isDivisibleBy2(s.slice(-1));
}
var lastDigit = Number(s);
return (lastDigit % 2) === 0;
}
function isDivisibleBy3(s) {
if (s.length === 0) {
return false;
}
if (s.length > 1) {
var digits = s.split("")
.map(Number);
var sum = digits.reduce(function(a, b) {
return a + b
});
return isDivisibleBy3(String(sum));
}
var num = Number(s);
return (num % 3) === 0;
}
console.log(isDivisibleBy6("29070521868839257"));
console.log(isDivisibleBy6("29070521868839256"));
This is purely to demonstrate the rules of division and how they can be applied to strings. You have to create numbers that will be divisible by 6 and to do that, you have to replace an asterisk. The easiest way to do it is like you did - generate all possibilities (e.g., 1*0 will be 100, 110, 120, 130, 140, 150, 160, 170, 180, 190) and then filter out whatever is not divisible by 6:
function isDivisibleBy6(s) {
var allDigits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
var allPossibleNumbers = allDigits.map(function(digit) {
return s.replace("*", digit);
});
var numbersDibisibleBySix = allPossibleNumbers.filter(function(s) {
return isDivisibleBy2(s) && isDivisibleBy3(s);
})
return numbersDibisibleBySix;
};
function isDivisibleBy2(s) {
var lastDigit = Number(s.slice(-1));
return (lastDigit % 2) === 0;
}
function isDivisibleBy3(s) {
var digits = s.split("")
.map(Number);
var sum = digits.reduce(function(a, b) {
return a + b
});
return (sum % 3) === 0;
}
console.log(isDivisibleBy6("29070521868839*57"));
console.log(isDivisibleBy6("29070521868839*56"));
As a last note, this can be written more concisely by removing intermediate values and using arrow functions:
function isDivisibleBy6(s) {
return [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
.map(digit => s.replace("*", digit))
.filter(s => isDivisibleBy2(s) && isDivisibleBy3(s));
};
const isDivisibleBy2 = s => Number(s.slice(-1)) % 2 === 0;
const isDivisibleBy3 = s => s.split("")
.map(Number)
.reduce((a, b) => a + b) % 3 === 0
console.log(isDivisibleBy6("29070521868839*57"));
console.log(isDivisibleBy6("29070521868839*56"));
Sum of all digits is divisible by three and the last digit is divisible by two.
An approach:
Get the index of the star.
Get left and right string beside of the star.
Return early if the last digit is not divisible by two.
Take the sum of all digits.
Finally create an array with missing digits:
Start loop from either zero (sum has no rest with three) or take the delta of three and the rest (because you want a number which is divisible by three).
Go while value is smaller then ten.
Increase the value either by 3 or by 6, if the index of the star is the last character.
Take left, value and right part for pushing to the result set.
Return result.
function get6(s) {
var index = s.indexOf('*'),
left = s.slice(0, index),
right = s.slice(index + 1),
result = [],
sum = 0,
i, step;
if (s[s.length - 1] % 2) return [];
for (i = 0; i < s.length; i++) if (i !== index) sum += +s[i];
i = sum % 3 && 3 - sum % 3;
step = s.length - 1 === index ? 6 : 3;
for (; i < 10; i += step) result.push(left + i + right);
return result;
}
console.log(get6("*")); // ["0", "6"]
console.log(get6("10*")); // ["102", "108"]
console.log(get6("1*0")); // ["120", "150", "180"]
console.log(get6("*1")); // []
console.log(get6("1234567890123456789012345678*0")); // ["123456789012345678901234567800","123456789012345678901234567830","123456789012345678901234567860","123456789012345678901234567890"]
.as-console-wrapper { max-height: 100% !important; top: 0; }
The problem is with:
parseInt(number, 10);
You can check and see that when number is large enough, this result converted back to string is not equal to the original value of number, due to the limit on floating point precision.
This challenge can be solved without having to convert the given string to number. Instead use a property of numbers that are multiples of 6. They are multiples of 3 and even. Multiples of 3 have the property that the sum of the digits (in decimal representation) are also multiples of 3.
So start by checking that the last digit is not 1, 3, 5, 7, or 9, because in that case there is no solution.
Otherwise, sum up the digits (ignore the asterisk). Determine which value you still need to add to that sum to get to a multiple of 3. This will be 0, 1 or 2. If the asterisk is not at the far right, produce solutions with this digit, and 3, 6, 9 added to it (until you get double digits).
If the asterisk is at the far right, you can do the same, but you must make sure that you exclude odd digits in that position.
If you are desperate, here is a solution. But I hope you can make it work yourself.
function isDivisibleBy6(s) {
// If last digit is odd, it can never be divisable by 6
if ("13579".includes(s[s.length-1])) return [];
let [left, right] = s.split("*");
// Calculate the sum of the digits (ignore the asterisk)
let sum = 0;
for (let ch of s) sum += +ch || 0;
// What value remains to be added to make the digit-sum a multiple of 3?
sum = (3 - sum%3) % 3;
// When asterisk in last position, then solution digit are 6 apart, otherwise 3
let mod = right.length ? 3 : 6;
if (mod === 6 && sum % 2) sum += 3; // Don't allow odd digit at last position
// Build the solutions, by injecting the found digit values
let result = [];
for (; sum < 10; sum += mod) result.push(left + sum + right);
return result;
}
// Demo
console.log(isDivisibleBy6("1234567890123456789012345678*0"));
BigInt
There is also another way to get around the floating point precision problem: use BigInt instead of floating point. However, BigInt is not supported on CodeWars, at least not in that specific Kata, where the available version of Node goes up to 8.1.3, while BigInt was introduced only in Node 10.
function isDivisibleBy6(s) {
let [left, right] = s.split("*");
let result = [];
for (let i = 0; i < 10; i++) {
let k = BigInt(left + i + right);
if (k % 6n === 0n) result.push(k.toString());
}
return result;
}
// Demo
console.log(isDivisibleBy6("1234567890123456789012345678*0"));
This would anyway feel like "cheating" (if it were accepted), as it's clearly not the purpose of the Kata.
As mentioned, the values you are using are above the maximum integer value and therefore unsafe, please see the docmentation about this over here Number.MAX_SAFE_INTEGER. You can use BigInt(string) to use larger values.
Thanks for all the responses. I have now created successful code!
function isDivisibleBy6(s) {
var results = [];
for(i=0;i<10;i++) {
var string = i.toString();
var array = Array.from(s);
var index = array.indexOf("*");
array[index] = string;
var div2 = 0;
var div3 = 0;
if(parseInt((array[array.length-1]),10) % 2 == 0) {
div2 = 1;
}
var numarray = array.map((x) => parseInt(x));
if(numarray.reduce(function myFunc(acc, value) {return acc+value}) % 3 == 0) {
div3 = 1;
}
if(div2 == 1 && div3 == 1) {
results.push(array.join(""));
}
}
return(results);
};
I know this could be factored down quite a bit by merging the if expressions together, but I like to see things split out so that when I look back over previous solutions my thought process is clearer.
Thanks again for all the help!

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.

Need help writing code to convert decimal to binary without the use of the toString

I'm trying to create my own decimal to binary converter with the method of decrementing the inputted variable (decimal value), by dividing it by 2 and storing the remainder (like 2nd grade math remainder), which is always either 0 or 1. Each of the remainder values i thin should be stored in an array and I think maybe put in backwards so that the most significant digit is first in the array (this is because when decrementing the remainer values are filled in left to right). Soooo yea i dont really know how to store the remainder values in an array using a function
Thanks in advance and if something is confusing then feel free to ask because im not even sure if this is the best method of doing this its just what i came up with
function decimalToBinary(num) {
var bin = 0;
while (num > 0) {
bin = num % 2 + bin;
num >>= 1; // basically /= 2 without remainder if any
}
alert("That decimal in binary is " + bin);
}
Your code is almost correct. The main problem is that bin starts out as 0; when you add a digit, they are added numerically, so your code ends up just counting the binary 1s: in this manner, 10 is initial 0, and +1+0+1+0, resulting in 2. You want to handle it as a string: ""+1+0+1+0 results in 1010. So, the only needed change is:
var bin = "";
If you want to solve it using arrays, with minimal changes to your code, it would be:
function decimalToBinary(num) {
var bin = [];
while (num > 0) {
bin.unshift(num % 2);
num >>= 1; // basically /= 2 without remainder if any
}
alert("That decimal in binary is " + bin.join(''));
}
Here, I use .unshift to add an element to the head of the array (and renumbering the remaining elements); .join() to collect them all into a string.
Or this:
function decimalToBinary(num) {
var bin = [];
while (num > 0) {
bin[bin.length] = num % 2;
num >>= 1; // basically /= 2 without remainder if any
}
alert("That decimal in binary is " + bin.reverse().join(''));
}
This is not as good, but illustrates some more things you can do with arrays: taking their length, setting an arbitrary element, and flipping them around.
I have written a custom Decimal to Binary method:
function toBinary (input) {
let options = [1];
let max = 0;
let i = 1;
while(i) {
max = Math.pow(2, i);
if (max > input) break;
options.push(max);
i++;
}
let j = options.length;
let result = new Array(j);
result.fill("0");
while(j >= 0) {
if (options[j] <= input) {
result[j] = "1"
input = input - options[j];
}
j--;
}
return [...result].reverse().join("");
}
//Test the toBin method with built-in toString(2)
toBinary(100) === (100).toString(2) // true
toBinary(1) === (1).toString(2) // true
toBinary(128) === (128).toString(2) // true

Categories

Resources