Home > Blockchain >  Correct Data structure to be used for the given problem - Java
Correct Data structure to be used for the given problem - Java

Time:09-29

I want to process the subset of values in Java. The scenario is like

Example 1:

localIDA - {x, y}
localIDB - {x, y, z}
localIDC - {z}

This should be clubbed in a JSON as follows,

{data : {x,y}, provider: {localIDA, localIDB }}
{data : {z}, provider: {localIDB, localIDC }}

Example 2:

localIDA - {x, y, z}
localIDB - {y, z}
localIDC - {z}

JSON structure should be

{data : {y, z}, provider: {localIDA, localIDB }}
{data : {x}, provider: {localIDA }}
{data : {z}, provider: {localIDC }}

Here, I would like to have idea about using the correct data structure in Java.

Note: In the given example, the data is taken with three entries. But , there may be 'n' number of entries.

CodePudding user response:

To arrange the data in the desired way, we can use a couple of intermediate maps.

Also, we would need a few custom types to perform transformations and to store the final result that would be serialized into JSON. I'll use Java 16 records for that purpose (but they could also be implemented as classes).

Map<provider, List<data>>

           |   intermediate transformation: 
           v   entry -> group of objects `provider   data`

Map<data, Set<provider>>

           |
           v

Map<Set<provider>, List<data>>

           |
           v
List of custom type that would be serialized into JSON

These are the main steps:

  • Split every entry of the initial map into a group of custom objects, each holding a reference only one reference to data and provider;

  • Group these objects by data and form the first auxiliary map of type Map<String, Set<String>> (providers by data).

  • Iterate over the entries of the newly created map and rearrange the data by grouping the entries by value, which is of type Set<String> (set of providers) into the second auxiliary map of type Map<Set<String>, List<String>>. The values of the second map would contain data-strings mapped to identical sets of providers (set is used to make the comparison not sensitive to ordering of providers).

    • Note that in this case it's safe to use a mutable collection (which can be created via Collectors.toSet() - see the code below) as a map-key because we need it only to perform intermediate transformation, and we're not storing references neither to the keys, no to the map. But if the reader would want to use a piece of the solution provided below to generate a map which would be stored as a field and passed around, then to avoid accidental changes the key should be represented by an immutable collection. Therefore, to make the solution more versatile, I've Collectors.toUnmodifiableSet().
  • The final step is to generate a List of objects that would be serialized into JSON. For that, we need to iterate over the second auxiliary map and turn each entry into a custom object.

  • The data is ready to be marshaled into JSON. For demo-purposes, I've used Jackson (reader can apply any other tool of their choice).

Custom objects:

public record DataProvider(String data, String provider) {}
public record DataProviders(Set<String> data, List<String> providers) {}

The map logic:

Map<String, List<String>> dataByProvider = Map.of(
    "localIDA", List.of("x", "y"),
    "localIDB", List.of("x", "y", "z"),
    "localIDC", List.of("z")
);
        
List<DataProviders> dataProviders = dataByProvider.entrySet().stream()
    .flatMap(entry -> entry.getValue().stream()
        .map(data -> new DataProvider(data, entry.getKey()))
    )
    .collect(Collectors.groupingBy(
        DataProvider::data,
        Collectors.mapping(DataProvider::provider,
            Collectors.toUnmodifiableSet())
    ))
    .entrySet().stream()
    .collect(
        HashMap::new,
        (Map<Set<String>, List<String>> map, Map.Entry<String, Set<String>> entry) ->
            map.computeIfAbsent(entry.getValue(), k -> new ArrayList<>()).add(entry.getKey()),
        (left, right) ->
            right.forEach((k, v) -> left.merge(k, v, (oldV, newV) -> {
                oldV.addAll(newV);
                return oldV;
            }))
    )
    .entrySet().stream()
    .map(entry -> new DataProviders(entry.getKey(), entry.getValue()))
    .toList(); // for Java 16  or collect(Collectors.toList())
        
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT); // to make the output look nicely (can be omitted)
        
String json = mapper.writeValueAsString(dataProviders);
        
System.out.println(json);

Output:

[ {
  "data" : [ "localIDB", "localIDA" ],
  "providers" : [ "x", "y" ]
}, {
  "data" : [ "localIDB", "localIDC" ],
  "providers" : [ "z" ]
} ]
  • Related