Java 8 groupingby with returning multiple field - java-8

In Java 8 group by how to groupby on a single field which returns more than one field. In the below code by I am passing name and the field to be summed which is 'total' in this scenario. however I would like to return sum of 'total' and 'balance' field for every 'name' in the Customer list (can be a map with key and value as array).
Can it be done by using a single groupingBy with the return values?
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
public class Sample {
public static void main(String str[]){
Customer custa = new Customer("A",1000,1500);
Customer custa1 = new Customer("A",2000,2500);
Customer custb = new Customer("B",3000,3500);
Customer custc = new Customer("C",4000,4500);
Customer custa2 = new Customer("A",1500,2500);
List<Customer> listCust = new ArrayList<>();
listCust.add(custa);
listCust.add(custa1);
listCust.add(custb);
listCust.add(custc);
listCust.add(custa2);
Map<String, Double> retObj =
listCust.stream().collect(Collectors.groupingBy(Customer::getName,Collectors.summingDouble(Customer::getTotal)));
System.out.println(retObj);
}
private static class Customer {
private String name;
private double total;
private double balance;
public Customer(String name, double total, double balance) {
super();
this.name = name;
this.total = total;
this.balance = balance;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getTotal() {
return total;
}
public void setTotal(double total) {
this.total = total;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
#Override
public String toString() {
return "Customer [name=" + name + ", total=" + total + ", balance=" + balance + "]";
}
}
}
Expected Output -
{
A = [4500,6500],
B = [3000,3500] ,
C = [4000,4500]
}

You can write your own collector to sum total and balance
Collector<Customer, List<Double>, List<Double>> collector = Collector.of(
() -> Arrays.asList(0.0, 0.0),
(a, t) -> {
a.set(0, a.get(0) + t.getTotal());
a.set(1, a.get(1) + t.getBalance());
},
(a, b) -> {
a.set(0, a.get(0) + b.get(0));
a.set(1, a.get(1) + b.get(1));
return a;
}
);
Map<String, List<Double>> retObj = listCust
.stream()
.collect(Collectors.groupingBy(Customer::getName, collector));
System.out.println(retObj);
result
{A=[4500.0, 6500.0], B=[3000.0, 3500.0], C=[4000.0, 4500.0]}

You can also use the toMap collector to accomplish the task at hand.
Map<String, List<Double>> retObj =
listCust.stream()
.collect(Collectors.toMap(Customer::getName,
c -> new ArrayList<>(Arrays.asList(c.getTotal(), c.getBalance())),
(l, l1) -> {
l.set(0, l.get(0) + l1.get(0));
l.set(1, l.get(1) + l1.get(1));
return l;
}));

Related

How to change form data body feign client interceptor

I have some api with content-type: form-data.
Every request have some common field in the body
Here is my code
package com.example.demo.request;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.math.BigDecimal;
public class CreateNhanhProductRequest {
private String nuctk;
private Long storeId;
private String name;
private Integer typeId;
private String parentIdName;
private Long parentId;
private String code;
private String barcode;
private BigDecimal importPrice;
private BigDecimal vat;
private BigDecimal price;
private BigDecimal wholesalePrice;
private BigDecimal oldPrice;
private Integer status;
private Object multiUnitItems;
private Object comboItems;
private Long categoryId;
private Long internalCategoryId;
private Long brandId;
private Double shippingWeight;
private String unit;
private Integer length;
private Integer width;
private Integer height;
private Long countryId;
private Long warrantyAddress;
private String warrantyPhone;
private Long warranty;
private String warrantyContent;
private Integer firstRemain;
private Integer importType;
private Long depotId;
private String supplierName;
private Long supplierId;
private Integer checkcopyImg;
private Integer attributeCombinated;
private String metaTitle;
private String metaDescription;
private Object metaKeywords;
private Object tags;
#JsonProperty("tag-suggest")
private Object tagSuggest;
public String getNuctk() {
return nuctk;
}
public void setNuctk(String nuctk) {
this.nuctk = nuctk;
}
public Long getStoreId() {
return storeId;
}
public void setStoreId(Long storeId) {
this.storeId = storeId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getTypeId() {
return typeId;
}
public void setTypeId(Integer typeId) {
this.typeId = typeId;
}
public String getParentIdName() {
return parentIdName;
}
public void setParentIdName(String parentIdName) {
this.parentIdName = parentIdName;
}
public Long getParentId() {
return parentId;
}
public void setParentId(Long parentId) {
this.parentId = parentId;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getBarcode() {
return barcode;
}
public void setBarcode(String barcode) {
this.barcode = barcode;
}
public BigDecimal getImportPrice() {
return importPrice;
}
public void setImportPrice(BigDecimal importPrice) {
this.importPrice = importPrice;
}
public BigDecimal getVat() {
return vat;
}
public void setVat(BigDecimal vat) {
this.vat = vat;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public BigDecimal getWholesalePrice() {
return wholesalePrice;
}
public void setWholesalePrice(BigDecimal wholesalePrice) {
this.wholesalePrice = wholesalePrice;
}
public BigDecimal getOldPrice() {
return oldPrice;
}
public void setOldPrice(BigDecimal oldPrice) {
this.oldPrice = oldPrice;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public Object getMultiUnitItems() {
return multiUnitItems;
}
public void setMultiUnitItems(Object multiUnitItems) {
this.multiUnitItems = multiUnitItems;
}
public Object getComboItems() {
return comboItems;
}
public void setComboItems(Object comboItems) {
this.comboItems = comboItems;
}
public Long getCategoryId() {
return categoryId;
}
public void setCategoryId(Long categoryId) {
this.categoryId = categoryId;
}
public Long getInternalCategoryId() {
return internalCategoryId;
}
public void setInternalCategoryId(Long internalCategoryId) {
this.internalCategoryId = internalCategoryId;
}
public Long getBrandId() {
return brandId;
}
public void setBrandId(Long brandId) {
this.brandId = brandId;
}
public Double getShippingWeight() {
return shippingWeight;
}
public void setShippingWeight(Double shippingWeight) {
this.shippingWeight = shippingWeight;
}
public String getUnit() {
return unit;
}
public void setUnit(String unit) {
this.unit = unit;
}
public Integer getLength() {
return length;
}
public void setLength(Integer length) {
this.length = length;
}
public Integer getWidth() {
return width;
}
public void setWidth(Integer width) {
this.width = width;
}
public Integer getHeight() {
return height;
}
public void setHeight(Integer height) {
this.height = height;
}
public Long getCountryId() {
return countryId;
}
public void setCountryId(Long countryId) {
this.countryId = countryId;
}
public Long getWarrantyAddress() {
return warrantyAddress;
}
public void setWarrantyAddress(Long warrantyAddress) {
this.warrantyAddress = warrantyAddress;
}
public String getWarrantyPhone() {
return warrantyPhone;
}
public void setWarrantyPhone(String warrantyPhone) {
this.warrantyPhone = warrantyPhone;
}
public Long getWarranty() {
return warranty;
}
public void setWarranty(Long warranty) {
this.warranty = warranty;
}
public String getWarrantyContent() {
return warrantyContent;
}
public void setWarrantyContent(String warrantyContent) {
this.warrantyContent = warrantyContent;
}
public Integer getFirstRemain() {
return firstRemain;
}
public void setFirstRemain(Integer firstRemain) {
this.firstRemain = firstRemain;
}
public Integer getImportType() {
return importType;
}
public void setImportType(Integer importType) {
this.importType = importType;
}
public Long getDepotId() {
return depotId;
}
public void setDepotId(Long depotId) {
this.depotId = depotId;
}
public String getSupplierName() {
return supplierName;
}
public void setSupplierName(String supplierName) {
this.supplierName = supplierName;
}
public Long getSupplierId() {
return supplierId;
}
public void setSupplierId(Long supplierId) {
this.supplierId = supplierId;
}
public Integer getCheckcopyImg() {
return checkcopyImg;
}
public void setCheckcopyImg(Integer checkcopyImg) {
this.checkcopyImg = checkcopyImg;
}
public Integer getAttributeCombinated() {
return attributeCombinated;
}
public void setAttributeCombinated(Integer attributeCombinated) {
this.attributeCombinated = attributeCombinated;
}
public String getMetaTitle() {
return metaTitle;
}
public void setMetaTitle(String metaTitle) {
this.metaTitle = metaTitle;
}
public String getMetaDescription() {
return metaDescription;
}
public void setMetaDescription(String metaDescription) {
this.metaDescription = metaDescription;
}
public Object getMetaKeywords() {
return metaKeywords;
}
public void setMetaKeywords(Object metaKeywords) {
this.metaKeywords = metaKeywords;
}
public Object getTags() {
return tags;
}
public void setTags(Object tags) {
this.tags = tags;
}
public Object getTagSuggest() {
return tagSuggest;
}
public void setTagSuggest(Object tagSuggest) {
this.tagSuggest = tagSuggest;
}
#Override
public String toString() {
return "CreateNhanhProductRequest{" +
"nuctk='" + nuctk + '\'' +
", storeId=" + storeId +
", name='" + name + '\'' +
", typeId=" + typeId +
", parentIdName='" + parentIdName + '\'' +
", parentId=" + parentId +
", code='" + code + '\'' +
", barcode='" + barcode + '\'' +
", importPrice=" + importPrice +
", vat=" + vat +
", price=" + price +
", wholesalePrice=" + wholesalePrice +
", oldPrice=" + oldPrice +
", status=" + status +
", multiUnitItems=" + multiUnitItems +
", comboItems=" + comboItems +
", categoryId=" + categoryId +
", internalCategoryId=" + internalCategoryId +
", brandId=" + brandId +
", shippingWeight=" + shippingWeight +
", unit='" + unit + '\'' +
", length=" + length +
", width=" + width +
", height=" + height +
", countryId=" + countryId +
", warrantyAddress=" + warrantyAddress +
", warrantyPhone='" + warrantyPhone + '\'' +
", warranty=" + warranty +
", warrantyContent='" + warrantyContent + '\'' +
", firstRemain=" + firstRemain +
", importType=" + importType +
", depotId=" + depotId +
", supplierName='" + supplierName + '\'' +
", supplierId=" + supplierId +
", checkcopyImg=" + checkcopyImg +
", attributeCombinated=" + attributeCombinated +
", metaTitle='" + metaTitle + '\'' +
", metaDescription='" + metaDescription + '\'' +
", metaKeywords=" + metaKeywords +
", tags=" + tags +
", tagSuggest=" + tagSuggest +
'}';
}
// private Multi imageUpload;
}
I want to Add same nuctk in every request in the body.
But request use the formData i can't convert this to object in interceptor.
My client
package com.example.demo.client;
import com.example.demo.config.NhanhPageFeignClientInterceptor;
import com.example.demo.request.CreateNhanhProductRequest;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
#FeignClient(
value = "nhanhPage",
url = "https://nhanh.vn/product",
configuration = NhanhPageFeignClientInterceptor.class
)
public interface NhanhProductV1Client {
#PostMapping(value = "/item/add", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
String createProduct(#RequestBody CreateNhanhProductRequest request);
}
This is my interceptor
package com.example.demo.config;
import com.example.demo.request.CreateNhanhProductRequest;
import com.fasterxml.jackson.databind.ObjectMapper;
import feign.RequestInterceptor;
import feign.form.FormData;
import feign.form.FormEncoder;
import feign.form.spring.SpringFormEncoder;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import java.io.IOException;
public class NhanhPageFeignClientInterceptor {
#Bean
public RequestInterceptor requestInterceptor() {
return requestTemplate -> {
String bodyTemplate = requestTemplate.bodyTemplate();
byte[] body = requestTemplate.body();
FormData formData = new FormData(MediaType.MULTIPART_FORM_DATA_VALUE, "data" , body);
requestTemplate.header(HttpHeaders.COOKIE, "Some cookie");
};
}
}
How to change the body in feign client interceptor.
Thank!

Field value in a Class does not get updated with Spring Boot MVC Controller

I have a Parcel Entity, and I am populating the fields through #PostMapping. The value Volume should be gotten from a method taking in values from the agruments in the constructor.
Controller Class:
#Controller
public class Controller {
#Value("#{${listOfBases}}")
private List<String> listOfBases;
#Value("#{${listOfDestinations}}")
private List<String> listOfDestinations;
#Autowired
ParcelRepository parcelRepository;
#GetMapping("/register_parcel")
public String showParcelRegistrationForm(Model model) {
Parcel parcel = new Parcel();
model.addAttribute("parcel", parcel);
model.addAttribute("listOfBases", listOfBases);
model.addAttribute("listOfDestinations", listOfDestinations);
return "parcel/register_form_parcel";
}
#PostMapping("register_parcel")
public String registerParcel(#ModelAttribute("parcel") Parcel parcel) {
System.out.println(parcel);
parcelRepository.save(parcel);
return "user/user_registration_success_form";
}
}
Parcel Class:
#Entity
public class Parcel {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
int id;
String length;
String width;
String height;
String weight;
String base;
String destination;
String creationDate;
String volume;
No args constructor:
public Parcel() {
creationDate = getCreationDateAndTime();
}
public Parcel(int id, String length, String width, String height, String weight, String base, String destination) {
this.id = id;
this.length = length;
this.width = width;
this.height = height;
this.weight = weight;
this.base = base;
this.destination = destination;
creationDate = getCreationDateAndTime();
volume = String.valueOf(calculateVolume(width, height, length));
}
Getters and Setters:
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getLength() {
return length;
}
public void setLength(String length) {
this.length = length;
}
public String getWidth() {
return width;
}
public void setWidth(String width) {
this.width = width;
}
public String getHeight() {
return height;
}
public void setHeight(String height) {
this.height = height;
}
public String getWeight() {
return weight;
}
public void setWeight(String weight) {
this.weight = weight;
}
public String getBase() {
return base;
}
public void setBase(String base) {
this.base = base;
}
public String getDestination() {
return destination;
}
public void setDestination(String destination) {
this.destination = destination;
}
public String getCreationDate() {
return creationDate;
}
public void setCreationDate(String creationDate) {
this.creationDate = creationDate;
}
public String getVolume() {
return volume;
}
public void setVolume(String volume) {
this.volume = volume;
}
private String getCreationDateAndTime() {
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime now = LocalDateTime.now();
return dtf.format(now);
}
CalculateVolume method that is the root of the problem:
private int calculateVolume(String width, String height, String length) {
int w = Integer.parseInt(width);
int h = Integer.parseInt(height);
int l = Integer.parseInt(length);
return w * h * l;
}
But the value volume is null in my database. Even System.out.println(); in calculateVolume does not print anything on the console, but when I run create an instance in main, all runs fine and dandy.
Any ideas on how I should proceed, or is my question too vague?
Thank you
#Override
public String toString() {
return "Parcel{" +
"id=" + id +
", length='" + length + '\'' +
", width='" + width + '\'' +
", height='" + height + '\'' +
", weight='" + weight + '\'' +
", base='" + base + '\'' +
", destination='" + destination + '\'' +
", creationDate='" + creationDate + '\'' +
", volume='" + volume + '\'' +
'}';
}
}
Change Post method in your controller
from
public String registerParcel(#ModelAttribute("parcel") Parcel parcel)
to
public String registerParcel(#RequestBody Parcel parcel)

How to export huge result set from database into several csv files and zip them on the fly?

I need to create a REST controller which extracts data from a database and write it into CSV files that will ultimately be zipped together. Each CSV file should contain exactly 10 lines. Eventually all CSV files should be zipped into a one zip file. I want everything to happen on the fly, meaning - saving files to a temporary location on the disk is not an option. Can someone provide me with an example?
I found a very nice code to export huge amount of rows from database into several csv files and zip it.
I think this is a nice code that can assist alot of developers.
I have tested the solution and you can find the entire example at : https://github.com/idaamit/stream-from-db/tree/master
The conroller is :
#GetMapping(value = "/employees/{employeeId}/cars") #ResponseStatus(HttpStatus.OK) public ResponseEntity<StreamingResponseBody> getEmployeeCars(#PathVariable int employeeId) {
log.info("Going to export cars for employee {}", employeeId);
String zipFileName = "Cars Of Employee - " + employeeId;
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_TYPE, "application/zip")
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + zipFileName + ".zip")
.body(
employee.getCars(dataSource, employeeId));
The employee class, first checks if we need to prepare more than one csv or not :
public class Employee {
public StreamingResponseBody getCars(BasicDataSource dataSource, int employeeId) {
StreamingResponseBody streamingResponseBody = new StreamingResponseBody() {
#Override
public void writeTo(OutputStream outputStream) throws IOException {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
String sqlQuery = "SELECT [Id], [employeeId], [type], [text1] " +
"FROM Cars " +
"WHERE EmployeeID=? ";
PreparedStatementSetter preparedStatementSetter = new PreparedStatementSetter() {
public void setValues(PreparedStatement preparedStatement) throws SQLException {
preparedStatement.setInt(1, employeeId);
}
};
StreamingZipResultSetExtractor zipExtractor = new StreamingZipResultSetExtractor(outputStream, employeeId, isMoreThanOneFile(jdbcTemplate, employeeId));
Integer numberOfInteractionsSent = jdbcTemplate.query(sqlQuery, preparedStatementSetter, zipExtractor);
}
};
return streamingResponseBody;
}
private boolean isMoreThanOneFile(JdbcTemplate jdbcTemplate, int employeeId) {
Integer numberOfCars = getCount(jdbcTemplate, employeeId);
return numberOfCars >= StreamingZipResultSetExtractor.MAX_ROWS_IN_CSV;
}
private Integer getCount(JdbcTemplate jdbcTemplate, int employeeId) {
String sqlQuery = "SELECT count([Id]) " +
"FROM Cars " +
"WHERE EmployeeID=? ";
return jdbcTemplate.queryForObject(sqlQuery, new Object[] { employeeId }, Integer.class);
}
}
This class StreamingZipResultSetExtractor is responsible to split the csv streaming data into several files and zip it.
#Slf4j
public class StreamingZipResultSetExtractor implements ResultSetExtractor<Integer> {
private final static int CHUNK_SIZE = 100000;
public final static int MAX_ROWS_IN_CSV = 10;
private OutputStream outputStream;
private int employeeId;
private StreamingCsvResultSetExtractor streamingCsvResultSetExtractor;
private boolean isInteractionCountExceedsLimit;
private int fileCount = 0;
public StreamingZipResultSetExtractor(OutputStream outputStream, int employeeId, boolean isInteractionCountExceedsLimit) {
this.outputStream = outputStream;
this.employeeId = employeeId;
this.streamingCsvResultSetExtractor = new StreamingCsvResultSetExtractor(employeeId);
this.isInteractionCountExceedsLimit = isInteractionCountExceedsLimit;
}
#Override
#SneakyThrows
public Integer extractData(ResultSet resultSet) throws DataAccessException {
log.info("Creating thread to extract data as zip file for employeeId {}", employeeId);
int lineCount = 1; //+1 for header row
try (PipedOutputStream internalOutputStream = streamingCsvResultSetExtractor.extractData(resultSet);
PipedInputStream InputStream = new PipedInputStream(internalOutputStream);
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(InputStream))) {
String currentLine;
String header = bufferedReader.readLine() + "\n";
try (ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream)) {
createFile(employeeId, zipOutputStream, header);
while ((currentLine = bufferedReader.readLine()) != null) {
if (lineCount % MAX_ROWS_IN_CSV == 0) {
zipOutputStream.closeEntry();
createFile(employeeId, zipOutputStream, header);
lineCount++;
}
lineCount++;
currentLine += "\n";
zipOutputStream.write(currentLine.getBytes());
if (lineCount % CHUNK_SIZE == 0) {
zipOutputStream.flush();
}
}
}
} catch (IOException e) {
log.error("Task {} could not zip search results", employeeId, e);
}
log.info("Finished zipping all lines to {} file\\s - total of {} lines of data for task {}", fileCount, lineCount - fileCount, employeeId);
return lineCount;
}
private void createFile(int employeeId, ZipOutputStream zipOutputStream, String header) {
String fileName = "Cars for Employee - " + employeeId;
if (isInteractionCountExceedsLimit) {
fileCount++;
fileName += " Part " + fileCount;
}
try {
zipOutputStream.putNextEntry(new ZipEntry(fileName + ".csv"));
zipOutputStream.write(header.getBytes());
} catch (IOException e) {
log.error("Could not create new zip entry for task {} ", employeeId, e);
}
}
}
The class StreamingCsvResultSetExtractor is responsible for transfer the data from the resultset into csv file. There is more work to do to handle special character set which are problematic in csv cell.
#Slf4j
public class StreamingCsvResultSetExtractor implements ResultSetExtractor<PipedOutputStream> {
private final static int CHUNK_SIZE = 100000;
private PipedOutputStream pipedOutputStream;
private final int employeeId;
public StreamingCsvResultSetExtractor(int employeeId) {
this.employeeId = employeeId;
}
#SneakyThrows
#Override
public PipedOutputStream extractData(ResultSet resultSet) throws DataAccessException {
log.info("Creating thread to extract data as csv and save to file for task {}", employeeId);
this.pipedOutputStream = new PipedOutputStream();
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(() -> {
prepareCsv(resultSet);
});
return pipedOutputStream;
}
#SneakyThrows
private Integer prepareCsv(ResultSet resultSet) {
int interactionsSent = 1;
log.info("starting to extract data to csv lines");
streamHeaders(resultSet.getMetaData());
StringBuilder csvRowBuilder = new StringBuilder();
try {
int columnCount = resultSet.getMetaData().getColumnCount();
while (resultSet.next()) {
for (int i = 1; i < columnCount + 1; i++) {
if(resultSet.getString(i) != null && resultSet.getString(i).contains(",")){
String strToAppend = "\"" + resultSet.getString(i) + "\"";
csvRowBuilder.append(strToAppend);
} else {
csvRowBuilder.append(resultSet.getString(i));
}
csvRowBuilder.append(",");
}
int rowLength = csvRowBuilder.length();
csvRowBuilder.replace(rowLength - 1, rowLength, "\n");
pipedOutputStream.write(csvRowBuilder.toString().getBytes());
interactionsSent++;
csvRowBuilder.setLength(0);
if (interactionsSent % CHUNK_SIZE == 0) {
pipedOutputStream.flush();
}
}
} finally {
pipedOutputStream.flush();
pipedOutputStream.close();
}
log.debug("Created all csv lines for Task {} - total of {} rows", employeeId, interactionsSent);
return interactionsSent;
}
#SneakyThrows
private void streamHeaders(ResultSetMetaData resultSetMetaData) {
StringBuilder headersCsvBuilder = new StringBuilder();
for (int i = 1; i < resultSetMetaData.getColumnCount() + 1; i++) {
headersCsvBuilder.append(resultSetMetaData.getColumnLabel(i)).append(",");
}
int rowLength = headersCsvBuilder.length();
headersCsvBuilder.replace(rowLength - 1, rowLength, "\n");
pipedOutputStream.write(headersCsvBuilder.toString().getBytes());
}
}
In order to test this, you need to execute http://localhost:8080/stream-demo/employees/3/cars

Java8 Stream Collectors - Splitting a list based on sum of values

I am trying partition a list into multiple sublists based on a condition that sum of a particular field should be less than 'x'. Below is sameple code:
public class TestGrouping {
public static class Transaction{
String txnId;
String comment;
Amount amount;
public Transaction(String txnId, String comment, Amount amount) {
this.txnId = txnId;
this.comment = comment;
this.amount = amount;
}
}
public static class Amount{
String amountValue;
public Amount(String amountValue) {
this.amountValue = amountValue;
}
}
public static void main(String[] args) {
List<Transaction> transactionList = new ArrayList<>();
Transaction txn1 = new Transaction("T1","comment1",new Amount("81"));
Transaction txn2 = new Transaction("T2","comment2",new Amount("5"));
Transaction txn3 = new Transaction("T3","comment3",new Amount("12"));
Transaction txn4 = new Transaction("T4","comment4",new Amount("28"));
transactionList.add(txn1);
transactionList.add(txn2);
transactionList.add(txn3);
transactionList.add(txn4);
//below is what i thought might work
// transactionList.stream().collect(groupingBy (r->Collectors.summingInt(Integer.valueOf(r.amount.amountValue)),Collectors.mapping(t -> t, toList())));
}
The goal is to split the transactionList into 2 (or more) sublists - where the sum of 'amount' is less than 100. So i could have a sublist have only txn1 - having amount as 81; and the other sublist have txn2, txn3, txn4 (as sum of these is less 100). Other possibility is - have sublist1 having txn1, txn2, txn3; and another sublist with just txn4. Not trying to create the most 'optimal' lists basically, just that sum of amounts should be less than 100.
Any clues?
The Idea is to use a custom collector to generate a list of pair(amountSum, transactions), the list should initialy be sorted. The accumulator method (here Accumulator.accept) do the grouping logic, I didn't implement combine because there is no need for a combiner in non parallel stream.
Bellow the code snippet, hope it helps.
public class TestStream {
public class Transaction {
String txnId;
String comment;
Amount amount;
public Transaction(String txnId, String comment, Amount amount) {
this.txnId = txnId;
this.comment = comment;
this.amount = amount;
}
}
public class Amount {
String amountValue;
public Amount(String amountValue) {
this.amountValue = amountValue;
}
}
#Test
public void test() {
List<Transaction> transactionList = new ArrayList<>();
Transaction txn1 = new Transaction("T1", "comment1", new Amount("81"));
Transaction txn2 = new Transaction("T2", "comment2", new Amount("5"));
Transaction txn3 = new Transaction("T3", "comment3", new Amount("12"));
Transaction txn4 = new Transaction("T4", "comment4", new Amount("28"));
transactionList.add(txn1);
transactionList.add(txn2);
transactionList.add(txn3);
transactionList.add(txn4);
transactionList.stream()
.sorted(Comparator.comparing(tr -> Integer.valueOf(tr.amount.amountValue)))
.collect(ArrayList<Pair<Integer, List<Transaction>>>::new, Accumulator::accept, (x, y) -> {
})
.forEach(t -> {
System.out.println(t.left);
});
}
static class Accumulator {
public static void accept(List<Pair<Integer, List<Transaction>>> lPair, Transaction tr) {
Pair<Integer, List<Transaction>> lastPair = lPair.isEmpty() ? null : lPair.get(lPair.size() - 1);
Integer amount = Integer.valueOf(tr.amount.amountValue);
if (Objects.isNull(lastPair) || lastPair.left + amount > 100) {
lPair.add(
new TestStream().new Pair<Integer, List<Transaction>>(amount,
Arrays.asList(tr)));
} else {
List<Transaction> newList = new ArrayList<>();
newList.addAll(lastPair.getRight());
newList.add(tr);
lastPair.setLeft(lastPair.getLeft() + amount);
lastPair.setRight(newList);
}
}
}
class Pair<T, V> {
private T left;
private V right;
/**
*
*/
public Pair(T left, V right) {
this.left = left;
this.right = right;
}
public V getRight() {
return right;
}
public T getLeft() {
return left;
}
public void setLeft(T left) {
this.left = left;
}
public void setRight(V right) {
this.right = right;
}
}
}

Unable to update entities via spring repository

Here is my code. I'm changing
#RequestMapping(value={"/set_channels"}, method={RequestMethod.POST})
public void setChannels(#RequestParam(value = "fromIndex") int fromIndex,
#RequestParam(value = "toIndex") int toIndex,
#RequestBody Channel channel) {
List<Channel> allChannels = repository.findAllByOrderByBinIndexAsc();
if (allChannels.isEmpty()) {
createChannels(fromIndex, toIndex, channel);
}
else {
updateAndOrCreateChannels(allChannels, fromIndex, toIndex, channel);
}
}
private void updateAndOrCreateChannels(List<Channel> allChannels, int fromIndex, int toIndex, Channel channel) {
int minBinIndex = allChannels.get(0).getBinIndex();
int maxBinIndex = allChannels.get(allChannels.size() - 1).getBinIndex();
for (Channel ch: allChannels) {
if (ch.getBinIndex() >= fromIndex && ch.getBinIndex() <= toIndex) {
channel.copySettings(channel);
}
}
// error here!!!
repository.save(allChannels);
if (fromIndex < minBinIndex || toIndex > maxBinIndex) {
List<Channel> newChannels = new ArrayList<>(minBinIndex - fromIndex + toIndex - maxBinIndex);
for (int i = fromIndex; i < minBinIndex; i ++) {
newChannels.add(new Channel(channel, i));
}
for (int i = maxBinIndex + 1; i <= fromIndex; i++) {
newChannels.add(new Channel(channel, i));
}
repository.save(newChannels);
}
}
Here is my channel entity.
package demo.model.processing;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.validation.constraints.NotNull;
/**
* Created by michael on 21/08/15.
*/
#Entity
public class Channel {
#NotNull
private ChannelMode mode;
#NotNull
private boolean excluded;
#NotNull
private double manualCoefficient;
#NotNull
private double c1;
#Override
public String toString() {
return "Channel{" +
", mode=" + mode +
", excluded=" + excluded +
", manualCoefficient=" + manualCoefficient +
", c1=" + c1 +
", c2=" + c2 +
", binIndex=" + binIndex +
'}';
}
#NotNull
private double c2;
#Id
private int binIndex;
public void copySettings(Channel other) {
setMode(other.getMode());
setC1(other.getC1());
setC2(other.getC2());
setManualCoefficient(other.getManualCoefficient());
setExcluded(other.isExcluded());
}
public Channel() {}
public Channel(ChannelMode mode,
double c2,
double c1,
double manualCoefficient,
boolean excluded,
int binIndex) {
setMode(mode);
setC2(c2);
setC1(c1);
setManualCoefficient(manualCoefficient);
setExcluded(excluded);
setBinIndex(binIndex);
}
Here is my Channel Entity. As you can see I'm trying to use binIndex as Id.
public Channel(Channel other, int newBinIndex) {
setMode(other.getMode());
setC1(other.getC1());
setC2(other.getC2());
setManualCoefficient(other.getManualCoefficient());
setExcluded(other.isExcluded());
setBinIndex(newBinIndex);
}
public ChannelMode getMode() {
return mode;
}
public void setMode(ChannelMode mode) {
this.mode = mode;
}
public boolean isExcluded() {
return excluded;
}
public void setExcluded(boolean excluded) {
this.excluded = excluded;
}
public double getManualCoefficient() {
return manualCoefficient;
}
public void setManualCoefficient(double manualCoefficient) {
this.manualCoefficient = manualCoefficient;
}
public double getC1() {
return c1;
}
public void setC1(double c1) {
this.c1 = c1;
}
public double getC2() {
return c2;
}
public void setC2(double c2) {
this.c2 = c2;
}
public int getBinIndex() {
return binIndex;
}
public void setBinIndex(int binIndex) {
this.binIndex = binIndex;
}
}
And this gives me the error.
DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint ["PRIMARY KEY ON PUBLIC.CHANNEL(BIN_INDEX)"; SQL statement:
insert into channel (c1, c2, excluded, manual_coefficient, mode, bin_index) values (?, ?, ?, ?, ?, ?) [23505-187]]; nested exception is org.hibernate.exception
Well repository trying to insert instead of update then this message is not surprise, but why insert in the first place. I haven't changed the id.
EDIT1
The idea behind this channel is that there is a fix number of this channels in the system. They unique by binIndex. And this binIndex is a always a number from 0 to n. So if we have 10 channels they would have bin indices 0..10. And after they have been created we only need to update them.
This request is about "Create or update existing channels with binIndices from x to y and with settings like in this example channel".

Resources