GroupBy in Javascript

4

I'm getting the following result from a query:

        [
          {
            "disciplina": "Portugues",
            "periodo": "1º Bimestre",
            "tipo": "1ª avaliacao",
            "valor": 9.5
          },
          {
            "disciplina": "Matematica",
            "periodo": "1º Bimestre",
            "tipo": "1ª avaliacao",
            "valor": 9.5
          }
        ]

What is the best way to leave the result grouped by period, then by discipline and then by type? I'm looking for this result:

         {
            "periodo": "1º Bimestre",
            "disciplinas": [
              {
                "nome": "Português",
                "tipo": [
                  {
                    "nome": "1ª avaliacao",
                    "valor": 9.5
                  },
                  {
                    "nome": "2ª avaliacao",
                    "valor": 9.5
                  }
                ]
              },
              {
                "nome": "Matemática",
                "tipo": [
                  {
                    "nome": "1ª avaliacao",
                    "valor": 8.5
                  },
                  {
                    "nome": "2ª avaliacao",
                    "valor": 7.5
                  }
                ]
              }
            ]
          }

Can anyone help? Thanks.

    
asked by anonymous 04.02.2017 / 01:32

2 answers

5

I ended up developing a more generic function, presented below.

function group_by (lista, coluna) {
  var colunas = {};
  var resultado = [];

  lista.forEach(function (item) {
    var reg = {};

    colunas[item[coluna]] = colunas[item[coluna]] || [];

    for (var i in item) 
      if (i != coluna) 
        reg[i] = item[i]; 

    colunas[item[coluna]].push(reg);
  });

  for (var i in colunas) 
    resultado.push({key: i, values: colunas[i]});

  return resultado;
}

It naturally does groud by on one level, returning a list of objects with the key attributes, with the value of the selected column, and values , with the remaining values.

Consider the entry below:

[
  {
    "disciplina": "Portugues",
    "periodo": "1º Bimestre",
    "tipo": "1ª avaliacao",
    "valor": 9.5
  },
  {
    "disciplina": "Matematica",
    "periodo": "1º Bimestre",
    "tipo": "1ª avaliacao",
    "valor": 9.5
  },
  {
    "disciplina": "Matematica",
    "periodo": "1º Bimestre",
    "tipo": "Trabalho",
    "valor": 9.5
  },
  {
    "disciplina": "Matematica",
    "periodo": "2º Bimestre",
    "tipo": "1ª avaliacao",
    "valor": 6.3
  }
]

Running group_by on column periodo , group_by(lista, "periodo") , we would have the following output:

[
  {
    "key":"1º Bimestre",
    "values":[
      {
        "disciplina":"Portugues",
        "tipo":"1ª avaliacao",
        "valor":9.5
      },
      {
        "disciplina":"Matematica",
        "tipo":"1ª avaliacao",
        "valor":9.5
      },
      {
        "disciplina":"Matematica",
        "tipo":"Trabalho",
        "valor":9.5
      }
    ]
  },
  {
    "key":"2º Bimestre",
    "values":[
      {
        "disciplina":"Matematica",
        "tipo":"1ª avaliacao",
        "valor":6.3
      }
    ]
  }
]

To get a group by of second degree, so to speak, just run the function on the list again in values , in the desired column. For example, to now do group by on the disciplina column and get the expected result, we do:

group_by(lista, "periodo").map(function (item) {
  return {key: item.key, values: group_by(item.values, "disciplina")};
});

Producing the following output:

[
  {
    "key":"1º Bimestre",
    "values":[
      {
        "key":"Portugues",
        "values":[
          {
            "tipo":"1ª avaliacao",
            "valor":9.5
          }
        ]
      },
      {
        "key":"Matematica",
        "values":[
          {
            "tipo":"1ª avaliacao",
            "valor":9.5
          },
          {
            "tipo":"Trabalho",
            "valor":9.5
          }
        ]
      }
    ]
  },
  {
    "key":"2º Bimestre",
    "values":[
      {
        "key":"Matematica",
        "values":[
          {
            "tipo":"1ª avaliacao",
            "valor":6.3
          }
        ]
      }
    ]
  }
]

How about testing for a larger ticket? Let's suppose that we want to separate the records according to tipo , to separate notes of proofs, lists, etc.

group_by(lista, "periodo").map(function (item) {
  return {key: item.key, values: group_by(item.values, "disciplina").map(function(item){
    return {key: item.key, values: group_by(item.values, "tipo")}
  })};
})

The output:

[
  {
    "key":"1º Bimestre",
    "values":[
      {
        "key":"Portugues",
        "values":[
          {
            "key":"Prova",
            "values":[
              {
                "valor":9.5
              }
            ]
          },
          {
            "key":"Lista",
            "values":[
              {
                "valor":8.8
              },
              {
                "valor":5.2
              }
            ]
          }
        ]
      },
      {
        "key":"Matematica",
        "values":[
          {
            "key":"Prova",
            "values":[
              {
                "valor":9.5
              }
            ]
          }
        ]
      }
    ]
  },
  {
    "key":"2º Bimestre",
    "values":[
      {
        "key":"Biologia",
        "values":[
          {
            "key":"Prova",
            "values":[
              {
                "valor":9.5
              }
            ]
          },
          {
            "key":"Lista",
            "values":[
              {
                "valor":10
              }
            ]
          }
        ]
      }
    ]
  },
  {
    "key":"3º Bimestre",
    "values":[
      {
        "key":"Geografia",
        "values":[
          {
            "key":"Lista",
            "values":[
              {
                "valor":9.5
              },
              {
                "valor":9.9
              }
            ]
          }
        ]
      }
    ]
  }
]
    
04.02.2017 / 04:11
6

I do not know if it's the best way, or the most efficient, but it generates the result you need. I believe that with the comments in the code it is possible to understand the logic.

var lista = [
  {
    "disciplina": "Portugues",
    "periodo": "1º Bimestre",
    "tipo": "1ª avaliacao",
    "valor": 9.5
  },
  {
    "disciplina": "Matematica",
    "periodo": "1º Bimestre",
    "tipo": "1ª avaliacao",
    "valor": 9.5
  },
  {
    "disciplina": "Matematica",
    "periodo": "2º Bimestre",
    "tipo": "1ª avaliacao",
    "valor": 6.3
  }
]

function group_by (lista) {
  
  var resultado = [];
  
  // Percorre todos os registros
  lista.forEach(function (item) {
    
    var idp = -1;
    var idd = -1;
    
    // Verifica se o periodo já está na lista final
    // Se sim, idp será o índice do respectivo objeto
    for (i = 0; i < resultado.length; i++) {
      if (item.periodo == resultado[i].periodo) {
        idp = i;
        break;
      }
    }
    
    // Se não, idp será -1, então cria o objeto na lista
    if (idp == -1) {
      idp = resultado.length;
      resultado.push({"periodo": item.periodo, "disciplinas": []});
    }
    
    // Verifica se a disciplina já está na lista do periodo
    // Se sim, idd será o índice do respectivo objeto
    for (i = 0; i < resultado[idp].disciplinas.length; i++) {
      if (item.disciplina == resultado[idp].disciplinas[i].nome) {
        idd = i;
        break;
      }
    }
    
    // Se não, idd será -1, então cria o objeto na lista
    if (idd == -1) {
      idd = resultado[idp].disciplinas.length;
      resultado[idp].disciplinas.push({"nome": item.disciplina, "tipo": []});
    }
    
    // Insere o tipo/valor na respectiva disciplina e periodo
    resultado[idp].disciplinas[idd].tipo.push({"nome": item.tipo, "valor": item.valor});
    
  });
  
  return resultado;
  
}

$(function () {
  $('pre').html( JSON.stringify(group_by(lista), null, 2) );
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<pre>teste</pre>
  

Press Run to see the code working.

    
04.02.2017 / 02:59