2

OK i have been trying to create the right algorithm to solve this problem for week now, i don't seem to come with the right approach. I have an array of objects which i need to group by more than one property but also have the ability to break at x prop. For example:

var arr = [
 {car: 'ford', model: 'mustang', color: 'black', price: 400},
 {car: 'ford', model: 'focus', color: 'yellow', price: 300},
 {car: 'toyota', model: 'corolla', color: 'blue', price: 100},
 {car: 'toyota', model: 'camry', color: 'green', price: 200},
 {car: 'toyota', model: 'camry', color: 'black', price: 250},
 {car: 'toyota', model: 'camry', color: 'black', price: 350},
]

and then groupBy(arr, ['car', 'model', 'color'], {break: 'model'})

this would print something like

[
 {
  "ford - mustang": [
    "black": [
      {car: 'ford', model: 'mustang', color: 'black', price: 400}
     ]
   ]
 },
 {
  "ford - focus": [
    "yellow": [
     {car: 'ford', model: 'focus', color: 'yellow', price: 300}
    ]
  ]
 },
 {
  "toyota - corolla": [
    "blue": [
      {car: 'toyota', model: 'corolla', color: 'blue', price: 100},
    ]
  ]
 },
 {
  "toyota - camry": [
    "green": [
     {car: 'toyota', model: 'camry', color: 'green', price: 200}
    ],
    "black": [
      {car: 'toyota', model: 'camry', color: 'black', price: 250},
      {car: 'toyota', model: 'camry', color: 'black', price: 350}
    ]
  ]
 }
]

Edit 1: this the function i created to group by. this function takes into consideration three props "criteria1, criteria2 and criteria3", this function does the grouping job however i haven't figure out how to do the breaking part

const criteriaMapping = {
 Lenders: "lenderStr",
 Branches: "branchStr",
 Officers: "officerStr",
 Programs: "programStr",
 Stages: "stageStr"
}
private createNewArray(data){
let keys1 = []
let keys2 = []
let keys3 = []
let result = []
let orderBy = 'reservationNo'
let orderByDirection1 = ''
let orderByDirection2 = ''
let orderByDirection3 = ''

if(this.query.criteria1.description){
  keys1 = Object.keys(_.groupBy(data, criteriaMapping[this.query.criteria1.description]))
  orderByDirection1 = this.query.criteria1.desc ? 'desc' : 'asc'
}
if(this.query.criteria1.description && this.query.criteria2.description){
  keys2 = Object.keys(_.groupBy(data, criteriaMapping[this.query.criteria2.description]))
  orderByDirection2 = this.query.criteria2.desc ? 'desc' : 'asc'
}
if(this.query.criteria2.description && this.query.criteria3.description){
  keys3 = Object.keys(_.groupBy(data, criteriaMapping[this.query.criteria3.description]))
  orderByDirection3 = this.query.criteria3.desc ? 'desc' : 'asc'
}

let i = 0
do{
  let j = 0
  do{
    let k = 0
    do{
      let orderProp = criteriaMapping[this.query.criteria1.description]
      let order = 'asc'
      if(keys3[k])
        order = orderByDirection3
      else if(keys2[j])
        order = orderByDirection2
      else if(keys1[i])
        order = orderByDirection1
      let concat = _.orderBy(data.filter(x => {
        return (keys1[i] ? x[criteriaMapping[this.query.criteria1.description]] === keys1[i] : true)
          && (keys2[j] ? x[criteriaMapping[this.query.criteria2.description]] === keys2[j] : true)
          && (keys3[k] ? x[criteriaMapping[this.query.criteria3.description]] === keys3[k] : true)
      }), ['reservationNo'], [order])
      if(concat && concat.length){
        if(keys1[i]) result.push({"header": true, 0: keys1[i], 1: keys2[j], 2: keys3[k]})
        result = result.concat(concat)
      }
      k++
    }while(keys3.length > k)
    j++
  }while (keys2.length > j)
  i++
}while (keys1.length > i)

return result

}

So i don't think i made myself clear (my fault). the break part is hard to explain i am going to try to explain it better. so having the array i previously wrote if i were to call groupBy(arr, ['car', 'model', 'color'], {break: 'model'}) it would print

{
 "ford - mustang": {
  "black": Array(1)
 },
 "ford - focus": {
   "yellow": Array(1)
 },
 "toyota - corolla": {
  "blue": Array(1)
 },
 "toyota - camry": {
  "green": Array(1),
  "black": Array(2)
 }
}

now if i were to call

groupBy(arr, ['car', 'model', 'color'], {break: 'car'})

it would print

{
 "ford": {
  "mustang - black": Array(1),
  "focus - yellow": Array(1)
 },
 "toyota": {
  "corolla - blue": Array(1),
  "camry - green": Array(1),
  "camry - black": Array(2)
 }
}
4
  • 2
    Can you show us your attempt so we can help you get it to a working solution? Commented Dec 11, 2017 at 20:15
  • Why format the data in that way (array of objects with a single key) rather than a single object with multiple keys? Commented Dec 11, 2017 at 20:22
  • @AndrewLohr just updated my question Commented Dec 11, 2017 at 20:25
  • @Damon that works too Commented Dec 11, 2017 at 20:25

3 Answers 3

2

You can use reduce() method for this.

var arr = [
 {car: 'ford', model: 'mustang', color: 'black', price: 400},
 {car: 'ford', model: 'focus', color: 'yellow', price: 300},
 {car: 'toyota', model: 'corolla', color: 'blue', price: 100},
 {car: 'toyota', model: 'camry', color: 'green', price: 200},
 {car: 'toyota', model: 'camry', color: 'black', price: 250},
 {car: 'toyota', model: 'camry', color: 'black', price: 350},
]

var result = arr.reduce(function(r, e) {
  let cm = `${e.car} - ${e.model}`;
  if (!r[cm]) r[cm] = {}
  if (!r[cm][e.color]) r[cm][e.color] = [];
  r[cm][e.color].push(e);
  return r;
}, {})

console.log(result)

You can also use reduce() to create custom function for more dynamic solution.

var arr = [
 {car: 'ford', model: 'mustang', color: 'black', price: 400},
 {car: 'ford', model: 'focus', color: 'yellow', price: 300},
 {car: 'toyota', model: 'corolla', color: 'blue', price: 100},
 {car: 'toyota', model: 'camry', color: 'green', price: 200},
 {car: 'toyota', model: 'camry', color: 'black', price: 250},
 {car: 'toyota', model: 'camry', color: 'black', price: 350},
]

function groupBy(data, group, {divide} = {}) {
  return arr.reduce(function(r, e) {
    let d = e[divide], cm = group
    .filter(a => a != divide)
    .map(a => e[a]).join(' - ')
    
    if(!r[cm]) r[cm] = divide ? {} : []
    if(!r[cm][d] && divide) r[cm][d] = [];
    divide ? r[cm][d].push(e) : r[cm].push(e)
    return r;
  }, {})
}

let result = groupBy(arr, ['car', 'model', 'color'], {divide: 'model'})
let result2 = groupBy(arr, ['car', 'model', 'color'], {divide: 'car'})
let result3 = groupBy(arr, ['car', 'color'])


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

Sign up to request clarification or add additional context in comments.

3 Comments

this is (weirdly) not the output described by OP (asked for an array of objects with a single key)
[result], fixed.
@NenadVracar this is awesome and very close to what i want i didnt think it would be possible to do it using reduce(), however the breaking point can be dynamic. for example groupBy(arr, ['car', 'model', 'color'], {break: 'car'})
1

Is this what you are looking for?

function groupBy(arr, keys, breakVal){
    return arr.reduce( (grouped, next) => {
        var idx = keys.reduce( (idx, nextkey) => {
            idx.push(next[nextkey])
            return idx;
        }, [] ).join(' - ')
        if(breakVal){
            if(!grouped[idx]) {
                grouped[idx] = {}
            }
            if(!grouped[idx][next[breakVal]]) {
                grouped[idx][next[breakVal]] = [];
            }
            grouped[idx][next[breakVal]].push(next);
        }else{
            if(!grouped[idx]) {
                grouped[idx] = [];
            }
            grouped[idx].push(next);
        }
        return grouped;
    }, {})
}

console.log(groupBy(arr,  ['car', 'color'], 'model'))

1 Comment

this is close to what i need, i just update my question please see my edit, i tried to explained myself better. thank you
0

Thanks to @Nenad and @asosnovsky i was able to come up with a function that works my needs

function groupBy(arr, props, dividers){
return arr.reduce((r, e) => {
    var prop = []
    var divide = []
    for(var i = 0; i < props.length; i++){
        prop.push(e[props[i]])
    }
    for(var i = 0; i < dividers.length; i++){
        divide.push(e[dividers[i]])
    }
    prop = prop.join(' - ')
    divide = divide.join(' - ')
    if(!r[prop]) r[prop] = divide ? {} : []
    if(divide){
        if(!r[prop][divide]) r[prop][divide] = []
        r[prop][divide].push(e)
    }else
        r[prop].push(e)
    return r
}, {})

}

thank you very much.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.