Получить обратное уравнение - JavaScript

Допустим, у меня есть эта формула, например:

function getExperience(level) {
  let a = 0;
  for (let x = 1; x < level; x += 1) {
    a += Math.floor(x + (200 * (2 ** (x / 3))));
  }

  return Math.floor(a / 4);
}


for (var i = 1; i < 100; i++) {
  console.info(`Level ${i}: ${getExperience(i)}`);
}

Чтобы получить опыт, необходимый для 50-го уровня, вам нужно сделать: getExperience(50).

Но как бы вы изменили это и получили УРОВЕНЬ, необходимый для получения опыта? Таким образом, getLevel(20010272) выведет 50.

Вы можете тестировать getExperience в цикле, пока не получите тот же результат (уровень).

SphynxTech 30.01.2019 07:51

Кажется, опечатка. getExperience(50) - это 20012272, а не 20010272. Поэтому я думаю, что getLevel(20010272) должен возвращать 49, а не 50.

Eric Duminil 30.01.2019 10:45

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

Scuba Steve 31.01.2019 02:18
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
24
4
2 955
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Грубым (но неэлегантным) решением будет просто вызывать getExperience для получения уровней, пока вы не достигнете уровня, требующего больше опыта, чем пройденный exp:

function getLevel(exp) {
  if (exp === 0) return 0;
  let level = 0;
  let calcExp = 0;
  while (exp > calcExp) {
    calcExp = getExperience(level);
    if (calcExp > exp) break;
    level++;
  }
  return level - 1;
}

console.info(getLevel(20012272)); // experience required for 50 on the dot
console.info(getLevel(20012270));
console.info(getLevel(20012274));
console.info(getLevel(0));

function getExperience(level) {
  let a = 0;
  for (let x = 1; x < level; x += 1) {
    a += Math.floor(x + (200 * (2 ** (x / 3))));
  }

  return Math.floor(a / 4);
}

Просто интересно, можем ли мы минимизировать итерацию, установив level на относительно близкое значение

Rajesh 30.01.2019 08:00

Простое решение, которое выполняет свою работу. Почему я подумал об этом. Арх. Спасибо.

Green Mtn 30.01.2019 08:08

Конечно, вы можете предварительно вычислить все значения и получить их в таблице поиска. Я сомневаюсь, что требования или уровни XP будут постоянно меняться, поэтому их динамическое вычисление каждый раз кажется излишним.

VLAZ 30.01.2019 10:10

Если бы вы также знали минимальный/максимальный уровни, вы могли бы выполнить псевдобинарный поиск (то есть, если бы вы знали, что существует ровно 100 уровней, вы видите, меньше или больше ваш опыт, чем уровень 50, а затем 25/75 в зависимости от этого). , затем 38/12/62/88 и т. д.). Или где-то между этими шагами (делите пополам, пока у вас не будет 3 варианта, затем проверьте все).

Delioth 30.01.2019 21:54

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

(хотя я сомневаюсь, что прирост значителен для списка длиной 100)

Вы можете использовать алгоритм бинарного поиска, чтобы избежать перебора всех возможностей.

Здесь — это пример, который я адаптировал к вашему случаю.

Вам сначала нужно создать массив для сопоставления всех ваших level => experience, это действие нужно сделать только ОДИН РАЗ, потом больше никогда не придется это делать.

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

// You first have to create an array with all your levels.
// This has to be done only ONCE because it's an expensive one!
const list = [];
for (let i = 1; i <= 1000; i++) {
  list[i] = getExperience(i);
}

function getExperience(level) {
  let a = 0;
  for (let x = 1; x < level; x += 1) {
    a += Math.floor(x + (200 * (2 ** (x / 3))));
  }

  return Math.floor(a / 4);
}

function getLevel(value) {
  // initial values for start, middle and end
  let start = 0
  let stop = list.length - 1
  let middle = Math.floor((start + stop) / 2)
  let iterations = 0;
  
  // While the middle is not what we're looking for and the list does not have a single item.
  while (list[middle] !== value && start < stop) {
    iterations++;
    if (value < list[middle]) {
      stop = middle - 1
    } else {
      start = middle + 1
    }

    // Recalculate middle on every iteration.
    middle = Math.floor((start + stop) / 2)
  }
  
  console.info(`${value} is Level ${middle} (Result found after ${iterations} iterations)`);
  return middle;
}

// Then you can search your level according to the experience
getLevel(0);
getLevel(72);
getLevel(20010272);
getLevel(getExperience(50));
getLevel(33578608987644589722);

Вы можете улучшить создание списка, зациклив Math.floor(x + (200 * (2 ** (x / 3)))) от 1 до 1000 и сохранив значения a, а затем снова зациклив, чтобы получить Math.floor(a / 4).

JollyJoker 30.01.2019 09:46

@JollyJoker Хм, возможно, вы можете отредактировать мой ответ с вашим предложением?

Armel 30.01.2019 09:50
Попробуйте онлайн! Отредактируйте его, если хотите; это не так читабельно, как хотелось бы
JollyJoker 30.01.2019 10:04

@EricDuminil Безусловно, это была проблема index с инициализацией списка, я исправил ее, спасибо.

Armel 30.01.2019 10:58
Попробуйте онлайн! Разделить на именованные функции, может быть, немного читабельнее
JollyJoker 30.01.2019 11:08

Вам не нужно составить список всех впечатлений.

Salman A 30.01.2019 11:32

@SalmanA Да, это зависит от того, как вы используете это в своем приложении. По крайней мере, у Green Mtn есть несколько решений, и он может выбрать лучшее для своего случая.

Armel 30.01.2019 15:48
Ответ принят как подходящий

Короткий ответ

Вы можете использовать 4.328085 * Math.log(0.00519842 * xp + 1.259921045) как очень хорошее приближение к соответствующему уровню.

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

Длинный ответ

Слегка измененная функция

Я не думаю, что можно найти точную выражение в закрытой форме для обратной этой функции. Однако это должно быть возможно, если вы немного модифицируете getExperience(level).

  • Во-первых, вы можете заметить, что x растет много медленнее, чем 2 ** (x / 3).
  • Тогда Math.floor не имеет большого влияния на большие числа.

Итак, давайте удалим их! Вот немного измененная функция:

function getExperienceEstimate(level) {
  let a = 0;
  for (let x = 1; x < level; x += 1) {
    a += 200 * (2 ** (x / 3));
  }
  return a / 4;
}

Преимущество этого метода в том, что теперь это геометрический ряд, поэтому можно вычислить сумму напрямую без какого-либо цикла:

function getExperienceEstimate(level) {
  let a = 50;
  let r = 2 ** (1 / 3);
  return a * (r**level - r) / (r - 1);
};

getExperienceEstimate(50) возвращает 20011971.993575357, что всего на 0,0015% меньше, чем getExperience(50).

Обратная функция

Согласно вольфрам Альфа, вот функция, обратная getExperienceEstimate:

function getLevelEstimate(xp){
  let a = 50;
  let r = 2 ** (1 / 3);
  return Math.log(xp * (r - 1) / a + r) / Math.log(r);
};

С некоторой незначительной потерей точности вы можете упростить его еще больше:

function getLevelEstimate(xp){
  return 4.328085 * Math.log(0.00519842 * xp + 1.259921045)
};

Это всего лишь оценка, но она работает довольно хорошо и не требует никакого цикла!

Контрольная работа

Для XP 20012272 приближенная обратная функция возвращает 50.00006263463371, что должно быть хорошей отправной точкой, если вы хотите найти точный результат.

function getExperience(level) {
  let a = 0;
  for (let x = 1; x < level; x += 1) {
    a += Math.floor(x + (200 * (2 ** (x / 3))));
  }
  return Math.floor(a / 4);
}

function getLevelEstimate(xp){
  return 4.328085 * Math.log(0.00519842 * xp + 1.259921045)
};

for (var i = 1; i < 100; i++) {
  console.info(`Level ${i} (XP = ${getExperience(i)}). Estimated level : ${getLevelEstimate(getExperience(i))}`);
}

Это решение, которое я бы выбрал. В своей игре я убедился, что могу напрямую инвертировать формулу уровня, используя один и тот же показатель степени как для log, так и для pow. Я бы порекомендовал OP сделать это, если это вообще возможно, чтобы свести к минимуму беспокойство о приближении и вместо этого сделать его точным соответствием.

Seiyria 30.01.2019 16:36

Вы можете начать с этого приближения, а затем перебрать несколько ближайших чисел, чтобы найти правильное.

Barmar 30.01.2019 22:38

Судя по вашим результатам, простое использование Math.round(getLevelEstimate(experience)) сработает.

Barmar 30.01.2019 22:39

Я также пытался найти обратную формулу опыта, но потерпел неудачу. +1.

Salman A 31.01.2019 08:01

@Barmar: Интересная информация заключается в том, для какого опыта getLevelEstimate переключается на следующий уровень. Если вы позвоните в Math.round, вы выбросите эту информацию. При идеальном getLevelEstimateMath.floor должен возвращать правильный уровень.

Eric Duminil 31.01.2019 12:15

Я возвращаюсь и вижу это. Вы слишком добры. Спасибо.

Green Mtn 31.01.2019 18:10

@GreenMtn: С удовольствием, мне всегда нравятся короткие математические задачи. И я получил труднодоступный значок с этим ответом, так что тоже спасибо ;) Наконец: обратите внимание, что вам не нужно принимать мой ответ, поскольку он дает только приблизительный результат.

Eric Duminil 31.01.2019 18:12

Другие вопросы по теме