Search code examples
javadata-structuresjava-8java-streamcartesian-product

How to create a data structure similar to the cartesian product of three lists of different types?


I want to create a DataStructure which will be similar to the cartesian product of three List. I've also referred the existing answer by Jurgen which suggest to use flatMap. I tried in that way as well. But I've condition that filterValue list is inside types list. So flatMap will not work here. As filterValues can be 0 or more. So depending on that cartesian product (we might call it as combination) will change.

Size of measures, types & filterValues can be different for each list. If measure list is empty. Then the combination will be of only types & filterValues (and measure will be set to null. I've added these different scenarios in my comments of if-else block

I've following types of list:

  1. List<String> measures
  2. List<Type> types
  3. List<FilterValue> filterValues

For example the Input structure is:

{
  "measures": [
    "m1",
    "m2",
    "m3"
  ],
  "types": [
    {
      "type": "type-1",
      //some more fields
      "filterValues": [
        //no filter values present
      ]
    },
    {
      "type": "type-2",
      //some more fields
      "filterValues": [
        {
          "filterValue": "t2f1"
          //some more fields
        },
        {
          "filterValue": "t2f2"
          //some more fields
        }
      ]
    }
  ]
}

Then in above case the output data structure I'm expecting is

m1  type-1 null
m1  type-2 t2f1 
m1  type-2 t2f2 

m2  type-1 null
m2  type-2 t2f1 
m2  type-2 t2f2 

m3  type-1 null
m3  type-2 t2f1 
m3  type-2 t2f2 

Then the same above values I'm setting into the following classes:

class SearchArea {
    String measure;
    String type;
    TypeCombi typeFileter;
    //constructor for measure & type
    //constructor for all three
    //getters & setters
}

class TypeCombi {
    String type;
    String name; //it is mapped with filterValue
    //constructor for above two fields
    //getters & setters
}

The class Type & FilterValue is as below

class Type {
    String type;
    List<FilterValue> filterValues;
    //some more fields
    //getters and setters
}

class FilterValue {
    String filterValue;
    //some more fields
    //getters and setters
}

I'm able to achieve the expected output using following getSearchAreas function. But in this case I'm using multiple(two) for loops. Can this code block cleaned up using stream/flatmap instead of two for loops ? Also is there any better way to handle multiple if/else block ?(I've added comment above each if/else block for it's scenario)

private List<SearchArea> getSearchAreas(List<String> measures, List<Type> types){
    List<SearchArea> searchAreas = new ArrayList<>();

    //measures & types both are empty
    if ((measures == null || measures.isEmpty())
            && (types == null || types.isEmpty()))
        return Collections.emptyList();

    //one or more measure and zero types
    else if (measures != null && !measures.isEmpty()
            && (types == null || types.isEmpty())) {
        searchAreas = measures
                .stream()
                .map(measure -> new SearchArea(measure, null))
                .collect(Collectors.toList());
        return searchAreas;
    }
    //zero measures and one or more types
    else if ((measures == null || measures.isEmpty())) {
        for (type type : types) {
            if (type.getFilterValues() == null
                    || type.getFilterValues().isEmpty()) {
                searchAreas.add(new SearchArea(null, type.getType()));
            } else {
                searchAreas.addAll(type.getFilterValues()
                        .stream()
                        .map(filterValue -> new SearchArea(null,
                                type.getType(),
                                new TypeCombi(type.getType(),
                                        filterValue.getFilterValue())))
                        .collect(Collectors.toList()));
            }
        }
        return searchAreas;
    }
    //one or more measures and one or more types
    else {
        for (String measure : measures) {
            for (Type type : types) {
                if (type.getFilterValues() == null
                        || type.getFilterValues().isEmpty()) {
                    searchAreas.add(new SearchArea(measure, type.getType()));
                } else {
                    searchAreas.addAll(type.getFilterValues()
                            .stream()
                            .map(filterValue -> new SearchArea(measure,
                                    type.getType(),
                                    new TypeCombi(type.getType(),
                                            filterValue.getFilterValue())))
                            .collect(Collectors.toList()));
                }
            }
        }
        return searchAreas;
    }
}

It will be great if someone can help me in restructuring above in cleaner fashion.


Solution

  • I think this is what you want. Note that it is sometimes cleaner not to use streams.

    public static void main(String[] args) throws Exception {
        List<String> strings = Collections.emptyList();
        List<Integer> ints = Arrays.asList(1, 2, 3);
    
        if (strings == null || strings.isEmpty()) {
            strings = Collections.singletonList(null);
        }
    
        if (ints == null || ints.isEmpty()) {
            ints = Collections.singletonList(null);
        }
    
        for (String str : strings) {
            for (Integer integer : ints) {
                // In your code doubles comes from a property of integer.
                List<Double> doubles = integer == null ? Collections.emptyList() : Arrays.asList(1.0d, 2.0d, 3.0d);
    
                if (doubles == null || doubles.isEmpty()) {
                    doubles = Collections.singletonList(null);
                }
    
                for (Double doubler : doubles) {
                    // Create your object here.
                    System.out.format(Locale.US, "    str = %s, int = %d, double = %f %n", str, integer, doubler);
                }
            }
        }
    }
    

    Output follows:

    str = null, int = 1, double = 1.000000 
    str = null, int = 1, double = 2.000000
    str = null, int = 1, double = 3.000000
    str = null, int = 2, double = 1.000000
    str = null, int = 2, double = 2.000000 
    str = null, int = 2, double = 3.000000
    str = null, int = 3, double = 1.000000
    str = null, int = 3, double = 2.000000
    str = null, int = 3, double = 3.000000