Working with equal occurrences of characters in a string of characters - javascript

I have a little problem here. I am solving some random questions from my book. Here is the task:
Task
A balanced string is one in which every character in the string appears an equal number of times as every other character. For example, "ab", "aaabbb" and "ababaabb" are balanced, but "abb" and "abbbaa" are not.
Additionally, strings may also include a wildcard character, "*". This wildcard character can represent any other character you wish. Furthermore, wildcards must represent another character; they cannot be left unused. A wild balanced string is a string in which all wildcards can be transformed into characters in such a way to produce a simple balanced string.
This challenge involves writing a function balanced(s) to check whether s is balanced.
Input is restricted to strings containing upper and lowercase alphabetical characters and the "*" wildcard character. The input string will match the regular expression
^[A-Za-z*]$*
Other Examples:
balanced("a") ⟹ true
balanced("ab") ⟹ true
balanced("abc") ⟹ true
balanced("abcb") ⟹ false
balanced("Aaa") ⟹ false
balanced("***********") ⟹ true
I have been able to get some answers but my algorithm is really failing me. I am thinking if there is anything I can do to adjust this code:
function balanced(s) {
const cMap = {};
for (let c of s) {
cMap[c] ? cMap[c]++ : (cMap[c] = 1);
}
const freq = new Set(Object.values(cMap));
if(s.includes('*')){
return true;
}
if (freq.size === 0 || freq.size === 1){
return true;
}
if (freq.size === 1) {
const max = Math.max(...freq);
const min = Math.min(...freq);
}
return false;
}

Proceeding mathematically
Another way to think about this is to simply do some arithmetic. To be balanced, each unique character after replacing the asterisks must occur the same number of times. So that number of times (counts) multiplied by the number of unique characters (letters) must equal the length of the input string (including the asterisks.) This count must be at least as large as the largest count of an individual character. And the number of letters in the output must be at least as large as the number of unique letters in the input. The only other restriction is that since the letters are taken from the lower- and upper-case letters, there can be no more than 52 of them.
In other words:
A string (of length `n`) is balanceable if
there exist positive integers `count` and `letters`
such that
`count` * `letters` = `n` and
`letters` <= 52 and
`letters` >= number of unique letters in the input (ignoring asterisks) and
`count` >= max of the counts of each individual (non-asterisk) letter in the input
With a helper function to find all the factor-pairs for a number, we can then write this logic directly:
// double counts [x, x] for x^2 -- not an issue for this problem
const factorPairs = (n) =>
[...Array (Math .floor (Math .sqrt (n)))] .map ((_, i) => i + 1)
.flatMap (f => n % f == 0 ? [[f, n / f], [n / f, f]] : [])
const balanced = ([...ss]) => {
const chars = [...new Set (ss .filter (s => s != '*'))]
const counts = ss .reduce (
(counts, s) => s == '*' ? counts : ((counts [s] += 1), counts),
Object .fromEntries (chars .map (l => [l, 0]))
)
const maxCount = Math.max (... Object.values (counts))
return factorPairs (ss .length) .some (
([count, letters]) =>
letters <= 52 &&
letters >= chars .length &&
count >= maxCount
)
}
const tests = [
'a', 'ab', 'abc', 'abcb', 'Aaa', '***********',
'****rfdd****', 'aaa**bbbb*', 'aaa**bbbb******',
'C****F***R***US***R**D***YS*****H***', 'C****F***R***US***R**D***YS*****H**',
'KSFVBX'
]
tests .forEach (s => console .log (`balanced("${s}") //=> ${balanced(s)}`))
.as-console-wrapper {max-height: 100% !important; top: 0}
factorPairs simply finds all the factoring of a number into ordered pairs of number. for instance, factorPairs (36) yields [[1, 36], [36, 1], [2, 18], [18, 2], [3, 12], [12, 3], [4, 9], [9, 4], [6, 6], [6, 6]]. Because we are only checking for the existence of one, we don't need to improve this function to return the values in a more logical order or to only return [6, 6] once (whenever the input is a perfect square.)
We test each result of the above (as [count, letters]) until we find one that matches and return true, or we make it through the list without finding one and return false.
Examples
So in testing this: 'C****F***R***US***R**D***YS*****H***', we have a string of length 36. We end up with these 8 unique characters: ['C', 'F', 'R', 'U', 'S', 'D', 'Y', 'H'], and these counts: {C: 1, F: 1, R: 2, U: 1, S: 2, D: 1, Y: 1, H: 1}, and our maxCount is 2
We then test the various factor-pairs generated for 36
count: 1, letters: 36 (fails because count is less than 2)
count: 36, letters: 1 (fails because letters is less than 8)
count: 2, letters: 18 (succeeds, and we return true)
And we don't need to test the remaining factor-pairs.
An example of using 18 letters, twice each could be:
C****F***R***US***R**D***YS*****H***
CCaabFFbcRcdUUSdeeRffDDgYYSghhiiHHjj - balanced
Note that this is not necessarily the only pair that will work. For instance, if we'd made it to count: 4, letters: 9, we could also make it work:
C****F***R***US***R**D***YS*****H***
CCCCFFFFRRRUUUSUDDRDYDSYYYSSxxxxHHHH - balanced
But the question is whether there was any such solution, so we stop when finding the first one.
If, on the other hand, we had one fewer asterisk in the input, we would test this: 'C****F***R***US***R**D***YS*****H**', with a length of 35. We end up with the same 8 unique characters: ['C', 'F', 'R', 'U', 'S', 'D', 'Y', 'H'], and these same counts: {C: 1, F: 1, R: 2, U: 1, S: 2, D: 1, Y: 1, H: 1}, and our maxCount is still 2.
We then test the various factor-pairs generated for 35
count: 1, letters: 35 (fails because count is less than 2)
count: 35, letters: 1 (fails because letters is less than 8)
count: 5, letters: 7 (fails because letters is less than 8)
count: 7, letters: 5 (fails because letters is less than 8)
and we've run out of factor-pairs, so we return false.
An alternate formulation
There's nothing particularly interesting in the code itself. It does the obvious thing at each step. (Although do note the destructuring of the input string to balanced, turning the String into an array of characters.) But it does something I generally prefer not to do, using assignment statements and a return statement. I prefer to work with expressions instead of statements as much as possible. I also prefer to extract helper functions, even if they're only used once, if they help clarify the flow. So I'm might rewrite as shown here:
const range = (lo, hi) =>
[... Array (hi - lo + 1)] .map ((_, i) => i + lo)
// double counts [x, x] for x^2 -- not an issue for this problem
const factorPairs = (n) =>
range (1, Math .floor (Math .sqrt (n)))
.flatMap (f => n % f == 0 ? [[f, n / f], [n / f, f]] : [])
const getUniqueChars = ([...ss]) =>
[... new Set (ss .filter (s => s != '*'))]
const maxOccurrences = ([...ss], chars) =>
Math.max (... Object .values (ss .reduce (
(counts, s) => s == '*' ? counts : ((counts [s] += 1), counts),
Object .fromEntries (chars .map (l => [l, 0]))
)))
const balanced = (
str,
chars = getUniqueChars (str),
maxCount = maxOccurrences (str, chars)
) => factorPairs (str .length) .some (
([count, letters]) =>
letters <= 52 &&
letters >= chars .length &&
count >= maxCount
)
const tests = [
'a', 'ab', 'abc', 'abcb', 'Aaa', '***********',
'****rfdd****', 'aaa**bbbb*', 'aaa**bbbb******',
'C****F***R***US***R**D***YS*****H***', 'C****F***R***US***R**D***YS*****H**',
'KSFVBX'
]
tests .forEach (s => console .log (`balanced("${s}") //=> ${balanced(s)}`))
.as-console-wrapper {max-height: 100% !important; top: 0}
But that changes nothing logically. The algorithm is the same.

Here is an algorithm that accomplishes this task:
First of all, sort the string:
var sorted = s.split("").sort().join("");
Now that the string is sorted, group all similar characters into an array. This is fairly easy to do using regular expressions:
var matches = sorted.match(/([A-Za-z])(\1)+/g);
If there are no matches (i.e. the string is empty or only has asterisks) then it is balanceable:
if (!matches) return true;
Next, get the number of asterisk * characters in the string:
var asterisks = sorted.match(/\*+/) ? sorted.match(/\*+/)[0].length : 0;
Now, find the most repeated character in the string and get the number of its occurences (i.e. find the mode of the string):
var maxocc = Math.max(...matches.map(match => match.length));
Calculate the number of required asterisks. This is done by subtracting the length of each of the matches from maxocc...
var reqAsterisks = matches.map(match => maxocc - match.length)
...then summing up the results:
.reduce((acc, val) => acc + val);
Get the number of extra asterisks by the subtracting the number of required asterisks from the total number of asterisks:
var remAsterisks = asterisks - reqAsterisks;
The question that arises now is, what to do with the remaining asterisks? You can either 1. distribute them evenly among the groups, 2. use them to create another group, or 3. do both at the same time. Note that you can do either or both of 1 and 2 multiple times. To do this, first define a variable that will hold the group length:
var groupLength = maxocc;
Then, repeatedly give each group one asterisk from the remaining asterisks. After that, check whether you can do 1 or 2 (described above) to get rid of the remaining asterisks. Everytime you do this, decrement remAsterisks by the number of asterisks you use and increment groupLength by one. This is accomplished by the following loop:
while(remAsterisks >= 0) {
if(remAsterisks == 0 || !(remAsterisks % matches.length) || remAsterisks == groupLength) {
return true;
} else {
remAsterisks -= matches.length;
groupLength++;
}
}
Here is the complete code
function balanced(s) {
var sorted = s.split("").sort().join("");
var matches = sorted.match(/([A-Za-z])(\1)*/g);
if (!matches) return true;
var asterisks = sorted.match(/\*+/) ? sorted.match(/\*+/)[0].length : 0;
var maxocc = Math.max(...matches.map(match => match.length));
var reqAsterisks = matches.map(match => maxocc - match.length).reduce((acc, val) => acc + val);
var remAsterisks = asterisks - reqAsterisks;
var groupLength = maxocc;
while(remAsterisks >= 0) {
if(remAsterisks == 0 || !(remAsterisks % matches.length) || remAsterisks == groupLength) {
return true;
} else {
remAsterisks -= matches.length;
groupLength++;
}
}
return false;
}
console.log(balanced("a"));
console.log(balanced("ab"));
console.log(balanced("abc"));
console.log(balanced("abcb"));
console.log(balanced("Aaa"));
console.log(balanced("***********"));
console.log(balanced("aaa**bbbb******));

You could count the characters and maintain a max count variable.
The return either the result of the length check of the keys with one or check every character without star by adjusting star count.
At the end check this property for falsyness to have either a zero count or just undefined.
function balanced(string) {
let max = 0,
stars = 0,
counts = [...string].reduce((r, c) => {
if (c === '*') {
stars++;
return r;
}
r[c] = (r[c] || 0) + 1;
if (max < r[c]) max = r[c];
return r;
}, {}),
keys = Object.keys(counts);
if (keys.length <= 1) return true;
return keys.every(c => {
if (counts[c] === max) return true;
if (stars >= max - counts[c]) {
stars -= max - counts[c];
return true;
}
}) && (!stars || stars % keys.length === 0);
}
console.log(balanced("a")); // true
console.log(balanced("ab")); // true
console.log(balanced("abc")); // true
console.log(balanced("***********")); // true
console.log(balanced("****rfdd****")); // true
console.log(balanced("aaa**bbbb*")); // true
console.log(balanced("abcb")); // false
console.log(balanced("Aaa")); // false
.as-console-wrapper { max-height: 100% !important; top: 0; }

This is a bit late as I was just wrapping this up as Nina posted. Posting for posterity.
Update
After editing this got a little longer than expected. The approach here is to first map the count of letters and number of wildcards. There are several checks going on but the main idea is to distribute wildcards and check the character count. There are 4 scenarios:
We evenly distribute all wildcards to each letter. (balanced)
We distribute enough wildcards such that the characterCount for each letter is equal to the remaining wildcards. (balanced)
There are simply not enough wildcards to distribute so that each letter can be balanced. (not balanced)
The remaining wildcards after distribution cannot be distributed to produce scenarios 1 or 2. (not balanced)
function balanced(s) {
const MAX = 52; // All lowercase and uppercase characters.
let wildcards = 0;
const map = {};
let characterCount = 0;
for (const char of s) {
if (char === '*') {
wildcards++;
} else {
if (!map[char]) {
map[char] = 0;
}
map[char]++;
if (map[char] > characterCount) {
characterCount = map[char];
}
}
}
const mapSize = Object.keys(map).length;
// Edge case: All characters are mapped and we see only 1 of each. This is balanced iff we can allocate wildcards uniformly.
if (mapSize === MAX && characterCount === 1) {
return wildcards % MAX === 0;
}
// Edge case: Not all characters are mapped and the number of wildcards is less than the count of remaining map slots.
if (mapSize < MAX && characterCount === 1 && wildcards <= (MAX - mapSize)) {
return true;
}
// Edge case: The string contains only wildcards. We can then assign all wildcards to 'a' and this will be balanced.
if (wildcards === s.length) {
return true;
}
for (const char in map) {
while (map[char] + 1 <= characterCount) {
map[char]++;
wildcards--;
}
// cannot distribute enough to balance out (scenario 3)
if (wildcards < 0) return false;
}
// If the remaining wildcards is a multiple (places) of the largest count, and the total count is less than MAX, this is balanced.
if (wildcards % characterCount === 0) {
const places = wildcards / characterCount;
if (mapSize + places <= MAX) {
return true;
}
}
/*
We can quickly check for scenario 2 by noting that it is equivalent to solving this equation for n
wildcards - n * mapSize === characterCount + n
*/
if (Number.isInteger((wildcards - characterCount) / (mapSize + 1))) {
return true;
}
// We distribute the most wildcards possible to each map entry.
wildcards -= parseInt(wildcards / mapSize);
// remaining wildcards cannot be distributed evenly (scenario 4)
if (wildcards && wildcards % mapSize !== 0) {
return false;
}
// remaining wildcards can be distributed evenly (scenario 1)
return true;
}
console.log(balanced("a")); // true
console.log(balanced("ab")); // true
console.log(balanced("ab*")); // true
console.log(balanced("abc")); // true
console.log(balanced("abcb")); // false
console.log(balanced("Aaa")); // false
console.log(balanced("***********")); // true
console.log(balanced("***KSFV***BX")); // true
console.log(balanced("C****F***R***US***R**D***YS*****H***")); // true
console.log(balanced("aaa**bbbb******")); // true
console.log(balanced("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ*")); // false
console.log(balanced("N****UWIQRXNW*QRE*")); // true
console.log(balanced("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ****************************************************")); // true

Here's my approach to solving this problem.
const isBalanced = string => {
const props = {};
let isBalanced = true;
const pattern = new RegExp(`^[A-Za-z\*]*$`);
if(pattern.test(string) && string.length <= 52){
const strArr = string.split("");
let previous = "";
while(strArr.length){
let current = strArr.shift();
if(current === "*"){
//transform all wildcards
if(previous.length > 0){
const propsArr = Object.keys(props);
let prevKey = "";
while(propsArr.length){
let key = propsArr.shift();
if(prevKey.length > 0){
//take the lowest value
let val = props[key] > props[prevKey] ? prevKey : key;
if(props[key] !== props[prevKey]){
//increment the value
props[val] = props[val] + 1;
break;
}else if(propsArr.length === 0){
strArr.push(val);
}
}
prevKey = key;
}//end while
}
}else{
if(!props[current]){
props[current] = 1;
}else{
props[current] = props[current] + 1;
}
previous = current;
}//end else
}//end while
}//end regex
if(Object.keys(props).length > 0 && props.constructor === Object){
const checkArr = Object.keys(props);
let previous = "";
while(checkArr.length){
let key = checkArr.shift();
if(previous.length > 0 && props[key] !== props[previous]){
isBalanced = false;
break;
}
previous = key;
}
}
return isBalanced;
}
console.log(isBalanced("a")); //true
console.log(isBalanced("ab")); //true
console.log(isBalanced("abc")); //true
console.log(isBalanced("abcb")); //false
console.log(isBalanced("Aaa")); //false
console.log(isBalanced("***********")); //true
console.log(isBalanced("aaa**bbbb******")); //flase
The aim is to arrange the strings into an object with the strings as the keys and the number of occurence as the value.

Related

Efficient way to check if a number's digits repeat X times?

I'm working with numbers like 0.3333333333333333, 0.1111111111111111, 0.6666666666666666 and I'd like to round them to the nearest hundredth while keeping numbers like 0.14285714285714285 intact.
Sorry if this question's been asked. It's gotta be a common question, but I can't seem to come up with the right keywords.
There may be a mathematical way to detect those, but you can detect those on the string version of the number using a regular expression like this: /^0\.(\d)\1+$/. That says
^0. Must start with 0.
(\d) Followed by a digit (we capture this digit)
\1+ Followed by the same digit (\1 refers back to the capture group) one or more times (+
Then grab just the first four characters of the string if you want to truncate (0.6666666666666666 becomes 0.66) or the first five and convert back to number and round (0.6666666666666666 becomes 0.67).
Live Example:
const numbers = [
0.3333333333333333,
0.1111111111111111,
0.6666666666666666,
0.14285714285714285,
0.1444444444444444,
];
for (const number of numbers) {
const str = String(number);
if (/^0\.(\d)\1+$/.test(str)) {
const truncated = Number(str.substring(0, 4));
const rounded = Math.round(Number(str.substring(0, 5) * 100)) / 100;
console.log(`Truncated: ${truncated}, rounded: ${rounded}`);
} else {
console.log(`Unchanged; ${number}`);
}
}
(Convert back to number if you need the number value.)
In modern environments, you can make that expression a bit clearer by using a named capture group rather than the traditional anonymous version above: /^0\.(?<digit>\d)\k<digit>+$/ In that, (?<digit>\d) is the capture group named "digit" and \d<digit> is a backreference to that capture group.
Live Example:
const numbers = [
0.3333333333333333,
0.1111111111111111,
0.6666666666666666,
0.14285714285714285,
0.1444444444444444
];
for (const number of numbers) {
const str = String(number);
if (/^0\.(?<digit>\d)\k<digit>+$/.test(str)) {
const truncated = Number(str.substring(0, 4));
const rounded = Math.round(Number(str.substring(0, 5) * 100)) / 100;
console.log(`Truncated: ${truncated}, rounded: ${rounded}`);
} else {
console.log(str);
}
}
There's question what you want done with 0.1444444444444444 (do you want 0.1444444444444444 or 0.14)? All your examples repeat starting at the ., but you're rounding to hundreths. Now, those are two separate things, but people are interpreting your question to say that 0.1444444444444444 should become 0.14. If so, it's a trivial change to allow any digit in the tens place: Just add \d after . in the expression: /^0\.\d(\d)\1+$/
Live Example:
const numbers = [
0.3333333333333333,
0.1111111111111111,
0.6666666666666666,
0.14285714285714285,
0.1444444444444444,
0.1555555555555555,
];
for (const number of numbers) {
const str = String(number);
if (/^0\.\d(\d)\1+$/.test(str)) {
const truncated = Number(str.substring(0, 4));
const rounded = Math.round(Number(str.substring(0, 5) * 100)) / 100;
console.log(`Truncated: ${truncated}, rounded: ${rounded}`);
} else {
console.log(str);
}
}
Since your question is not so clear, I took it as follows:
If there is a repeating chunk of digits, keep the pattern twice and
truncate the rest. If there is not repeating digits, rounding to hundreths.
For example, the following is what you get by running the proposed code. The suffix ... is to indicate the repeated digits.
0.14285714285714285 => 0.142857142857... (repeating pattern = 142857)
0.1444444444444444 => 0.144... (repeating pattern = 4)
0.3333333333333333 => 0.33... (repeating pattern = 3)
0.1428824114288241 => 0.1428824114288241... (repeating pattern = 14288241)
0.1288241128824112 => 0.12882411288241... (repeating pattern = 1288241)
0.12128824112882411 => 0.1212882411288241... (repeating pattern = 1288241)
0.1231231231231231 => 0.123123... (repeating pattern = 123)
0.101010101010101 => 0.1010... (repeating pattern = 10)
0.12300123123123123 => 0.12300123123... (repeating pattern = 123)
0.4254250042542542 => 0.42542500425425... (repeating pattern = 425)
0.1232435213443346 => 0.12 (repeating pattern = None)
I had to create the test case to make sure the code works for various patterns. The nums array contains the input and the expected answer.
You can use the code as
const {result, pattern} = testRepeatingDigits (0.1444444)
If you want to round the answer, you can modify the code where it returns the number string with ....
If you give me your requirement I can always edit and improve the answer.
Here is the complete code that you can run and see.
/**
* Returns the logest repeating substring from the beginning.
*/
function findLongestSubstring (str) {
let candidate = "";
for (let i = 1; i <= str.length - i; i++) {
if (str.indexOf(str.substring(0, i), i) === i)
candidate = str.substring(0, i);
}
return candidate;
}
/**
* Rotate the substring and find the left most matched point
*/
function rotateAndMoveLeft (str, substr, fromIndex) {
const rotate = (str) => `${str[str.length-1]}${str.slice(0, str.length-1)}`;
const lastIndex = substr.length - 1;
let rotatedStr = substr;
let pos;
// console.log(`str=${str}, substr=${substr}, fromIndex=${fromIndex}`);
for (pos = fromIndex - 1; pos >= 0; pos--) {
if (rotatedStr[lastIndex] === str[pos]) {
rotatedStr = rotate(rotatedStr);
} else {
pos++;
break;
}
}
const from = pos !== -1 ? pos : 0;
return {
subStr: rotatedStr,
from,
numMoved: fromIndex - from
};
}
function shrinkPattern (pattern) {
const _shrink = (head, tail) => {
if (tail.length === 0)
return head;
return tail.split(head).every(item => item.length === 0) ?
head : _shrink(`${head}${tail[0]}`, tail.slice(1));
}
return _shrink(pattern[0], pattern.slice(1));
}
function testRepeatingDigits (num) {
const str = num.toString();
const idx = str.indexOf('.');
if (idx < 0)
return false;
const digitStr = str.substring(idx + 1);
const [...digits] = digitStr;
// the first guess of repeating pattern from the right-most digit
const init = [...findLongestSubstring(digits.slice(0).reverse().join(''))].reverse().join('');
// no repeating patterns found
if (init.length === 0)
return {
result: (Math.round(num * 100) / 100).toString(),
pattern: "None"
};
// rotate the first guessed pattern to the left to find the beginning of the repeats
const searchFrom = digitStr.length - (init.length * 2);
const { subStr, from, numMoved } = searchFrom > 0 ?
rotateAndMoveLeft(digitStr, init, searchFrom) : { subStr: init, from: 0, numMoved: 0 };
// shrink the pattern to minimum
const pattern = shrinkPattern(subStr);
// truncate the digits overflows the two repeatings of the pattern
return {
result: `${str.substring(0, idx+1)}${digitStr.substring(0, from + pattern.length * 2)}...`,
pattern
};
}
// test cases
const nums = [{
num: 0.14285714285714285, // rep: 142857, truncated: [14285]
str: '0.142857142857...'
},
{
num: 0.1444444444444444, // rep: 4, truncated: [4444444444444]
str: '0.144...'
},
{
num: 0.3333333333333333, // rep: 3, truncated: [33333333333333]
str: '0.33...'
},
{
num: 0.1428824114288241, // rep: 14288241, truncated: []
str: '0.1428824114288241...'
},
{
num: 0.1288241128824112, // 1288241, [12]
str: '0.12882411288241...'
},
{
num: 0.12128824112882411, // 1288241, [1]
str: '0.1212882411288241...'
},
{
num: 0.1231231231231231, // 123, [1]
str: '0.123123...'
},
{
num: 0.1010101010101010, // 10, [101010101010]
str: '0.1010...'
},
{
num: 0.12300123123123123, // 123, [123123]
str: '0.12300123123...'
},
{
num: 0.4254250042542542, // 425, [42]
str: '0.42542500425425...'
},
{
num: 0.1232435213443346, // no repeat
str: '0.12'
},
];
nums.forEach(({ num, str }) => {
const { result, pattern } = testRepeatingDigits(num);
console.log(`${num} => ${result} (repeating pattern = ${pattern}) ${result === str ? 'OK' : 'Incorrect!'} `);
});
Not perfectly clear but here is my take.
It feels like you would like to test the floating part of given number against a repeating pattern. So perhaps you can do like;
function truncAtRepeat(n){
var [is,fs] = String(n).split("."),
index = (fs + fs).indexOf(fs,1);
return index === fs.length ? n
: parseFloat(is + "." + fs.slice(0,index));
}
console.log(truncAtRepeat(1.177177177177177));
console.log(truncAtRepeat(1.17717717717717));
console.log(truncAtRepeat(3.14159265359));
use a list and iterate over each number in the string and find the repeating numbers
def find_repeating_numbers(string):
# create a list of numbers
numbers = [int(x) for x in string]
# create a list of repeating numbers
repeating_numbers = []
# iterate through the list of numbers
for i in range(len(numbers)):
# if the number is already in the list of repeating numbers, continue
if numbers[i] in repeating_numbers:
continue
# if the number is not in the list of repeating numbers, check if it is repeated
else:
# if the number is repeated, add it to the list of repeating numbers
if numbers.count(numbers[i]) > 1:
repeating_numbers.append(numbers[i])
# return the list of repeating numbers
return repeating_numbers
data=[0.14285714285714285,0.1444444444444444,0.3333333333333333
,0.1428824114288241,0.1288241128824112,0.12128824112882411,0.1231231231231231
,0.101010101010101,0.12300123123123123,0.4254250042542542,0.1232435213443346
]
# print the list of repeating numbers
#print(find_repeating_numbers('14285714285714285'))
for item in data:
item=re.sub('0.','',str(item))
result=find_repeating_numbers(item)
repeating_number=''.join([str(n) for n in result])
print(item,repeating_number)
output:
14285714285714285 142857
1444444444444444 4
3333333333333333 3
1428824114288241 1428
1288241128824112 1284
12128824112882411 1284
1231231231231231 123
1
123123123123123 123
42542542542542 425
1232435213443346 1234

Searching through a repeated string

Lilah has a string, s, of lowercase English letters that she repeated infinitely many times.
Given an integer, n, find and print the number of letter 'a' in the first n letters of Lilah's infinite string.
This is my solution, but it is not correct, and I'm struggling to figure out why:
function repeatedString(s, n) {
let counter = 0;
const remainder = n % s.length;
const substring = s.substring(0, remainder);
const concatString = s + substring;
for (let letter of concatString) {
if (letter === 'a') {
counter++;
}
}
return (counter * n);
}
const str = "dhfgjhdfoiahwiuerhiguhzlkjvxzlkjghatriaeriaauih";
console.log(
repeatedString(str, 20)
);
I think it may be the
const concatString = s + substring;
would you please just reference the substring instead...
for (let letter of substring) {
if (letter === 'a') {
counter++;
}
}
return counter
You just need to loop through the s.substring(0, n) and return the counter value, (counter * n) doesn't make any sense.
function repeatedString(s, n) {
let counter = 0;
//const remainder = n % s.length;
const substring = s.substring(0, n);
console.log(substring);
//const concatString = s + substring;
for (let letter of substring) {
if (letter === 'a') {
counter++;
}
}
return counter;
}
const str = "dhfgjhdfoiahwiuerhiguhzlkjvxzlkjghatriaeriaauih";
console.log(
repeatedString(str, 20)
);
You can do that in following steps:
Get the count of the letter in given string. i.e c1.
Then get the part of the substring using slice(). The part will start of 0 up the the remainder of n and length of string.(c2)
Then multiply c1 with the number of times the given string will be in string of length n. i.e c1 * Math.floor(n/str.length)
Add the other count of remaining part c2 to the result and return
You can do that using filter() and check the count of given letter in given string. And then multiply it with no of times the string will repeat in for length n.
function func(str,l,n){
let c1 = [...str].filter(x => x === l).length;
let c2 = [...str.slice(0,n%str.length)].filter(x => x === l).length;
return (c1 * Math.floor(n/str.length)) + c2;
}
console.log(func('abcac','a',10))
This should give you the number of times a appears in the numbers of n length
const input = s;
var subStr = input.substr(0,n).split('');
console.log(subStr);
var output = 0;
subStr.forEach((e) => {
if (e === 'a') {
console.log(e);
output++;
}
})
console.log(output);
Using the next link as the source of the question:
https://medium.com/#yashka.troy/want-to-know-the-major-player-in-programming-18f2d35d91f7
Where it is explained better:
Lilah has a string, s, of lowercase English letters that she repeated infinitely many times.
Given an integer, n, find and print the number of letter a's in the first letters of Lilah's infinite string.
For example, if the string s=’abcac’ and n=10, the sub string we consider is ‘abcacabcac’, the first 10 characters of her infinite string. There are 4 occurrences of a in the substring.
A solution could be:
function repeatedString(s, n)
{
let res = 0;
const remainder = s.slice(0, n % s.length);
// Get number of times 'a' is present on "n / s.length" repetitions.
for (let letter of s)
{
if (letter === 'a') res++;
}
res *= Math.floor(n / s.length);
// Add the number of times 'a' is present on the remainder.
for (let letter of remainder)
{
if (letter === 'a') res++;
}
return res;
}
const str = "abca";
console.log(`${repeatedString(str, 10)} a's on first 10 letters:`);
console.log(`${repeatedString(str, 4)} a's on first 4 letters:`);
console.log(`${repeatedString(str, 0)} a's on first 0 letters:`);
console.log(`${repeatedString(str, 22)} a's on first 22 letters:`);
.as-console {background-color:black !important; color:lime;}
.as-console-wrapper {max-height:100% !important; top:0;}

How do I check if a string is made up exclusively of same-length character groups?

I want to identify strings that are made up exclusively of same-length character groups. Each one of these groups consists of at least two identical characters. So, here are some examples:
aabbcc true
abbccaa false
xxxrrrruuu false (too many r's)
xxxxxfffff true
aa true (shortest possible positive example)
aabbbbcc true // I added this later to clarify my intention
#ilkkachu: Thanks for your remark concerning the repetition of the same character group. I added the example above. Yes, I want the last sample to be tested as true: a string made up of the two letter groups aa, bb, bb, cc.
Is there a simple way to apply this condition-check on a string using regular expressions and JavaScript?
My first attempt was to do something like
var strarr=['aabbcc','abbccaa','xxxrrrruuu',
'xxxxxfffff','aa','negative'];
var rx=/^((.)\2+)+$/;
console.log(strarr.map(str=>str+': '+!!str.match(rx)).join('\n'));
It does look for groups of repeated characters but does not yet pay attention to these groups all being of the same length, as the output shows:
aabbcc: true
abbccaa: false
xxxrrrruuu: true // should be false!
xxxxxfffff: true
aa: true
aabbbbcc: true
negative: false
How do I get the check to look for same-length character groups?
To get all the groups of the same character has an easy regex solution:
/(.)\1*/g
Just repeating the backreference \1 of the character in capture group 1.
Then just check if there's a length in the array of same character strings that doesn't match up.
Example snippet:
function sameLengthCharGroups(str)
{
if(!str) return false;
let arr = str.match(/(.)\1*/g) //array with same character strings
.map(function(x){return x.length}); //array with lengths
let smallest_length = arr.reduce(function(x,y){return x < y ? x : y});
if(smallest_length === 1) return false;
return arr.some(function(n){return (n % smallest_length) !== 0}) == false;
}
console.log("-- Should be true :");
let arr = ['aabbcc','xxxxxfffff','aa'];
arr.forEach(function(s){console.log(sameLengthCharGroups(s)+' : '+ s)});
console.log("-- Should also be true :");
arr = ['aabbbbcc','224444','444422',
'666666224444666666','666666444422','999999999666666333'];
arr.forEach(function(s){console.log(sameLengthCharGroups(s)+' : '+ s)});
console.log("-- Should be false :");
arr = ['abbcc','xxxrrrruuu','a','ab','',undefined];
arr.forEach(function(s){console.log(sameLengthCharGroups(s)+' : '+ s)});
ECMAScript 6 version with fat arrows (doesn't work in IE)
function sameLengthCharGroups(str)
{
if(!str) return false;
let arr = str.match(/(.)\1*/g)
.map((x) => x.length);
let smallest_length = arr.reduce((x,y) => x < y ? x : y);
if(smallest_length === 1) return false;
return arr.some((n) => (n % smallest_length) !== 0) == false;
}
Or using exec instead of match, which should be faster for huge strings.
Since it can exit the while loop as soon a different length is found.
But this has the disadvantage that this way it can't get the minimum length of ALL the lengths before comparing them.
So those with the minimum length at the end can't be found as OK this way.
function sameLengthCharGroups(str)
{
if(!str) return false;
const re = /(.)\1*/g;
let m, smallest_length;
while(m = re.exec(str)){
if(m.index === 0) {smallest_length = m[0].length}
if(smallest_length > m[0].length && smallest_length % m[0].length === 0){smallest_length = m[0].length}
if(m[0].length === 1 ||
// m[0].length !== smallest_length
(m[0].length % smallest_length) !== 0
) return false;
}
return true;
}
console.log("-- Should be true :");
let arr = ['aabbcc','xxxxxfffff','aa'];
arr.forEach(function(s){console.log(sameLengthCharGroups(s)+' : '+ s)});
console.log("-- Should also be true :");
arr = ['aabbbbcc','224444','444422',
'666666224444666666','666666444422','999999999666666333'];
arr.forEach(function(s){console.log(sameLengthCharGroups(s)+' : '+ s)});
console.log("-- Should be false :");
arr = ['abbcc','xxxrrrruuu','a','ab','',undefined];
arr.forEach(function(s){console.log(sameLengthCharGroups(s)+' : '+ s)});
Here's one that runs in linear time:
function test(str) {
if (str.length === 0) return true;
let lastChar = str.charAt(0);
let seqLength = 1;
let lastSeqLength = null;
for (let i = 1; i < str.length; i++) {
if (str.charAt(i) === lastChar) {
seqLength++;
}
else if (lastSeqLength === null || seqLength === lastSeqLength) {
lastSeqLength = seqLength;
seqLength = 1;
lastChar = str.charAt(i);
}
else {
return false;
}
}
return (lastSeqLength === null || lastSeqLength === seqLength);
}
Since requirements changed or weren't clear as now this is the third solution I'm posting. To accept strings that could be divided into smaller groups like aabbbb we could:
Find all lengths of all different characters which are 2 and 4 in this case.
Push them into an array named d.
Find the lowest length in set named m.
Check if all values in d have no remainder when divided by m
Demo
var words = ['aabbbcccdddd', 'abbccaa', 'xxxrrrruuu', 'xxxxxfffff', 'aab', 'aabbbbccc'];
words.forEach(w => {
var d = [], m = Number.MAX_SAFE_INTEGER;
var s = w.replace(/(.)\1+/gy, x => {
d.push(l = x.length);
if (l < m) m = l;
return '';
});
console.log(w + " => " + (s == '' && !d.some(n => n % m != 0)));
});
Using sticky flag y and replace method you could do this much more faster. This trick replaces occurrences of first one's length with an empty string (and stops as soon as an occurrence with different length happens) then checks if there are some characters left:
var words = ['aabbcc', 'abbccaa', 'xxxrrrruuu', 'xxxxxfffff', 'aa'];
words.forEach(w => {
console.log(w + " => " + (w.replace(/(.)\1+/gy, ($0, $1, o) => {
return $0.length == (o == 0 ? l = $0.length : l) ? '' : $0;
}).length < 1));
});
Another workaround would be using replace() along with test(). First one replaces different characters with their corresponding length and the second looks for same repeated numbers in preceding string:
var str = 'aabbc';
/^(\d+\n)\1*$/.test(str.replace(/(.)\1+/gy, x => x.length + '\n'));
Demo:
var words = ['aabbcc', 'abbccaa', 'xxxrrrruuu', 'xxxxxfffff', 'aa'];
words.forEach(w =>
console.log(/^(\d+\n)\1*$/.test(w.replace(/(.)\1+/gy, x => x.length + '\n')))
);
Since regex has never been my forte here's an approach using String#replace() to add delimiter to string at change of letter and then use that to split into array and check that all elements in array have same length
const values = ['aabbcc', 'abbccaa', 'xxxrrrruuu', 'xxxxxfffff', 'aa'];
const expect = [true, false, false, true, true];
const hasMatchingGroups = (str) => {
if(!str || str.length %2) return false;
const groups = str.replace(/[a-z]/g,(match, offset, string) => {
return string[offset + 1] && match !== string[offset + 1] ? match + '|' : match;
}).split('|');
return groups.every(s => s.length === groups[0].length)
}
values.forEach((s, i) => console.log(JSON.stringify([s,hasMatchingGroups(s), expect[i]])))
The length of the repeated pattern of same charcters needs to be specified within the regular expression. The following snippet creates regular expressions looking for string lengths of 11 down to 2. The for-loop is exited once a match is found and the function returns the length of the pattern found:
function pat1(s){
for (var i=10;i;i--)
if(RegExp('^((.)\\2{'+i+'})+$').exec(s))
return i+1;
return false;}
If nothing is found false is returned.
If the length of the pattern is not required, the regular expression can also be set up in one go (without the need of the for loop around it):
function pat2(s){
var rx=/^((.)\2)+$|^((.)\4{2})+$|^((.)\6{4})+$|^((.)\8{6})+$/;
return !!rx.exec(s);
}
Here are the results from both tests:
console.log(strarr.map(str=>
str+': '+pat1(str)
+' '+pat2(str)).join('\n')+'\n');
aabbcc: 2 true
abbccaa: false false
xxxrrrruuu: false false
xxxxxfffff: 5 true
aa: 2 true
aabbbbcc: 2 true
negative: false false
The regex in pat2 looks for certain repetition-counts only. When 1, 2, 4 or 6 repetitions of a previous character are found then the result is positive. The found patterns have lengths of 2,3,5 or 7 characters (prime numbers!). With these length-checks any pattern-length dividable by one of these numbers will be found as positive (2,3,4,5,6,7,8,9,10,12,14,15,16,18,20,21,22,24,...).

Flipping 0's and 1's from a natural number

I want to create a javascript function to flip 1's to 0's in a natural number and I'm out of Ideas to achieve this,
Actually, I had a couple of URL's, and I replaced all 0's from a query parameter with 1's and now I no longer know the original parameter value, because there were few 1's in the original parameter value and now both are mixed, so basically I screwed myself,
The only solution for me is to try flipping each 1 to 0 and then 0's to 1's and test each number as the parameter.
This is the parameter value (after replacing 0's with 1's)
11422971
using above input I want to generate numbers as follows and test each of these
11422970
10422971
10422970
01422971
As you can see only 1's and 0's are changing, the change according to binary,
Each position in your string can be one of n characters:
A "0" can be either "0" or "1"
A "1" can be either "0" or "1"
Any other character c can only be c
We can store this in an array of arrays:
"11422971" -> [ ["0", "1"], ["0, "1"], ["4"], ... ]
To transform your string to this format, you can do a split and map:
const chars = "11422971"
.split("")
.map(c => c === "1" || c === "0" ? ["1", "0"] : [ c ]);
Once you got this format, the remaining logic is to create all possible combinations from this array. There are many ways to do so (search for "array combinations" or "permutations"). I've chosen to show a recursive pattern:
const chars = "11422971"
.split("")
.map(c =>
c === "1" || c === "0"
? ["1", "0"]
: [ c ]
);
const perms = ([ xs, ...others ], s = "", ps = []) =>
xs
? ps.concat(...xs.map(x => perms(others, s + x, ps)))
: ps.concat(s);
console.log(perms(chars));
you can do it with a number like a string, and after parse it, something like that
var number= "12551";
number= number.replace("1","0");
The result of number will be "02550"
after that parse number to int
This will generate all permutations.
const generatePermutations = (number) => {
let digits = number.split('');
// find out which digits can be flipped
let digitPositions = digits.reduce((acc, val, i) => {
if (val === '0' || val === '1') acc.push(i);
return acc;
}, []);
// we're going to be taking things in reverse order
digitPositions.reverse();
// how many digits can we flip
let noBits = digitPositions.length;
// number of permutations is 2^noBits i.e. 3 digits means 2^3 = 8 permutations.
let combinations = Math.pow(2, digitPositions.length);
let permutations = [];
// for each permutation
for (var p = 0; p < combinations; p++) {
// take a copy of digits for this permutation
permutations[p] = digits.slice();
// set each of the flippable bits according to the bit positions for this permutation
// i = 3 = 011 in binary
for (var i = 0; i < noBits; i++) {
permutations[p][digitPositions[i]] = '' + ((p >> i) & 1);
}
permutations[p] = permutations[p].join('');
}
return permutations;
};
console.log(generatePermutations('11422970'));
In case your looking for a recursive approach:
function recursive(acc, first, ...rest) {
if(!first) return acc;
if(first == '0' || first == '1') {
var acc0 = acc.map(x => x + '0');
var acc1 = acc.map(x => x + '1');
return recursive([].concat(acc0, acc1), ...rest);
} else {
return recursive(acc.map(x => x + first), ...rest);
}
}
recursive([''], ...'11422971')
// output ["00422970", "10422970", "01422970", "11422970", "00422971", "10422971", "01422971", "11422971"]
This just counts in binary and fills out a template for each value.
function getPossibleValues(str) {
function getResult(n) {
let nIndex = 0;
const strValue = n.toString(2).padStart(nDigits, '0');
return str.replace(rxMatch, () => strValue.charAt(nIndex++));
}
const rxMatch = /[01]/g;
const nDigits = str.length - str.replace(rxMatch, '').length;
const nMax = Math.pow(2, nDigits);
const arrResult = [];
for(let n = 0; n<nMax; n++) {
arrResult.push(getResult(n));
}
return arrResult;
}
console.log(getPossibleValues('11422970'));
Thank you all to respond, you saved my life, btw the approach I used was,
0- convert the number into a string. (so we can perform string operations like split())
1- count the number of 1's in the string (let's say the string is "11422971", so we get three 1's, I used split('1')-1 to count)
2- generate binary of three-digit length,(ie from 000 to 111). three came from step 1.
2- break down the string to single chars, (we'll get
array=['1','1','4','2','2','9','7','1'] )
3- take the first binary number (ie b=0b000)
4- replace first 1 from the character array with the first binary digit of b (ie replace 1 with 0), similarly replace second 1 with the second binary digit of b and so on.
5- we'll get the first combination (ie "00422970")
5- repeat step 3 and 4 for all binary numbers we generated in step 2.

Convert number to alphabet letter

I want to convert a number to its corresponding alphabet letter. For example:
1 = A
2 = B
3 = C
Can this be done in javascript without manually creating the array?
In php there is a range() function that creates the array automatically. Anything similar in javascript?
Yes, with Number#toString(36) and an adjustment.
var value = 10;
document.write((value + 9).toString(36).toUpperCase());
You can simply do this without arrays using String.fromCharCode(code) function as letters have consecutive codes. For example: String.fromCharCode(1+64) gives you 'A', String.fromCharCode(2+64) gives you 'B', and so on.
Snippet below turns the characters in the alphabet to work like numerical system
1 = A
2 = B
...
26 = Z
27 = AA
28 = AB
...
78 = BZ
79 = CA
80 = CB
var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
var result = ""
function printToLetter(number){
var charIndex = number % alphabet.length
var quotient = number/alphabet.length
if(charIndex-1 == -1){
charIndex = alphabet.length
quotient--;
}
result = alphabet.charAt(charIndex-1) + result;
if(quotient>=1){
printToLetter(parseInt(quotient));
}else{
console.log(result)
result = ""
}
}
I created this function to save characters when printing but had to scrap it since I don't want to handle improper words that may eventually form
Just increment letterIndex from 0 (A) to 25 (Z)
const letterIndex = 0
const letter = String.fromCharCode(letterIndex + 'A'.charCodeAt(0))
console.log(letter)
UPDATE (5/2/22): After I needed this code in a second project, I decided to enhance the below answer and turn it into a ready to use NPM library called alphanumeric-encoder. If you don't want to build your own solution to this problem, go check out the library!
I built the following solution as an enhancement to #esantos's answer.
The first function defines a valid lookup encoding dictionary. Here, I used all 26 letters of the English alphabet, but the following will work just as well: "ABCDEFG", "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", "GFEDCBA". Using one of these dictionaries will result in converting your base 10 number into a base dictionary.length number with appropriately encoded digits. The only restriction is that each of the characters in the dictionary must be unique.
function getDictionary() {
return validateDictionary("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
function validateDictionary(dictionary) {
for (let i = 0; i < dictionary.length; i++) {
if(dictionary.indexOf(dictionary[i]) !== dictionary.lastIndexOf(dictionary[i])) {
console.log('Error: The dictionary in use has at least one repeating symbol:', dictionary[i])
return undefined
}
}
return dictionary
}
}
We can now use this dictionary to encode our base 10 number.
function numberToEncodedLetter(number) {
//Takes any number and converts it into a base (dictionary length) letter combo. 0 corresponds to an empty string.
//It converts any numerical entry into a positive integer.
if (isNaN(number)) {return undefined}
number = Math.abs(Math.floor(number))
const dictionary = getDictionary()
let index = number % dictionary.length
let quotient = number / dictionary.length
let result
if (number <= dictionary.length) {return numToLetter(number)} //Number is within single digit bounds of our encoding letter alphabet
if (quotient >= 1) {
//This number was bigger than our dictionary, recursively perform this function until we're done
if (index === 0) {quotient--} //Accounts for the edge case of the last letter in the dictionary string
result = numberToEncodedLetter(quotient)
}
if (index === 0) {index = dictionary.length} //Accounts for the edge case of the final letter; avoids getting an empty string
return result + numToLetter(index)
function numToLetter(number) {
//Takes a letter between 0 and max letter length and returns the corresponding letter
if (number > dictionary.length || number < 0) {return undefined}
if (number === 0) {
return ''
} else {
return dictionary.slice(number - 1, number)
}
}
}
An encoded set of letters is great, but it's kind of useless to computers if I can't convert it back to a base 10 number.
function encodedLetterToNumber(encoded) {
//Takes any number encoded with the provided encode dictionary
const dictionary = getDictionary()
let result = 0
let index = 0
for (let i = 1; i <= encoded.length; i++) {
index = dictionary.search(encoded.slice(i - 1, i)) + 1
if (index === 0) {return undefined} //Attempted to find a letter that wasn't encoded in the dictionary
result = result + index * Math.pow(dictionary.length, (encoded.length - i))
}
return result
}
Now to test it out:
console.log(numberToEncodedLetter(4)) //D
console.log(numberToEncodedLetter(52)) //AZ
console.log(encodedLetterToNumber("BZ")) //78
console.log(encodedLetterToNumber("AAC")) //705
UPDATE
You can also use this function to take that short name format you have and return it to an index-based format.
function shortNameToIndex(shortName) {
//Takes the short name (e.g. F6, AA47) and converts to base indecies ({6, 6}, {27, 47})
if (shortName.length < 2) {return undefined} //Must be at least one letter and one number
if (!isNaN(shortName.slice(0, 1))) {return undefined} //If first character isn't a letter, it's incorrectly formatted
let letterPart = ''
let numberPart= ''
let splitComplete = false
let index = 1
do {
const character = shortName.slice(index - 1, index)
if (!isNaN(character)) {splitComplete = true}
if (splitComplete && isNaN(character)) {
//More letters existed after the numbers. Invalid formatting.
return undefined
} else if (splitComplete && !isNaN(character)) {
//Number part
numberPart = numberPart.concat(character)
} else {
//Letter part
letterPart = letterPart.concat(character)
}
index++
} while (index <= shortName.length)
numberPart = parseInt(numberPart)
letterPart = encodedLetterToNumber(letterPart)
return {xIndex: numberPart, yIndex: letterPart}
}
this can help you
static readonly string[] Columns_Lettre = new[] { "A", "B", "C"};
public static string IndexToColumn(int index)
{
if (index <= 0)
throw new IndexOutOfRangeException("index must be a positive number");
if (index < 4)
return Columns_Lettre[index - 1];
else
return index.ToString();
}

Categories