所以我试着弄清楚如何取一组数字,并将值缩小到适合这个范围。这样做的原因是我试图在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页面

其他回答

以下是我的理解:


x在范围内的百分比是多少

假设你有一个从0到100的范围。给定这个范围内的任意一个数字,它在这个范围内的“百分比”是多少?这应该很简单,0是0% 50是50% 100是100%。

现在,如果你的范围是20到100呢?我们不能应用与上面相同的逻辑(除以100),因为:

20 / 100

不是0(现在20应该是0%)。这应该很简单,我们只需要让分子为0。我们可以通过减去:

(20 - 20) / 100

然而,这不再适用于100,因为:

(100 - 20) / 100

不能给我们100%的答案。同样,我们也可以通过减去分母来解决这个问题:

(100 - 20) / (100 - 20)

求% x在范围中的位置的一个更一般化的方程是:

(x - MIN) / (MAX - MIN)

将范围缩放到另一个范围

现在我们知道了一个数字在一个范围内所占的百分比,我们可以应用它来将这个数字映射到另一个范围。让我们看一个例子。

old range = [200, 1000]
new range = [10, 20]

如果我们有一个在旧范围内的数,在新范围内的数是多少?假设这个数字是400。首先,计算出在旧的范围内百分之400是多少。我们可以应用上面的方程。

(400 - 200) / (1000 - 200) = 0.25

400只占原来范围的25%我们只需要算出新范围的25%是多少。想想[0,20]的50%是多少。应该是10,对吧?你是怎么得出那个答案的?我们可以这样做:

20 * 0.5 = 10

但是,从[10,20]呢?我们得在10点前全部转移。例如:

((20 - 10) * 0.5) + 10

更一般化的公式是:

((MAX - MIN) * PERCENT) + MIN

对于原示例[10,20]的25%是多少:

((20 - 10) * 0.25) + 10 = 12.5

因此,范围[200,1000]中的400将映射到范围[10,20]中的12.5


TLDR

将x从旧范围映射到新范围:

OLD PERCENT = (x - OLD MIN) / (OLD MAX - OLD MIN)
NEW X = ((NEW MAX - NEW MIN) * OLD PERCENT) + NEW MIN

为了方便起见,下面是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

我有时发现这种方法的变体很有用。

将缩放函数包装在一个类中,这样如果在几个地方缩放相同的范围,就不需要传递最小/最大值 添加两个小检查,确保结果值保持在预期范围内。

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;
}

假设你想要将范围[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的新的最小值。