How to group inside Flink with my model - java

Im using Flink with Java to make my recommendation system using our logic.
So i have a dataset:
[user] [item]
100 1
100 2
100 3
100 4
100 5
200 1
200 2
200 3
200 6
300 1
300 6
400 7
So i map all to a tuple :
DataSet<Tuple3<Long, Long, Integer>> csv = text.flatMap(new LineSplitter()).groupBy(0, 1).reduceGroup(new GroupReduceFunction<Tuple2<Long, Long>, Tuple3<Long, Long, Integer>>() {
#Override
public void reduce(Iterable<Tuple2<Long, Long>> iterable, Collector<Tuple3<Long, Long, Integer>> collector) throws Exception {
Long customerId = 0L;
Long itemId = 0L;
Integer count = 0;
for (Tuple2<Long, Long> item : iterable) {
customerId = item.f0;
itemId = item.f1;
count = count + 1;
}
collector.collect(new Tuple3<>(customerId, itemId, count));
}
});
After i get all Customers and is Items inside arraylist:
DataSet<CustomerItems> customerItems = csv.groupBy(0).reduceGroup(new GroupReduceFunction<Tuple3<Long, Long, Integer>, CustomerItems>() {
#Override
public void reduce(Iterable<Tuple3<Long, Long, Integer>> iterable, Collector<CustomerItems> collector) throws Exception {
ArrayList<Long> newItems = new ArrayList<>();
Long customerId = 0L;
for (Tuple3<Long, Long, Integer> item : iterable) {
customerId = item.f0;
newItems.add(item.f1);
}
collector.collect(new CustomerItems(customerId, newItems));
}
});
Now i need get all "similar" customers. But after try a lot of things, nothing work.
The logic will be:
for ci : CustomerItems
c1 = c1.customerId
for ci2 : CustomerItems
c2 = ci2.cstomerId
if c1 != c2
if c2.getItems() have any item inside c1.getItems()
collector.collect(new Tuple2<c1, c2>)
I tried it using reduce, but i cant iterate on iterator two time (loop inside loop).
Can anyone help me?

You can cross the dataset with itself and basically insert your logic 1:1 into a cross function (excluding the 2 loops since the cross does that for you).

I solve the problem, but i need group and reduce after the "cross". I dont know that it is the best method. Can anyone suggest something?
The result is here:
package org.myorg.quickstart;
import org.apache.flink.api.common.functions.CrossFunction;
import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.common.functions.GroupReduceFunction;
import org.apache.flink.api.java.DataSet;
import org.apache.flink.api.java.ExecutionEnvironment;
import org.apache.flink.api.java.functions.KeySelector;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.api.java.tuple.Tuple3;
import org.apache.flink.util.Collector;
import java.io.Serializable;
import java.util.ArrayList;
public class UserRecommendation {
public static void main(String[] args) throws Exception {
final ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
// le o arquivo cm o dataset
DataSet<String> text = env.readTextFile("/Users/paulo/Downloads/dataset.csv");
// cria tuple com: customer | item | count
DataSet<Tuple3<Long, Long, Integer>> csv = text.flatMap(new LineFieldSplitter()).groupBy(0, 1).reduceGroup(new GroupReduceFunction<Tuple2<Long, Long>, Tuple3<Long, Long, Integer>>() {
#Override
public void reduce(Iterable<Tuple2<Long, Long>> iterable, Collector<Tuple3<Long, Long, Integer>> collector) throws Exception {
Long customerId = 0L;
Long itemId = 0L;
Integer count = 0;
for (Tuple2<Long, Long> item : iterable) {
customerId = item.f0;
itemId = item.f1;
count = count + 1;
}
collector.collect(new Tuple3<>(customerId, itemId, count));
}
});
// agrupa os items do customer dentro do customer
final DataSet<CustomerItems> customerItems = csv.groupBy(0).reduceGroup(new GroupReduceFunction<Tuple3<Long, Long, Integer>, CustomerItems>() {
#Override
public void reduce(Iterable<Tuple3<Long, Long, Integer>> iterable, Collector<CustomerItems> collector) throws Exception {
ArrayList<Long> newItems = new ArrayList<>();
Long customerId = 0L;
for (Tuple3<Long, Long, Integer> item : iterable) {
customerId = item.f0;
newItems.add(item.f1);
}
collector.collect(new CustomerItems(customerId, newItems));
}
});
// obtém todos os itens do customer que pertence a um usuário parecido
DataSet<CustomerItems> ci = customerItems.cross(customerItems).with(new CrossFunction<CustomerItems, CustomerItems, CustomerItems>() {
#Override
public CustomerItems cross(CustomerItems customerItems, CustomerItems customerItems2) throws Exception {
if (!customerItems.customerId.equals(customerItems2.customerId)) {
boolean has = false;
for (Long item : customerItems2.items) {
if (customerItems.items.contains(item)) {
has = true;
break;
}
}
if (has) {
for (Long item : customerItems2.items) {
if (!customerItems.items.contains(item)) {
customerItems.ritems.add(item);
}
}
}
}
return customerItems;
}
}).groupBy(new KeySelector<CustomerItems, Long>() {
#Override
public Long getKey(CustomerItems customerItems) throws Exception {
return customerItems.customerId;
}
}).reduceGroup(new GroupReduceFunction<CustomerItems, CustomerItems>() {
#Override
public void reduce(Iterable<CustomerItems> iterable, Collector<CustomerItems> collector) throws Exception {
CustomerItems c = new CustomerItems();
for (CustomerItems current : iterable) {
c.customerId = current.customerId;
for (Long item : current.ritems) {
if (!c.ritems.contains(item)) {
c.ritems.add(item);
}
}
}
collector.collect(c);
}
});
ci.first(100).print();
System.out.println(ci.count());
}
public static class CustomerItems implements Serializable {
public Long customerId;
public ArrayList<Long> items = new ArrayList<>();
public ArrayList<Long> ritems = new ArrayList<>();
public CustomerItems() {
}
public CustomerItems(Long customerId, ArrayList<Long> items) {
this.customerId = customerId;
this.items = items;
}
#Override
public String toString() {
StringBuilder itemsData = new StringBuilder();
if (items != null) {
for (Long item : items) {
if (itemsData.length() == 0) {
itemsData.append(item);
} else {
itemsData.append(", ").append(item);
}
}
}
StringBuilder ritemsData = new StringBuilder();
if (ritems != null) {
for (Long item : ritems) {
if (ritemsData.length() == 0) {
ritemsData.append(item);
} else {
ritemsData.append(", ").append(item);
}
}
}
return String.format("[ID: %d, Items: %s, RItems: %s]", customerId, itemsData, ritemsData);
}
}
public static final class LineFieldSplitter implements FlatMapFunction<String, Tuple2<Long, Long>> {
#Override
public void flatMap(String value, Collector<Tuple2<Long, Long>> out) {
// normalize and split the line
String[] tokens = value.split("\t");
if (tokens.length > 1) {
out.collect(new Tuple2<>(Long.valueOf(tokens[0]), Long.valueOf(tokens[1])));
}
}
}
}
Link with gist:
https://gist.github.com/prsolucoes/b406ae98ea24120436954967e37103f6

Related

Error with Paginated TableView sort for whole list

I m trying to implement a Paginated TableView that allows sorting by all items in JavaFX. I implemented the paginated tableview from here: https://stackoverflow.com/a/25424208/12181863. provided by jewelsea and tim buthe.
I was thinking that because the table view is only accessing a sublist of items, i wanted to extend the sorting from the table columns to the full list based on what i understand on the section about sorting on the Java Docs: https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/TableView.html#setItems-javafx.collections.ObservableList-
// bind the sortedList comparator to the TableView comparator
//i m guessing it extends the sorting from the table to the actual list?
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
and then refresh the tableview for the same sublist indexes (which should now be sorted since the whole list is sorted).
Basically, I want to use the table column comparator to sort the full list, and then "refresh" the tableview using the new sorted list. Is this feasible? Or is there a simpler way to go about this?
I also referred to other reference material such as : https://incepttechnologies.blogspot.com/p/javafx-tableview-with-pagination-and.html but i found it hard to understand since everything was all over the place with vague explanation.
A quick extract of the core components in my TouchDisplayEmulatorController class
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Pagination;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Callback;
import java.util.ArrayList;
import java.util.List;
public class TouchDisplayEmulatorController extends Application {
public TableView sensorsTable;
public List<Sensor> sensors;
public int rowsPerPage = 14;
public GridPane grids = new GridPane();
public long timenow;
public void start(final Stage stage) throws Exception {
grids = new GridPane();
setGridPane();
Scene scene = new Scene(grids, 1024, 768);
stage.setScene(scene);
stage.setTitle("Table pager");
stage.show();
}
//public static void main(String[] args) throws Exception {
// launch(args);
//}
public void setGridPane(){
processSensors();
sensorsGrid();
}
public void sensorsGrid(){
buildTable();
int numOfPages = 1;
if (sensors.size() % rowsPerPage == 0) {
numOfPages = sensors.size() / rowsPerPage;
} else if (sensors.size() > rowsPerPage) {
numOfPages = sensors.size() / rowsPerPage + 1;
}
Pagination pagination = new Pagination((numOfPages), 0);
pagination.setPageFactory(this::createPage);
pagination.setMaxPageIndicatorCount(numOfPages);
grids.add(pagination, 0, 0);
}
private Node createPage(int pageIndex) {
int fromIndex = pageIndex * rowsPerPage;
int toIndex = Math.min(fromIndex + rowsPerPage, sensors.size());
sensorsTable.setItems(FXCollections.observableArrayList(sensors.subList(fromIndex, toIndex)));
return new BorderPane(sensorsTable);
}
public void processSensors(){
sensors = new ArrayList<>();
// long timenow = OffsetDateTime.now(ZoneOffset.UTC).toInstant().toEpochMilli()/1000;
// StringTokenizer hildetoken = new StringTokenizer(msg);
for (int i=0; i<20; i++) {
sensors.add(new Sensor(String.valueOf(i), "rid-"+i, "sid-"+i, "0", "0", "no condition"));
}
}
public void buildTable() {
sensorsTable = new TableView();
TableColumn<Sensor, String> userid = new TableColumn<>("userid");
userid.setCellValueFactory(param -> param.getValue().userid);
userid.setPrefWidth(100);
TableColumn<Sensor, String> resourceid = new TableColumn<>("resourceid");
resourceid.setCellValueFactory(param -> param.getValue().resourceid);
resourceid.setPrefWidth(100);
TableColumn<Sensor, String> column1 = new TableColumn<>("sid");
column1.setCellValueFactory(param -> param.getValue().sid);
column1.setPrefWidth(100);
TableColumn<Sensor, String> column2 = new TableColumn<>("timestamp");
column2.setCellValueFactory(param -> param.getValue().timestamp);
column2.setPrefWidth(100);
TableColumn<Sensor, String> column3 = new TableColumn<>("reading");
column3.setCellValueFactory(param -> param.getValue().reading);
column3.setPrefWidth(100);
TableColumn<Sensor, String> column4 = new TableColumn<>("last contacted");
column4.setCellFactory(new Callback<TableColumn<Sensor, String>, TableCell<Sensor, String>>() {
#Override
public TableCell<Sensor, String> call(TableColumn<Sensor, String> sensorStringTableColumn) {
return new TableCell<Sensor, String>() {
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (!isEmpty()) {
this.setTextFill(Color.WHITE);
if (item.contains("#")) {
this.setTextFill(Color.BLUEVIOLET);
} else if (item.equals("> 8 hour ago")) {
this.setStyle("-fx-background-color: red;");
} else if (item.equals("< 8 hour ago")) {
this.setStyle("-fx-background-color: orange;");
//this.setTextFill(Color.ORANGE);
} else if (item.equals("< 4 hour ago")) {
this.setStyle("-fx-background-color: yellow;");
this.setTextFill(Color.BLACK);
} else if (item.equals("< 1 hour ago")) {
this.setStyle("-fx-background-color: green;");
//this.setTextFill(Color.GREEN);
}
setText(item);
}
}
};
}
});
column4.setCellValueFactory(param -> param.getValue().condition);
column4.setPrefWidth(100);
sensorsTable.getColumns().addAll(userid, resourceid, column1, column2, column3, column4);
}
}
class Sensor {
public SimpleStringProperty userid;
public SimpleStringProperty resourceid;
public SimpleStringProperty sid;
public SimpleStringProperty timestamp;
public SimpleStringProperty reading;
public SimpleStringProperty condition;
public Sensor(String userid, String resourceid, String sid, String timestamp, String reading, String condition){
this.userid = new SimpleStringProperty(userid);
this.resourceid = new SimpleStringProperty(resourceid);
this.sid = new SimpleStringProperty(sid);
this.timestamp = new SimpleStringProperty(timestamp);
this.reading = new SimpleStringProperty(reading);
this.condition = new SimpleStringProperty(condition);
//we can use empty string or condition 3 here
}
public Sensor(String sid, String timestamp, String reading, String condition){
this.userid = new SimpleStringProperty("-1");
this.resourceid = new SimpleStringProperty("-1");
this.sid = new SimpleStringProperty(sid);
this.timestamp= new SimpleStringProperty(timestamp);
this.reading= new SimpleStringProperty(reading);
this.condition = new SimpleStringProperty(condition);
}
public String getUserid() { return this.userid.toString(); }
public String getResourceid() { return this.resourceid.toString(); }
public String getSid() { return this.sid.toString(); }
public String getTimestamp() { return this.timestamp.toString(); }
public String getReading() { return this.reading.toString(); }
public String getCondition() { return this.condition.toString(); }
public String toString() { return "userid: "+getUserid()+" resourceid: "+getResourceid()+" sid: "+getSid()+
"\ntimestamp: "+getTimestamp()+" reading: "+getReading()+" condition: "+getCondition();}
}
separate class:
public class tester {
public static void main(String[] args) {
Application.launch(TouchDisplayEmulatorController.class, args);
}
}
Pagination of a TableView is not directly supported, so we have to do it ourselves. Note that the solutions referenced in the question pre-date the (re-introduction of Sorted-/FilteredList)
Nowadays, the basic approach is to use a FilteredList that contains only the rows which are on the current page. This filteredList must be the value of the table's itemsProperty. To also allow sorting, we need to wrap the original data into a SortedList and bind its comparator to the comparator provided by the table. Combining all:
items = observableArrayList(... //my data);
sortedList = new SortedList(items);
filteredList = new FilteredList(sortedList);
table.setItems(filteredList);
sortedList.comparatorProperty().bind(table.comparatorProperty());
Looks good, doesn't it? Unfortunately, nothing happens when clicking onto a column header. The reason:
the collaborator that's responsible for the sort is the sortPolicy
the default policy checks whether the table's items is a sorted list: if so (and its comparator is bound to the table's), sorting is left to that list, otherwise it falls back to FXCollections.sort(items, ...)
collections.sort fails to do anything because a filtered list is unmodifiable
In pseudo code:
if (items instanceof SortedList) {
return sortedList.getComparator().isBoundTo(table.getComparator());
}
try {
FXCollections.sort(items);
// sorting succeeded
return true;
} catch (Exception ex) {
// sorting failed
return false;
}
The way out is to implement a custom sort policy: instead of only checking the table's items for being a sortedList, it walks up the chain of transformationList (if available) sources until it finds a sorted (or not):
ObservableList<?> lookup = items;
while (lookup instanceof TransformationList) {
if (lookup instanceof SortedList) {
items = lookup;
break;
} else {
lookup = ((TransformationList<?, ?>) lookup).getSource();
}
}
// ... same as original policy
Now we have the sorting (of the complete list) ready - next question is what should happen to the paged view after sorting. Options:
keep the page constant and updated the filter
keep any of the current items visible and update the page
Both require to trigger the update when the sort state of the list changes, which to implement depends on UX guidelines.
A runnable example:
public class TableWithPaginationSO extends Application {
public static <T> Callback<TableView<T>, Boolean> createSortPolicy(TableView<T> table) {
// c&p of DEFAULT_SORT_POLICY except adding search up a chain
// of transformation lists until we find a sortedList
return new Callback<TableView<T>, Boolean>() {
#Override
public Boolean call(TableView<T> table) {
try {
ObservableList<?> itemsList = table.getItems();
// walk up the source lists to find the first sorted
ObservableList<?> lookup = itemsList;
while (lookup instanceof TransformationList) {
if (lookup instanceof SortedList) {
itemsList = lookup;
break;
} else {
lookup = ((TransformationList<?, ?>) lookup).getSource();
}
}
if (itemsList instanceof SortedList) {
SortedList<?> sortedList = (SortedList<?>) itemsList;
boolean comparatorsBound = sortedList.comparatorProperty()
.isEqualTo(table.comparatorProperty()).get();
return comparatorsBound;
} else {
if (itemsList == null || itemsList.isEmpty()) {
// sorting is not supported on null or empty lists
return true;
}
Comparator comparator = table.getComparator();
if (comparator == null) {
return true;
}
// otherwise we attempt to do a manual sort, and if successful
// we return true
FXCollections.sort(itemsList, comparator);
return true;
}
} catch (UnsupportedOperationException e) {
return false;
}
};
};
}
private Parent createContent() {
initData();
// wrap sorted list around data
sorted = new SortedList<>(data);
// wrap filtered list around sorted
filtered = new FilteredList<>(sorted);
// use filtered as table's items
table = new TableView<>(filtered);
addColumns();
page = new BorderPane(table);
// install custom sort policy
table.setSortPolicy(createSortPolicy(table));
// bind sorted comparator to table's
sorted.comparatorProperty().bind(table.comparatorProperty());
pagination = new Pagination(rowsPerPage, 0);
pagination.setPageCount(sorted.size() / rowsPerPage);;
pagination.setPageFactory(this::createPage);
sorted.addListener((ListChangeListener<Locale>) c -> {
// update page after changes to list
updatePage(true);
});
return pagination;
}
private Node createPage(int pageIndex) {
updatePredicate(pageIndex);
return page;
}
/**
* Update the filter to show the current page.
*/
private void updatePredicate(int pageIndex) {
int first = rowsPerPage * pageIndex;
int last = Math.min(first + rowsPerPage, sorted.size());
Predicate<Locale> predicate = loc -> {
int index = sorted.indexOf(loc);
return index >= first && index < last;
};
filtered.setPredicate(predicate);
// keep reference to first on page
firstOnPage = filtered.get(0);
}
/**
* Update the page after changes to the list.
*/
private void updatePage(boolean keepItemVisible) {
if (keepItemVisible) {
int sortedIndex = sorted.indexOf(firstOnPage);
int pageIndex = sortedIndex >= 0 ? sortedIndex / rowsPerPage : 0;
pagination.setCurrentPageIndex(pageIndex);
} else {
updatePredicate(pagination.getCurrentPageIndex());
}
}
private void addColumns() {
TableColumn<Locale, String> name = new TableColumn<>("Name");
name.setCellValueFactory(new PropertyValueFactory<>("displayName"));
TableColumn<Locale, String> country = new TableColumn<>("Country");
country.setCellValueFactory(new PropertyValueFactory<>("displayCountry"));
table.getColumns().addAll(name, country);
}
private void initData() {
Locale[] availableLocales = Locale.getAvailableLocales();
data = observableArrayList(
Arrays.stream(availableLocales)
.filter(e -> e.getDisplayName().length() > 0)
.limit(120)
.collect(toList())
);
}
private TableView<Locale> table;
private Pagination pagination;
private BorderPane page;
private ObservableList<Locale> data;
private FilteredList<Locale> filtered;
private SortedList<Locale> sorted;
private Locale firstOnPage;
private int rowsPerPage = 15;
#Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
First off, it's not clear that Pagination is the correct control for this, although the UI for page selection is really nice. Your problems might stem from the fact that you're putting the TableView into a new BorderPane each time, or it might come from the fact that you're using TableView.setItems().
An approach that works is to use FilteredList to handle the pagination, and just keep the TableView as a static element in the layout, not in the dynamic graphic area of the Pagination. To satisfy the need to have the Pagination do something, a Text with the page number was created.
A new property was added to Sensor - ordinalNumber. This is used to control the filter for the paging. The filter will dynamically change to select only those Sensor's with an ordinalNumber in a particular range. The range is controlled by the Pagination's currentPageIndexProperty. There's a listener on that property that regenerates the FilteredList's predicate property each time the page is changed.
That handles the page changes, but what about sorting the whole list? First, the FilteredList is wrapped in a SortedList, and it's the SortedList that's set into the TableView. The SortedList's Comparator is bound to the TableView's Comparator.
But the SortedList only sees the Sensors included under the current filter. So a listener was added to the TableView's comparatorProperty. The action for this listener Streams the underlying ObservableList, sorts it using the new Comparator, and resets each Sensor's ordinalNumber according to the new sort order.
Finally, in order to have the FilteredList re-evaluate the ObservableList, these ordinalNumber changes need to trigger a ListChange event. So an extractor was added to the ObservableList based on the ordinalNumber.
The result works pretty well, except for the goofy page numbering Text sliding onto the screen with each page change.
The entire code was cleaned up for readability and unused stuff was stripped out to keep the example minimal.
Here's the Sensor class:
class Sensor {
public SimpleStringProperty userid;
public SimpleStringProperty resourceid;
public SimpleStringProperty sid;
public SimpleStringProperty timestamp;
public SimpleStringProperty reading;
public IntegerProperty ordinalNumber = new SimpleIntegerProperty(0);
public Sensor(int userid, String resourceid, String sid, String timestamp, String reading, String condition) {
this.userid = new SimpleStringProperty(Integer.toString(userid));
this.resourceid = new SimpleStringProperty(resourceid);
this.sid = new SimpleStringProperty(sid);
this.timestamp = new SimpleStringProperty(timestamp);
this.reading = new SimpleStringProperty(reading);
this.ordinalNumber.set(userid);
}
}
Here's the layout code:
public class PaginationController extends Application {
public TableView<Sensor> sensorsTable = new TableView<>();
public ObservableList<Sensor> sensorObservableList = FXCollections.observableArrayList(sensor -> new Observable[]{sensor.ordinalNumber});
public FilteredList<Sensor> sensorFilteredList = new FilteredList<>(sensorObservableList);
public SortedList<Sensor> sensorSortedList = new SortedList<>(sensorFilteredList);
public IntegerProperty currentPage = new SimpleIntegerProperty(0);
public int rowsPerPage = 14;
public void start(final Stage stage) throws Exception {
processSensors();
stage.setScene(new Scene(buildScene(), 1024, 768));
stage.setTitle("Table pager");
stage.show();
}
public Region buildScene() {
buildTable();
int numOfPages = calculateNumOfPages();
Pagination pagination = new Pagination((numOfPages), 0);
pagination.setPageFactory(pageIndex -> {
Text text = new Text("This is page " + (pageIndex + 1));
return text;
});
pagination.setMaxPageIndicatorCount(numOfPages);
currentPage.bind(pagination.currentPageIndexProperty());
sensorFilteredList.predicateProperty().bind(Bindings.createObjectBinding(() -> createPageFilter(pagination.getCurrentPageIndex()), pagination.currentPageIndexProperty()));
return new VBox(sensorsTable, pagination);
}
#NotNull
private Predicate<Sensor> createPageFilter(int currentPage) {
int lowerLimit = (currentPage) * rowsPerPage;
int upperLimit = (currentPage + 1) * rowsPerPage;
return sensor -> (sensor.ordinalNumber.get() >= lowerLimit) &&
(sensor.ordinalNumber.get() < upperLimit);
}
private int calculateNumOfPages() {
int numOfPages = 1;
if (sensorObservableList.size() % rowsPerPage == 0) {
numOfPages = sensorObservableList.size() / rowsPerPage;
} else if (sensorObservableList.size() > rowsPerPage) {
numOfPages = sensorObservableList.size() / rowsPerPage + 1;
}
return numOfPages;
}
public void processSensors() {
Random random = new Random();
for (int i = 0; i < 60; i++) {
sensorObservableList.add(new Sensor(i, "rid-" + i, "sid-" + i, Integer.toString(random.nextInt(100)), "0", "no condition"));
}
}
public void buildTable() {
addStringColumn("userid", param1 -> param1.getValue().userid);
addStringColumn("resourceid", param1 -> param1.getValue().resourceid);
addStringColumn("sid", param1 -> param1.getValue().sid);
addStringColumn("timestamp", param1 -> param1.getValue().timestamp);
addStringColumn("reading", param1 -> param1.getValue().reading);
TableColumn<Sensor, Number> ordinalCol = new TableColumn<>("ordinal");
ordinalCol.setCellValueFactory(param -> param.getValue().ordinalNumber);
ordinalCol.setPrefWidth(100);
sensorsTable.getColumns().add(ordinalCol);
sensorsTable.setItems(sensorSortedList);
sensorSortedList.comparatorProperty().bind(sensorsTable.comparatorProperty());
sensorSortedList.comparatorProperty().addListener(x -> renumberRecords());
}
private void renumberRecords() {
AtomicInteger counter = new AtomicInteger(0);
Comparator<Sensor> newValue = sensorsTable.getComparator();
if (newValue != null) {
sensorObservableList.stream().sorted(newValue).forEach(sensor -> sensor.ordinalNumber.set(counter.getAndIncrement()));
} else {
sensorObservableList.forEach(sensor -> sensor.ordinalNumber.set(counter.getAndIncrement()));
}
}
#NotNull
private void addStringColumn(String columnTitle, Callback<TableColumn.CellDataFeatures<Sensor, String>, ObservableValue<String>> callback) {
TableColumn<Sensor, String> column = new TableColumn<>(columnTitle);
column.setCellValueFactory(callback);
column.setPrefWidth(100);
sensorsTable.getColumns().add(column);
}
}
For demonstration purposes, the timestamp field in the Sensor was initialized to a random number so that it would give an obvious change when that column was sorted. Also, the ordinalNumber field was added to the table so that it could be easily verified that they had been re-evaluated when a new sort column was chosen.

Get the top 3 elements from the memory cache

When I need to get the top 3 items from a Map, I can write the code,
private static Map<String, Integer> SortMapBasedOnValues(Map<String, Integer> map, int n) {
Map<String, Integer> sortedDecreasingly = map.entrySet().stream()
.sorted(Collections.reverseOrder(Map.Entry.comparingByValue())).limit(n)
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e2, LinkedHashMap::new));
return sortedDecreasingly;
}
I have a memory cache that I use to keep track of some app data,
public class MemoryCache<K, T> {
private long timeToLive;
private LRUMap map;
protected class CacheObject {
public long lastAccessed = System.currentTimeMillis();
public T value;
protected CacheObject(T value) {
this.value = value;
}
}
public MemoryCache(long timeToLive, final long timerInterval, int maxItems) {
this.timeToLive = timeToLive * 1000;
map = new LRUMap(maxItems);
if (this.timeToLive > 0 && timerInterval > 0) {
Thread t = new Thread(new Runnable() {
public void run() {
while (true) {
try {
Thread.sleep(timerInterval * 1000);
} catch (InterruptedException ex) {
}
cleanup();
}
}
});
t.setDaemon(true);
t.start();
}
}
public void put(K key, T value) {
synchronized (map) {
map.put(key, new CacheObject(value));
}
}
#SuppressWarnings("unchecked")
public T get(K key) {
synchronized (map) {
CacheObject c = (CacheObject) map.get(key);
if (c == null)
return null;
else {
c.lastAccessed = System.currentTimeMillis();
return c.value;
}
}
}
public void remove(K key) {
synchronized (map) {
map.remove(key);
}
}
public int size() {
synchronized (map) {
return map.size();
}
}
#SuppressWarnings("unchecked")
public void cleanup() {
long now = System.currentTimeMillis();
ArrayList<K> deleteKey = null;
synchronized (map) {
MapIterator itr = map.mapIterator();
deleteKey = new ArrayList<K>((map.size() / 2) + 1);
K key = null;
CacheObject c = null;
while (itr.hasNext()) {
key = (K) itr.next();
c = (CacheObject) itr.getValue();
if (c != null && (now > (timeToLive + c.lastAccessed))) {
deleteKey.add(key);
}
}
}
for (K key : deleteKey) {
synchronized (map) {
map.remove(key);
}
Thread.yield();
}
}
}
Inside the app, I initialize it,
MemoryCache<String, Integer> cache = new MemoryCache<String, Integer>(200, 500, 100);
Then I can add the data,
cache.put("productId", 500);
I would like to add functionality in the MemoryCache class so if called will return a HashMap of the top 3 items based on the value.
Do you have any advise how to implement that?
While I don't have a good answer, I convert the MemoryCache to the HashMap with an additional functionality implemented inside the class of MemoryCache and later, use it with the function provided earlier to retrieve the top 3 items based on the value,
Here is my updated code,
/**
* convert the cache full of items to regular HashMap with the same
* key and value pair
*
* #return
*/
public Map<Product, Integer> convertToMap() {
synchronized (lruMap) {
Map<Product, Integer> convertedMap = new HashMap<>();
MapIterator iterator = lruMap.mapIterator();
K k = null;
V v = null;
CacheObject o = null;
while (iterator.hasNext()) {
k = (K) iterator.next();
v = (V) iterator.getValue();
Product product = (Product) k;
o = (CacheObject) v;
int itemsSold = Integer.valueOf((o.value).toString());
convertedMap.put(product, itemsSold);
}
return convertedMap;
}
}

Java for loop executed twice

I am experiencing some troubles when executing a for loop. The loop is called twice. Here is the code that does the work:
import java.util.ArrayList;
import java.util.List;
public class PoolItemMapper {
public List<Item> mapJsonObjectsToItems(JsonResponse jsonResponse) {
int count = 0;
List<Item> itemsList = new ArrayList<>();
List<Item> js = jsonResponse.getItems();
for (Item item : jsonResponse.getItems()) {
itemsList.add(addNormalItemProperties(item, new Item()));
count++;
}
System.out.println("Call count: " + count);
return itemsList;
}
private Item addNormalItemProperties(Item oldItem, Item newItem) {
if(oldItem.getMembersReference().getItems().size() <= 0) {
return oldItem;
} else if (oldItem.getMembersReference().getItems().size() > 0) {
for (SubItem subItem: oldItem.getMembersReference().getItems()) {
oldItem.getSubItems().add(creteNewSubItem(subItem));
}
}
return oldItem;
}
private Item creteNewSubItem(SubItem oldItem) {
Item i = new Item();
i.setDynamicRatio(oldItem.getDynamicRatio());
i.setEphermal(oldItem.getEphermal());
i.setInheritProfile(oldItem.getInheritProfile());
i.setLogging(oldItem.getLogging());
i.setRateLimit(oldItem.getRateLimit());
i.setRatio(oldItem.getRatio());
i.setSession(oldItem.getSession());
i.setAddress(oldItem.getAddress());
i.setName(oldItem.getName());
i.setState(oldItem.getState());
return i;
}
}
The list has a size of 134, so I receive an output of two times 'Call count 134'. This results in having duplicates in the list.
Here are the POJOs:
JSON response where getItems() for the foor loop is called:
public class JsonResponse {
private String kind;
private String selfLink;
private List<Item> items = new ArrayList<Item>();
public JsonResponse() {
}
public String getKind() {
return kind;
}
public void setKind(String kind) {
this.kind = kind;
}
public String getSelfLink() {
return selfLink;
}
public void setSelfLink(String selfLink) {
this.selfLink = selfLink;
}
public List<Item> getItems() {
return items;
}
public void setItems(List<Item> items) {
this.items = items;
}
}
The Item class is a simple DTO, containing only variables and their getters/setters:
Here is where the method is invoked:
itemTree = new PoolTreeBuilderImpl().buildTree(j);
itemTree.stream().forEach(i -> {
System.out.println("[PARENT] " + i.getData().toString());
i.getData().getSubItems().stream().forEach(si -> {
System.out.println(" [CHILD] " + si.toString());
});
});
}
and the PoolTreeBuilderImpl calls:
#Override
public List<TreeNode<Item>> buildTree(JsonResponse jsonResponse) {
List<TreeNode<Item>> itemTree = new ArrayList<>();
List<Item> mappedItems = new PoolItemMapper().mapJsonObjectsToItems(jsonResponse);
for (Item i : mappedItems) {
TreeNode<Item> item = new TreeNode<>(i);
if (i.getSubItems().size() > 0) {
for (Item subItem : i.getSubItems()) {
item.addChild(subItem);
}
}
itemTree.add(item);
}
return itemTree;
}
Could someone explain me why this loop is called twice resulting in having each subitem twice in the list?
Update
When executing this code, I don't have the duplicates:
List<Item> mappedItems = new PoolItemMapper().mapJsonObjectsToItems(jsonResponse);
mappedItems.forEach(i -> {
System.out.println("[PARENT] " + i.toString());
i.getMembersReference().getItems().forEach(s -> {
System.out.println(" [CHILD] " + s.toString());
});
});
The problem lies in the JsonResponse object, which is always the same. The objects within the JsonResponse list are modified twice, so there are duplicates. That is why (#Joakim Danielson) there is the second parameter newItem.
Additionally I had to change the signature of the buildTree method of the TreeBuilder to accept a list of Items, the one returned by the mapper.

How can I restrict the generation of permutations? (In Java)

Dataset:
P1: Lion, Snow, Chair
P2: Min: 0, Max: 28
P3: Min: 34, Max is 39.
My Program is fed the above dataset (P1, P2, P3) as a series of arraylists. From this it continuously outputs different variations of a sequence including one element from each part (P1, P2, P3), until all possible permutations have been generated. (When generated P2 and P3 can be any number between their respective Min and Max.)
Examples of these sequences:
[Lion, 2, 37]
[Lion, 3, 34]
[Lion, 3, 35]
[Chair, 15, 35]
[Chair, 15, 36]
[Chair, 15, 37]
[Snow, 25, 36]
[Snow, 25, 37]
[Snow, 26, 34]
How?
To achieve this, I make use of the getCombinations function with P1,
P2 and P3 as parameters. To prepare the P2 and P3 arraylists for
use, I make use of the fillArrayList function which iterates from a
min to a max filling and then returning the relevant arraylist.
The problem I am facing is, I'm confused (lost) as to how I restrict the output of permutations which can lead to a 'Bad outcome' as below:
e.g.
P1 = Lion && P2 > 23 && P3 <= 35 Then Bad Outcome.
P1 = Lion && P2 < 13 && P3 >= 37 Then Bad Outcome.
P1 = Chair && P2 < 7 && P3 = 34 Then Bad Outcome.
Although I would be content to statically encoding a series of conditional statements for each, as these steps are read from a file which can change, this approach isn't applicable.
Code:
static ArrayList<ArrayList<String>> dataset = new ArrayList<ArrayList<String>>();
static ArrayList<ArrayList<String>> rows = new ArrayList<ArrayList<String>>();
static ArrayList<String> NegativePredictions = new ArrayList<String>();
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
init();
for (ArrayList<String> curArrayList : dataset) {
ArrayList<String> currentRule = new ArrayList<String>();
if (curArrayList.size() > 2) {
currentRule = curArrayList;
} else {
currentRule = new ArrayList<String>(
fillArrayList(Integer.parseInt(curArrayList.get(0)), Integer.parseInt(curArrayList.get(1))));
}
rows.add(currentRule);
}
getCombinations(rows).forEach(System.out::println);
}
public static void init() throws IOException {
ArrayList<String> P1 = new ArrayList<String>(Arrays.asList("Lion", "Snow", "Chair"));
ArrayList<String> P2 = new ArrayList<String>(Arrays.asList("0", "28"));
ArrayList<String> P3 = new ArrayList<String>(Arrays.asList("34", "37"));
dataset = new ArrayList<ArrayList<String>>(Arrays.asList(P1, P2, P3));
NegativePredictions = new ArrayList<String>(Files.readAllLines(Paths.get("Predict.txt")));
}
public static ArrayList<String> fillArrayList(Integer start, Integer end) {
ArrayList<String> returnedList = new ArrayList<String>();
for (int i = start; i <= end; i++) {
returnedList.add(String.valueOf(i));
}
return returnedList;
}
#SuppressWarnings("unchecked")
public static <T> List<List<T>> getCombinations(Collection<? extends Iterable<T>> valueSetCollection) {
Iterable<T>[] valueSets = new Iterable[valueSetCollection.size()];
Iterator<T>[] valueIters = new Iterator[valueSetCollection.size()];
T[] values = (T[]) new Object[valueSetCollection.size()];
int i = 0;
for (Iterable<T> valueSet : valueSetCollection) {
valueSets[i] = valueSet; // Copy to array for fast index lookup
valueIters[i] = valueSet.iterator();
values[i] = valueIters[i].next(); // Fail if a wordSet is empty
i++;
}
List<List<T>> combinations = new ArrayList<>();
NEXT_COMBO: for (;;) {
combinations.add(Arrays.asList(values.clone()));
for (i = values.length - 1; i >= 0; i--) {
if (valueIters[i].hasNext()) {
values[i] = valueIters[i].next();
continue NEXT_COMBO;
}
valueIters[i] = valueSets[i].iterator();
values[i] = valueIters[i].next();
}
return combinations;
}
}
}
What would you recommend?
Consider encoding a rule, where a set of rules determines if that particular candidate permutation is included or excluded. For example, an interface to define a rule:
public interface ExclusionRule<X> {
public boolean isExcluded(X x);
}
along with several implementations which know how to make comparisons with a String or an Integer:
public class ExcludeIfEqualStringRule implements ExclusionRule<String> {
private final String exclusion;
public ExcludeIfEqualStringRule(String exclusion) {
this.exclusion = exclusion;
}
#Override
public boolean isExcluded(String x) {
return x.equals(exclusion);
}
}
public abstract class AbstractExcludeIntegerRule implements ExclusionRule<Integer> {
private final int threshold;
private final ExclusionRule<Integer> or;
public AbstractExcludeIntegerRule(int threshold, ExclusionRule<Integer> or) {
this.threshold = threshold;
this.or = or;
}
#Override
public final boolean isExcluded(Integer x) {
if (or != null) {
return or.isExcluded(x) || doComparison(x, threshold);
}
return doComparison(x, threshold);
}
protected abstract boolean doComparison(int x, int threshold);
}
public class ExcludeIfGreaterThanIntegerRule extends AbstractExcludeIntegerRule {
public ExcludeIfGreaterThanIntegerRule(int threshold, ExclusionRule<Integer> or) {
super(threshold, or);
}
public ExcludeIfGreaterThanIntegerRule(int threshold) {
this(threshold, null);
}
#Override
protected boolean doComparison(int x, int threshold) {
return x > threshold;
}
}
public class ExcludeIfLessThanIntegerRule extends AbstractExcludeIntegerRule {
public ExcludeIfLessThanIntegerRule(int threshold, ExclusionRule<Integer> or) {
super(threshold, or);
}
public ExcludeIfLessThanIntegerRule(int threshold) {
this(threshold, null);
}
#Override
protected boolean doComparison(int x, int threshold) {
return x < threshold;
}
}
public class ExcludeIfEqualIntegerRule extends AbstractExcludeIntegerRule {
public ExcludeIfEqualIntegerRule(int threshold, ExclusionRule<Integer> or) {
super(threshold, or);
}
public ExcludeIfEqualIntegerRule(int threshold) {
this(threshold, null);
}
#Override
protected boolean doComparison(int x, int threshold) {
return x == threshold;
}
}
Along with another class which defines the set of rules to evaluate any candidate permutation:
public class ExclusionEvaluator<T, U, V> {
private final ExclusionRule<T> tRule;
private final ExclusionRule<U> uRule;
private final ExclusionRule<V> vRule;
public ExclusionEvaluator(ExclusionRule<T> tRule, ExclusionRule<U> uRule, ExclusionRule<V> vRule) {
this.tRule = tRule;
this.uRule = uRule;
this.vRule = vRule;
}
public boolean isExcluded(T t, U u, V v) {
return tRule.isExcluded(t) && uRule.isExcluded(u) && vRule.isExcluded(v);
}
}
With the three lists as separate objects, this can be encapsulated within another class which provides the getCombinations() method:
public class PermutationProvider<T, U, V> {
private final List<T> tItems;
private final List<U> uItems;
private final List<V> vItems;
private final List<ExclusionEvaluator<T, U, V>> exclusionEvaluators;
public PermutationProvider(List<T> tItems, List<U> uItems, List<V> vItems, List<ExclusionEvaluator<T, U, V>> exclusionEvaluators) {
this.tItems = tItems;
this.uItems = uItems;
this.vItems = vItems;
this.exclusionEvaluators = exclusionEvaluators;
}
public List<Permutation<T, U, V>> getCombinations() {
List<Permutation<T, U, V>> combinations = new ArrayList<>();
for (T tElement : tItems) {
for (U uElement : uItems) {
for (V vElement : vItems) {
Permutation<T, U, V> p = new Permutation<>(tElement, uElement, vElement);
if (isExcluded(tElement, uElement, vElement)) {
System.out.println(p + " IS EXCLUDED");
} else {
combinations.add(p);
}
}
}
}
return combinations;
}
private boolean isExcluded(T tElement, U uElement, V vElement) {
for (ExclusionEvaluator<T, U, V> exclusionEvaluator : exclusionEvaluators) {
if (exclusionEvaluator.isExcluded(tElement, uElement, vElement)) {
return true;
}
}
return false;
}
}
and result class to hold a permutation:
public class Permutation<T, U, V> {
private final T t;
private final U u;
private final V v;
public Permutation(T t, U u, V v) {
this.t = t;
this.u = u;
this.v = v;
}
public String toString() {
return t.toString() + " " + u.toString() + " " + v.toString();
}
}
along with a driver class to build the lists, exclusion rules, and get the accepted permutations:
public class PermuteWithExclusionsApp {
public static void main(String[] args) {
new PermuteWithExclusionsApp().permute();
}
private void permute() {
List<String> p1 = Arrays.asList("Lion", "Chair", "Snow");
List<Integer> p2 = new ArrayList<>();
for (int i = 0; i <= 28; i++) {
p2.add(i);
}
List<Integer> p3 = new ArrayList<>();
for (int i = 34; i <= 39; i++) {
p3.add(i);
}
// read from a file or some other source
List<String> compoundExclusionRules = Arrays.asList("P1 = Lion && P2 > 23 && P3 <= 35", "P1 = Lion && P2 < 13 && P3 >= 37", "P1 = Chair && P2 < 7 && P3 = 34");
ExclusionRuleFactory<String> stringRuleFactory = new StringExclusionRuleFactory();
ExclusionRuleFactory<Integer> integerRuleFactory = new IntegerExclusionRuleFactory();
ExclusionEvaluatorFactory<String, Integer, Integer> evaluatorFactory = new ExclusionEvaluatorFactory<>(stringRuleFactory, integerRuleFactory, integerRuleFactory);
List<ExclusionEvaluator<String, Integer, Integer>> evaluators = new ArrayList<>();
for (String compoundExclusionRule : compoundExclusionRules) {
evaluators.add(evaluatorFactory.create(compoundExclusionRule));
}
// List<ExclusionEvaluator<String, Integer, Integer>> evaluators = new ArrayList<>();
// evaluators.add(getExclusionRul1());
// evaluators.add(getExclusionRul2());
// evaluators.add(getExclusionRul3());
PermutationProvider<String, Integer, Integer> provider = new PermutationProvider<>(p1, p2, p3, evaluators);
List<Permutation<String, Integer, Integer>> permuations = provider.getCombinations();
for (Permutation<String, Integer, Integer> p : permuations) {
System.out.println(p);
}
}
// private ExclusionEvaluator<String, Integer, Integer> getExclusionRul3() {
// ExclusionRule<String> p1Rule = new ExcludeIfEqualStringRule("Chair");
// ExclusionRule<Integer> p2Rule = new ExcludeIfLessThanIntegerRule(7);
// ExclusionRule<Integer> p3Rule = new ExcludeIfEqualIntegerRule(34);
// return new ExclusionEvaluator<String, Integer, Integer>(p1Rule, p2Rule, p3Rule);
// }
//
// private ExclusionEvaluator<String, Integer, Integer> getExclusionRul2() {
// ExclusionRule<String> p1Rule = new ExcludeIfEqualStringRule("Lion");
// ExclusionRule<Integer> p2Rule = new ExcludeIfLessThanIntegerRule(13);
// ExclusionRule<Integer> p3Rule = new ExcludeIfGreaterThanIntegerRule(37, new ExcludeIfEqualIntegerRule(37));
// return new ExclusionEvaluator<String, Integer, Integer>(p1Rule, p2Rule, p3Rule);
// }
//
// private ExclusionEvaluator<String, Integer, Integer> getExclusionRul1() {
// ExclusionRule<String> p1Rule = new ExcludeIfEqualStringRule("Lion");
// ExclusionRule<Integer> p2Rule = new ExcludeIfGreaterThanIntegerRule(23);
// ExclusionRule<Integer> p3Rule = new ExcludeIfLessThanIntegerRule(35, new ExcludeIfEqualIntegerRule(35));
// return new ExclusionEvaluator<String, Integer, Integer>(p1Rule, p2Rule, p3Rule);
// }
Here's an example of a factory to parse the exclusion rules if for example the rules were defined as text.
public interface ExclusionRuleFactory<Z> {
public ExclusionRule<Z> create(String operator, String operand);
}
public class StringExclusionRuleFactory implements ExclusionRuleFactory<String> {
#Override
public ExclusionRule<String> create(String operator, String operand) {
return new ExcludeIfEqualStringRule(operand);
}
}
public class IntegerExclusionRuleFactory implements ExclusionRuleFactory<Integer> {
#Override
public ExclusionRule<Integer> create(String operator, String operand) {
int threshold = Integer.parseInt(operand);
switch (operator) {
case "=":
return new ExcludeIfEqualIntegerRule(threshold);
case ">":
return new ExcludeIfGreaterThanIntegerRule(threshold);
case "<":
return new ExcludeIfLessThanIntegerRule(threshold);
case ">=":
return new ExcludeIfGreaterThanIntegerRule(threshold, new ExcludeIfEqualIntegerRule(threshold));
case "<=":
return new ExcludeIfLessThanIntegerRule(threshold, new ExcludeIfEqualIntegerRule(threshold));
}
throw new IllegalArgumentException("Unsupported operator " + operator);
}
}
public class ExclusionEvaluatorFactory<T, U, V> {
private final ExclusionRuleFactory<T> p1RuleFactory;
private final ExclusionRuleFactory<U> p2RuleFactory;
private final ExclusionRuleFactory<V> p3RuleFactory;
public ExclusionEvaluatorFactory(ExclusionRuleFactory<T> p1RuleFactory, ExclusionRuleFactory<U> p2RuleFactory, ExclusionRuleFactory<V> p3RuleFactory) {
this.p1RuleFactory = p1RuleFactory;
this.p2RuleFactory = p2RuleFactory;
this.p3RuleFactory = p3RuleFactory;
}
public ExclusionEvaluator<T, U, V> create(String compoundExclusionRule) {
ExclusionRule<T> p1Rule = null;
ExclusionRule<U> p2Rule = null;
ExclusionRule<V> p3Rule = null;
String[] exclusionSubRules = compoundExclusionRule.split("&&");
for (int sr = 0; sr < exclusionSubRules.length; sr++) {
String[] ruleParts = exclusionSubRules[sr].trim().split(" ");
String whichRule = ruleParts[0].trim();
String operator = ruleParts[1].trim();
String operand = ruleParts[2].trim();
switch (whichRule) {
case "P1":
p1Rule = p1RuleFactory.create(operator, operand);
break;
case "P2":
p2Rule = p2RuleFactory.create(operator, operand);
break;
case "P3":
p3Rule = p3RuleFactory.create(operator, operand);
break;
}
}
return new ExclusionEvaluator<T, U, V>(p1Rule, p2Rule, p3Rule);
}
}
You could pass a Predicate function to your getCombinations method to filter or accept certain combinations:
/** Returns true if this combination is accepted, false if it should be filtered. */
public static boolean myFilter(Object[] combo) {
// Object arrays and casting is gross
if (combo.length != 3) {
return false;
}
try {
String p1 = (String) combo[0];
// Why are they strings?
Integer p2 = Integer.valueOf((String) combo[1]);
Integer p3 = Integer.valueOf((String) combo[2]);
return !("Lion".equals(p1) && (13 > p2) && (35 >= p3))
&& !("Lion".equals(p1) && (13 > p2) && (37 <= p3))
&& !("Chair".equals(p1) && (7 > p2) && (34 == p3));
} catch (Exception e) {
// invalid combination, filter it
return false;
}
}
#SuppressWarnings("unchecked")
public static <T> List<List<T>> getCombinations(
Collection<? extends Iterable<T>> valueSetCollection,
Predicate<Object[]> filter) {
Iterable<T>[] valueSets = new Iterable[valueSetCollection.size()];
Iterator<T>[] valueIters = new Iterator[valueSetCollection.size()];
T[] values = (T[]) new Object[valueSetCollection.size()];
int i = 0;
for (Iterable<T> valueSet : valueSetCollection) {
valueSets[i] = valueSet; // Copy to array for fast index lookup
valueIters[i] = valueSet.iterator();
values[i] = valueIters[i].next(); // Fail if a wordSet is empty
i++;
}
List<List<T>> combinations = new ArrayList<>();
NEXT_COMBO: for (;;) {
T[] v = values.clone();
if (filter.test(v)) {
combinations.add(Arrays.asList(v));
} else {
System.out.println("rejected " + Arrays.asList(v));
}
for (i = values.length - 1; i >= 0; i--) {
if (valueIters[i].hasNext()) {
values[i] = valueIters[i].next();
continue NEXT_COMBO;
}
valueIters[i] = valueSets[i].iterator();
values[i] = valueIters[i].next();
}
return combinations;
}
}
public static void main(String[] args){
// ...
getCombinations(rows, MyClass::myFilter).forEach(System.out::println);
System.out.println("##############################");
// accept all
getCombinations(rows, o -> true).forEach(System.out::println);
}
Also, rather than passing lists of lists it may be better to make a data container class like this:
public class MyCombination {
private final String s;
private final int i1;
private final int i2;
public MyCombination(String s, int i1, int i2){
this.s = s;
this.i1 = i1;
this.i2 = i2;
}
}

myBATIS foreach hitting limit of 1000

Here's what myBATIS has on their own documentation for foreach.
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
WHERE ID in
<foreach item="item" index="index" collection="list"
open="(" separator="," close=")">
#{item}
</foreach>
</select>
However, if list contains over 1000 items and you're using Oracle DB, you get this exception:
java.sql.SQLSyntaxErrorException: ORA-01795: maximum number of expressions in a list is 1000
What can I do to fix this so it works with more than 1000 elements?
I'm not sure if this is the most elegant solution or not, but here's what I did:
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
WHERE ID in
<trim suffixOverrides=" OR ID IN ()">
<foreach item="item" index="index" collection="list"
open="(" close=")">
<if test="index != 0">
<choose>
<when test="index % 1000 == 999">) OR ID IN (</when>
<otherwise>,</otherwise>
</choose>
</if>
#{item}
</foreach>
</trim>
</select>
Explanation
Lets start with the foreach. We want to surround it in ( and ). Most elements we want commas between, except every thousand elements we want to stop the list and OR with another one. That's what the choose, when, otherwise construct handles. Except we don't want either of those before the first element, thus the if that the choose is inside of. Finally, the foreach ends with actually having the #{item} inserted.
The outer trim is just so that if we have exactly 1000 elements, for example, we don't end with OR ID IN () which would be invalid ((), specifically, is the invalid part. That's a syntax error in SQL, not an empty list like I hoped it would be.)
We have tried delete query in clause more then 1000 records with above reference:
<delete id="delete" parameterType="Map">
The following query working:
DELETE FROM Employee
where
emp_id = #{empId}
<foreach item="deptId" index= "index" collection="ids" open="AND DEPT_ID NOT IN (" close=")" >
<if test="index != 0">
<choose>
<when test="index % 1000 == 999">) AND DEPT_ID NOT IN (</when>
<otherwise>,</otherwise>
</choose>
</if>
#{deptId}
</foreach>
</delete>
Mybatis plugin query and then combine partitioned params :
#Intercepts({
#Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
#Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})}
)
public class BigSizeParamQueryPlugin implements Interceptor {
private final int singleBatchSize;
private static final HeavyParamContext NO_BIG_PARAM = new HeavyParamContext();
public BigSizeParamQueryPlugin() {
this.singleBatchSize = 1000;
}
public BigSizeParamQueryPlugin(Integer singleBatchSize) {
if (singleBatchSize < 500) {
throw new IllegalArgumentException("batch size less than 500 is not recommended");
}
this.singleBatchSize = singleBatchSize;
}
#Override
public Object intercept(Invocation invocation) throws Throwable {
Object[] args = invocation.getArgs();
Object parameter = args[1];
if (parameter instanceof MapperMethod.ParamMap && RowBounds.DEFAULT == args[2]) {
MapperMethod.ParamMap paramMap = (MapperMethod.ParamMap) parameter;
if (MapUtils.isNotEmpty(paramMap)) {
try {
HeavyParamContext context = findHeavyParam(paramMap);
if (context.hasHeavyParam()) {
QueryExecutor queryExecutor = new QueryExecutor(invocation, context);
return queryExecutor.query();
}
} catch (Throwable e) {
log.warn("BigSizeParamQueryPlugin process error", e);
return invocation.proceed();
}
}
}
return invocation.proceed();
}
private class QueryExecutor {
private final MappedStatement ms;
private final Map<String, Object> paramMap;
private final RowBounds rowBounds;
private final ResultHandler resultHandler;
private final Executor executor;
private final List<Object> finalResult;
private final Iterator<HeavyParam> heavyKeyIter;
public QueryExecutor(Invocation invocation, HeavyParamContext context) {
Object[] args = invocation.getArgs();
this.ms = (MappedStatement) args[0];
this.paramMap = context.getParameter();
this.rowBounds = (RowBounds) args[2];
this.resultHandler = (ResultHandler) args[3];
this.executor = (Executor) invocation.getTarget();
List<HeavyParam> heavyParams = context.getHeavyParams();
this.finalResult = new ArrayList<>(heavyParams.size() * singleBatchSize);
this.heavyKeyIter = heavyParams.iterator();
}
public Object query() throws SQLException {
while (heavyKeyIter.hasNext()) {
HeavyParam currKey = heavyKeyIter.next();
List<List<Object>> param = partitionParam(currKey.getParam());
doQuery(currKey, param);
}
return finalResult;
}
private void doQuery(HeavyParam currKey, List<List<Object>> param) throws SQLException {
if (!heavyKeyIter.hasNext()) {
for (List<Object> currentParam : param) {
updateParamMap(currKey, currentParam);
List<Object> oneBatchResult = executor.query(ms, paramMap, rowBounds, resultHandler);
finalResult.addAll(oneBatchResult);
}
return;
} else {
HeavyParam nextKey = heavyKeyIter.next();
log.warn("get mutil heavy key [{}], batchSize[{}]", nextKey.shadowHeavyKeys, nextKey.getParam().size());
List<List<Object>> nextParam = partitionParam(nextKey.getParam());
for (List<Object> currParam : param) {
updateParamMap(currKey, currParam);
doQuery(nextKey, nextParam);
}
}
}
private void updateParamMap(HeavyParam currKey, List<Object> param) {
for (String shadowKey : currKey.getShadowHeavyKeys()) {
paramMap.put(shadowKey, param);
}
}
}
private HeavyParamContext findHeavyParam(Map<String, Object> parameterMap) {
List<Map.Entry<String, Object>> heavyKeys = doFindHeavyParam(parameterMap);
if (heavyKeys == null) {
return BigSizeParamQueryPlugin.NO_BIG_PARAM;
} else {
HeavyParamContext result = new HeavyParamContext();
List<HeavyParam> heavyParams;
if (heavyKeys.size() == 1) {
heavyParams = buildSingleHeavyParam(heavyKeys);
} else {
heavyParams = buildMultiHeavyParam(heavyKeys);
}
result.setHeavyParams(heavyParams);
result.setParameter(new HashMap<>(parameterMap));
return result;
}
}
private List<HeavyParam> buildSingleHeavyParam(List<Map.Entry<String, Object>> heavyKeys) {
Map.Entry<String, Object> single = heavyKeys.get(0);
return Collections.singletonList(new HeavyParam((Collection) single.getValue(), Collections.singletonList(single.getKey())));
}
private List<List<Object>> partitionParam(Object o) {
Collection c = (Collection) o;
List res;
if (c instanceof List) {
res = (List) c.stream().distinct().collect(Collectors.toList());
} else {
res = new ArrayList(c);
}
return Lists.partition(res, singleBatchSize);
}
private List<HeavyParam> buildMultiHeavyParam(List<Map.Entry<String, Object>> heavyKeys) {
//when heavy keys used multi time in xml, its name will be different.
TreeMap<Collection, List<String>> params = new TreeMap<>(new Comparator<Collection>() {
#Override
public int compare(Collection o1, Collection o2) {
//fixme workable but have corner case.
return CollectionUtils.isEqualCollection(o1, o2) == true ? 0 : o1.hashCode() - o2.hashCode();
}
});
for (Map.Entry<String, Object> keyEntry : heavyKeys) {
String key = keyEntry.getKey();
List<String> keys = params.computeIfAbsent((Collection) keyEntry.getValue(), k -> new ArrayList<>(1));
keys.add(key);
}
List<HeavyParam> hps = new ArrayList<>(params.size());
for (Map.Entry<Collection, List<String>> heavyEntry : params.entrySet()) {
List<String> shadowKeys = heavyEntry.getValue();
hps.add(new HeavyParam(heavyEntry.getKey(), shadowKeys));
}
return hps;
}
private List<Map.Entry<String, Object>> doFindHeavyParam(Map<String, Object> parameterMap) {
List<Map.Entry<String, Object>> result = null;
for (Map.Entry<String, Object> p : parameterMap.entrySet()) {
if (p != null) {
Object value = p.getValue();
if (value != null && value instanceof Collection) {
int size = CollectionUtils.size(value);
if (size > singleBatchSize) {
if (result == null) {
result = new ArrayList<>(1);
}
result.add(p);
}
}
}
}
return result;
}
#Getter
#Setter
private static class HeavyParamContext {
private Boolean hasHeavyParam;
private List<HeavyParam> heavyParams;
private Map<String, Object> parameter;
public Boolean hasHeavyParam() {
return heavyParams != null;
}
}
#Data
#AllArgsConstructor
#NoArgsConstructor
private class HeavyParam {
private Collection param;
private List<String> shadowHeavyKeys;
}
#Override
public Object plugin(Object o) {
return Plugin.wrap(o, this);
}
#Override
public void setProperties(Properties properties) {
}
}

Categories