Home > Enterprise >  Java stream groupingby nested string
Java stream groupingby nested string

Time:01-30

I have a list of string, and I want to be able to group them hierarchically.

Example of the list:

var list = new String[]{"caso.id",
"caso.unidadeDoCaso.id",
"caso.etiqueta",
"caso.sigiloso",
"caso.idPecaSegredoJustica",
"caso.numeroAno",
"caso.numero",
"caso.competencia.id",
"caso.competencia.ativo",
"caso.competencia.nome",
"caso.responsavel.id",
"caso.responsavel.dadosPessoais.nome",
"caso.escrivao.id",
"caso.escrivao.dadosPessoais.nome"};

I want to group them in Maps. Like:

caso->
   id
   sigiloso,
   ...
   unidadeDoCaso->
              id
   competencia->
              id
              ativo
              ...
   responsavel->
             id
             dadosPessoais->
                       nome
                       ...
              ...
             
  ...

I was able to group just one level. I was wondering if there's a way to do it recursively.

CodePudding user response:

Here is how you could do this using a Map<String, Map> and mutable reduction using the collect method that takes a supplier, accumulator, and combiner. The API is not the most pleasant to use, as WJS pointed out.

It requires unchecked casts because you can't represent these recursive structures of unknown depth using generics.

class Scratch {
public static void main(String[] args) {
    var list = new String[]{"caso.id",
            "caso.unidadeDoCaso.id",
            "caso.etiqueta",
            "caso.sigiloso",
            "caso.idPecaSegredoJustica",
            "caso.numeroAno",
            "caso.numero",
            "caso.competencia.id",
            "caso.competencia.ativo",
            "caso.competencia.nome",
            "caso.responsavel.id",
            "caso.responsavel.dadosPessoais.nome",
            "caso.escrivao.id",
            "caso.escrivao.dadosPessoais.nome"};

    Map<String, Map> result = Arrays.stream(list).collect(HashMap::new, Scratch::mapRecursively, HashMap::putAll);

    System.out.println(result);
    // {caso={unidadeDoCaso={id=null}, etiqueta=null, idPecaSegredoJustica=null, escrivao={id=null, dadosPessoais={nome=null}}, sigiloso=null, numero=null, id=null, numeroAno=null, responsavel={id=null, dadosPessoais={nome=null}}, competencia={ativo=null, nome=null, id=null}}}

    System.out.println(result.get("caso").keySet());
    // [unidadeDoCaso, etiqueta, idPecaSegredoJustica, escrivao, sigiloso, numero, id, numeroAno, responsavel, competencia]
}

@SuppressWarnings("unchecked")
private static void mapRecursively(HashMap<String, Map> map, String s) {
    // first recursion: s = caso.competencia.id
    // second recursion: s = id

    int dot = s.indexOf('.');

    // Base case 1
    if (dot == -1) {
        map.put(s, null);
        return;
    }

    String key = s.substring(0, dot); // caso
    String value = s.substring(dot   1); // competencia.id

    boolean isFirstTimeToComeAcrossWord = !map.containsKey(key);
    if (isFirstTimeToComeAcrossWord) {
        map.put(key, new HashMap<>());
    }

    // Base case 2
    int dot2 = value.indexOf('.');

    if (dot2 == -1) {
        map.get(key).put(value, null);
        return;
    }

    String newKey = value.substring(0, dot2); // competencia
    String leftover = value.substring(dot2   1); // id

    boolean isFirstTimeWeComeAcrossNestedWord = !map.get(key).containsKey(newKey);
    // Recursive cases
    if (isFirstTimeWeComeAcrossNestedWord) {
        var newMap = new HashMap<String, Map>();
        map.get(key).put(newKey, newMap);
        mapRecursively(newMap, leftover);
    } else {
        mapRecursively((HashMap<String, Map>) map.get(key).get(newKey), leftover);
    }
}

}

CodePudding user response:

In spite of my suggestion I decided to provide this. There are two recursive routines.

  • one to fill the map.
  • the other to print it.
String[] array = {
        "caso.id","caso.unidadeDoCaso.id","caso.etiqueta",
        "caso.sigiloso","caso.idPecaSegredoJustica","caso.numeroAno",
        "caso.numero","caso.competencia.id","caso.competencia.ativo",
        "caso.competencia.nome","caso.responsavel.id",
        "caso.responsavel.dadosPessoais.nome","caso.escrivao.id",
        "caso.escrivao.dadosPessoais.nome"
};
  • Create the map
  • Then iterated across the data, splitting on the dot.
  • then call fill with the map, just split nodes, and the starting node index.
Map<String, Object> map = new HashMap<>();

for (String s : array) {
    String[] nodes = s.split("\\.");
    fill(map, nodes, 0);
}

print(map, "");

prints

caso
   unidadeDoCaso
      id
   etiqueta
   idPecaSegredoJustica
   escrivao
      id
      dadosPessoais
         nome
   sigiloso
   numero
   id
   numeroAno
   responsavel
      id
      dadosPessoais
         nome
   competencia
      ativo
      nome
      id

The fill method continues until the supplied nodes are all processed.

  • first the map is checked to see if the node exists or not(equal to null)
  • if not present, a new map is constructed and added to the supplied map. Then the method is called to process the next node.
  • otherwise, the method is called to add the current node to the map after the one that exists and continue processing the nodes.
public static void fill(Map<String, Object> map, String[] nodes, int i) {
    if (i >= nodes.length) {
        return;
    }
    String node = nodes[i];
    @SuppressWarnings("unchecked")
    Map<String, Object> obj = (Map<String, Object>)(node);
    if (obj == null) {
        Map<String, Object> m = new HashMap<>();
        map.put(node, m);
        fill(m, nodes, i   1);
    } else {
        fill( obj, nodes, i   1);
    }
}

This prints the map elements and indents each subsequent nested map level on a separate line.

@SuppressWarnings("unchecked")
public static void print(Map<String, Object> map, String indent) {

    for (String key : map.keySet()) {
        if (key != null) {
            System.out.println(indent   key);
            print((Map<String, Object>) map.get(key), indent   "   ");
        }
    }
}
  • Related