如何计算4点的加权中心点?

时间:2017-02-28 22:53:33

标签: javascript math

如果我有4分

        var x1;
        var y1;
        var x2;
        var y2;
        var x3;
        var y3;
        var x4;
        var y4;

组成一个盒子。所以

(x1,y1) is top left
(x2,y2) is top right
(x3,y3) is bottom left
(x4,y4) is bottom right

然后每个点的重量范围为0-522。如何计算位于框内的坐标(tx,ty),其中该点更接近具有最小权重的位置(但考虑所有权重)。所以举个例子。 if(x3,y3)的权重为0,其他权重为522,(tx,ty)应为(x3,y3)。如果那时(x2,y2)的权重像400那么,那么(tx,ty)应该从(x3,y3)向(x2,y2)移动一点点。

有没有人知道是否有这个公式? 感谢

4 个答案:

答案 0 :(得分:2)

创建最小,完整,可验证的例外

这里有一点棘手的问题,但它真的很有趣。可能有更好的方法来解决它,但我发现使用PointVector数据抽象来更好地模拟问题是最可靠的

我将从一个非常简单的数据集开始 - 可以读取下面的数据(例如)点 D 是笛卡尔坐标(1,1),权重< / em> of 100.

|
|
| B(0,1) #10        D(1,1) #100
|                
| 
|         ? solve weighted average
|
| 
| A(0,0) #20        C(1,0) #40
+----------------------------------

以下是我们如何做的

  1. 找到未加权的中点m
  2. 使用Vector(degrees, magnitude)作为原点
  3. 将每个Point转换为m的向量
  4. 将所有向量添加到一起,vectorSum
  5. vectorSum的幅度除以总幅度
  6. 将矢量转换为点p
  7. 通过未加权的中点p
  8. 偏移m

    可能的JavaScript实现

    我会一次一个地穿过这些碎片,然后在底部会有一个完整的可运行的例子。

    Math.atan2Math.cosMath.sin函数我们将以弧度为单位使用返回答案。这有点麻烦,所以有几个助手可以在学位上工作。

    // math
    const pythag = (a,b) => Math.sqrt(a * a + b * b)
    const rad2deg = rad => rad * 180 / Math.PI
    const deg2rad = deg => deg * Math.PI / 180
    const atan2 = (y,x) => rad2deg(Math.atan2(y,x))
    const cos = x => Math.cos(deg2rad(x))
    const sin = x => Math.sin(deg2rad(x))
    

    现在我们需要一种方式来表示我们的Point和与点相关的函数

    // Point
    const Point = (x,y) => ({
      x,
      y,
      add: ({x: x2, y: y2}) =>
        Point(x + x2, y + y2),
      sub: ({x: x2, y: y2}) =>
        Point(x - x2, y - y2),
      bind: f =>
        f(x,y),
      inspect: () =>
        `Point(${x}, ${y})`
    })
    
    Point.origin = Point(0,0)
    Point.fromVector = ({a,m}) => Point(m * cos(a), m * sin(a))
    

    当然Vector同样如此 - 奇怪的是,当你将它们转换回 x y 笛卡尔坐标时,将Vector一起添加到一起实际上更容易。除此之外,这段代码非常简单

    // Vector
    const Vector = (a,m) => ({
      a,
      m,
      scale: x =>
        Vector(a, m*x),
      add: v =>
        Vector.fromPoint(Point.fromVector(Vector(a,m)).add(Point.fromVector(v))),
      inspect: () =>
        `Vector(${a}, ${m})`
    })
    
    Vector.zero = Vector(0,0)
    Vector.fromPoint = ({x,y}) => Vector(atan2(y,x), pythag(x,y))
    

    最后,我们需要在JavaScript中表示上面的数据并创建一个计算加权点的函数。我们这边有PointVector,这将是小菜一碟

    // data
    const data = [
      [Point(0,0), 20],
      [Point(0,1), 10],
      [Point(1,1), 100],
      [Point(1,0), 40],
    ]
    
    // calc weighted point
    const calcWeightedMidpoint = points => {
      let midpoint = calcMidpoint(points)
      let totalWeight = points.reduce((acc, [_, weight]) => acc + weight, 0)
      let vectorSum = points.reduce((acc, [point, weight]) =>
        acc.add(Vector.fromPoint(point.sub(midpoint)).scale(weight/totalWeight)), Vector.zero)
      return Point.fromVector(vectorSum).add(midpoint)
    }
    
    console.log(calcWeightedMidpoint(data))
    // Point(0.9575396819442366, 0.7079725827019256)
    

    可运行脚本

    &#13;
    &#13;
    // math
    const pythag = (a,b) => Math.sqrt(a * a + b * b)
    const rad2deg = rad => rad * 180 / Math.PI
    const deg2rad = deg => deg * Math.PI / 180
    const atan2 = (y,x) => rad2deg(Math.atan2(y,x))
    const cos = x => Math.cos(deg2rad(x))
    const sin = x => Math.sin(deg2rad(x))
    
    // Point
    const Point = (x,y) => ({
      x,
      y,
      add: ({x: x2, y: y2}) =>
        Point(x + x2, y + y2),
      sub: ({x: x2, y: y2}) =>
        Point(x - x2, y - y2),
      bind: f =>
        f(x,y),
      inspect: () =>
        `Point(${x}, ${y})`
    })
    
    Point.origin = Point(0,0)
    Point.fromVector = ({a,m}) => Point(m * cos(a), m * sin(a))
    
    // Vector
    const Vector = (a,m) => ({
      a,
      m,
      scale: x =>
        Vector(a, m*x),
      add: v =>
        Vector.fromPoint(Point.fromVector(Vector(a,m)).add(Point.fromVector(v))),
      inspect: () =>
        `Vector(${a}, ${m})`
    })
    
    Vector.zero = Vector(0,0)
    Vector.unitFromPoint = ({x,y}) => Vector(atan2(y,x), 1)
    Vector.fromPoint = ({x,y}) => Vector(atan2(y,x), pythag(x,y))
    
    
    // data
    const data = [
      [Point(0,0), 20],
      [Point(0,1), 10],
      [Point(1,1), 100],
      [Point(1,0), 40],
    ]
    
    // calc unweighted midpoint
    const calcMidpoint = points => {
      let count = points.length;
      let midpoint = points.reduce((acc, [point, _]) => acc.add(point), Point.origin)
      return midpoint.bind((x,y) => Point(x/count, y/count))
    }
    
    // calc weighted point
    const calcWeightedMidpoint = points => {
      let midpoint = calcMidpoint(points)
      let totalWeight = points.reduce((acc, [_, weight]) => acc + weight, 0)
      let vectorSum = points.reduce((acc, [point, weight]) =>
        acc.add(Vector.fromPoint(point.sub(midpoint)).scale(weight/totalWeight)), Vector.zero)
      return Point.fromVector(vectorSum).add(midpoint)
    }
    
    console.log(calcWeightedMidpoint(data))
    // Point(0.9575396819442366, 0.7079725827019256)
    &#13;
    &#13;
    &#13;

    回到我们原来的可视化,一切看起来都正确!

    |
    |
    | B(0,1) #10        D(1,1) #100
    |
    |
    |                 * <-- about right here
    |
    | 
    | 
    | A(0,0) #20        C(1,0) #40
    +----------------------------------
    

    检查我们的工作

    使用相等加权的一组点,我们知道加权中点应该是什么。让我们验证我们的两个主要功能calcMidpointcalcWeightedMidpoint是否正常工作

    const data = [
      [Point(0,0), 5],
      [Point(0,1), 5],
      [Point(1,1), 5],
      [Point(1,0), 5],
    ]
    
    calcMidpoint(data)
    // => Point(0.5, 0.5)
    
    calcWeightedMidpoint(data)
    // => Point(0.5, 0.5)
    

    大!现在我们将测试一下其他一些权重是如何工作的。首先让我们尝试所有的点,但只有一个权重

    const data = [
      [Point(0,0), 0],
      [Point(0,1), 0],
      [Point(1,1), 0],
      [Point(1,0), 1],
    ]
    
    calcWeightedMidpoint(data)
    // => Point(1, 0)
    

    请注意,如果我们将这个重量改为一些荒谬的数字,那就无所谓了。向量的缩放基于权重的点百分比。如果它获得100%的重量,它(点)将不会拉过加权中点(点)本身

    const data = [
      [Point(0,0), 0],
      [Point(0,1), 0],
      [Point(1,1), 0],
      [Point(1,0), 1000],
    ]
    
    calcWeightedMidpoint(data)
    // => Point(1, 0)
    

    最后,我们会再确认一组,以确保加权工作正常 - 这次我们将有两对加权相等的点。输出完全我们期待的

    const data = [
      [Point(0,0), 0],
      [Point(0,1), 0],
      [Point(1,1), 500],
      [Point(1,0), 500],
    ]
    
    calcWeightedMidpoint(data)
    // => Point(1, 0.5)
    

    数百万分

    在这里,我们将使用随机权重创建一个随机坐标的巨大点云。如果点是随机的并且事情对我们的函数正常工作,答案应该非常接近Point(0,0)

    const RandomWeightedPoint = () => [
      Point(Math.random() * 1000 - 500, Math.random() * 1000 - 500),
      Math.random() * 1000
    ]
    
    let data = []
    for (let i = 0; i < 1e6; i++)
      data[i] = RandomWeightedPoint()
    
    calcWeightedMidpoint(data)
    // => Point(0.008690554978970092, -0.08307212085822799)
    

    A ++

答案 1 :(得分:1)

这是一个非常简单的方法:

  1. 将每个点的重量转换为522减去实际重量。
  2. 将每个x / y坐标乘以其调整后的重量。
  3. 将所有相乘的x / y坐标汇总在一起,并且 -
  4. 除以所有积分的调整总重量,以获得调整后的平均排名。
  5. 这应该产生一个点,其位置与“最轻”点成比例偏差,如上所述。假设权重是前缀w,快速片段(后跟JSFiddle示例)是:

    var tx = ((522-w1)*x1 + (522-w2)*x2 + (522-w3)*x3 + (522-w4)*x4) / (2088-(w1+w2+w3+w4));
    var ty = ((522-w1)*y1 + (522-w2)*y2 + (522-w3)*y3 + (522-w4)*y4) / (2088-(w1+w2+w3+w4));
    

    JSFiddle example of this

答案 2 :(得分:1)

假设w1w2w3w4是权重。 你可以从这开始(伪代码):

M = 522
a = 1
b = 1 / ( (1 - w1/M)^a + (1 - w2/M)^a + (1 - w3/M)^a + (1 - w4/M)^a )

tx = b * (x1*(1-w1/M)^a + x2*(1-w2/M)^a + x3*(1-w3/M)^a + x4*(1-w4/M)^a)
ty = b * (y1*(1-w1/M)^a + y2*(1-w2/M)^a + y3*(1-w3/M)^a + y4*(1-w4/M)^a)

这应该接近您想要完成的行为。对于最简单的案例集a=1,您的公式将更简单。您可以通过更改a

来调整行为

如果您使用Javascript,请务必使用Math.pow代替^

答案 3 :(得分:1)

尽管已经回答了这个问题,但我觉得一个简短的代码片段显示了计算加权平均值的简单性:

function weightedAverage(v1, w1, v2, w2) {
  if (w1 === 0) return v2;
  if (w2 === 0) return v1;
  return ((v1 * w1) + (v2 * w2)) / (w1 + w2);
}

现在,为了解决您的问题,您必须通过减速器将其应用于您的积分。减速器使其成为移动平均线:它返回的值表示合并点的权重

// point: { x: xCoordinate, y: yCoordinate, w: weight }
function avgPoint(p1, p2) {
  return {
    x: weightedAverage(p1.x, p1.w, p2.x, p2.w),
    x: weightedAverage(p1.x, p1.w, p2.x, p2.w),
    w: p1.w + pw.2,
  }
}

现在,您可以减少任何点列表以获得平均坐标及其代表的权重:

[ /* points */ ].reduce(avgPoint, { x: 0, y: 0, w: 0 })

我希望用户naomik不介意,但我在这个可运行的示例中使用了一些测试用例:

&#13;
&#13;
function weightedAverage(v1, w1, v2, w2) {
  if (w1 === 0) return v2;
  if (w2 === 0) return v1;
  return ((v1 * w1) + (v2 * w2)) / (w1 + w2);
}

function avgPoint(p1, p2) {
  return {
    x: weightedAverage(p1.x, p1.w, p2.x, p2.w),
    y: weightedAverage(p1.y, p1.w, p2.y, p2.w),
    w: p1.w + p2.w,
  }
}

function getAvgPoint(arr) {
  return arr.reduce(avgPoint, {
    x: 0,
    y: 0,
    w: 0
  });
}


const testCases = [
  { 
    data: [
      { x: 0, y: 0, w: 1 },
      { x: 0, y: 1, w: 1 },
      { x: 1, y: 1, w: 1 },
      { x: 1, y: 0, w: 1 },
    ],
    result: { x: 0.5, y: 0.5 }
  },
  
  { 
    data: [
      { x: 0, y: 0, w: 0 },
      { x: 0, y: 1, w: 0 },
      { x: 1, y: 1, w: 500 },
      { x: 1, y: 0, w: 500 },
    ],
    result: { x: 1, y: 0.5 }
  }
];

testCases.forEach(c => {
  var expected = c.result;
  var outcome = getAvgPoint(c.data);

  console.log("Expected:", expected.x, ",", expected.y);
  console.log("Returned:", outcome.x, ",", outcome.y);
  console.log("----");
});



const rndTest = (function() {
  const randomWeightedPoint = function() {
    return {
      x: Math.random() * 1000 - 500,
      y: Math.random() * 1000 - 500,
      w: Math.random() * 1000
    };
  };

  let data = []
  for (let i = 0; i < 1e6; i++)
    data[i] = randomWeightedPoint()

  return getAvgPoint(data);
}());

console.log("Expected: ~0 , ~0, 500000000")
console.log("Returned:", rndTest.x, ",", rndTest.y, ",", rndTest.w);
&#13;
.as-console-wrapper {
  min-height: 100%;
}
&#13;
&#13;
&#13;