计算一组 N 个对象的每个类别的所有值的出现次数

Count the appereances of all values per category of a set of N objects

给定一组具有属性的对象,我想计算 每个属性类型的总出现次数。

我提供了 3 个数组的示例,代表 3 个不同的 实体(在生产中最多可以有 20.000 个实体):

const arr = 
  [ [ { attribute_type: 'Background', value: 'Orange'  } 
    , { attribute_type: 'Fur',        value: 'Black'   } 
    , { attribute_type: 'Outfit',     value: 'Casual'  } 
    , { attribute_type: 'Earring',    value: 'None'    } 
    , { attribute_type: 'Eyes',       value: 'Fiery'   } 
    , { attribute_type: 'Mouth',      value: 'Smiling' } 
    , { attribute_type: 'Shoes',      value: 'Sandals' } 
    ] 
  , [ { attribute_type: 'Background', value: 'Orange'  } 
    , { attribute_type: 'Fur',        value: 'Brown'   } 
    , { attribute_type: 'Outfit',     value: 'Casual'  } 
    , { attribute_type: 'Earring',    value: 'None'    } 
    , { attribute_type: 'Eyes',       value: 'Gold'    } 
    , { attribute_type: 'Mouth',      value: 'Smiling' } 
    ] 
  , [ { attribute_type: 'Background', value: 'Diamond' } 
    , { attribute_type: 'Fur',        value: 'Gold'    } 
    , { attribute_type: 'Outfit',     value: 'Dress'   } 
    , { attribute_type: 'Earring',    value: 'None'    } 
    , { attribute_type: 'Eyes',       value: 'Gold'    } 
    , { attribute_type: 'Mouth',      value: 'Smiling' } 
    ] 
  ]

属性类型可能会有所不同,并且事先是未知的。并不是 每个属性类型都保证存在于数组中。

我想以一个包含 # of attribute appereances 的列表结束 每个类别(如果可能,按出现率升序排序):

const expected = 
  [ { Background: { Diamond: 1, Orange: 2          }} 
  , { Fur:        { Black:   1, Brown:  1, Gold: 1 }} 
  , { Outfit:     { Dress:   1, Casual: 2          }} 
  , { Earring:    { None:    3                     }} 
  , { Eyes:       { Fiery:   1, Gold:   2          }} 
  , { Mouth:      { Smiling: 3                     }} 
  , { Shoes:      { Sandals: 1                     }} 
  ] 

我已经花了很多时间来解决这个问题,并且我已经尝试过 看看 Map data structures and merge 但到目前为止没有成功。最终结果不必符合提供的格式,但我只是尝试应用最佳实践。

只需遍历您的数组并有一个对象来收集您想要的结果。

const data = [ // your data goes here ];
const result = {};

// loop through first array
data.forEach((el) => {
  // loop through nested array
  el.forEach((el2) => {
    // check if the element is added already
    if (result[el2.attribute_type] === undefined) {
      // first item added
      result[el2.attribute_type] = {
        [el2.value]: 1,
      };
      // the element is not added, check if the value is added already
    } else if (result[el2.attribute_type][el2.value] === undefined) {
      // value doesn't exists yet, set the value
      result[el2.attribute_type][el2.value] = 1;
    } else {
      // value found, add 1
      result[el2.attribute_type][el2.value] += 1;
    }
  });
});

console.log(result);

一个简单的函数就可以做到,只需将对象放入其中,它就会 return 另一个对象得到结果。

const arr = 
  [ [ { attribute_type: 'Background', value: 'Orange'  } 
    , { attribute_type: 'Fur',        value: 'Black'   } 
    , { attribute_type: 'Outfit',     value: 'Casual'  } 
    , { attribute_type: 'Earring',    value: 'None'    } 
    , { attribute_type: 'Eyes',       value: 'Fiery'   } 
    , { attribute_type: 'Mouth',      value: 'Smiling' } 
    , { attribute_type: 'Shoes',      value: 'Sandals' } 
    ] 
  , [ { attribute_type: 'Background', value: 'Orange'  } 
    , { attribute_type: 'Fur',        value: 'Brown'   } 
    , { attribute_type: 'Outfit',     value: 'Casual'  } 
    , { attribute_type: 'Earring',    value: 'None'    } 
    , { attribute_type: 'Eyes',       value: 'Gold'    } 
    , { attribute_type: 'Mouth',      value: 'Smiling' } 
    ] 
  , [ { attribute_type: 'Background', value: 'Diamond' } 
    , { attribute_type: 'Fur',        value: 'Gold'    } 
    , { attribute_type: 'Outfit',     value: 'Dress'   } 
    , { attribute_type: 'Earring',    value: 'None'    } 
    , { attribute_type: 'Eyes',       value: 'Gold'    } 
    , { attribute_type: 'Mouth',      value: 'Smiling' } 
    ] 
  ]
  
  function count_() {
    let result = [], attr = {};
    arr.forEach(function a(ar){
      ar.forEach(function t(e) {
        if (!attr.hasOwnProperty(e.attribute_type)) attr[e.attribute_type] = {};
        if (!attr[e.attribute_type].hasOwnProperty(e.value))  attr[e.attribute_type][e.value] = 0;
        attr[e.attribute_type][e.value] ++;
      });
    });
    Object.keys(attr).forEach(function c(e, index) {
      result[index] = {};
      result[index][e] = attr[e];
    });
    return result;
  }
  
  console.log(count_());

这个想法是从一个已经排序的列表开始,这样输出的列表也被排序了。 我还对值属性进行了排序

const arr = 
  [ [ { attribute_type: 'Background', value: 'Orange'  } 
    , { attribute_type: 'Fur',        value: 'Black'   } 
    , { attribute_type: 'Outfit',     value: 'Casual'  } 
    , { attribute_type: 'Earring',    value: 'None'    } 
    , { attribute_type: 'Eyes',       value: 'Fiery'   } 
    , { attribute_type: 'Mouth',      value: 'Smiling' } 
    , { attribute_type: 'Shoes',      value: 'Sandals' } 
    ] 
  , [ { attribute_type: 'Background', value: 'Orange'  } 
    , { attribute_type: 'Fur',        value: 'Brown'   } 
    , { attribute_type: 'Outfit',     value: 'Casual'  } 
    , { attribute_type: 'Earring',    value: 'None'    } 
    , { attribute_type: 'Eyes',       value: 'Gold'    } 
    , { attribute_type: 'Mouth',      value: 'Smiling' } 
    ] 
  , [ { attribute_type: 'Background', value: 'Diamond' } 
    , { attribute_type: 'Fur',        value: 'Gold'    } 
    , { attribute_type: 'Outfit',     value: 'Dress'   } 
    , { attribute_type: 'Earring',    value: 'None'    } 
    , { attribute_type: 'Eyes',       value: 'Gold'    } 
    , { attribute_type: 'Mouth',      value: 'Smiling' } 
    ] 
  ] 

const result = Object.entries(arr
  .flat()           // set simple array with all sub array elements
  .sort((a,b)=>     // sort on attribute_type + value
    {
    let r = a.attribute_type.localeCompare(b.attribute_type)
    if (r === 0) r = a.value.localeCompare(b.value) 
    return r
    })
  .reduce((r,c)=>
    {
    r[c.attribute_type] = r[c.attribute_type] ?? {}
    r[c.attribute_type][c.value] = r[c.attribute_type][c.value] ?? 0
    r[c.attribute_type][c.value]++
    return r
    },{}))
  .map(([k,v])=>({[k]:v})) // change obj to array

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

按照 PO 在评论中的要求,这里是相同的列表,按属性类型的字母顺序排序,然后按每个值的条目数按升序排序

将每个 attribute_type 的结果按升序排序有点棘手,我还按字母顺序排列它们以防出现平局

const arr = 
  [ [ { attribute_type: 'Background', value: 'Orange'  } 
    , { attribute_type: 'Fur',        value: 'Black'   } 
    , { attribute_type: 'Outfit',     value: 'Casual'  } 
    , { attribute_type: 'Earring',    value: 'None'    } 
    , { attribute_type: 'Eyes',       value: 'Fiery'   } 
    , { attribute_type: 'Mouth',      value: 'Smiling' } 
    , { attribute_type: 'Shoes',      value: 'Sandals' } 
    ] 
  , [ { attribute_type: 'Background', value: 'Orange'  } 
    , { attribute_type: 'Fur',        value: 'Brown'   } 
    , { attribute_type: 'Outfit',     value: 'Casual'  } 
    , { attribute_type: 'Earring',    value: 'None'    } 
    , { attribute_type: 'Eyes',       value: 'Gold'    } 
    , { attribute_type: 'Mouth',      value: 'Smiling' } 
    ] 
  , [ { attribute_type: 'Background', value: 'Diamond' } 
    , { attribute_type: 'Fur',        value: 'Gold'    } 
    , { attribute_type: 'Outfit',     value: 'Dress'   } 
    , { attribute_type: 'Earring',    value: 'None'    } 
    , { attribute_type: 'Eyes',       value: 'Gold'    } 
    , { attribute_type: 'Mouth',      value: 'Smiling' } 
    ] 
  ] 
let result =
  arr
  .flat()
  .sort((a,b)=>a.attribute_type.localeCompare(b.attribute_type) )
  .reduce((r,{attribute_type, value},i,{[i+1]:nxt})=>
    {
    if (r.att != attribute_type)
      {
      r.att = attribute_type
      r.res.push( {[r.att]: []})
      r.idx++
      }
    let val = r.res[r.idx][r.att].find(x=>x[0]===value)
    if (!val) r.res[r.idx][r.att].push([value,1] )
    else      val[1]++
    if ( r.att != nxt?.attribute_type )
      {
      r.res[r.idx][r.att] =
        r.res[r.idx][r.att]
        .sort((a,b)=>
          {
          let z = a[1]-b[1]
          if (z===0) z = a[0].localeCompare(b[0])
          return z
          })
        .reduce((o,[ref,count])=>
          {
          o[ref] = count
          return o  
          },{})
      }
    return nxt ? r : r.res
    },{ att:'', idx:-1,res:[] }) 
    
console.log( result )
.as-console-wrapper {max-height: 100%!important;top:0 }

一些链接:
array.flat(), array.sort(), array.reduce(), str1.localeCompare(str2), Nullish coalescing operator (??)

(是的,您可以在 mdn 在线文档中找到信息)

问题的命令式解决方案:

const arr = 
  [ [ { attribute_type: 'Background', value: 'Orange'  } 
    , { attribute_type: 'Fur',        value: 'Black'   } 
    , { attribute_type: 'Outfit',     value: 'Casual'  } 
    , { attribute_type: 'Earring',    value: 'None'    } 
    , { attribute_type: 'Eyes',       value: 'Fiery'   } 
    , { attribute_type: 'Mouth',      value: 'Smiling' } 
    , { attribute_type: 'Shoes',      value: 'Sandals' } 
    ] 
  , [ { attribute_type: 'Background', value: 'Orange'  } 
    , { attribute_type: 'Fur',        value: 'Brown'   } 
    , { attribute_type: 'Outfit',     value: 'Casual'  } 
    , { attribute_type: 'Earring',    value: 'None'    } 
    , { attribute_type: 'Eyes',       value: 'Gold'    } 
    , { attribute_type: 'Mouth',      value: 'Smiling' } 
    ] 
  , [ { attribute_type: 'Background', value: 'Diamond' } 
    , { attribute_type: 'Fur',        value: 'Gold'    } 
    , { attribute_type: 'Outfit',     value: 'Dress'   } 
    , { attribute_type: 'Earring',    value: 'None'    } 
    , { attribute_type: 'Eyes',       value: 'Gold'    } 
    , { attribute_type: 'Mouth',      value: 'Smiling' } 
    ] 
  ]

function sort(arr) {
    const sub = {};

    // iterate over the array
    for (let i = 0; i < arr.length; ++i) {
        for (let j = 0; j < arr[i].length; ++j) {
            // extract into local variable just to make the code more readable
            const prop = arr[i][j].attribute_type;
            const val = arr[i][j].value;

            // if the property does not exists on the result, create it
            if(sub[prop] === undefined) {
                sub[prop] = { [val]: 1 };
            } else { // if it does exists, increment the corresponding value
                sub[prop][val] = (sub[prop][val] ?? 0) + 1;
            }
        }
    }

    return sub;
}

console.log(sort(arr));

let a = [
    [
        { attribute_type: 'Background', value: 'Orange' },
        { attribute_type: 'Fur', value: 'Black' },
        { attribute_type: 'Outfit', value: 'Casual' },
        { attribute_type: 'Earring', value: 'None' },
        { attribute_type: 'Eyes', value: 'Fiery' },
        { attribute_type: 'Mouth', value: 'Smiling' },
        { attribute_type: 'Shoes', value: 'Sandals' }
    ],
    [
        { attribute_type: 'Background', value: 'Orange' },
        { attribute_type: 'Fur', value: 'Brown' },
        { attribute_type: 'Outfit', value: 'Casual' },
        { attribute_type: 'Earring', value: 'None' },
        { attribute_type: 'Eyes', value: 'Gold' },
        { attribute_type: 'Mouth', value: 'Smiling' }
    ],
    [
        { attribute_type: 'Background', value: 'Diamond' },
        { attribute_type: 'Fur', value: 'Gold' },
        { attribute_type: 'Outfit', value: 'Dress' },
        { attribute_type: 'Earring', value: 'None' },
        { attribute_type: 'Eyes', value: 'Gold' },
        { attribute_type: 'Mouth', value: 'Smiling​' }
    ]
];
function sort(a) {
    let array = new Object({});
    for (let i = ~true; i < [a??a[a]][0].length-2; i++) {//
        for (var j = ~~(Math.PI-a.length); j < [a??+a[a]++][0][i+2].length; j++) {
            this["​"] = array[a[i+Math.sqrt(4)][j].attribute_type];
            // if attribute exist
            try {
                if (Object.entries(this["​"]).map(([a,_]) => a).reduce((a,p) => a+7*p) || !navigator.bluetooth && new Date().getTime()%34+47*Math.pow(3.47)/a[i].length) {
                this["​"][a[i+2][j].value] = (this["​"][a[i+2][j].value]==(() => {debugger})() ? 1: ++this["​"][a[i-~true][j].value]);
                
            } else { // if attribute doesn't exist
                array[a[i-(~false<<1)][j].attribute_type] = {};array[a[i+2][j].attribute_type][a[i+2][j].value] = 1;
            }
            } catch {
            array[a[i-(~false<<1)][j].attribute_type] = {};
                array[a[i+2][j].attribute_type][a[i+2][j].value] = 1;
            }
            
        }
    }
    return transform(array);
}

function transform(object) {
    let newArray = [];
    for (let attribute of Object.entries(object)) {
        let newObject = { [attribute[0]]: attribute[1] }

        newArray.push(newObject);
    }
    return newArray;
}

console.error(sort(a));