首页 > 解决方案 > 使用比例重新映射数字

问题描述

例如,我有一个数字域domain = [100, 200]和多个用于划分范围的波段,例如bands = 5. 我知道每个波段对应一个值:

band #1 --> v = 0.2
band #2 --> v = 0.4
band #3 --> v = 0.6
band #4 --> v = 0.8
band #5 --> v = 1.0

这些值是固定的(硬编码):如果bands 变成bands = 6了,那么开发人员就可以选择band #6.

我想将域划分为大小根据使用的比例而变化的波段。例如,我可能想使用线性或对数或 pow 比例。

然后我想要一个函数,它在输入中接受一个数字x ∈ domain,并且必须返回v与 inout 数字所属的波段关联的值。

这里有一个类似的问题,但现在我想使用不同的比例(例如我可以使用d3 scales)但我不知道如何..

这里有一段代码:

function getLinearScaledValue(x, min, max, bands) {
  const range = max - min
  if (x === max) {
    return 1
  } else {
    return Math.floor(1 + ((x - min) / range) * bands) / bands
  }
}

其中minmax是域的最小值和最大值。

我认为梦游的例子很好,所以我把它们放在这里:

如果波段 = 5:

band #1 --> v = 0.2
band #2 --> v = 0.4
band #3 --> v = 0.6
band #4 --> v = 0.8
band #5 --> v = 1.0

(1) 如果比例是线性的并且 domain = [0, 100] --> 波段是:

band #1 --> v = 0.2 --> [0, 20]
band #2 --> v = 0.4 --> [21, 40]
band #3 --> v = 0.6 --> [41, 60]
band #4 --> v = 0.8 --> [61, 80]
band #5 --> v = 1.0 --> [81, 100]

例如:

if x = 0  --> v = 0.2
if x = 10 --> v = 0.2
if x = 21 --> v = 0.4
if x = 98 --> v = 1.0

(2) 如果比例是线性的并且 domain = [100, 200] --> 波段是:

band #1 --> v = 0.2 --> [100, 120]
band #2 --> v = 0.4 --> [121, 140]
band #3 --> v = 0.6 --> [141, 160]
band #4 --> v = 0.8 --> [161, 180]
band #5 --> v = 1.0 --> [181, 200]

例如:

if x = 100 --> v = 0.2
if x = 110 --> v = 0.2
if x = 121 --> v = 0.4
if x = 198 --> v = 1.0

(3) 如果 scale 是对数且 domain = [0, 100] --> 波段是:

band #1 --> v = 0.2 --> [?, ?]
band #2 --> v = 0.4 --> [?, ?]
band #3 --> v = 0.6 --> [?, ?]
band #4 --> v = 0.8 --> [?, ?]
band #5 --> v = 1.0 --> [?, ?]

例如:

if x = 0  --> v = ?
if x = 10 --> v = ?
if x = 21 --> v = ?
if x = 98 --> v = ?

标签: javascript

解决方案


在我之前的回答中,我展示了计算某个范围内数字的波段索引的正确函数:

const index = (min, max, bands, n) =>
    Math.floor(bands * (n - min) / (max - min + 1));

const band = n => index(0, 100, 5, n);

console.log(band(0),  band(20));  // 0 0
console.log(band(21), band(40));  // 1 1
console.log(band(41), band(60));  // 2 2
console.log(band(61), band(80));  // 3 3
console.log(band(81), band(100)); // 4 4

上述函数使用线性刻度。但是,很容易将其推广到使用另一个尺度:

const index = (scale, min, max, bands, n) =>
    Math.floor(bands * scale(n - min) / scale(max - min + 1));

const log = x => Math.log(x + 1);

const logBand = n => index(log, 0, 100, 5, n);

console.log(logBand(0),  logBand(1));   // 0 0
console.log(logBand(2),  logBand(5));   // 1 1
console.log(logBand(6),  logBand(15));  // 2 2
console.log(logBand(16), logBand(39));  // 3 3
console.log(logBand(40), logBand(100)); // 4 4

这里我们使用了对数刻度。请注意,我们在计算其对数之前增加了索引,因为零的对数是未定义的,尽管 JavaScript 很高兴地返回 as 的自然对数的极限x趋于x零(即-Infinity)。但是,-Infinity它不是一个有效的索引。

无论如何,我们的范围如下:

i: 0   -->  [0  - 1]   -->  0.2
i: 1   -->  [2  - 5]   -->  0.4
i: 2   -->  [6  - 15]  -->  0.6
i: 3   -->  [16 - 39]  -->  0.8
i: 4   -->  [40 - 100] -->  1

请注意,尽管我们的规模是对数的,但我们的范围呈指数增长。这是有道理的,因为当我们以对数方式缩放范围时,我们将数字压缩在一起。因此,当我们将压缩范围划分为带时,每个带中的元素数量呈指数增长。下图可以最好地解释它:

对数刻度

在 x 轴之一,我们有我们的线性刻度,其值从1101。在 y 轴上,我们有我们的对数刻度,其值从log(1)log(101)(表示为 5/5 用于教育目的)。如您所见,我们将我们的对数范围划分为均匀大小的波段。然而,在我们的线性尺度上,这些波段会呈指数级增长。


推荐阅读