junelane
junelane

Reputation: 151

Java 8 Streams: How to match values between two ArrayList of Strings and create a list of another object

I am having two lists of Strings and want to compare the values in the lists and construct a new list of another object. I am able to do this with nested loops but looking for a more performant and neater solution.

List<String> list1 = new ArrayList();
list1.add("A,Airplane");
list1.add("B,Boat");

List<String> list2 = new ArrayList();
list2.add("A90, Boing Airplane");
list2.add("A70, Boing777");
list2.add("B80, Boing Boat");

There is a Vehicle object.

class Vehicle {

private String model;
private String submodel;
private String type;
private String subtype;
// setters getters
}

Now, I need to build the vehicle object using by matching the model (the first character from list1) with the first character in list2 and build something like this

private List<Vehicle> buildVehicle(List<String> list1, List<String> list2) {
        List<Vehicle> vehicles = new ArrayList<>();
        if (ObjectUtils.isNotEmpty(list2)) {
            list2.forEach(v -> {
                Vehicle vehicle = new Vehicle();
                if (v.contains(",")) {
                    String[] val = StringUtils.split(v,",");
                    vehicle.setSubtype(val[0]);
                    vehicle.setSubmodel(val[1]);
                }
                for (String c : list1) {
                    //Matching the first character from element in list1 
                    if (c.substring(0,1).equals(vehicle.getSubtype().substring(0,1))
                            && c.contains(",")) {
                        String[] val = StringUtils.split(c, ",");
                        vehicle.setType(val[0]);
                        vehicle.setModel(val[1]);
                        }
                        break;
                    }
                }
                vehicles.add(vehicle);
            });
        }
        return vehicles;
    }

Can the nested loop be avoided by using streams?

Upvotes: 0

Views: 2387

Answers (2)

WJS
WJS

Reputation: 40057

The data as provided.

List<String> list1 = new ArrayList<>();
list1.add("A,Airplane");
list1.add("B,Boat");

List<String> list2 = new ArrayList<>();
list2.add("A90, Boeing Airplane");
list2.add("A70, Boeing777");
list2.add("B80, Boeing Boat");

Regardless of how the model type information is obtained, this conversion might be more efficient and certainly easier if instead of using Lists you used Maps to hold the information. If the information was read in from a file it would be best to pre-process them (split if need be) as they are read in and put them in a map. If it was entered by hand then putting the information in a map would negate the need for any pre-processing at all. Here is a simple example.

Map<String,String> map1 = new HashMap<>();
map1.put("A","Airplane");
map1.put("B","Boat");

However, using the information as provided here is how I proceeded.

First, I created a Lambda to assist in the List conversion.

Function<List<String>, Map<String, String>> makeMap =
        lst -> lst.stream().map(st -> st.split("\\s*,\\s*")).collect(
                Collectors.toMap(a -> a[0], a -> a[1]));

// create the TypeModel map
Map<String, String> mapTM = makeMap.apply(list1);
// create the subTypeSubModel Map;
Map<String, String> mapSTSM = makeMap.apply(list2);

Now just use the keysets of each to go thru and piece it together. I created a constructor in my example for Vehicle which imho makes a cleaner object creation.

List<Vehicle> vehicles = mapTM.keySet().stream()
        .flatMap(type -> mapSTSM.keySet().stream()
                .filter(subType -> subType.startsWith(type))
                .map(sbType -> new Vehicle(mapTM.get(type),
                        mapSTSM.get(sbType), type, sbType)))
        .collect(Collectors.toList());

vehicles.forEach(System.out::println);

Prints based on toString (which can be changed).

[Airplane, Boeing Airplane,A,A90]
[Airplane, Boeing777,A,A70]
[Boat, Boeing Boat,B,B80]

Here is the class sans setters and getters.

class Vehicle {
    
    private String model;
    private String submodel;
    private String type;
    private String subtype;
    
    public Vehicle() {
    }
    
    public Vehicle(String model, String submodel, String type,
            String subtype) {
        this.model = model;
        this.submodel = submodel;
        this.type = type;
        this.subtype = subtype;
    }
    
    public String toString() {
       return "[" + String.join(", ", model, submodel, type, subtype)
                + "]";
    }
}

Upvotes: 2

Utku &#214;zdemir
Utku &#214;zdemir

Reputation: 7745

I'd follow an approach like the following:

List<String> list1 = new ArrayList<>();
list1.add("A,Airplane");
list1.add("B,Boat");

List<String> list2 = new ArrayList<>();
list2.add("A90, Boing Airplane");
list2.add("A70, Boing777");
list2.add("B80, Boing Boat");

Pattern commaPattern = Pattern
    .compile("\\s*,\\s*"); // a regex pattern to split by comma and the whitespace around it

Map<String, String> modelToType = list1.stream().map(commaPattern::split)
    .collect(Collectors
        .toMap(modelAndType -> modelAndType[0],
            modelAndType -> modelAndType[1])); // mapping models to types for o(1) lookups

List<Vehicle> vehicles = list2.stream().map(commaPattern::split)
    .map(subModelAndSubType -> {
      Vehicle vehicle = new Vehicle();
      vehicle.submodel = subModelAndSubType[0];
      vehicle.subtype = subModelAndSubType[1];
      vehicle.model = vehicle.submodel.substring(0, 1);
      vehicle.type = modelToType.get(vehicle.model);
      return vehicle;
    }).collect(Collectors.toList());

Upvotes: 4

Related Questions