java 8 use reduce and Collectors grouping by to get list

天涯浪子 提交于 2019-12-10 11:34:29

问题


EDIT **Request to provide answer to First approach also using reduce method **

 public class Messages {
  int id;
  String message;
  String field1;
  String field2;
  String field3;
  int audId;
  String audmessage;
 //constructor
   //getter or setters
 }


public class CustomMessage {

 int id;
 String msg;
 String field1;
 String field2;
 String field3;
 List<Aud> list;
 //getters and setters
}


  public class Aud {

    int id;
    String message;
   //getters and setters
   }


   public class Demo {

    public static  void main(String args[]){
      List<Messages> list = new ArrayList<Messages>();
      list.add(new Messages(1,"abc","c","d","f",10,"a1"));
      list.add(new Messages(2,"ac","d","d","f",21,"a2"));
      list.add(new Messages(3,"adc","s","d","f",31,"a3"));
      list.add(new Messages(4,"aec","g","d","f",40,"a4"));
      list.add(new Messages(1,"abc","c","d","f",11,"a5"));
      list.add(new Messages(2,"ac","d","d","f",22,"a5"));
     }

I want the message to be mapped with audits CustomMessage must have ->1,"abc","c","d","f"----->List of 2 audits (10,a1) and (11,"a5");

There are two ways to do it

1.Reduce-I would like to use reduce also to create my own accumulator and combiner

   List<CustomMessage> list1= list.stream().reduce(new ArrayList<CustomMessage>(),
            accumulator1,
            combiner1);

    **I am unable to write a accumulator and combiner**

2.Collectors.groupingBy-

  • I do not want to use constructors for creating the Message and neither for Custom Message.here I have less fields my actual object has many fields.Any way to have a static method for object creation
  • Is there is a way to do it via reduce by writing accumulator or combiner

          List<CustomMessage> l = list.stream()
            .collect(Collectors.groupingBy(m -> new SimpleEntry<>(m.getId(), m.getMessage()),
                    Collectors.mapping(m -> new Aud(m.getAudId(), m.getAudMessage()), Collectors.toList())))
            .entrySet()
            .stream()
            .map(e -> new CustomMessage(e.getKey().getKey(), e.getKey().getValue(), e.getValue()))
            .collect(Collectors.toList());
    

Can anyone help me with both the approaches.


回答1:


This code will create a Collection of CustomMessage. I would recommend putting a constructor in CustomMessage that takes a Messages argument. And maybe also move the mergeFunction out of the collect.

Collection<CustomMessage> customMessages = list.stream()
        .collect(toMap(
                Messages::getId,
                m -> new CustomMessage(m.getId(), m.getMessage(), m.getField1(), m.getField2(), m.getField3(),
                        new ArrayList<>(singletonList(new Aud(m.getAudId(), m.getAudmessage())))),
                (m1, m2) -> {
                    m1.getList().addAll(m2.getList());
                    return m1;
                }))
        .values();

What toMap does here is : The first time a Messages id is encountered, it will put it to a Map as key with value the newly created CustomMessage by the second argument to toMap (the "valueMapper"). The next times it will merge two CustomMessage with the 3rd argument the "mergeFunction" that will effectively concatenate the 2 lists of Aud.

And if you absolutely need a List and not a Collection:

List<CustomMessage> lm = new ArrayList<>(customMessages);



回答2:


You cannot do this by either grouping or reducing. You need both: group first and then reduce. I coded the reduction differently:

    List<CustomMessage> list1 = list.stream()
            .collect(Collectors.groupingBy(Messages::getId))
            .values()
            .stream() // stream of List<Messages>
            .map(lm -> {
                List<Aud> la = lm.stream()
                        .map(m -> new Aud(m.getAudId(), m.getAudmessage()))
                        .collect(Collectors.toList());
                Messages m0 = lm.get(0);
                return new CustomMessage(m0.getId(), m0.getMessage(), 
                        m0.getField1(), m0.getField2(), m0.getField3(), la);
            })
            .collect(Collectors.toList());

I have introduced a constructor in Aud and then read your comment that you are trying to avoid constructors. I will revert to this point in the end. Anyway, you can rewrite the creation of Aud objects to be the same way as in your question. And the construction of CustomMessage objects too if you like.

Result:

[1 abc c d f [10 a1, 11 a5], 3 adc s d f [31 a3], 4 aec g d f [40 a4], 2 ac d d f [21 a2, 22 a5]]

I grouped messages only by ID since you said their equals method uses ID only. You may also group by more fields like in your question. A quick and dirty way wold be

            .collect(Collectors.groupingBy(m -> "" + m.getId() + '-' + m.getMessage() 
                    + '-' + m.getField1() + '-' + m.getField2() + '-' + m.getField3()))

Avoiding public constructors and using static methods for object creation doesn’t change a lot. For example if you have

public static Aud createAud(int id, String message) {
    return new Aud(id, message);
}

(well, this didn’t eliminate the constructor completely, but now you can declare it private; if still not satisfied, you can also rewrite the method into not using a declared constructor). Now in the stream you just need to do:

                        .map(m -> Aud.createAud(m.getAudId(), m.getAudmessage()))

You can do similarly for CustomMessage. In this case your static method may take a Messages argument if you like, a bit like Manos Nikolaidis suggested, this could simplify the stream code a bit.

Edit: You couldn’t just forget about the three-argument reduce method, could you? ;-) It can be used. If you want to do that, I suggest you first fit CustomMessage with a range of methods for the purpose:

private CustomMessage(int id, String msg, 
                      String field1, String field2, String field3, List<Aud> list) {
    this.id = id;
    this.msg = msg;
    this.field1 = field1;
    this.field2 = field2;
    this.field3 = field3;
    this.list = list;
}

public static CustomMessage create(Messages m, List<Aud> la) {
    return new CustomMessage(m.getId(), m.getMessage(), 
                             m.getField1(), m.getField2(), m.getField3(), la);
}

/**
 * @return original with the Aud from m added
 */
public static CustomMessage adopt(CustomMessage original, Messages m) {
    if (original.getId() != m.getId()) {
        throw new IllegalArgumentException("adopt(): incompatible messages, wrong ID");
    }
    Aud newAud = Aud.createAud(m.getAudId(), m.getAudmessage());
    original.addAud(newAud);
    return original;
}

public static CustomMessage merge(CustomMessage cm1, CustomMessage cm2) {
    if (cm1.getId() != cm2.getId()) {
        throw new IllegalArgumentException("Cannot merge non-matching custom messages, id "
                + cm1.getId() + " and " + cm2.getId());
    }
    cm1.addAuds(cm2.getList());
    return cm1;
}

private void addAud(Aud aud) {
    list.add(aud);
}

private void addAuds(List<Aud> list) {
    this.list.addAll(list);
}

With these in place it’s not so bad:

    List<CustomMessage> list2 = list.stream()
            .collect(Collectors.groupingBy(Messages::getId))
            .values()
            .stream()
            .map(lm -> lm.stream()
                        .reduce(CustomMessage.create(lm.get(0), new ArrayList<>()),
                                CustomMessage::adopt, 
                                CustomMessage::merge))
            .collect(Collectors.toList());


来源:https://stackoverflow.com/questions/43938467/java-8-use-reduce-and-collectors-grouping-by-to-get-list

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!