/**
* 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!'} `);
});