Число (целое или десятичное) в массиве, массив в число (целое или десятичное) без использования строк - PullRequest
0 голосов
/ 30 января 2019

Требование:

Преобразование входного целого или десятичного числа в массив и преобразование массива целых чисел, которые могут включать десятичное число в число.

Ограничение:

Не используйте строковые методы и не конвертируйте ввод или вывод в строку во время процедуры (добровольное ограничение, применяемое в каждой версии составленного кода).

Контекст и варианты использования

BigInt доступны в некоторых браузерах, но не BigDecimal.Преобразование из целого числа или десятичного числа в массив и массива в целое число или десятичное должно быть возможно с использованием языка программирования JavaScript.Входные и выходные данные не должны быть преобразованы в строку во время процедуры.

Возможность настроить nth цифру целого или десятичного числа путем корректировки десятичного или целого числа в nth индекс массива, чтобы попытаться решить OEIS A217626 непосредственно, например

~~(128.625*9*1.074)//1243
~~(128.625*9*1.144)//1324

, где десятичной частью можно манипулировать, ссылаясь на индекс массива, а затем преобразовывая массив обратно в число.

Текущей спецификацией является WIP иможно было бы назвать сложным описание, относящееся к обработке десятичной части входных данных, особенно там, где есть начальные нули.

Input <----------> Output

-123               [-1,-2,-3]
4.4                [4,0.4]
44.44              [4,4,0.4,4]
-0.01              [-0.01]
123                [1,2,3]
200                [2,0,0]
2.718281828459     [2,0.7,1,8,2,8,1,8,2,8,4,5,8,9]
321.7000000001     [3,2,1,0.7,0,0,0,0,0,0,0,0,1]
809.56             [8,0,9,0.5,6]
1.61803398874989   [1,0.6,1,8,0,3,3,9,8,8,7,4,9,8,9]
1.999              [1,0.9,9,9]
100.01             [1,0,0,0.01]
545454.45          [5,4,5,4,5,4,0.4,5]
-7                 [-7]
-83.782            [-8,-3,-0.7,-8,-2]
1.5                [1,0.5]
100.0001           [1,0,0,0.0001]

По сути, я пытаюсь распространить целое или десятичное число в массив.Функция, которая преобразует число или целое число в массив, должна быть способна преобразовываться в функцию генератора, чтобы можно было достичь

[...Math.E] -> [2, 0.7, 1, 8, 2, 8, 1, 8, 2, 8, 4, 5, 9] -> 2.718281828459

, установив для функции значение от Number.prototype[Symbol.iterator] до numberToArray.

Самая последняя версия кода (некоторые концепции и оригинальные версии кода были основаны на вопросах и ответах в Получить десятичную часть числа с помощью JavaScript ; Преобразование значения int в String без использования toString и метода parseInt ; Преобразование целого числа в массив цифр ), который имеет две ошибки при результирующем выводе тестовых случаев из arrayToNumber 100.05010000000497 и должен составлять 100.00015 и -83.082 должно быть -83.782.

function numberToArray(n) {

  if (Math.abs(n) == 0 || Math.abs(n) == -0) {
    return [n]
  }

  const r = [];

  let [
    a, int = Number.isInteger(a), d = g = [], e = i = 0
  ] = [ n || this.valueOf()];

  if (!int) {
    let e = ~~a;
    d = a - e;
    do {
      if (d < 1) ++i;
      d *= 10;
    } while (!Number.isInteger(d));
  }

  for (; ~~a; r.unshift(~~(a % 10)), a /= 10);

  if (!int) {
    for (; ~~d; g.unshift(~~(d % 10)), d /= 10);
    g[0] = g[0] * (1 * (10 ** -i))
    r.push(...g);
  }

  return r;

}

function arrayToNumber(a) {
  if ((Math.abs(a[0]) == 0 || Math.abs(a[0]) == -0) 
     && a.length == 1) return a[0];
  const [
    g, r = x => x.length == 1 
                ? x[0] 
                : x.length === 0 
                  ? x 
                  : x.reduce((a, b) => a + b)
    , b = a.find(x => g(x)), p = a.findIndex(x => g(x))
  ] = [x => !Number.isInteger(x)];

  let [i, j] = [b ? p : a.length, -1];

  return a.length === 1 
    ? a[0] 
    : b && p 
      ? r(a.slice(0, p).map(x => i ? x * (10 ** --i) : x)) 
        + (a[p] + (a[p + 1] !== undefined 
          ? r(a.slice(p + 1).map(x => x * (10 ** --j))) 
          : 0)) 
      : r(a.map(x => i ? x * (10 ** --i) : x))
}

let tests = [0, 200, 100.00015, -123, 4.4, 44.44, -0.01, 123
            , 2.718281828459, 321.7000000001, 809.56
            , 1.61803398874989, 1.999, 100.01, 545454.45
            , -7, -83.782, 12, 1.50, 100.0001];

let arrays = tests.map(n => [...numberToArray(n)]);

let numbers = arrays.map(n => arrayToNumber(n));

console.log({tests, arrays, numbers});

Вопросы:

  1. Как исправить перечисленные ошибки в существующем коде?
  2. В пределахограничение неиспользования строковых методов или преобразования ввода или вывода в строку во время процедуры, может ли код быть улучшен или составлен по-другому, чтобы соответствовать требованию?
  3. Можно ли улучшить текущую спецификацию, чтобыясность используемых терминов и недопущение путаницы относительно ожидаемого результата для десятичных дробей?

1 Ответ

0 голосов
/ 30 января 2019

Метод numberToArray():

Я некоторое время работал над вашей реализацией и подумал сначала проанализировать метод numberToArray().Для начала я решил создать метод для анализа десятичного числа и вернуть statistics об этом, в основном информацию, которую вы получили из этой части вашего кода:

if (!int) {
    let e = ~~a;
    d = a - e;
    do {
        if (d < 1) ++i;
        d *= 10;
    } while (!Number.isInteger(d));
}

Метод, который у меня естьmade on является следующим (будет использоваться внутри numberToArray()) и в основном получает следующую информацию:

1) Целочисленная секция (iSection) десятичного числа (как целое число)).

2) Десятичная часть (dSection) десятичного числа (в виде целого числа).

3) Количество цифр послеточка (dDigits).

4) Количество ведущих нулей после точки (dZeros).

function getDecimalStats(dec)
{
    let dDigits = 0, test = dec, factor = 1, dZeros = 0;

    // Store the integer section of the decimal number.

    let iSection = ~~dec;

    // Get the numbers of digits and zeros after the comma.
    
    while (!Number.isInteger(test))
    {
        factor = Math.pow(10, ++dDigits);
        test = dec * factor;
        dZeros += Math.abs(test - (iSection * factor)) < 1 ? 1 : 0;
    }

    // Store the decimal section as integer.

    let dSection = test - (iSection * factor);

    // Return an object with all statistics.

    return {iSection, dSection, dZeros, dDigits};
};

console.log(getDecimalStats(10.001));
console.log(getDecimalStats(-210.1));
console.log(getDecimalStats(-0.00001));
.as-console {background-color:black !important; color:lime;}
.as-console-wrapper {max-height:100% !important; top:0;}

Конечно, если вам не нравится, вы можете поместить эту же логику прямо в метод numberToArray().Итак, после выполнения предыдущей функции я провел некоторую реорганизацию вашего кода и добавил несколько комментариев, которые помогут мне понять, что вы делаете.Наконец, и после адаптации вашего кода я обнаружил, что неправильное отображение на массивы было главным образом из-за арифметической точности при работе с числом с плавающей точкой.После некоторого изучения этой проблемы я нашел решение, основанное на математическом correction factor (оно комментируется кодом при его применении).В общем, и до этого времени я пришел к следующему решению для метода numberToArray().

function getDecimalStats(dec)
{
    let dDigits = 0, test = dec, factor = 1, dZeros = 0;

    // Store the integer section of the decimal number.

    let iSection = ~~dec;

    // Get the numbers of digits and zeros after the comma.
    
    while (!Number.isInteger(test))
    {
        factor = Math.pow(10, ++dDigits);
        test = dec * factor;
        dZeros += Math.abs(test - (iSection * factor)) < 1 ? 1 : 0;
    }

    // Store the decimal section as integer.

    let dSection = test - (iSection * factor);

    // Return an object with all statistics.

    return {iSection, dSection, dZeros, dDigits};
};

function numberToArray(n)
{
    let r = [];

    if (Math.abs(n) == 0)
        return [n];

    let [a, int = Number.isInteger(a), g = []] = [n || this.valueOf()];

    // Get the stats of the decimal number.

    let {dSection, dZeros} = getDecimalStats(a);

    // Push the integer part on the array.

    for (; ~~a; r.unshift(~~(a % 10)), a /= 10);

    // Push the decimal part on the array.

    if (!int)
    {
        // Push decimal digits on temporal array "g".
        for (; ~~dSection; g.unshift(~~(dSection % 10)), dSection /= 10);

        // Define the correction factor for the next operation.
        let cf = 10 ** (++dZeros);

        // Map g[0] to a decimal number and push elements on the array.
        g[0] = (g[0] * cf) * ((10 ** -dZeros) * cf) / (cf * cf);
        r.push(...g);
    }

    return r;
}

let tests = [
0, 200, 100.00015, -123, 4.4, 44.44, -0.01, 123,
2.718281828459, 321.7000000001, 809.56,
1.61803398874989, 1.999, 100.01, 545454.45,
-7, -83.782, 12, 1.50, 100.0001
];

let arrays = tests.map(n => [...numberToArray(n)]);
console.log({tests, arrays});
.as-console {background-color:black !important; color:lime;}
.as-console-wrapper {max-height:100% !important; top:0;}

Метод arrayToNumber():

Для этого я решил пойти самостоятельно (фактически игнорируя вашу текущую логику).Следующий подход будет использовать ранее упомянутый getDecimalStats() и, в основном, Array :: redu () :

function getDecimalStats(dec)
{
    let dDigits = 0, test = dec, factor = 1, dZeros = 0;

    // Store the integer section of the decimal number.

    let iSection = ~~dec;

    // Get the numbers of digits and zeros after the comma.
    
    while (!Number.isInteger(test))
    {
        factor = Math.pow(10, ++dDigits);
        test = dec * factor;
        dZeros += Math.abs(test - (iSection * factor)) < 1 ? 1 : 0;
    }

    // Store the decimal section as integer.

    let dSection = test - (iSection * factor);

    // Return an object with all statistics.

    return {iSection, dSection, dZeros, dDigits};
};

function arrayToNumber(a)
{
    // Get the index of the first decimal number.

    let firstDecIdx = a.findIndex(
        x => Math.abs(x) > 0 && Math.abs(x) < 1
    );

    // Get stats about the previous decimal number.

    let {dZeros} = getDecimalStats(firstDecIdx >= 0 ? a[firstDecIdx] : 0);

    // Normalize firstDecIdx.

    firstDecIdx = firstDecIdx < 0 ? a.length : firstDecIdx;

    // Reduce the array to get the number.
    
    let number = a.reduce(
        ({num, dIdx, dPow}, n, i) =>
        {
            // Define the correction factor.
            let cf = 10 ** (dPow + i - dIdx);

            if (i < dIdx)
               num += n * (10 ** (dIdx - i - 1));
            else if (i === dIdx)
               num = ((num * cf) + (n * cf)) / cf;
            else
               num = ((num * cf) + n) / cf;

            return {num, dIdx, dPow};
        },
        {num: 0, dIdx: firstDecIdx, dPow: ++dZeros}
    );

    return number.num;
}

let tests = [
    [0],
    [2, 0, 0],
    [1, 0, 0, 0.0001, 5],
    [-1, -2, -3],
    [4, 0.4],
    [4, 4, 0.4, 4],
    [-0.01],
    [1, 2, 3],
    [2, 0.7, 1, 8, 2, 8, 1, 8, 2, 8, 4, 5, 9],
    [3, 2, 1, 0.7, 0, 0, 0, 0, 0, 0, 0, 0, 1],
    [8, 0, 9, 0.5, 6],
    [1, 0.6, 1, 8, 0, 3, 3, 9, 8, 8, 7, 4, 9, 8, 9],
    [1, 0.9, 9, 9],
    [1, 0, 0, 0.01],
    [5, 4, 5, 4, 5, 4, 0.4, 5, 0],
    [-7],
    [-8,-3, -0.7, -8, -2],
    [1, 2],
    [1, 0.5],
    [1, 0, 0, 0.0001]
];

let numbers = tests.map(n => arrayToNumber(n));
console.log(numbers);
.as-console {background-color:black !important; color:lime;}
.as-console-wrapper {max-height:100% !important; top:0;}

Наконец, я надеюсь, что вы сможете оценить мои усилия, и, очевидно, в моём решении может быть много улучшений (поэтому любая рекомендация приветствуется).Например, в настоящее время нет ни одного, или нескольких проверок на безопасность.

...