如果满足条件,则求和并组合数组中相似的对象元素 - Javascript

发布于 2025-01-17 18:13:31 字数 1037 浏览 2 评论 0 原文

我有一个这种形状的数组:

[
  {
    unique: 8,
    views: 24,
    name: "https://l.instagram.com/",
    sec: 5.39,
  },
  {
    unique: 2,
    views: 20,
    name: "",
    sec: 0,
  },
  {
    unique: 2,
    views: 5,
    name: "https://www.tiktok.com/",
    sec: 5.39,
  },
  {
    unique: 4,
    views: 3,
    name: "https://l.instagram.com",
    sec: 2.00,
  },
  {
    unique: 1,
    views: 2,
    name: "https://www.tiktok.com",
    sec: 2.00,
  },
];

我试图将相同的引用与值(视图、唯一等)的总和组合到一个新数组中。 新数组需要根据条件形成,基本上引用者名称需要根据类型进行格式化。例如,所有(“https://l.instagram.com/”、“l.instagram.com”、“https://www.instagram.com/”等)推荐人都需要被称为 Instagram,并且与其他人相同 - YouTube 、TikTok 等,如果为空,则为“Direct”。 这就是我想到的最终结果:

[
  {
    unique: 12,
    views: 27,
    name: "Instagram",
    sec: 7.39,
  },
  {
    unique: 2,
    views: 20,
    name: "Direct",
    sec: 0,
  },
  {
    unique: 3,
    views: 7,
    name: "TikTok",
    sec: 7.39,
  },
];

我的想法是映射数组,如果该值包含所需的字符串,它将对新对象中的值求和。也许是映射或减少,或者两者的组合? 任何指导或帮助表示赞赏。提前致谢!

I have an array of this shape:

[
  {
    unique: 8,
    views: 24,
    name: "https://l.instagram.com/",
    sec: 5.39,
  },
  {
    unique: 2,
    views: 20,
    name: "",
    sec: 0,
  },
  {
    unique: 2,
    views: 5,
    name: "https://www.tiktok.com/",
    sec: 5.39,
  },
  {
    unique: 4,
    views: 3,
    name: "https://l.instagram.com",
    sec: 2.00,
  },
  {
    unique: 1,
    views: 2,
    name: "https://www.tiktok.com",
    sec: 2.00,
  },
];

And I'm trying to combine the same referrers with sum of the values (views, unique etc) into a new array.
The new array needs to be formed based on condition, basically referrer names are need to be formatted based on the type. E.g. all ("https://l.instagram.com/", "l.instagram.com", "https://www.instagram.com/" etc) referrers need to be called Instagram and same with others - YouTube, TikTok etc. and 'Direct' if it's null.
This is what I have in mind as final results:

[
  {
    unique: 12,
    views: 27,
    name: "Instagram",
    sec: 7.39,
  },
  {
    unique: 2,
    views: 20,
    name: "Direct",
    sec: 0,
  },
  {
    unique: 3,
    views: 7,
    name: "TikTok",
    sec: 7.39,
  },
];

My thought is to map through the array and if the value includes the desired string it will sum the values in a new object. Maybe map or reduce, or a combination of both?
Any guide or help is appreciated. Thanks in advance!

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

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

发布评论

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

评论(4

日记撕了你也走了 2025-01-24 18:13:31

首先,请在未来展示你自己的尝试。如果其他人还没有发布解决方案,我就不会发布解决方案,因为问题本身没有表现出足够的努力。


其中最大的挑战就是简单地将 "https://www.tiktok.com" 转换为 "TikTok"。我最初的解决方案使用正则表达式测试:/tiktok/i .test (name)。这里有针对 Instagram 和 TikTok 的单独的内容,并且很明显如何添加其他内容。其他一切都相当简单。它看起来像这样:

const groupBy = (fn) => (xs) =>
  xs .reduce ((a, x, _, __, k = fn (x)) => ((a [k] = (a [k] || []) .concat (x)), a), {})

const sumOn = (fields) => (xs) =>
  Object .fromEntries (fields .map ((field) => [field, xs .reduce ((a, x) => a + x [field], 0)]))

const groupName = ((rules = [
  ['Instagram', ({name}) => /instagram/i .test (name)],
  ['TikTok', ({name}) => /tiktok/i .test (name)],
  ['Direct', () => true] // must come last
]) => (name) => rules .find (([_, test]) => test (name)) [0])()

const convert = (data) => Object .entries (
  groupBy (groupName) (data)
) .map (([name, xs]) => ({name, ...sumOn (['unique', 'views', 'sec']) (xs)}))


const data = [{unique: 8, views: 24, name: "https: //l.instagram.com/", sec: 5.39}, {unique: 2, views: 20, name: "", sec: 0}, {unique: 2, views: 5, name: "https: //www.tiktok.com/", sec: 5.39}, {unique: 4, views: 3, name: "https: //l.instagram.com", sec: 2}, {unique: 1, views: 2, name: "https: //www.tiktok.com", sec: 2}]

console .log (convert (data))
.as-console-wrapper {max-height: 100% !important; top: 0}

  • groupBy(现在是一个阶段-3 提议Array.prototype进行扩展,因此可能不再需要太久)根据键生成函数的结果对数组的元素进行分组。也就是说,

    groupBy ((n) => n % 2 == 0 ? '偶' : '奇数') ([1, 2, 3, 4, 5 ])
    //=> {奇数: [1, 3, 5], 偶数: [2, 4]} 
    
  • sumOn 接受一个属性名称列表并返回一个函数,该函数接受一个对象列表并返回一个新对象,其中每个属性名称都经过计算作为每个提供的对象的该属性的总和。

    sumOn (['a', 'c']) ([{a: 1, b: 2, c: 3}, {a: 10, b :20,c:30},{a:100,b:200,c:300})
    //=> {甲:111,丙:333}
    
  • groupName 是我们上面讨论的。它对 Instagram 和 TikTok 的正则表达式进行硬编码,并进行测试以查看这些字符串是否包含在名称中,如果不匹配则默认为“Direct”

  • convert 是主要函数,它只不过是组合这些函数,使用 groupName 作为 groupBy,获取条目,然后将组名称与调用 sumOn 获取值的结果组合起来,键为 ['unique', 'views', 'sec ']

这可能很好,但我正在考虑如何使其更可配置,虽然我不会详细介绍,但我确实有一个解决方案,可以让我们配置 "Instagram" 和直接“TikTok”,以及列出我们想要总计的字段。它看起来像这样:

const groupBy = (fn) => (xs) =>
  xs .reduce ((a, x, _, __, k = fn (x)) => ((a [k] = (a [k] || []) .concat (x)), a), {})

const sumOn = (fields) => (xs) =>
  Object .fromEntries (fields .map ((field) => [field, xs .reduce ((a, x) => a + x [field], 0)]))

const groupName = ((groups, defaultGroup, rules = [
  ...groups .map (group => [group, ({name}) => new RegExp (group.toLowerCase (), 'i') .test (name)]),
  [defaultGroup, () => true]
]) => (name) => rules .find (([_, test]) => test (name)) [0])

const makeConverter = (
  {groups, defaultGroup, totals}, 
  grouper = groupName (groups, defaultGroup),
  totaler = sumOn (totals)
) => (data) => 
  Object .entries (groupBy (grouper) (data)) .map (([name, xs]) => ({name, ... totaler (xs)}))

const convert = makeConverter ({
  groups: ['Instagram', 'TikTok'], 
  defaultGroup: 'Direct', 
  totals: ['unique', 'views', 'sec']
}) 

const data = [{unique: 8, views: 24, name: "https: //l.instagram.com/", sec: 5.39}, {unique: 2, views: 20, name: "", sec: 0}, {unique: 2, views: 5, name: "https: //www.tiktok.com/", sec: 5.39}, {unique: 4, views: 3, name: "https: //l.instagram.com", sec: 2}, {unique: 1, views: 2, name: "https: //www.tiktok.com", sec: 2}]

console .log (convert (data))
.as-console-wrapper {max-height: 100% !important; top: 0}

First off, please in the future, show your own attempts. I wouldn't be posting a solution if others hadn't already, as there was not enough effort demonstrated in the question itself.


The biggest challenge in this is simply converting, say, "https://www.tiktok.com" to "TikTok". My initial solution uses a regex test: /tiktok/i .test (name). Separate ones for Instagram and TikTok are here, and it's obvious how to add additional ones. Everything else is fairly straightforward. It looks like this:

const groupBy = (fn) => (xs) =>
  xs .reduce ((a, x, _, __, k = fn (x)) => ((a [k] = (a [k] || []) .concat (x)), a), {})

const sumOn = (fields) => (xs) =>
  Object .fromEntries (fields .map ((field) => [field, xs .reduce ((a, x) => a + x [field], 0)]))

const groupName = ((rules = [
  ['Instagram', ({name}) => /instagram/i .test (name)],
  ['TikTok', ({name}) => /tiktok/i .test (name)],
  ['Direct', () => true] // must come last
]) => (name) => rules .find (([_, test]) => test (name)) [0])()

const convert = (data) => Object .entries (
  groupBy (groupName) (data)
) .map (([name, xs]) => ({name, ...sumOn (['unique', 'views', 'sec']) (xs)}))


const data = [{unique: 8, views: 24, name: "https: //l.instagram.com/", sec: 5.39}, {unique: 2, views: 20, name: "", sec: 0}, {unique: 2, views: 5, name: "https: //www.tiktok.com/", sec: 5.39}, {unique: 4, views: 3, name: "https: //l.instagram.com", sec: 2}, {unique: 1, views: 2, name: "https: //www.tiktok.com", sec: 2}]

console .log (convert (data))
.as-console-wrapper {max-height: 100% !important; top: 0}

  • groupBy (now a stage-3 proposal for an extension to Array.prototype, so may not be needed for much longer) groups the elements of an array according to a the result of a key-generating function. That is,

    groupBy ((n) => n % 2 == 0 ? 'even' : 'odd') ([1, 2, 3, 4, 5])
    //=> {odd: [1, 3, 5], even: [2, 4]} 
    
  • sumOn takes a list of property names and returns a function that takes a list of objects and returns a new object with each of those property names, calculated as the sum of that property for each of the supplied objects.

    sumOn (['a', 'c']) ([{a: 1, b: 2, c: 3}, {a: 10, b: 20, c: 30}, {a: 100, b: 200, c: 300})
    //=> {a: 111, c: 333}
    
  • groupName is what we discussed above. It hard-codes regular expressions for Instagram and TikTok, with tests to see if those strings are included in the name, defaulting to "Direct" if those don't match.

  • convert is the main function, which does little more than combine those function, using groupName as the key-generating function for groupBy, taking the entries, then combining the group names with the result of calling sumOn for the values, with keys of ['unique', 'views', 'sec'].

This may be fine as is, but I was thinking about how to make this more configurable, and while I won't go through the details, I do have a solution that lets us configure "Instagram" and "TikTok" directly, as well as list the fields that we want to total. It looks like this:

const groupBy = (fn) => (xs) =>
  xs .reduce ((a, x, _, __, k = fn (x)) => ((a [k] = (a [k] || []) .concat (x)), a), {})

const sumOn = (fields) => (xs) =>
  Object .fromEntries (fields .map ((field) => [field, xs .reduce ((a, x) => a + x [field], 0)]))

const groupName = ((groups, defaultGroup, rules = [
  ...groups .map (group => [group, ({name}) => new RegExp (group.toLowerCase (), 'i') .test (name)]),
  [defaultGroup, () => true]
]) => (name) => rules .find (([_, test]) => test (name)) [0])

const makeConverter = (
  {groups, defaultGroup, totals}, 
  grouper = groupName (groups, defaultGroup),
  totaler = sumOn (totals)
) => (data) => 
  Object .entries (groupBy (grouper) (data)) .map (([name, xs]) => ({name, ... totaler (xs)}))

const convert = makeConverter ({
  groups: ['Instagram', 'TikTok'], 
  defaultGroup: 'Direct', 
  totals: ['unique', 'views', 'sec']
}) 

const data = [{unique: 8, views: 24, name: "https: //l.instagram.com/", sec: 5.39}, {unique: 2, views: 20, name: "", sec: 0}, {unique: 2, views: 5, name: "https: //www.tiktok.com/", sec: 5.39}, {unique: 4, views: 3, name: "https: //l.instagram.com", sec: 2}, {unique: 1, views: 2, name: "https: //www.tiktok.com", sec: 2}]

console .log (convert (data))
.as-console-wrapper {max-height: 100% !important; top: 0}

深爱不及久伴 2025-01-24 18:13:31

(抱歉,我错过了此“直接”要求,会修改和附加)

array.map 和所有数组循环方法,一次通过数组一次迭代一个元素,而无需保留以前元素的信息,并且对未来元素视而不见,因此在循环循环您的对象时,需要一些方法来跟踪以前的对象。

在我提出的摘要解决方案中,我声明了一个中间对象,其中包含您要一起收集的每个不同组的对象(在您的情况下,Tiktok和Instagram, - 如果您的初始数据集非常复杂,但在这个示例我只是用眼睛阅读它们)。

接下来,通过使用 for-east 循环来循环原始的对象数组。对于每个元素(对象),检查了 .name 属性,并尝试提取可以用作中间数组中匹配对象的键的子字符串。这是通过将 .name 分配到数组中来完成的,在发现周期(。)的任何地方都打破名称字符串。因为您的名字是所有类型(某物).tiktok。(某物),可以将唯一键(tiktok)提取为该拆分数组的seceond元素(index 1):

// suppose current element.name is "https://www.tiktok.com";
key = element.name.split(".")[1];
// key now holds 'tiktok';

this key 现在可以用来在我们之前声明的对象的中间对象中引用相关对象,并且可以这样访问和更新单个属性:

 intermediateObjectOfObjects[key].unique += element.unique;

for-east 的语句如果块将循环放入有条件的中,以忽略任何未定义名称的地方。

for-each 循环完成后,数据现在将在对象的对象中(内在对象都按照您需要更新)。

因此,最后,我们只需要将更新的对象移动到它们自己的数组中:

// define a new array:
const finalObjectArray=[];
// iterate through the intermediate array, adding each inner object to the new array:

     for (const property in intermediateObjectOfObjects) {
       finalObjectArray.push(intermediateObjectOfObjects[property]);
     } // end for-in object iterations;

最终结果是一个包含每个命名组的对象的数组,其值来自每个对象组合的数组。

const arrayOfObjects = [
  {
    unique: 8,
    views: 24,
    name: "https://l.instagram.com/",
    sec: 5.39,
  },
  {
    unique: 2,
    views: 20,
    name: "",
    sec: 0,
  },
  {
    unique: 2,
    views: 5,
    name: "https://www.tiktok.com/",
    sec: 5.39,
  },
  {
    unique: 4,
    views: 3,
    name: "https://l.instagram.com",
    sec: 2.00,
  },
  {
    unique: 1,
    views: 2,
    name: "https://www.tiktok.com",
    sec: 2.00,
  },
];

const intermediateObjectOfObjects = {

   tiktok: {
           unique: 0,
           views: 0,
           name: "https://www.tiktok.com",
           sec: 0
           },

instagram: {
           unique: 0,
           views: 0,
           name: "https://www.instagram.com",
           sec: 0
           },

direct: {
           unique: 0,
           views: 0,
           name: "direct",
           sec: 0
           }

} // end intermediate object;

const finalObjectArray=[];


  arrayOfObjects.forEach(element => {

     key = (element.name.split(".")[1]) ? element.name.split(".")[1] : 'direct' ;
     intermediateObjectOfObjects[key].unique += element.unique;
     intermediateObjectOfObjects[key].views += element.views;
     intermediateObjectOfObjects[key].sec += element.sec;

});


     for (const property in intermediateObjectOfObjects) {
       finalObjectArray.push(intermediateObjectOfObjects[property]);
     } // end for-in object iterations;

console.log(finalObjectArray);

编辑不确定对象名称的“直接”组:

而不是有条件测试名称的,我使用三元运算符来问“ dod dig split('。'。)返回有效的字符串 [1]中?

 key = (element.name.split(".")[1]) ? element.name.split(".")[1] : 'direct' ;

元素 直接为命名对象;

(sorry, I missed this 'direct' requirement, will modify and append)

Array.map, and all array looping methods, iterate through the array an element at a time, without retaining information of previous elements, and blind to future elements so some means of keeping track of previous objects is needed while looping through your object.

In my proposed snippet solution, I declare an intermediate object containing objects for each of the different groups you want to collect together (tiktok and instagram in your case, - you may beed to extract these programatically if your initial data set is very complex but in this example I simply read them by eye).

Next, the original array of objects is looped through using a for-each loop. for each element (object), the .name property is examined and an attempt to extract a substring that can be used as a key to reference the matching object in the intermediate array. This was done by splitting the .name into an array, breaking the name string wherever a period (.) was found. Because your names are all of the type (something).tiktok.(something), the unique key (tiktok) can be extracted as the seceond element (index 1) of that split array:

// suppose current element.name is "https://www.tiktok.com";
key = element.name.split(".")[1];
// key now holds 'tiktok';

This key can now be used to reference the relevant object in the intermediate object of objects we declared earlier, and individual properties can be accessed and updated like this:

 intermediateObjectOfObjects[key].unique += element.unique;

The statements of the for-each loop are put inside a conditional if block to ignore any where no name is defined.

When the for-each loop is finished, the data will now be in an object of objects (with the inner objects all updated as you want them).

So, finally we just need to move the updated objects into an array of their own:

// define a new array:
const finalObjectArray=[];
// iterate through the intermediate array, adding each inner object to the new array:

     for (const property in intermediateObjectOfObjects) {
       finalObjectArray.push(intermediateObjectOfObjects[property]);
     } // end for-in object iterations;

The final result is an array containing one object for each named group, with the values from the original array of objects combined for each.

const arrayOfObjects = [
  {
    unique: 8,
    views: 24,
    name: "https://l.instagram.com/",
    sec: 5.39,
  },
  {
    unique: 2,
    views: 20,
    name: "",
    sec: 0,
  },
  {
    unique: 2,
    views: 5,
    name: "https://www.tiktok.com/",
    sec: 5.39,
  },
  {
    unique: 4,
    views: 3,
    name: "https://l.instagram.com",
    sec: 2.00,
  },
  {
    unique: 1,
    views: 2,
    name: "https://www.tiktok.com",
    sec: 2.00,
  },
];

const intermediateObjectOfObjects = {

   tiktok: {
           unique: 0,
           views: 0,
           name: "https://www.tiktok.com",
           sec: 0
           },

instagram: {
           unique: 0,
           views: 0,
           name: "https://www.instagram.com",
           sec: 0
           },

direct: {
           unique: 0,
           views: 0,
           name: "direct",
           sec: 0
           }

} // end intermediate object;

const finalObjectArray=[];


  arrayOfObjects.forEach(element => {

     key = (element.name.split(".")[1]) ? element.name.split(".")[1] : 'direct' ;
     intermediateObjectOfObjects[key].unique += element.unique;
     intermediateObjectOfObjects[key].views += element.views;
     intermediateObjectOfObjects[key].sec += element.sec;

});


     for (const property in intermediateObjectOfObjects) {
       finalObjectArray.push(intermediateObjectOfObjects[property]);
     } // end for-in object iterations;

console.log(finalObjectArray);

Edits to include 'direct' group for undefined object names:

Instead of the if conditional to test for a name, I instead used a ternary operator to ask "did split('.') return a valid string in element[1]? is so use it as name, if not use 'direct' as name"

(the syntax looks odd but means what I quotted above:

 key = (element.name.split(".")[1]) ? element.name.split(".")[1] : 'direct' ;

Of course, the intermediate object of objects also had to be amended to include 'direct' as a named object;

孤独患者 2025-01-24 18:13:31

您可以使用:

const data = [{unique: 8,views: 24,name: "https://l.instagram.com/",sec: 5.39}, {unique: 2,views: 20,name: "",sec: 0}, {unique: 2,views: 5,name: "https://www.tiktok.com/",sec: 5.39}, {unique: 4,views: 3,name: "https://l.instagram.com",sec: 2.00}, {unique: 1,views: 2,name: "https://www.tiktok.com",sec: 2.00}];

const result = Object.entries( 
    data
    .map(({name,...rest}) => ({...rest,name:name.split('.').slice(-2)[0] || "direct" }))
    .reduce((prev,{name,...rest}) =>
        ({...prev,[name]:Object.fromEntries(
            Object.entries(rest).map(([k,v]) => [k, (prev[name] && prev[name][k] || 0) + v])
        )}), {})
)
.map(([name,rest]) => ({name,...rest}));

console.log( result );

You can use a combination of:

const data = [{unique: 8,views: 24,name: "https://l.instagram.com/",sec: 5.39}, {unique: 2,views: 20,name: "",sec: 0}, {unique: 2,views: 5,name: "https://www.tiktok.com/",sec: 5.39}, {unique: 4,views: 3,name: "https://l.instagram.com",sec: 2.00}, {unique: 1,views: 2,name: "https://www.tiktok.com",sec: 2.00}];

const result = Object.entries( 
    data
    .map(({name,...rest}) => ({...rest,name:name.split('.').slice(-2)[0] || "direct" }))
    .reduce((prev,{name,...rest}) =>
        ({...prev,[name]:Object.fromEntries(
            Object.entries(rest).map(([k,v]) => [k, (prev[name] && prev[name][k] || 0) + v])
        )}), {})
)
.map(([name,rest]) => ({name,...rest}));

console.log( result );

魄砕の薆 2025-01-24 18:13:31

获取单独数组中的名称。迭代该数组并将其与主数组匹配。您还可以尝试通过用 (.) 分割来获取网站名称,并在 is 语句中将 === 更改为 includes
像这样的事情:

    let items = [
        {
          unique: 8,
          views: 24,
          name: "https://l.instagram.com",
          sec: 5.39,
        },
        {
          unique: 2,
          views: 20,
          name: "",
          sec: 0,
        },
        {
          unique: 2,
          views: 5,
          name: "https://www.tiktok.com",
          sec: 5.39,
        },
        {
          unique: 4,
          views: 3,
          name: "https://l.instagram.com",
          sec: 2.00,
        },
        {
          unique: 1,
          views: 2,
          name: "https://www.tiktok.com",
          sec: 2.00,
        },
    ];
  
  var resultArr = [];
  var valueArr = [...new Set(items.map(function(item){ return item.name }))];
  console.log(valueArr);
  valueArr.map((name) => {
      let crrItem = {
          name: name,
          unique: 0,
          views: 0,
          sec: 0
      }
      items.map((item) => {
        //   console.log(item.name === name);
          if(item.name === name){
              crrItem.unique += item.unique;
              crrItem.views += item.views;
              crrItem.sec += item.sec;
            //   console.log(crrItem);
          }
      });
      if(crrItem.unique > 0){
          resultArr.push(crrItem);
      }
  });
  console.log(resultArr);

Get the names in a separate array. Iterate that array and match it with the main array. You can also try getting the name of the website by splitting with (.) and in the is statement change === to includes.
Something like this:

    let items = [
        {
          unique: 8,
          views: 24,
          name: "https://l.instagram.com",
          sec: 5.39,
        },
        {
          unique: 2,
          views: 20,
          name: "",
          sec: 0,
        },
        {
          unique: 2,
          views: 5,
          name: "https://www.tiktok.com",
          sec: 5.39,
        },
        {
          unique: 4,
          views: 3,
          name: "https://l.instagram.com",
          sec: 2.00,
        },
        {
          unique: 1,
          views: 2,
          name: "https://www.tiktok.com",
          sec: 2.00,
        },
    ];
  
  var resultArr = [];
  var valueArr = [...new Set(items.map(function(item){ return item.name }))];
  console.log(valueArr);
  valueArr.map((name) => {
      let crrItem = {
          name: name,
          unique: 0,
          views: 0,
          sec: 0
      }
      items.map((item) => {
        //   console.log(item.name === name);
          if(item.name === name){
              crrItem.unique += item.unique;
              crrItem.views += item.views;
              crrItem.sec += item.sec;
            //   console.log(crrItem);
          }
      });
      if(crrItem.unique > 0){
          resultArr.push(crrItem);
      }
  });
  console.log(resultArr);

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