5

I have an entity class

    class Entity {
        private String customer;
        private String product;
        private String productDetail;
    }

I have an ArrayList<Entity> including many records, for example record in my list:

customer    product    productDetail
   A          A1            A11
   A          A1            A12
   A          A2            A21
   A          A2            A22
   B          B1            B11
   B          B2            B21
   C          C1            C11
   C          C1            C12

I have 3 entities below

   class ProductDetail{
       private String details;
   }

   class Product{
       private String product;
       private List<ProductDetail> detailList;
   }

   class Customer{
       private String customer;
       private List<Product> productList;
   }

I want to loop through my ArrayList<Entity> and group records into Customer class, Customer class includes productList, and productList includes detailList.

Please suggest me a solution.

Update my code:

Customer entity:

public class CustomerEntity {
    private int customer;

    private List<ProductEntity> productList;

    public int getCustomer() {
        return customer;
    }

    public void setCustomer(int customer) {
        this.customer = customer;
    }

    public List<ProductEntity> getProductList() {
        return productList;
    }

    public void setProductList(List<ProductEntity> productList) {
        this.productList = productList;
    }

}

Product Entity:

public class ProductEntity {

    private int product;
    private List<ProductDetailEntity> detailList;

    public int getProduct() {
        return product;
    }

    public void setProduct(int product) {
        this.product = product;
    }

    public List<ProductDetailEntity> getDetailList() {
        return detailList;
    }

    public void setDetailList(List<ProductDetailEntity> detailList) {
        this.detailList = detailList;
    }

}

Product detail entity:

public class ProductDetailEntity {
    private int details;

    public int getDetails() {
        return details;
    }

    public void setDetails(int details) {
        this.details = details;
    }

}

My dummy code test:

public class TestList {
    public static void main(String[] args) {
        TestEntity testEntity;
        List<TestEntity> list = new ArrayList<TestEntity>();

        // Create Dummy Data
        testEntity = new TestEntity();
        testEntity.setCustomer("A");
        testEntity.setProduct("A1");
        testEntity.setProductDetail("A11");
        list.add(testEntity);

        testEntity = new TestEntity();
        testEntity.setCustomer("A");
        testEntity.setProduct("A1");
        testEntity.setProductDetail("A12");
        list.add(testEntity);

        testEntity = new TestEntity();
        testEntity.setCustomer("A");
        testEntity.setProduct("A2");
        testEntity.setProductDetail("A21");
        list.add(testEntity);

        testEntity = new TestEntity();
        testEntity.setCustomer("A");
        testEntity.setProduct("A2");
        testEntity.setProductDetail("A22");
        list.add(testEntity);

        testEntity = new TestEntity();
        testEntity.setCustomer("B");
        testEntity.setProduct("B1");
        testEntity.setProductDetail("B11");
        list.add(testEntity);

        testEntity = new TestEntity();
        testEntity.setCustomer("B");
        testEntity.setProduct("B2");
        testEntity.setProductDetail("B21");
        list.add(testEntity);

        testEntity = new TestEntity();
        testEntity.setCustomer("C");
        testEntity.setProduct("C1");
        testEntity.setProductDetail("C11");
        list.add(testEntity);

        testEntity = new TestEntity();
        testEntity.setCustomer("C");
        testEntity.setProduct("C1");
        testEntity.setProductDetail("C12");
        list.add(testEntity);

        Map<String, List<TestEntity>> groupByCustomerMap = new LinkedHashMap<String, List<TestEntity>>();
        List<TestEntity> tempEntityList;
        TestEntity tempEntity;

        // Group record by customer
        for (TestEntity item : list) {
            String customer = item.getCustomer();

            tempEntityList = new ArrayList<TestEntity>();
            tempEntity = new TestEntity();

            tempEntity.setCustomer(customer);
            tempEntity.setProductDetail(item.getProductDetail());
            tempEntity.setProduct(item.getProduct());

            tempEntityList.add(tempEntity);

            if (!groupByCustomerMap.containsKey(customer)) {
                groupByCustomerMap.put(customer, tempEntityList);
            } else {
                groupByCustomerMap.get(customer).addAll(tempEntityList);
            }
        }

        // I think from groupByCustomerMap, read ProductDetail and group by product
        // Pleaes suggest me next solution
    }
}
ekad
  • 14,436
  • 26
  • 44
  • 46
Dragon
  • 95
  • 2
  • 9
  • 1
    Please show us what you have tried so far. I would think that you will also have a list of customers and a list of products. While iterating check if the customer is already in the list of customers. If not, add it. Then check for the customer if the product is already in his productList, if not add etc ect – Stefan Freitag Sep 20 '16 at 12:15
  • 2
    Make an attempt, post you code where you face difficulties. – c0der Sep 20 '16 at 12:16
  • @StefanFreitag, that’s a fulfledged answer already! :-) – Ole V.V. Sep 20 '16 at 12:24

4 Answers4

1

A typical approach that I would try is the following:

  • Create an empty list of customers
  • Create an empty list of products
  • Iterate over input array list consisting of Entity elements.
    • If the customer contained in the current entity is not already in the list of customers, then create and add the customer. Then check for the product. If the customer was created, then the productList should be empty and the product needs to be added. If the customer was already existing, check for the existence of the product in the list of products....
    • For filling the list of products you can choose a similar approach. The basic checks are the same.
Stefan Freitag
  • 3,578
  • 3
  • 26
  • 33
0

I could propose next decision:

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.ToString;
import org.junit.Test;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by smv on 19/09/16.
 */
public class MainTest {

    @AllArgsConstructor
    @ToString
    @Data
    class Entity {
        private String customer;
        private String product;
        private String productDetail;
    }

    @AllArgsConstructor
    @ToString
    @Data
    class ProductDetail{
        private String details;
    }

    @AllArgsConstructor
    @ToString
    @Data
    class Product{
        private String product;
        private List<ProductDetail> detailList;
    }

    @AllArgsConstructor
    @ToString
    @Data
    class Customer{
        private String customer;
        private List<Product> productList;
    }
    @Test
    public void run() throws Exception {
        ArrayList<Entity> entities = new ArrayList<>();
        entities.add(new Entity("A", "A1", "A11"));
        entities.add(new Entity("A", "A1", "A12"));
        entities.add(new Entity("A", "A2", "A21"));
        entities.add(new Entity("A", "A2", "A22"));
        entities.add(new Entity("B", "B1", "B11"));
        entities.add(new Entity("B", "B2", "B21"));
        entities.add(new Entity("C", "C1", "C11"));
        entities.add(new Entity("C", "C1", "C12"));

        ArrayList<Customer> customers = new ArrayList<>();
        entities.forEach(entity -> {
            Customer customer = customers.stream().filter(c -> c.getCustomer().equals(entity.getCustomer())).findFirst().orElse(new Customer(entity.getCustomer(), new ArrayList<>()));
            if (!customers.contains(customer)) {
                customers.add(customer);
            }
            Product product = customer.getProductList().stream().filter(p -> p.getProduct().equals(entity.getProduct())).findFirst().orElse(new Product(entity.getProduct(), new ArrayList<>()));
            if (!customer.getProductList().contains(product)) {
                customer.getProductList().add(product);
            }
            ProductDetail productDetail = product.getDetailList().stream().filter(pd -> pd.getDetails().equals(entity.getProductDetail())).findFirst().orElse(new ProductDetail(entity.getProductDetail()));
            if (!product.getDetailList().contains(productDetail)) {
                product.getDetailList().add(productDetail);
            }
        });

        customers.forEach(s -> System.out.println(s));
    }

}

I think it is not the best variant, but it's worked. I think there must be more elegant way to replace if in code.

UPD

Annotations on data beans it is Lombok lib annotations that provide the convenient way to declare a bean. It will work in java 7 too.

@AllArgsConstructor - adds the constructor with all fields.
@Data - generate getters/setters for all fields.
@ToString - add the method that returns the string representation of the bean.

Further syntax suitable for java 8 only. This is function List.forEach with the lambda expression, obviously, it iterates the list and can be replaced with for loop. Each data bean is found or created using the Java 8 stream API (filter with predicate expression) and Optional type (orElse). So this line:

Customer customer = customers.stream().filter(c -> c.getCustomer().equals(entity.getCustomer())).findFirst().orElse(new Customer(entity.getCustomer(), new ArrayList<>()));

can be replaced in java 7 with:

Customer customer = null;
for (Customer c: customers ) {
        if(c.getCustomer().equals(entity.getCustomer())) {
            customer = c;
            break;
        }
    }
if (customer == null) {
  customer  = new Customer(entity.getCustomer(), new ArrayList<>())
}

and so on..

mv200580
  • 702
  • 1
  • 6
  • 14
0

Here is my implementation of your task as my own CustomerList class.

The API of the class is ctor CustomerList(List<Entity> data) and the main method List<Customer> getCustomers() which does real work.

The implementation is based on Java 8 Collector conception. Internally it uses 3 specialized functions: ACCUMULATOR, COMBINER and FINISHER.

The whole idea of the implementation is transforming input List<Entity> into tree-style data structure Map<String, Map<String, Set<String>>. The outer map represents a customer name to products mapping. The internal map represents a product name to product details mapping. And Set<String> represents a set of product detail names. ACCUMULATOR is used for doing this transformation.

FINISHER is used to transform this internal tree-style structure to output List<Customer> data.

The collector uses COMBINER to merge more then one internal tree-style structures for concurrent processing. I haven't tested this but I think the collector can do this processing with simple data.parallelStream().collect(COLLECTOR) statement change.

public class CustomerList {

    private static final BiConsumer<Map<String, Map<String, Set<String>>>, Entity> ACCUMULATOR =
            (m, e) ->
                    m.computeIfAbsent(e.getCustomer(), k -> new HashMap<>())
                            .computeIfAbsent(e.getProduct(), l -> new HashSet<>())
                            .add(e.getProductDetail());

    private static final BinaryOperator<Map<String, Map<String, Set<String>>>> COMBINER =
            (m1, m2) -> {
                Map<String, Map<String, Set<String>>> r = new HashMap<>(m1);
                for (Map.Entry<String, Map<String, Set<String>>> e : m2.entrySet())
                    r.merge(e.getKey(), e.getValue(), CustomerList::mergeProducts);
                return r;
            };

    private static final Function<Map<String, Map<String, Set<String>>>, List<Customer>> FINISHER =
            CustomerList::createCustomerList;

    private static final Collector<Entity, Map<String, Map<String, Set<String>>>, List<Customer>> COLLECTOR =
            Collector.of(HashMap::new, ACCUMULATOR, COMBINER, FINISHER);

    private final List<Entity> data;

    public CustomerList(List<Entity> data) {
        this.data = data;
    }

    public List<Customer> getCustomers() {
        return data.stream().collect(COLLECTOR);
    }

    private static Map<String, Set<String>> mergeProducts(Map<String, Set<String>> m1, Map<String, Set<String>> m2) {
        Map<String, Set<String>> r = new HashMap<>(m1);
        for (Map.Entry<String, Set<String>> e : m2.entrySet())
            r.merge(e.getKey(), e.getValue(), CustomerList::mergeDescriptions);
        return r;
    }

    private static Set<String> mergeDescriptions(Set<String> s1, Set<String> s2) {
        Set<String> r = new HashSet<>(s1);
        r.addAll(s2);
        return r;
    }

    private static List<Customer> createCustomerList(Map<String, Map<String, Set<String>>> customers) {
        return customers.entrySet().stream()
                .map(e -> new Customer(e.getKey(), createProductList(e.getValue())))
                .collect(Collectors.toList());
    }

    private static List<Product> createProductList(Map<String, Set<String>> products) {
        return products.entrySet().stream()
                .map(e -> new Product(e.getKey(), createDetailsList(e.getValue())))
                .collect(Collectors.toList());
    }

    private static List<ProductDetail> createDetailsList(Set<String> details) {
        return details.stream()
                .map(e -> new ProductDetail(e))
                .collect(Collectors.toList());
    }
}
Andriy Kryvtsun
  • 3,220
  • 3
  • 27
  • 41
0

The demo

I was intrigued by the problem, so i put a few hours into it and came up with the following:

List<Entity> input = asList(
        new Entity("A", "A1", "A11"),
        new Entity("A", "A1", "A12"),
        new Entity("A", "A2", "A21"),
        new Entity("A", "A2", "A22"),
        new Entity("B", "B1", "B11"),
        new Entity("B", "B2", "B21"),
        new Entity("C", "C1", "C11"),
        new Entity("C", "C1", "C12"));

List<Customer> output = input.stream().collect(
    toTree(Entity::getCustomer, Customer::new, toList(),
        toTree(Entity::getProduct, Product::new, toList(),
            mapping(Entity::getProductDetail, mapping(ProductDetail::new, toList())))));

System.out.println(output);

The implementation

And the implementation of toTree looks like this:

<E, K, C, P, R> Collector<E, ?, R> toTree(Function<E, K> keyMapper,
                                          BiFunction<K, C, P> keyWithChildrenMerger,
                                          Collector<P, ?, R> parentsCollector,
                                          Collector<E, ?, C> childrenCollector) {
    return collectingAndThen(
        groupingBy(keyMapper, LinkedHashMap::new, childrenCollector),
        keysWithChildren -> keysWithChildren.entrySet().stream()
                .map(entryFunction(keyWithChildrenMerger))
                .collect(parentsCollector));
}

<K, V, R> Function<Map.Entry<K, V>, R> entryFunction(BiFunction<K, V, R> keyValueFunction) {
    return entry -> keyValueFunction.apply(entry.getKey(), entry.getValue());
}

It's a Collector that takes input E (Entities in our case), groups them on a key K, collects their children C, merges keys K with their children C to parents P, and collects them to some result, R (in our case, List<P>).

The explanation

What we're doing here is collecting the entities to a tree that is a List of Customers, that each contain a List of Products, that each contain a List of ProductDetails.

Building the tree from the bottom up

It does this by grouping the customers on Entity::getCustomer and the products on Entity::getProduct, and mapping entities to ProductDetails, before creating the tree from the bottom up, by invoking the constructor Product::new with all its ProductDetails, and the constructor Customer::new with all its Products.

The model

The model classes are as you'd expect:

class Entity {
    private final String customer;
    private final String product;
    private final String productDetail;

    public Entity(String customer, String product, String productDetail) {
        this.customer = customer;
        this.product = product;
        this.productDetail = productDetail;
    }

    public String getCustomer() {
        return customer;
    }

    public String getProduct() {
        return product;
    }

    public String getProductDetail() {
        return productDetail;
    }
}

class ProductDetail {
    private String details;

    public ProductDetail(String details) {
        this.details = details;
    }

    @Override
    public String toString() {
        return details;
    }
}

class Product {
    private String product;
    private List<ProductDetail> details;

    public Product(String product, List<ProductDetail> productDetails) {
        this.product = product;
        details = productDetails;
    }

    @Override
    public String toString() {
        return "Product{" + product +
                ", " + details +
                '}';
    }
}

class Customer {
    private String customer;
    private List<Product> products;

    public Customer(String customer, List<Product> products) {
        this.customer = customer;
        this.products = products;
    }

    @Override
    public String toString() {
        return "Customer{" + customer +
                ", " + products +
                '}';
    }
}
Christoffer Hammarström
  • 27,242
  • 4
  • 49
  • 58