Problem with defining linear programming constraints - math

Me and my friend are trying to implement a paper and the last step requires solving a linear programming problem to get the final result. We are not so familiar with LP so i'd like to ask for your help.
Here's the function which is based on the PROFSET model
and here's the proposed constraints
(1)
(2)
where:
Pa and Qi are the binary decision variables
J are all the available categories
F are sets of frequent categories
Φ is the total number of selected categories
Constraint (1) actually says that Qi is 1 if category i is included in some itemset A where Pa = 1
Basically, we are trying to use some common open source lp solvers (like joptimizer) but we dont know
how to define those constraints, especially those that define set inclusion rules. Most of those solvers
seem to accept just inequalities.
So, do you have any idea about how to define those constraints? Maybe transform them to inequalities or
something? Any help would be appreciated.
Thank you

A constraint that is written as an equality can be also written as two inequalities.
e.g.
A*x=b is the same as
A*x<=b and A*x>=b
In order to write such a LP there are two ways.
To hardcode it, meaning writing everything in code for example in Java.
Write it the mathematical way in a "language" called AMPL: https://ampl.com/resources/the-ampl-book/ For the second way you don't really need to know a programming language. AMPL transforms magically your LP into code and feeds it to a solver e.g. commercial: CPLEX, Gurobi (academic license available) or open source: GLPK. AMPL provides also an online platform that you can provide your model as .mod file and data as .dat files.
If you still want to hardcode your LP GLPK has nice examples e.g. in JAVA:
public class Lp {
// Minimize z = -.5 * x1 + .5 * x2 - x3 + 1
//
// subject to
// 0.0 <= x1 - .5 * x2 <= 0.2
// -x2 + x3 <= 0.4
// where,
// 0.0 <= x1 <= 0.5
// 0.0 <= x2 <= 0.5
// 0.0 <= x3 <= 0.5
public static void main(String[] arg) {
glp_prob lp;
glp_smcp parm;
SWIGTYPE_p_int ind;
SWIGTYPE_p_double val;
int ret;
try {
// Create problem
lp = GLPK.glp_create_prob();
System.out.println("Problem created");
GLPK.glp_set_prob_name(lp, "myProblem");
// Define columns
GLPK.glp_add_cols(lp, 3);
GLPK.glp_set_col_name(lp, 1, "x1");
GLPK.glp_set_col_kind(lp, 1, GLPKConstants.GLP_CV);
GLPK.glp_set_col_bnds(lp, 1, GLPKConstants.GLP_DB, 0, .5);
GLPK.glp_set_col_name(lp, 2, "x2");
GLPK.glp_set_col_kind(lp, 2, GLPKConstants.GLP_CV);
GLPK.glp_set_col_bnds(lp, 2, GLPKConstants.GLP_DB, 0, .5);
GLPK.glp_set_col_name(lp, 3, "x3");
GLPK.glp_set_col_kind(lp, 3, GLPKConstants.GLP_CV);
GLPK.glp_set_col_bnds(lp, 3, GLPKConstants.GLP_DB, 0, .5);
// Create constraints
// Allocate memory
ind = GLPK.new_intArray(3);
val = GLPK.new_doubleArray(3);
// Create rows
GLPK.glp_add_rows(lp, 2);
// Set row details
GLPK.glp_set_row_name(lp, 1, "c1");
GLPK.glp_set_row_bnds(lp, 1, GLPKConstants.GLP_DB, 0, 0.2);
GLPK.intArray_setitem(ind, 1, 1);
GLPK.intArray_setitem(ind, 2, 2);
GLPK.doubleArray_setitem(val, 1, 1.);
GLPK.doubleArray_setitem(val, 2, -.5);
GLPK.glp_set_mat_row(lp, 1, 2, ind, val);
GLPK.glp_set_row_name(lp, 2, "c2");
GLPK.glp_set_row_bnds(lp, 2, GLPKConstants.GLP_UP, 0, 0.4);
GLPK.intArray_setitem(ind, 1, 2);
GLPK.intArray_setitem(ind, 2, 3);
GLPK.doubleArray_setitem(val, 1, -1.);
GLPK.doubleArray_setitem(val, 2, 1.);
GLPK.glp_set_mat_row(lp, 2, 2, ind, val);
// Free memory
GLPK.delete_intArray(ind);
GLPK.delete_doubleArray(val);
// Define objective
GLPK.glp_set_obj_name(lp, "z");
GLPK.glp_set_obj_dir(lp, GLPKConstants.GLP_MIN);
GLPK.glp_set_obj_coef(lp, 0, 1.);
GLPK.glp_set_obj_coef(lp, 1, -.5);
GLPK.glp_set_obj_coef(lp, 2, .5);
GLPK.glp_set_obj_coef(lp, 3, -1);
// Write model to file
// GLPK.glp_write_lp(lp, null, "lp.lp");
// Solve model
parm = new glp_smcp();
GLPK.glp_init_smcp(parm);
ret = GLPK.glp_simplex(lp, parm);
// Retrieve solution
if (ret == 0) {
write_lp_solution(lp);
} else {
System.out.println("The problem could not be solved");
}
// Free memory
GLPK.glp_delete_prob(lp);
} catch (GlpkException ex) {
ex.printStackTrace();
ret = 1;
}
System.exit(ret);
}
/**
* write simplex solution
* #param lp problem
*/
static void write_lp_solution(glp_prob lp) {
int i;
int n;
String name;
double val;
name = GLPK.glp_get_obj_name(lp);
val = GLPK.glp_get_obj_val(lp);
System.out.print(name);
System.out.print(" = ");
System.out.println(val);
n = GLPK.glp_get_num_cols(lp);
for (i = 1; i <= n; i++) {
name = GLPK.glp_get_col_name(lp, i);
val = GLPK.glp_get_col_prim(lp, i);
System.out.print(name);
System.out.print(" = ");
System.out.println(val);
}
}}

Related

Why isin't the vector able to hold all 52 elements?

I have a program where I use a vector to simulate all the possible outcomes when counting cards in blackjack. There's only three possible values, -1, 0, and 1. There's 52 cards in a deck therefore the vector will have 52 elements, each assigned one of values mentioned above. The program works when I scale down the size of the vector, it still works when I have it as this size however I get no output and get the warning "warning C4267: '=': conversion from 'size_t' to 'int', possible loss of data".
#include<iostream>
#include"subtracter.h"
#include<time.h>
#include<vector>
#include<random>
using namespace std;
int acecard = 4;
int twocard = 4;
int threecard = 4;
int fourcard = 4;
int fivecard = 4;
int sixcard = 4;
int sevencard = 4;
int eightcard = 4;
int ninecard = 4;
int tencard = 16;
// declares how many of each card there is
vector<int> cardvalues = {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
// a vector that describes how many cards there are with a certain value
vector<int> deck = { acecard, twocard, threecard, fourcard, fivecard, sixcard, sevencard, eightcard, ninecard, tencard };
// a vector keeping track of how many of each cards there's left in the deck
int start()
{
int deckcount;
deckcount = 0;
int decksize;
decksize = cardvalues.size();
while (decksize >= 49)
{
deckcount += cardsubtracter(cardvalues);
};
return deckcount;
}
int cardcounting()
{
int deckcount;
deckcount = start();
deckcount += cardsubtracter(cardvalues);
return deckcount;
}
int main()
{
int value;
value = cardcounting();
int size;
size = cardvalues.size();
cout << value << "\n";
cout << size;
return 0;
}
#include<iostream>
#include<random>
using namespace std;
int numbergenerator(int x, int y)
{
int number;
random_device generator;
uniform_int_distribution<>distrib(x, y);
number = distrib(generator); //picks random element from vector
return number;
}
int cardsubtracter(vector<int> mynum)
{
int counter;
int size;
int number;
size = mynum.size() - 1;//gives the range of values to picked from the vectorlist
number = numbergenerator(0, size);//gives a random number to pick from the vectorlist
counter = mynum[number]; // uses the random number to pick a value from the vectorlist
mynum.erase(mynum.begin()+number); //removes that value from the vectorlist
return counter;
}
I looked up the max limit of vectors and it said that vectors can hold up 232 values with integers, which should work for this. So I also tried creating a new file and copying the code over to that in case there was something wrong with this file.
There could be different reasons why a vector may not be able to hold all 52 elements. Some possible reasons are:
Insufficient memory: Each element in a vector requires a certain amount of memory, and the total memory required for all 52 elements may exceed the available memory. This can happen if the elements are large, or if there are many other variables or data structures in the environment that consume memory.
Data type limitations: The data type of the vector may not be able to accommodate all 52 elements. For example, if the vector is of type "integer", it can only hold integers up to a certain limit, beyond which it will overflow or produce incorrect results.
Code errors: There may be errors in the code that prevent all 52 elements from being added to the vector. For example, if the vector is being filled in a loop, there may be a mistake in the loop condition or in the indexing that causes the loop to terminate early or skip some elements.
To determine the exact reason for the vector not being able to hold all 52 elements, it is necessary to examine the code, the data types involved, and the memory usage.

DP - Min cost climbing stairs

I'm trying to solve Min cost - climbing stairs problem.
This is the link: https://leetcode.com/problems/min-cost-climbing-stairs/
I've written the recursive version of the solution and it worked for the sample test cases, and obviously it showed TLE for the rest cases.
Here's the recursive version:
int solve(int[] arr, int idx, int cost) {
if(idx >= arr.length) return cost;
return Math.min(solve(arr, idx + 1, cost + arr[idx]),
solve(arr, idx + 2, cost + arr[idx]));
}
Now, the problem is when I'm trying to memoize it using 1-d array, I'm not getting correct answers for sample cases as well.
Here's the memoized version:
int[] t;
Solution() {
this.t = new int[1001];
}
public int minCostClimbingStairs(int[] arr) {
int n = arr.length;
Arrays.fill(t, -1);
return Math.min(solve(arr, 0, 0), solve(arr, 1, 0));
}
int solve(int[] arr, int idx, int cost) {
if(idx >= arr.length) return cost;
if(t[idx] != -1) return t[idx];
return t[idx] = Math.min(solve(arr, idx + 1, cost + arr[idx]),
solve(arr, idx + 2, cost + arr[idx]));
}
What am I missing?
Thanks in advance.
From a Noob in DP.

How to find two optimal weights in a vector?

Imagine you're given an unsorted array [5,11,7,4,19,8,11,6,17] and a max weight 23. [similar to Two Sum Problem by a bit different]
One needs to find two optimal weights (by which I mean if two weights that are (almost or not) half of the weight you're trying to find) in this case [5,17], [3,19], [11,11] so I need to return [11,11].
I was taken back by the problem, and could not solve it. [I was not allowed to use structs]
I tried to sort [3, 5, 6, 7, 8, 11, 11, 17, 19] and search from both ends and store indexes of values that were <= max weight in a vector as a pair (like v[i], v[i+1] and check them later by their pairs) then return a pair with both largest vals, but got confused.
[although, weights were doubles and I did not see duplicates at that set I did not use unsorted_map(hashMap), might it've worked?]
Can anyone suggest how should I go about this problem? is it similar to "knapsack problem"? Thank you
You can use Two Pointer Approach for the problem.
Algorithm:
Sort the array.
Have two pointers startIndex and endIndex to 0 and arraySize-1.
sum = arr[startIndex] + arr[endIndex]
If sum is less than or equal to 23, increment startIndex else decrement endIndex.
keep track of closest value using a variable.
finish when startIndex == endIndex
Code in Java:
public class Solution {
private ArrayList<Integer> twoSumClosest(ArrayList<Integer> a, int s) {
// Sort the arraylist
Collections.sort(a);
// closests sum we got till now
int sumClosest = Integer.MIN_VALUE;
// indexes used to traverse
int startIndex = 0;
int endIndex = a.size() - 1 ;
// answer Indexes
int firstIndex = 1;
int secondIndex = a.size() - 1;
while( startIndex < endIndex ) {
if( a.get(startIndex) + a.get(endIndex) > s) {
endIndex--;
continue;
} else {
if( a.get(startIndex) + a.get(endIndex) > sumClosest ) {
sumClosest = a.get(startIndex) + a.get(endIndex);
firstIndex = startIndex;
secondIndex = endIndex;
}
startIndex++;
}
}
ArrayList<Integer> ans = new ArrayList<>();
ans.add(firstIndex);
ans.add(secondIndex);
return ans;
}
}
Time Complexity: O(nlogn)
O(n) if array was already sorted
Extra Space Needed: O(1)

A recursion algorithm

Ok, this may seem trivial to some, but I'm stuck.
Here's the algorithm I'm supposed to use:
Here’s a recursive algorithm. Suppose we have n integers in a non-increasing sequence, of which the first is the number k. Subtract one from each of the first k numbers after the first. (If there are fewer than k such number, the sequence is not graphical.) If necessary, sort the resulting sequence of n-1 numbers (ignoring the first one) into a non-increasing sequence. The original sequence is graphical if and only if the second one is. For the stopping conditions, note that a sequence of all zeroes is graphical, and a sequence containing a negative number is not. (The proof of this is not difficult, but we won’t deal with it here.)
Example:
Original sequence: 5, 4, 3, 3, 2, 1, 1
Subtract 1 five times: 3, 2, 2, 1, 0, 1
Sort: 3, 2, 2, 1, 1, 0
Subtract 1 three times: 1, 1, 0, 1, 0
Sort: 1, 1, 1, 0, 0
Subtract 1 once: 0, 1, 0, 0
Sort: 1, 0, 0, 0
Subtract 1 once: -1, 0, 0
We have a negative number, so the original sequence is not graphical.
This seems simple enough to me, but when I try to execute the algorithm I get stuck.
Here's the function I've written so far:
//main
int main ()
{
//local variables
const int MAX = 30;
ifstream in;
ofstream out;
int graph[MAX], size;
bool isGraph;
//open and test file
in.open("input3.txt");
if (!in) {
cout << "Error reading file. Exiting program." << endl;
exit(1);
}
out.open("output3.txt");
while (in >> size) {
for (int i = 0; i < size; i++) {
in >> graph[i];
}
isGraph = isGraphical(graph, 0, size);
if (isGraph) {
out << "Yes\n";
}else
out << "No\n";
}
//close all files
in.close();
out.close();
cin.get();
return 0;
}//end main
bool isGraphical(int degrees[], int start, int end){
bool isIt = false;
int ender;
inSort(degrees, end);
ender = degrees[start] + start + 1;
for(int i = 0; i < end; i++)
cout << degrees[i];
cout << endl;
if (degrees[start] == 0){
if(degrees[end-1] < 0)
return false;
else
return true;
}
else{
for(int i = start + 1; i < ender; i++) {
degrees[i]--;
}
isIt = isGraphical(degrees, start+1, end);
}
return isIt;
}
void inSort(int x[],int length)
{
for(int i = 0; i < length; ++i)
{
int current = x[i];
int j;
for(j = i-1; j >= 0 && current > x[j]; --j)
{
x[j+1] = x[j];
}
x[j+1] = current;
}
}
I seem to get what that sort function is doing, but when I debug, the values keep jumping around. Which I assume is coming from my recursive function.
Any help?
EDIT:
Code is functional. Please see the history if needed.
With help from #RMartinhoFernandes I updated my code. Includes working insertion sort.
I updated the inSort funcion boundaries
I added an additional ending condition from the comments. But the algorithm still isn't working. Which makes me thing my base statements are off. Would anyone be able to help further? What am I missing here?
Ok, I helped you out in chat, and I'll post a summary of the issues you had here.
The insertion sort inner loop should go backwards, not forwards. Make it for(i = (j - 1); (i >= 0) && (key > x[i]); i--);
There's an out-of-bounds access in the recursion base case: degrees[end] should be degrees[end-1];
while (!in.eof()) will not read until the end-of-file. while(in >> size) is a superior alternative.
Are you sure you ender do not go beyond end? Value of ender is degrees[start] which could go beyond the value of end.
Then you are using ender in for loop
for(int i = start+1; i < ender; i++){ //i guess it should be end here
I think your insertion sort algorithm isn't right. Try this one (note that this sorts it in the opposite order from what you want though). Also, you want
for(int i = start + 1; i < ender + start + 1; i++) {
instead of
for(int i = start+1; i < ender; i++)
Also, as mentioned in the comments, you want to check if degrees[end - 1] < 0 instead of degrees[end].

How to scale down a range of numbers with a known min and max value

So I am trying to figure out how to take a range of numbers and scale the values down to fit a range. The reason for wanting to do this is that I am trying to draw ellipses in a java swing jpanel. I want the height and width of each ellipse to be in a range of say 1-30. I have methods that find the minimum and maximum values from my data set, but I won't have the min and max until runtime. Is there an easy way to do this?
Let's say you want to scale a range [min,max] to [a,b]. You're looking for a (continuous) function that satisfies
f(min) = a
f(max) = b
In your case, a would be 1 and b would be 30, but let's start with something simpler and try to map [min,max] into the range [0,1].
Putting min into a function and getting out 0 could be accomplished with
f(x) = x - min ===> f(min) = min - min = 0
So that's almost what we want. But putting in max would give us max - min when we actually want 1. So we'll have to scale it:
x - min max - min
f(x) = --------- ===> f(min) = 0; f(max) = --------- = 1
max - min max - min
which is what we want. So we need to do a translation and a scaling. Now if instead we want to get arbitrary values of a and b, we need something a little more complicated:
(b-a)(x - min)
f(x) = -------------- + a
max - min
You can verify that putting in min for x now gives a, and putting in max gives b.
You might also notice that (b-a)/(max-min) is a scaling factor between the size of the new range and the size of the original range. So really we are first translating x by -min, scaling it to the correct factor, and then translating it back up to the new minimum value of a.
Here's some JavaScript for copy-paste ease (this is irritate's answer):
function scaleBetween(unscaledNum, minAllowed, maxAllowed, min, max) {
return (maxAllowed - minAllowed) * (unscaledNum - min) / (max - min) + minAllowed;
}
Applied like so, scaling the range 10-50 to a range between 0-100.
var unscaledNums = [10, 13, 25, 28, 43, 50];
var maxRange = Math.max.apply(Math, unscaledNums);
var minRange = Math.min.apply(Math, unscaledNums);
for (var i = 0; i < unscaledNums.length; i++) {
var unscaled = unscaledNums[i];
var scaled = scaleBetween(unscaled, 0, 100, minRange, maxRange);
console.log(scaled.toFixed(2));
}
0.00, 18.37, 48.98, 55.10, 85.71, 100.00
Edit:
I know I answered this a long time ago, but here's a cleaner function that I use now:
Array.prototype.scaleBetween = function(scaledMin, scaledMax) {
var max = Math.max.apply(Math, this);
var min = Math.min.apply(Math, this);
return this.map(num => (scaledMax-scaledMin)*(num-min)/(max-min)+scaledMin);
}
Applied like so:
[-4, 0, 5, 6, 9].scaleBetween(0, 100);
[0, 30.76923076923077, 69.23076923076923, 76.92307692307692, 100]
For convenience, here is Irritate's algorithm in a Java form. Add error checking, exception handling and tweak as necessary.
public class Algorithms {
public static double scale(final double valueIn, final double baseMin, final double baseMax, final double limitMin, final double limitMax) {
return ((limitMax - limitMin) * (valueIn - baseMin) / (baseMax - baseMin)) + limitMin;
}
}
Tester:
final double baseMin = 0.0;
final double baseMax = 360.0;
final double limitMin = 90.0;
final double limitMax = 270.0;
double valueIn = 0;
System.out.println(Algorithms.scale(valueIn, baseMin, baseMax, limitMin, limitMax));
valueIn = 360;
System.out.println(Algorithms.scale(valueIn, baseMin, baseMax, limitMin, limitMax));
valueIn = 180;
System.out.println(Algorithms.scale(valueIn, baseMin, baseMax, limitMin, limitMax));
90.0
270.0
180.0
Here's how I understand it:
What percent does x lie in a range
Let's assume you have a range from 0 to 100. Given an arbitrary number from that range, what "percent" from that range does it lie in? This should be pretty simple, 0 would be 0%, 50 would be 50% and 100 would be 100%.
Now, what if your range was 20 to 100? We cannot apply the same logic as above (divide by 100) because:
20 / 100
doesn't give us 0 (20 should be 0% now). This should be simple to fix, we just need to make the numerator 0 for the case of 20. We can do that by subtracting:
(20 - 20) / 100
However, this doesn't work for 100 anymore because:
(100 - 20) / 100
doesn't give us 100%. Again, we can fix this by subtracting from the denominator as well:
(100 - 20) / (100 - 20)
A more generalized equation for finding out what % x lies in a range would be:
(x - MIN) / (MAX - MIN)
Scale range to another range
Now that we know what percent a number lies in a range, we can apply it to map the number to another range. Let's go through an example.
old range = [200, 1000]
new range = [10, 20]
If we have a number in the old range, what would the number be in the new range? Let's say the number is 400. First, figure out what percent 400 is within the old range. We can apply our equation above.
(400 - 200) / (1000 - 200) = 0.25
So, 400 lies in 25% of the old range. We just need to figure out what number is 25% of the new range. Think about what 50% of [0, 20] is. It would be 10 right? How did you arrive at that answer? Well, we can just do:
20 * 0.5 = 10
But, what about from [10, 20]? We need to shift everything by 10 now. eg:
((20 - 10) * 0.5) + 10
a more generalized formula would be:
((MAX - MIN) * PERCENT) + MIN
To the original example of what 25% of [10, 20] is:
((20 - 10) * 0.25) + 10 = 12.5
So, 400 in the range [200, 1000] would map to 12.5 in the range [10, 20]
TLDR
To map x from old range to new range:
OLD PERCENT = (x - OLD MIN) / (OLD MAX - OLD MIN)
NEW X = ((NEW MAX - NEW MIN) * OLD PERCENT) + NEW MIN
I came across this solution but this does not really fit my need. So I digged a bit in the d3 source code. I personally would recommend to do it like d3.scale does.
So here you scale the domain to the range. The advantage is that you can flip signs to your target range. This is useful since the y axis on a computer screen goes top down so large values have a small y.
public class Rescale {
private final double range0,range1,domain0,domain1;
public Rescale(double domain0, double domain1, double range0, double range1) {
this.range0 = range0;
this.range1 = range1;
this.domain0 = domain0;
this.domain1 = domain1;
}
private double interpolate(double x) {
return range0 * (1 - x) + range1 * x;
}
private double uninterpolate(double x) {
double b = (domain1 - domain0) != 0 ? domain1 - domain0 : 1 / domain1;
return (x - domain0) / b;
}
public double rescale(double x) {
return interpolate(uninterpolate(x));
}
}
And here is the test where you can see what I mean
public class RescaleTest {
#Test
public void testRescale() {
Rescale r;
r = new Rescale(5,7,0,1);
Assert.assertTrue(r.rescale(5) == 0);
Assert.assertTrue(r.rescale(6) == 0.5);
Assert.assertTrue(r.rescale(7) == 1);
r = new Rescale(5,7,1,0);
Assert.assertTrue(r.rescale(5) == 1);
Assert.assertTrue(r.rescale(6) == 0.5);
Assert.assertTrue(r.rescale(7) == 0);
r = new Rescale(-3,3,0,1);
Assert.assertTrue(r.rescale(-3) == 0);
Assert.assertTrue(r.rescale(0) == 0.5);
Assert.assertTrue(r.rescale(3) == 1);
r = new Rescale(-3,3,-1,1);
Assert.assertTrue(r.rescale(-3) == -1);
Assert.assertTrue(r.rescale(0) == 0);
Assert.assertTrue(r.rescale(3) == 1);
}
}
I sometimes find a variation of this useful.
Wrapping the scale function in a class so that I do not need to pass around the min/max values if scaling the same ranges in several places
Adding two small checks that ensures that the result value stays within the expected range.
Example in JavaScript:
class Scaler {
constructor(inMin, inMax, outMin, outMax) {
this.inMin = inMin;
this.inMax = inMax;
this.outMin = outMin;
this.outMax = outMax;
}
scale(value) {
const result = (value - this.inMin) * (this.outMax - this.outMin) / (this.inMax - this.inMin) + this.outMin;
if (result < this.outMin) {
return this.outMin;
} else if (result > this.outMax) {
return this.outMax;
}
return result;
}
}
This example along with a function based version comes from the page https://writingjavascript.com/scaling-values-between-two-ranges
Based on Charles Clayton's response, I included some JSDoc, ES6 tweaks, and incorporated suggestions from the comments in the original response.
/**
* Returns a scaled number within its source bounds to the desired target bounds.
* #param {number} n - Unscaled number
* #param {number} tMin - Minimum (target) bound to scale to
* #param {number} tMax - Maximum (target) bound to scale to
* #param {number} sMin - Minimum (source) bound to scale from
* #param {number} sMax - Maximum (source) bound to scale from
* #returns {number} The scaled number within the target bounds.
*/
const scaleBetween = (n, tMin, tMax, sMin, sMax) => {
return (tMax - tMin) * (n - sMin) / (sMax - sMin) + tMin;
}
if (Array.prototype.scaleBetween === undefined) {
/**
* Returns a scaled array of numbers fit to the desired target bounds.
* #param {number} tMin - Minimum (target) bound to scale to
* #param {number} tMax - Maximum (target) bound to scale to
* #returns {number} The scaled array.
*/
Array.prototype.scaleBetween = function(tMin, tMax) {
if (arguments.length === 1 || tMax === undefined) {
tMax = tMin; tMin = 0;
}
let sMax = Math.max(...this), sMin = Math.min(...this);
if (sMax - sMin == 0) return this.map(num => (tMin + tMax) / 2);
return this.map(num => (tMax - tMin) * (num - sMin) / (sMax - sMin) + tMin);
}
}
// ================================================================
// Usage
// ================================================================
let nums = [10, 13, 25, 28, 43, 50], tMin = 0, tMax = 100,
sMin = Math.min(...nums), sMax = Math.max(...nums);
// Result: [ 0.0, 7.50, 37.50, 45.00, 82.50, 100.00 ]
console.log(nums.map(n => scaleBetween(n, tMin, tMax, sMin, sMax).toFixed(2)).join(', '));
// Result: [ 0, 30.769, 69.231, 76.923, 100 ]
console.log([-4, 0, 5, 6, 9].scaleBetween(0, 100).join(', '));
// Result: [ 50, 50, 50 ]
console.log([1, 1, 1].scaleBetween(0, 100).join(', '));
.as-console-wrapper { top: 0; max-height: 100% !important; }
I've taken Irritate's answer and refactored it so as to minimize the computational steps for subsequent computations by factoring it into the fewest constants. The motivation is to allow a scaler to be trained on one set of data, and then be run on new data (for an ML algo). In effect, it's much like SciKit's preprocessing MinMaxScaler for Python in usage.
Thus, x' = (b-a)(x-min)/(max-min) + a (where b!=a) becomes x' = x(b-a)/(max-min) + min(-b+a)/(max-min) + a which can be reduced to two constants in the form x' = x*Part1 + Part2.
Here's a C# implementation with two constructors: one to train, and one to reload a trained instance (e.g., to support persistence).
public class MinMaxColumnSpec
{
/// <summary>
/// To reduce repetitive computations, the min-max formula has been refactored so that the portions that remain constant are just computed once.
/// This transforms the forumula from
/// x' = (b-a)(x-min)/(max-min) + a
/// into x' = x(b-a)/(max-min) + min(-b+a)/(max-min) + a
/// which can be further factored into
/// x' = x*Part1 + Part2
/// </summary>
public readonly double Part1, Part2;
/// <summary>
/// Use this ctor to train a new scaler.
/// </summary>
public MinMaxColumnSpec(double[] columnValues, int newMin = 0, int newMax = 1)
{
if (newMax <= newMin)
throw new ArgumentOutOfRangeException("newMax", "newMax must be greater than newMin");
var oldMax = columnValues.Max();
var oldMin = columnValues.Min();
Part1 = (newMax - newMin) / (oldMax - oldMin);
Part2 = newMin + (oldMin * (newMin - newMax) / (oldMax - oldMin));
}
/// <summary>
/// Use this ctor for previously-trained scalers with known constants.
/// </summary>
public MinMaxColumnSpec(double part1, double part2)
{
Part1 = part1;
Part2 = part2;
}
public double Scale(double x) => (x * Part1) + Part2;
}

Resources