所以我试着弄清楚如何取一组数字,并将值缩小到适合这个范围。这样做的原因是我试图在java swing jpanel中绘制椭圆。我希望每个椭圆的高度和宽度都在1-30的范围内。我有一些方法可以从我的数据集中找到最小值和最大值,但直到运行时才会有最小值和最大值。有什么简单的方法吗?
当前回答
我有时发现这种方法的变体很有用。
将缩放函数包装在一个类中,这样如果在几个地方缩放相同的范围,就不需要传递最小/最大值 添加两个小检查,确保结果值保持在预期范围内。
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;
}
}
这个例子以及一个基于函数的版本来自https://writingjavascript.com/scaling-values-between-two-ranges页面
其他回答
我将Irritate的答案进行重构,通过将其分解成最少的常数来最小化后续计算的计算步骤。其动机是允许在一组数据上训练一个缩放器,然后在新的数据上运行(对于ML算法)。实际上,它很像SciKit对Python的预处理MinMaxScaler。
因此,x' = (b-a)(x-min)/(max-min) +a(其中b!=a)变成x' = x(b-a)/(max-min) + min(-b+a)/(max-min) +a,可以化简为x' = x*Part1 + Part2的两个常数。
下面是一个带有两个构造函数的c#实现:一个用于训练,另一个用于重新加载训练过的实例(例如,支持持久性)。
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;
}
基于Charles Clayton的回复,我加入了一些JSDoc、ES6的调整,并在原始回复中加入了评论中的建议。
/** * 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; }
假设你想要将范围[min,max]扩展到[a,b]。你在寻找一个(连续的)函数满足
f(min) = a
f(max) = b
在你的例子中,a将是1,b将是30,但是让我们从更简单的东西开始,尝试将[min,max]映射到范围[0,1]。
将min放入函数中,得到0可以用
f(x) = x - min ===> f(min) = min - min = 0
这就是我们想要的。但是当我们想要1的时候,输入max会得到max - min。所以我们需要缩放它:
x - min max - min
f(x) = --------- ===> f(min) = 0; f(max) = --------- = 1
max - min max - min
这就是我们想要的。所以我们需要平移和缩放。现在如果我们想要得到a和b的任意值,我们需要一些更复杂的东西:
(b-a)(x - min)
f(x) = -------------- + a
max - min
你可以验证,输入x的min,得到a,输入max,得到b。
您可能还注意到(b-a)/(max-min)是新范围大小和原始范围大小之间的比例因子。所以实际上我们首先将x平移到-min,将它缩放到正确的因子,然后再将它平移回a的新的最小值。
我有时发现这种方法的变体很有用。
将缩放函数包装在一个类中,这样如果在几个地方缩放相同的范围,就不需要传递最小/最大值 添加两个小检查,确保结果值保持在预期范围内。
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;
}
}
这个例子以及一个基于函数的版本来自https://writingjavascript.com/scaling-values-between-two-ranges页面
为了方便起见,下面是Irritate的Java格式算法。添加错误检查、异常处理和必要的调整。
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;
}
}
测试人员:
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