Digamos que tengo una parcela (un terreno) de 8027 metros cuadrados y tengo más de un propietario, pero no tienen la misma cantidad de terreno.
Dueño A tiene: 1003.5m2 Dueño B tiene: 1003.5m2 Dueño C tiene 1337m2 Dueño D tiene 1338m2 Dueño E tiene 1338m2 Dueño F tiene 2007m2
Como salida, quiero obtener una fracción de números enteros con el mínimo común denominador posible, etc.
1/8 1/8 .. .. ..
Sé que es muy poco probable que sea tan pequeño...
pero cuando divide fracciones, la diferencia no debe exceder más de +/- 1m2 por propietario
lo que significa que si el propietario A tiene 1003,5 m2 al principio, y tengo 1/8 8027/8 = 1003,35, está bien, PERO, desafortunadamente grande, ¡PERO! La SUMA total de todos los propietarios debe permanecer igual al final :(
Por ahora, pude obtener el mínimo común denominador para los propietarios individualmente y ni siquiera estoy seguro de cuál es el siguiente paso para obtener el resultado deseado ...
¡Cualquier ayuda es muy apreciada!
gcd = function (a, b) { if (b < 0.0000001) return a; // Since there is a limited precision we need to limit the value. return gcd(b, Math.floor(a % b)); // Discard any fractions due to limitations in precision. }; var total_area = 8027; var area_parts = [1003.5, 1003.5, 1337, 1338, 1338, 2007]; ResaultArr = Array(); for (let i = 0; i < area_parts.length; i++) { var decimal_fraction = area_parts[i] / total_area; var denominator = Math.pow(10, 4); var numerator = decimal_fraction * denominator; var divisor = gcd(numerator, denominator); numerator /= divisor; denominator /= divisor; var fraction = Math.floor(numerator) + '/' + Math.floor(denominator); var decimal_fraction_after = Math.floor(numerator) / Math.floor(denominator); var area_difference = area_parts[i] - (total_area * decimal_fraction_after); var area_part_after = total_area * decimal_fraction_after; ResaultArr[i] = { area_difference: area_difference, fraction: fraction, decimal_fraction: decimal_fraction, decimal_fraction_after: decimal_fraction_after, area_part: area_parts[i], area_part_after: area_part_after, total_area: total_area }; } console.log(ResaultArr);Un enfoque de fuerza bruta simplemente prueba denominadores sucesivos hasta que encuentra uno que funcione:
const makeFractions = (xs, tolerance = 1, n = xs .length) => { const total = xs .reduce ((a, b) => a + b, 0) const numerators = xs .map (x => Math .round (x * n / total)) const denom = numerators .reduce ((a, b) => a + b, 0) const candidates = numerators .map (y => y * total / denom) return candidates .some ((y, i) => Math .abs (y - xs [i]) > tolerance) ? makeFractions (xs, tolerance, n + 1) : numerators .map (y => `${y}/${denom}`) } const parts = [1003.5, 1003.5, 1337, 1338, 1338, 2007], total = 8027 const results = makeFractions (parts) console .log ('parts: ', parts) console .log ('fractions:', results) console .log ('exact values: ', results .map ( s => s .split('/').map (Number)) .map (([n, d]) => n * total / d )) .as-console-wrapper {max-height: 100% !important; top: 0} Aquí comenzamos con n de 6 , la longitud de la matriz, y luego redondeamos los valores por cuántos sextos 6 total se aproximan, obteniendo numeradores potenciales de [1, 1, 1, 1, 1, 2] , que totaliza para darnos un denominador de 7 y un desglose de candidatos de [1146.7142857142858, 1146.7142857142858, 1146.7142857142858, 1146.7142857142858, 1146.7142857142858, 2293.4285714285716] Al menos uno de estos está demasiado lejos de nuestros números objetivo; de hecho, todos lo están, en este caso, por lo que intentamos nuevamente con 7 para n . Obtenemos el mismo desglose y nuevamente el mismo desglose para 8 . Cuando llegamos a 9 , obtenemos un denominador de 9 y candidatos de [891.8888888888889, 891.8888888888889, 891.8888888888889, 1783.7777777777778, 1783.7777777777778, 1783.7777777777778]} Estos todavía están demasiado lejos, y seguimos intentándolo. Eventualmente, llegamos a n de 22 , lo que nos da numeradores potenciales de [3, 3, 4, 4, 4, 6]] , y por lo tanto un denominador de 24 con valores candidatos de [1003.375, 1003.375, 1337.8333333333333, 1337.8333333333333, 1337.8333333333333, 2006.75] . Cada uno de estos está dentro de nuestra tolerancia de 1 de los números objetivo y nos detenemos aquí, devolviendo las fracciones [3/24, 3/24, 4/24, 4/24, 4/24, 6/24] .
Probablemente tenga sentido extraer una función de sum y almacenar el total calculado que no cambiará por invocación recursiva, por lo que quizás sea un poco más limpio:
const sum = (ns) => ns .reduce ((a, b) => a + b, 0) const makeFractions = (xs, tolerance = 1, total = sum (xs), n = xs .length) => { const numerators = xs .map (x => Math .round (x * n / total)) const denom = sum (numerators) const candidates = numerators .map (y => y * total / denom) return candidates .some ((y, i) => Math .abs (y - xs [i]) > tolerance) ? makeFractions (xs, tolerance, total, n + 1) : numerators .map (y => `${y}/${denom}`) }En tu ejemplo, las proporciones son aproximadamente 1/8, 1/8, 1/6, 1/6, 1/6 y 1/4, podemos darles un denominador común de 24, por lo que las fracciones se convierten en 3/24, 3 /24, 4/24, 4/24 y 6/24 dando áreas 1003.375, 1003.375,1337.833333,1337.833333,1337.833333, 2006.75 que suman exactamente 8027.
Esto nos da una idea de cómo definir mejor el problema. Encuentre el conjunto de fracciones a/n, b/n, c/n, d/n, e/n, f/n tales que su suma sea 1 y que el producto 8027 * a/n esté dentro de los límites de error deseados. por ejemplo | 8027*a/n - 1003.5 | < cota, etc. Encuentre el n más pequeño que satisfaga esta propiedad.
Un enfoque simple es trabajar iterativamente en n.
for(n=1;n<limit;++n) { a = round(n * 1003.5 / 8027); b = round(n * 1003.5 / 8027); c = round(n * 1337 / 8027); d = round(n * 1338 / 8027); e = round(n * 1338 / 8027); f = round(n * 2007 / 8027); errA = abs(8027*a/n - 1003.5); .... if(a+b+c+d+e+f==n && errA < bound && ... ) break; }