Search code examples
javascriptmathlogarithm

Calculating the subsegments of a line, using logarithmic scaling


I want to calculate the length of the subsegments of a line having a length of totalLineLength, where the subsegments are proportional to the log of the events given.

A visual representation: |---|-----|---------| a line composed of subsegments.

I want the sum of the calculated subsegments to be equal to totalLineLength, and Success must be printed. Log(x+1) must be used too (because it gives always positive results for positive arguments and scales 1 too).

Using linear scaling, I'm doing this, and it works.

var totalLineLength = 20;
var eventTotal;
var calcSubSegment = function(x) {
    return totalLineLength * x / eventTotal;
}

var events = [0.1, 1, 22];
eventTotal = events.reduce((a, b) => a + b, 0);
var subSegments = events.map(calcSubSegment);
var segmentLength = subSegments.reduce((a, b) => a + b);
if (segmentLength != totalLineLength) {
    console.log("Error:", segmentLength);
} else {
    console.log("Success");
}

Using log(x+1) (calcSubSegment is updated), it doesn't work.

var totalLineLength = 20;
var eventTotal;
var calcSubSegment = function(x) {
    return totalLineLength * Math.log(x+1) / Math.log(eventTotal+1);
}

var events = [0.1, 1, 22];
eventTotal = events.reduce((a, b) => a + b, 0);
var subSegments = events.map(calcSubSegment);
var segmentLength = subSegments.reduce((a, b) => a + b, 0);
if (segmentLength != totalLineLength) {
    console.log("Error:", segmentLength);
} else {
    console.log("Success");
}

What's wrong with my code? I suppose the eventTotal calculation, but I'm not sure how to proceed.

How should I use logarithmic scaling? I'd also like a mathematical explanation.


Solution

  • If I understand your task correctly, the simplest solution is first map all events using your log(x+1) functions and then just use linear split you've already implemented. Something like this:

    function linearSplit(events, totalLineLength) {
    	var eventTotal;
    	var calcSubSegment = function(x) {
    		return totalLineLength * x / eventTotal;
    	}
    
    	eventTotal = events.reduce((a, b) => a + b, 0);
    	var subSegments = events.map(calcSubSegment);
    	return subSegments;
    }
    
    
    function logSplit(events, totalLineLength) {
    	var logEvents = events.map(function(x) { return Math.log(x+1); } );
    	return linearSplit(logEvents, totalLineLength);
    }
    
    
    
    function testSplit(events, totalLineLength, fnSplit, splitName) {
    	var subSegments = fnSplit(events, totalLineLength);
    	var segmentLength = subSegments.reduce((a, b) => a + b, 0);
    	console.log(splitName + ":\n"  + events + " =>\n" + subSegments);
    	if (Math.abs(segmentLength - totalLineLength) > 0.001) {
    		console.log(splitName + " Error:", segmentLength);
    	} else {
    		console.log(splitName + " Success");
    	}
    }
    
    var totalLineLength = 20;
    var events = [0.1, 1, 22];
    
    testSplit(events, totalLineLength, linearSplit, "linearSplit");
    testSplit(events, totalLineLength, logSplit, "logSplit"); 

    The idea is that the only splitting that

    1. Has parts proportionally to some coefficients
    2. Makes sum of all parts equal to the whole

    at the same time is the linear splitting. So if you want to scale coefficients in any way, you should scale them before passing to the linear splitting logic. Otherwise the sum of parts will not equal the whole (in case of any non-linear scaling) as it happens in your code.

    P.S. It is not very good idea to compare floating point values by ==. You should use some tolerance for calculation/rounding errors.