Потоки Java 8: как сопоставить значения между двумя массивами строк и создать список другого объекта

#java #java-stream

#java #java-stream

Вопрос:

У меня есть два списка строк, и я хочу сравнить значения в списках и создать новый список другого объекта. Я могу сделать это с помощью вложенных циклов, но ищу более производительное и аккуратное решение.

 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");
  

Существует объект Vehicle.

 class Vehicle {

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

Теперь мне нужно создать объект vehicle, используя сопоставление модели (первый символ из list1) с первым символом в list2 и создать что-то вроде этого

 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))
                            amp;amp; c.contains(",")) {
                        String[] val = StringUtils.split(c, ",");
                        vehicle.setType(val[0]);
                        vehicle.setModel(val[1]);
                        }
                        break;
                    }
                }
                vehicles.add(vehicle);
            });
        }
        return vehicles;
    }
  

Можно ли избежать вложенного цикла с помощью потоков?

Комментарии:

1. Обратите внимание, что вы могли бы получить более компактное, читаемое и аккуратное решение, но, скорее всего, не более «производительное», как вы упомянули. Производительность в основном определяется используемыми вами структурами данных и сложностями во времени / пространстве вашего решения. Кроме того, stream api не так оптимизирован и, следовательно, немного медленнее, чем старые добрые циклы for / while.

2. Не могли бы вы, пожалуйста, объяснить, что такое компактное и аккуратное решение?

3. ObjectUtils и StringUtils не являются частью JDK.

Ответ №1:

Я бы придерживался подхода, подобного следующему:

 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());
  

Комментарии:

1. Большое спасибо. Это именно то, что я искал.

Ответ №2:

Данные, как указано.

 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");
  

Независимо от того, как получена информация о типе модели, это преобразование могло бы быть более эффективным и, безусловно, проще, если бы вместо использования Lists вы использовали Maps для хранения информации. Если информация была считана из файла, было бы лучше предварительно обработать их (при необходимости разделить) по мере считывания и поместить их в map. Если бы это было введено вручную, то помещение информации в map вообще свело бы на нет необходимость в какой-либо предварительной обработке. Вот простой пример.

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

Однако, используя предоставленную здесь информацию, я продолжил.

Сначала я создал лямбда-выражение для облегчения преобразования списка.

 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);
  

Теперь просто используйте наборы ключей каждого из них, чтобы просмотреть и собрать их воедино.
В моем примере я создал конструктор для Vehicle , который, имхо, делает
создание более чистого объекта.

 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);
  

Печатает на основе toString (который может быть изменен).

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

Вот класс без установщиков и геттеров.

 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)
                  "]";
    }
}