首页 文章

如何缩小具有已知最小值和最大值的数字范围

提问于
浏览
195

因此,我试图找出如何获取一系列数字并将值缩小到适合范围 . 想要这样做的原因是我试图在java swing jpanel中绘制省略号 . 我希望每个椭圆的高度和宽度在1-30的范围内 . 我有从我的数据集中找到最小值和最大值的方法,但是直到运行时才会有min和max . 是否有捷径可寻?

6 回答

  • 25

    假设您要将范围 [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
    

    这几乎就是我们想要的 . 但是当我们真正想要时,输入 max 会给我们 max - min 所以我们必须扩展它:

    x - min                                  max - min
    f(x) = ---------   ===>   f(min) = 0;  f(max) =  --------- = 1
           max - min                                 max - min
    

    这就是我们想要的 . 所以我们需要进行翻译和缩放 . 现在,如果我们想获得 ab 的任意值,我们需要更复杂的东西:

    (b-a)(x - min)
    f(x) = --------------  + a
              max - min
    

    您可以验证为 x 输入 min 现在给出 a ,并且输入 max 给出 b .

    您可能还注意到 (b-a)/(max-min) 是新范围大小与原始范围大小之间的缩放系数 . 所以我们首先将 x 翻译为 -min ,将其缩放到正确的因子,然后将其转换回新的最小值 a .

    希望这可以帮助 .

  • 38

    这里有一些用于复制粘贴的JavaScript(这是烦人的答案):

    function scaleBetween(unscaledNum, minAllowed, maxAllowed, min, max) {
      return (maxAllowed - minAllowed) * (unscaledNum - min) / (max - min) + minAllowed;
    }
    

    像这样应用,将范围10-50缩放到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:

    我知道我很久以前就回答了这个问题,但是我现在使用的是一个更清洁的功能:

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

    应用如下:

    [-4, 0, 5, 6, 9].scaleBetween(0, 100);
    

    [0,30.76923076923077,69.23076923076923,76.92307692307692,100]

  • 9

    为方便起见,这里是Java形式的Irritate算法 . 根据需要添加错误检查,异常处理和调整 .

    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
    
  • 1

    这是我理解的方式:


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

    假设您的范围从 0100 . 给定该范围内的任意数字,该范围内的"percent"是什么?这应该很简单, 00%5050%100100% .

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

    20 / 100
    

    不给我们 020 现在应该 0% ) . 这应该很容易修复,我们只需要为 20 的情况制作分子 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] 怎么样?我们现在需要通过_1716039改变一切 . 例如:

    ((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
    
  • 448

    我遇到了这个解决方案,但这并不适合我的需要 . 所以我挖了d3源代码中的一点 . 我个人会建议像d3.scale那样做 .

    所以在这里你将域缩放到范围 . 优点是您可以将标志翻转到目标范围 . 这很有用,因为计算机屏幕上的y轴自上而下,因此较大的值具有较小的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));
        }
    }
    

    这是测试,你可以看到我的意思

    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);
        }
    }
    
  • 9

    我采用了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;
    }
    

相关问题