Реализовать рекурсивные вычисления для моего ипотечного калькулятора

знатоки JavaScript!

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

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

Пока мой html:

$(document).ready(() => {

    //restrict input to certain types
    $('[restrict]').on('keyup', function () {
        switch ($(this).attr('restrict')) {
        case 'integer':
            $(this).val($(this).val().replace(/[^0-9]*/g, ''));
        case 'float':
            $(this).val($(this).val().replace(/[^\.0-9]*/g, ''));
        }
    });

    $('#breakdown').on('click', () => {
        //get the array of payments
        var amount = $('#amount').val();
        var months = $('#term').val();
        var interest = $('#interest').val();
        var breakdown = [];
        for(var i = 0; i < months; i++){
            var row = {};
            var monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
            row.month = monthNames[i%12]+', '+$('#year').val();
            row.principal = (amount/months).toFixed();
            row.interest = ((amount*interest)/months).toFixed();
            row.balance = (amount*(1-i/months)).toFixed();
            breakdown.push(row);
        }
        $('#mortgageTable').DataTable({
            data: breakdown,
            destroy: true,
            dom: 'ftip',
            columnDefs: [
                {targets: 0, data: 'month', title: 'Month'},
                {targets: 1, data: 'principal', title: 'Principal'},
                {targets: 2, data: 'interest', title: 'Interest'},
                {targets: 3, data: 'balance', title: 'Balance'},
            ]
        });
    });

});
<!doctype html>
<html>
<head>
  <script src = "https://code.jquery.com/jquery-3.3.1.min.js"></script>
  <script src = "https://cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js"></script>
  <script src = "mortgagecalc.js"></script>
  <link rel = "stylesheet" type = "text/css" href = "https://cdn.datatables.net/1.10.19/css/jquery.dataTables.min.css">
</head>
<body>
<div style = "display:block; margin: 5px"><label>Loan amount, USD:</label><input id = "amount" restrict = "integer"></input></div>
<div style = "display:block; margin: 5px"><label>Loan term, months:</label><input id = "term" restrict = "integer"></input></div>
<div style = "display:block; margin: 5px"><label>First month:</label><select id = "month">
    <option value = "Jan">Jan</option>
    <option value = "Jan">Feb</option>
    <option value = "Jan">Mar</option>
    <option value = "Jan">Apr</option>
    <option value = "Jan">May</option>
    <option value = "Jan">Jun</option>
    <option value = "Jan">Jul</option>
    <option value = "Jan">Aug</option>
    <option value = "Jan">Sep</option>
    <option value = "Jan">Oct</option>
    <option value = "Jan">Nov</option>
    <option value = "Jan">Dec</option>
</select>
<select id = "year">
    <option value = "2019">2019</option>
    <option value = "2020">2020</option>
</select></div>
<div style = "display:block; margin: 5px"><label>Interest rate, %:</label><input id = "interest" restrict = "float"></input></div>
<button id = "breakdown">Mortgage breakdown</button>
<table id = "mortgageTable"></table>
</body>
</html>

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

Есть ли способ справиться с этим, используя функции datatables?

Итак, мой ожидаемый результат за 100 000 долларов, 36 месяцев, 5% должен быть таким:

principal   interest    balance
2777.78 416.67  100000.00
2777.78 405.09  97222.22
2777.78 393.52  94444.44
2777.78 381.94  91666.67
2777.78 370.37  88888.89

В моей текущей реализации только первая строка рассчитывается правильно, а в остальных строках нет «баланса», а «проценты» уменьшаются пропорционально.

Поведение ключевого слова "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) для оценки ваших знаний,...
2
0
175
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

На самом деле, ваши вычисления не нуждаются в «рекурсии». Для достижения желаемого результата вы можете использовать опцию columns.render для расчета процентных платежей на основе текущего остатка и месяца.

Итак, в основном решение сводится к следующей строке:

render: (data, type, row, meta) => ($('#amount').val()*(1-meta.row/$('#term').val())*$('#interest').val()/1200).toFixed(2)}

Однако в вашем коде есть много других проблем:

  • ваша строка даты не будет правильно оцениваться, начиная со второго года, поэтому вам лучше сделать что-то, например:
const monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
const datatable = $('#mortgageTable').DataTable({
        ...
        columns: [
            {data: 'month', title: 'Month', render: (data, type, row, meta) => monthNames[meta.row%12]+', '+parseFloat(parseFloat($('#year').val())+Math.floor(meta.row/12))},
            ...
        ]
    });
  • вам действительно не нужно уничтожать и создавать свой DataTable при изменении входных данных, вы можете просто очистить его содержимое и заполнить его соответствующими данными, и это будет намного быстрее с точки зрения производительности
  • принимая во внимание, что вы можете достичь своей цели с помощью опций render, вы можете сэкономить еще немного производительности, отображая свою таблицу на лету (без предварительной подготовки объекта данных)

Кроме того, вы можете принять во внимание несколько предложений, которые могут сделать ваш код немного более эффективным:

  • вы можете заполнить параметры месяца HTML одной строкой:
const monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
$('#month').append(monthNames.reduce((options, month) => options += `<option value = "${month}">${month}</option>`,''));
  • ваши годы <select> также могут заполняться параметрами динамически, так что вам не нужно обновлять свой HTML-код каждый год:
$('#year').append([...Array(2)].reduce((options, dummy, index) => options += `<option value = "${(new Date()).getFullYear()+index}">${(new Date()).getFullYear()+index}</option>`,''))
  • вы можете реализовать пользовательскую сортировку для данных первого столбца для лучшего взаимодействия с пользователем, например. если у вас есть type: 'mmmyyyy', назначенный вашему первому столбцу, пользовательская сортировка может быть достигнута чем-то, например:
    const monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
    const dateVal = str => {
        const dateParts = str.split(', ');
        return parseFloat(12*dateParts[1])+monthNames.indexOf(dateParts[0]);
    };
    Object.assign($.fn.DataTable.ext.oSort, {
        'mmmyyyy-asc': (a, b) => dateVal(a)-dateVal(b),
        'mmmyyyy-desc': (a, b) => dateVal(b)-dateVal(a),
    });

В конце концов, полная живая демонстрация вашего кода может выглядеть примерно так:

$(document).ready(() => {

    //restrict input to certain types
    $('[restrict]').on('keyup', function () {
        switch ($(this).attr('restrict')) {
        case 'integer':
            $(this).val($(this).val().replace(/[^0-9]*/g, ''));
        case 'float':
            $(this).val($(this).val().replace(/[^\.0-9]*/g, ''));
        }
    });
	
	//re-used month names array
	const monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
	//turning 'MMM, YYYY' into value
	const dateVal = str => {
		const dateParts = str.split(', ');
		return parseFloat(12*dateParts[1])+monthNames.indexOf(dateParts[0]);
	};
	
	//populate 'year', 'month' options dynamically
	$('#month').append(monthNames.reduce((options, month) => options += `<option value = "${month}">${month}</option>`,''));
	$('#year').append([...Array(2)].reduce((options, dummy, index) => options += `<option value = "${(new Date()).getFullYear()+index}">${(new Date()).getFullYear()+index}</option>`,''))
	
	//breakdown frame
	const breakdown = () => [...Array(parseFloat($('#term').val()) || 0)].map(item => ['month', 'principal', 'interest', 'balance'].reduce((res, header) => ({...res, [header]:''}), {}));
	
	//feed datatable
	const datatable = $('#mortgageTable').DataTable({
		dom: 'ftip',
		data: breakdown(),
		columns: [
			{data: 'month', type: 'mmmyyyy', title: 'Month', render: (data, type, row, meta) => monthNames[meta.row%12]+', '+parseFloat(parseFloat($('#year').val())+Math.floor(meta.row/12))},
			{data: 'principal', title: 'Principal', render: () => ($('#amount').val()/$('#term').val()).toFixed(2)},
			{data: 'interest', title: 'Interest', render: (data, type, row, meta) => ($('#amount').val()*(1-meta.row/$('#term').val())*$('#interest').val()/1200).toFixed(2)},
			{data: 'balance', title: 'Balance', render: (data, type, row, meta) => ($('#amount').val()*(1-meta.row/$('#term').val())).toFixed(2)}
		]
	});
	
	//datatable sorting by 'MMM, YYYY' value
	Object.assign($.fn.DataTable.ext.oSort, {
		'mmmyyyy-asc': (a, b) => dateVal(a)-dateVal(b),
		'mmmyyyy-desc': (a, b) => dateVal(b)-dateVal(a),
	});
	
	//hide datatable initially
	$('.dataTables_wrapper').hide();
	
	//button click handler
	$('#breakdown').on('click', () => {
		datatable.clear().rows.add(breakdown()).draw();
		$('.dataTables_wrapper').show();
	});
	
});
<!doctype html>
<html>
<head>
  <script src = "https://code.jquery.com/jquery-3.3.1.min.js"></script>
  <script src = "https://cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js"></script>
  <script src = "mortgagecalc.js"></script>
  <link rel = "stylesheet" type = "text/css" href = "https://cdn.datatables.net/1.10.19/css/jquery.dataTables.min.css">
</head>
<body>
<div style = "display:block; margin: 5px"><label>Loan amount, USD:</label><input id = "amount" restrict = "integer"></input></div>
<div style = "display:block; margin: 5px"><label>Loan term, months:</label><input id = "term" restrict = "integer"></input></div>
<div style = "display:block; margin: 5px"><label>First month:</label><select id = "month"></select>
<select id = "year"></select></div>
<div style = "display:block; margin: 5px"><label>Interest rate, %:</label><input id = "interest" restrict = "float"></input></div>
<button id = "breakdown">Mortgage breakdown</button>
<table id = "mortgageTable"></table>
</body>
</html>

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