如何通过减去属性(深度水平如何)获得两个对象的差异?

发布于 2025-01-22 13:17:38 字数 2674 浏览 0 评论 0 原文

我想减去完全相同结构的两个对象的值。尽管存在一个答案在这里,它仅限于无数据的对象。就我而言,我正在寻找一个可靠的解决方案,只要它们具有相同的结构,就可以减去任何深度的对象。

示例

考虑以下两个对象, EarthData2022 earthdata2050

const earthData2022 = {
  distanceFromSun: 149280000,
  continents: {
    asia: {
      area: 44579000,
      population: 4560667108,
      countries: { japan: { temperature: 62.5 } },
    },
    africa: { area: 30370000, population: 1275920972 },
    europe: { area: 10180000, population: 746419440 },
    america: { area: 42549000, population: 964920000 },
    australia: { area: 7690000, population: 25925600 },
    antarctica: { area: 14200000, population: 5000 },
  },
};

const earthData2050 = {
  distanceFromSun: 149280000,
  continents: {
    asia: {
      area: 44579000,
      population: 4560767108,
      countries: { japan: { temperature: 73.6 } },
    },
    africa: { area: 30370000, population: 1275960972 },
    europe: { area: 10180000, population: 746419540 },
    america: { area: 42549000, population: 964910000 },
    australia: { area: 7690000, population: 25928600 },
    antarctica: { area: 14200000, population: 5013 },
  },
};

请注意,这两个对象都有:

  • 完全相同的结构
  • 所有 values 是数字,是整数或整数小数。没有字符串或布尔值,也没有数组。

我想减去: seraindata2050 - erasedata2022 获得一个新对象:

// desired output
// continents' areas aren't expected to change so their diff is `0`
// likewise, the distance of earth from sun
const earthDataDiff = {
  distanceFromSun: 0,
  continents: {
    asia: {
      area: 0,
      population: 100000,
      countries: { japan: { temperature: 11.1 } },
    },
    africa: { area: 0, population: 40000 },
    europe: { area: 0, population: 100 },
    america: { area: 0, population: -10000 },
    australia: { area: 0, population: 3000 },
    antarctica: { area: 0, population: 13 },
  },
};

如上所述,使用给定的甜蜜答案在这里:

function mySub(x, y) {
  return Object.keys(x).reduce((a, k) => {
    a[k] = x[k] - y[k];
    return a;
  }, {});
}

但是,当调用 mysub()时,我们会得到此不太奇怪的输出:

mySub(earthData2050, earthData2022)
// {"distanceFromSun":0,"continents":null}

我的问题,因此,我是如何如果对象具有相同的结构,我可以递归减去所有条目, 有多深。另外,当我在节点上运行此代码时,我很乐意利用任何可能派上用场的新ecmascript功能。

I want to subtract the values of two objects of the exact same structure. Although one answer exists here, it's limited to objects of no-depth. In my case, I'm looking for a robust solution that would allow subtracting objects of any depth, as long as they're of the same structure.

Example

Consider the following two objects, earthData2022 and earthData2050:

const earthData2022 = {
  distanceFromSun: 149280000,
  continents: {
    asia: {
      area: 44579000,
      population: 4560667108,
      countries: { japan: { temperature: 62.5 } },
    },
    africa: { area: 30370000, population: 1275920972 },
    europe: { area: 10180000, population: 746419440 },
    america: { area: 42549000, population: 964920000 },
    australia: { area: 7690000, population: 25925600 },
    antarctica: { area: 14200000, population: 5000 },
  },
};

const earthData2050 = {
  distanceFromSun: 149280000,
  continents: {
    asia: {
      area: 44579000,
      population: 4560767108,
      countries: { japan: { temperature: 73.6 } },
    },
    africa: { area: 30370000, population: 1275960972 },
    europe: { area: 10180000, population: 746419540 },
    america: { area: 42549000, population: 964910000 },
    australia: { area: 7690000, population: 25928600 },
    antarctica: { area: 14200000, population: 5013 },
  },
};

Please note that both objects have:

  • exact same structure
  • all values are numbers, either integers or decimals. There are no strings or booleans, nor arrays.

I want to subtract: earthData2050 - earthData2022 to get a new object:

// desired output
// continents' areas aren't expected to change so their diff is `0`
// likewise, the distance of earth from sun
const earthDataDiff = {
  distanceFromSun: 0,
  continents: {
    asia: {
      area: 0,
      population: 100000,
      countries: { japan: { temperature: 11.1 } },
    },
    africa: { area: 0, population: 40000 },
    europe: { area: 0, population: 100 },
    america: { area: 0, population: -10000 },
    australia: { area: 0, population: 3000 },
    antarctica: { area: 0, population: 13 },
  },
};

As mentioned above, it's tempting to use the sweet answer given here:

function mySub(x, y) {
  return Object.keys(x).reduce((a, k) => {
    a[k] = x[k] - y[k];
    return a;
  }, {});
}

However, when calling mySub() we get this unsurprising output:

mySub(earthData2050, earthData2022)
// {"distanceFromSun":0,"continents":null}

My question, therefore, is how I can recursively subtract all entries, no matter how deep, provided that the objects have the same structure. Also, as I'm running this code on Node, I'm happy to utilize any new ECMAScript feature that may come in handy.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

(り薆情海 2025-01-29 13:17:38

递归是你的朋友

const earthData2022 = {
  distanceFromSun: 149280000,
  continents: {
    asia: {
      area: 44579000,
      population: 4560667108,
      countries: { japan: { temperature: 62.5 } },
    },
    africa: { area: 30370000, population: 1275920972 },
    europe: { area: 10180000, population: 746419440 },
    america: { area: 42549000, population: 964920000 },
    australia: { area: 7690000, population: 25925600 },
    antarctica: { area: 14200000, population: 5000 },
  },
};

const earthData2050 = {
  distanceFromSun: 149280000,
  continents: {
    asia: {
      area: 44579000,
      population: 4560767108,
      countries: { japan: { temperature: 73.6 } },
    },
    africa: { area: 30370000, population: 1275960972 },
    europe: { area: 10180000, population: 746419540 },
    america: { area: 42549000, population: 964910000 },
    australia: { area: 7690000, population: 25928600 },
    antarctica: { area: 14200000, population: 5013 },
  },
};


function mySub(x, y) {
  const result = {}
  Object.keys(x).forEach((key) => {
    if (typeof x[key] === 'number') {
      result[key] = x[key] - y[key]
    } else {
      result[key] = mySub(x[key], y[key])
    }
  });
  return result;
}

console.log(mySub(earthData2050, earthData2022));

recursion is your friend here

const earthData2022 = {
  distanceFromSun: 149280000,
  continents: {
    asia: {
      area: 44579000,
      population: 4560667108,
      countries: { japan: { temperature: 62.5 } },
    },
    africa: { area: 30370000, population: 1275920972 },
    europe: { area: 10180000, population: 746419440 },
    america: { area: 42549000, population: 964920000 },
    australia: { area: 7690000, population: 25925600 },
    antarctica: { area: 14200000, population: 5000 },
  },
};

const earthData2050 = {
  distanceFromSun: 149280000,
  continents: {
    asia: {
      area: 44579000,
      population: 4560767108,
      countries: { japan: { temperature: 73.6 } },
    },
    africa: { area: 30370000, population: 1275960972 },
    europe: { area: 10180000, population: 746419540 },
    america: { area: 42549000, population: 964910000 },
    australia: { area: 7690000, population: 25928600 },
    antarctica: { area: 14200000, population: 5013 },
  },
};


function mySub(x, y) {
  const result = {}
  Object.keys(x).forEach((key) => {
    if (typeof x[key] === 'number') {
      result[key] = x[key] - y[key]
    } else {
      result[key] = mySub(x[key], y[key])
    }
  });
  return result;
}

console.log(mySub(earthData2050, earthData2022));

何处潇湘 2025-01-29 13:17:38

声明/功能解决方案:

const difference = (obj1, obj2) => Object.entries(obj1).reduce((t, [key, value]) => {
    const obj2Value = obj2[key];
    return {
        ...t,
        [key]: typeof value === "object" ?
            difference(value, obj2Value) :
            value - obj2Value
    };
}, {});

解释

object.entries 将对象转换为二维键值对的数组。使用 array.dray.duce 在对上迭代,它可以将数组减少到对象。一个类似的方法是,当您在烹饪时将肉汤减少到酱汁中时。如果该属性的值是对象,则结果属性应为子对象的差异(递归)的差异。如果不是,则必须是一个数字,因此可以减去。

进一步阅读:

Declarative/functional solution:

const difference = (obj1, obj2) => Object.entries(obj1).reduce((t, [key, value]) => {
    const obj2Value = obj2[key];
    return {
        ...t,
        [key]: typeof value === "object" ?
            difference(value, obj2Value) :
            value - obj2Value
    };
}, {});

Explaination

Object.entries converts the object to a two-dimensional array of key value pairs. Using array.reduce to iterate over the pairs, it can reduce the array to an object. An analogy of this would be when you're reducing a broth to a sauce while cooking. If the value of the property is an object, the resulting property should be the difference of the difference of the sub-object (recursion). If not, it has to be a number and can therefore be subtracted.

Further reading:

呆橘 2025-01-29 13:17:38

@Scott的答案非常有效和优雅。他在帖子中评论 -

这仅在您的音符正确的情况下起作用,这两个对象具有相同的结构,并且叶子节点都是数字。如果我们想处理其他案例,我们必须变得更加复杂。

我想分享它的外观。在这里,我们将 objdiff 作为 zipmap 的专业化 -

const objDiff = zipMap((p, q) =>
  is(p, Number) && is(q, Number)
    ? p - q
    : { error: "cannot compute", left: p, right: q }
)

其中 is zipmap 定义为 -

const is = (t, T) => t?.constructor === T

const zipMap = f => (p, q) =>
// Object
  is(p, Object) && is(q, Object)
    ? unique(Object.keys(p), Object.keys(q))
        .reduce((r, k) => Object.assign(r, ({ [k]: zipMap(f)(p[k], q[k]) })), {})
// Array
: is(p, Array) && is(q, Array)
    ? unique(p.keys(), q.keys())
        .map(k => zipMap(f)(p[k], q[k]))
// Else
: f(p, q)

取决于<<<。代码> unique -

const unique = (p, q) =>
  Array.from(new Set([...p, ...q]))

为了进行演示,我在每个对象中添加了 samplearray 属性, hello:“ world” 键对。将下面的代码运行到 objdiff 现在用于非对称输入和混合值类型 -

const is = (t, T) => t?.constructor === T

const unique = (p, q) =>
  Array.from(new Set([...p, ...q]))

const zipMap = f => (p, q) =>
// Object
  is(p, Object) && is(q, Object)
    ? unique(Object.keys(p), Object.keys(q))
        .reduce((r, k) => Object.assign(r, ({ [k]: zipMap(f)(p[k], q[k]) })), {})
// Array
: is(p, Array) && is(q, Array)
    ? unique(p.keys(), q.keys())
        .map(k => zipMap(f)(p[k], q[k]))
// Else
: f(p, q)

const objDiff = zipMap((p, q) =>
  is(p, Number) && is(q, Number)
    ? p - q
    : { error: "cannot compute", left: p, right: q }
)

const earthData2022 = {sampleArray: [10, 20, 30], distanceFromSun: 14928e4, continents: {asia: {area: 44579e3, population: 4560667108, countries: {japan: {temperature: 62.5}}}, africa: {area: 3037e4, population: 1275920972}, europe: {area: 1018e4, population: 746419440}, america: {area: 42549e3, population: 96492e4}, australia: {area: 769e4, population: 25925600}, antarctica: {area: 142e5, population: 5e3}}}
const earthData2050 = {sampleArray: [9, 40, 30, 100], distanceFromSun: 14928e4, continents: {asia: {area: 44579e3, population: 4560767108, countries: {japan: {temperature: 73.6}}}, africa: {area: 3037e4, population: 1275960972}, europe: {area: 1018e4, population: 746419540}, america: {area: 42549e3, population: 96491e4}, australia: {area: 769e4, population: 25928600}, antarctica: {area: 142e5, population: 5013, hello: "world"}}}

console.log(objDiff(earthData2050, earthData2022))
.as-console-wrapper {max-height: 100% !important; top: 0}

{
  "sampleArray": [
    -1,
    20,
    0,
    {
      "error": "cannot compute",
      "left": 100,
      "right": undefined
    }
  ],
  "distanceFromSun": 0,
  "continents": {
    "asia": {
      "area": 0,
      "population": 100000,
      "countries": {
        "japan": {
          "temperature": 11.099999999999994
        }
      }
    },
    "africa": {
      "area": 0,
      "population": 40000
    },
    "europe": {
      "area": 0,
      "population": 100
    },
    "america": {
      "area": 0,
      "population": -10000
    },
    "australia": {
      "area": 0,
      "population": 3000
    },
    "antarctica": {
      "area": 0,
      "population": 13,
      "hello": {
        "error": "cannot compute",
        "left": "world",
        "right": undefined
      }
    }
  }
}

@Scott's answer is remarkably efficient and elegant. In his post he comments -

This only works if your note is correct, that the two objects have identical structures and that leaf nodes are all numbers. We'd have to get more sophisticated if we wanted to handle other cases.

I wanted to share what that would look like. Here we write objDiff as a specialization of zipMap -

const objDiff = zipMap((p, q) =>
  is(p, Number) && is(q, Number)
    ? p - q
    : { error: "cannot compute", left: p, right: q }
)

Where is and zipMap is defined as -

const is = (t, T) => t?.constructor === T

const zipMap = f => (p, q) =>
// Object
  is(p, Object) && is(q, Object)
    ? unique(Object.keys(p), Object.keys(q))
        .reduce((r, k) => Object.assign(r, ({ [k]: zipMap(f)(p[k], q[k]) })), {})
// Array
: is(p, Array) && is(q, Array)
    ? unique(p.keys(), q.keys())
        .map(k => zipMap(f)(p[k], q[k]))
// Else
: f(p, q)

Which depends on unique -

const unique = (p, q) =>
  Array.from(new Set([...p, ...q]))

To demo this, I added a sampleArray property to each object and a hello: "world" key pair to one. Run the code below to objDiff now works on asymmetric inputs and mixed values types -

const is = (t, T) => t?.constructor === T

const unique = (p, q) =>
  Array.from(new Set([...p, ...q]))

const zipMap = f => (p, q) =>
// Object
  is(p, Object) && is(q, Object)
    ? unique(Object.keys(p), Object.keys(q))
        .reduce((r, k) => Object.assign(r, ({ [k]: zipMap(f)(p[k], q[k]) })), {})
// Array
: is(p, Array) && is(q, Array)
    ? unique(p.keys(), q.keys())
        .map(k => zipMap(f)(p[k], q[k]))
// Else
: f(p, q)

const objDiff = zipMap((p, q) =>
  is(p, Number) && is(q, Number)
    ? p - q
    : { error: "cannot compute", left: p, right: q }
)

const earthData2022 = {sampleArray: [10, 20, 30], distanceFromSun: 14928e4, continents: {asia: {area: 44579e3, population: 4560667108, countries: {japan: {temperature: 62.5}}}, africa: {area: 3037e4, population: 1275920972}, europe: {area: 1018e4, population: 746419440}, america: {area: 42549e3, population: 96492e4}, australia: {area: 769e4, population: 25925600}, antarctica: {area: 142e5, population: 5e3}}}
const earthData2050 = {sampleArray: [9, 40, 30, 100], distanceFromSun: 14928e4, continents: {asia: {area: 44579e3, population: 4560767108, countries: {japan: {temperature: 73.6}}}, africa: {area: 3037e4, population: 1275960972}, europe: {area: 1018e4, population: 746419540}, america: {area: 42549e3, population: 96491e4}, australia: {area: 769e4, population: 25928600}, antarctica: {area: 142e5, population: 5013, hello: "world"}}}

console.log(objDiff(earthData2050, earthData2022))
.as-console-wrapper {max-height: 100% !important; top: 0}

{
  "sampleArray": [
    -1,
    20,
    0,
    {
      "error": "cannot compute",
      "left": 100,
      "right": undefined
    }
  ],
  "distanceFromSun": 0,
  "continents": {
    "asia": {
      "area": 0,
      "population": 100000,
      "countries": {
        "japan": {
          "temperature": 11.099999999999994
        }
      }
    },
    "africa": {
      "area": 0,
      "population": 40000
    },
    "europe": {
      "area": 0,
      "population": 100
    },
    "america": {
      "area": 0,
      "population": -10000
    },
    "australia": {
      "area": 0,
      "population": 3000
    },
    "antarctica": {
      "area": 0,
      "population": 13,
      "hello": {
        "error": "cannot compute",
        "left": "world",
        "right": undefined
      }
    }
  }
}
下雨或天晴 2025-01-29 13:17:38

这是一种非常简单的递归方法:

const objDiff = (x, y) => 
  Object .fromEntries (Object .entries (x) .map (
    ([k, v]) => [k, Object (v) === v ? objDiff (v, y [k]) : v - y [k]]
  ))

const earthData2022 = {distanceFromSun: 14928e4, continents: {asia: {area: 44579e3, population: 4560667108, countries: {japan: {temperature: 62.5}}}, africa: {area: 3037e4, population: 1275920972}, europe: {area: 1018e4, population: 746419440}, america: {area: 42549e3, population: 96492e4}, australia: {area: 769e4, population: 25925600}, antarctica: {area: 142e5, population: 5e3}}}
const earthData2050 = {distanceFromSun: 14928e4, continents: {asia: {area: 44579e3, population: 4560767108, countries: {japan: {temperature: 73.6}}}, africa: {area: 3037e4, population: 1275960972}, europe: {area: 1018e4, population: 746419540}, america: {area: 42549e3, population: 96491e4}, australia: {area: 769e4, population: 25928600}, antarctica: {area: 142e5, population: 5013}}}

console .log (objDiff (earthData2050, earthData2022))
.as-console-wrapper {max-height: 100% !important; top: 0}

我们将第一个对象中的条目映射到具有相同键的新条目和一个值,这是递归调用或减法的结果,是基于第一个值是对象还是数字。然后,我们使用对象.fromentries 重建新对象。

这仅在您的音符正确的情况下起作用,这两个对象具有相同的结构,并且叶子节点都是数字。如果我们想处理其他情况,我们将必须变得更加复杂。

Here's a pretty simple recursive approach:

const objDiff = (x, y) => 
  Object .fromEntries (Object .entries (x) .map (
    ([k, v]) => [k, Object (v) === v ? objDiff (v, y [k]) : v - y [k]]
  ))

const earthData2022 = {distanceFromSun: 14928e4, continents: {asia: {area: 44579e3, population: 4560667108, countries: {japan: {temperature: 62.5}}}, africa: {area: 3037e4, population: 1275920972}, europe: {area: 1018e4, population: 746419440}, america: {area: 42549e3, population: 96492e4}, australia: {area: 769e4, population: 25925600}, antarctica: {area: 142e5, population: 5e3}}}
const earthData2050 = {distanceFromSun: 14928e4, continents: {asia: {area: 44579e3, population: 4560767108, countries: {japan: {temperature: 73.6}}}, africa: {area: 3037e4, population: 1275960972}, europe: {area: 1018e4, population: 746419540}, america: {area: 42549e3, population: 96491e4}, australia: {area: 769e4, population: 25928600}, antarctica: {area: 142e5, population: 5013}}}

console .log (objDiff (earthData2050, earthData2022))
.as-console-wrapper {max-height: 100% !important; top: 0}

We take the entries in your first object and map them to new entries with the same key and a value which is either the result of a recursive call or the subtraction, based on whether the first value is an object or a number. Then we use Object .fromEntries to rebuild a new object.

This only works if your note is correct, that the two objects have identical structures and that leaf nodes are all numbers. We'd have to get more sophisticated if we wanted to handle other cases.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文