Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

javascript

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.

like image 925
citywatcher Avatar asked Feb 02 '26 09:02

citywatcher


1 Answers

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!'} `);
});
like image 85
waterloos Avatar answered Feb 04 '26 00:02

waterloos