Convert list of complex objects to Map using Java 8 - java-8

I have an array of subjects
List<String> subjects = Arrays.asList(“physics”, “maths”);
I wanted to create a dummy list of users for each of these subjects and add them to a map with key as subject and value as List
Something like
Map<String,List<User>> userMap = new HashMap<>();
for(String subject: subjects){
List<User> users = new ArrayList<User>();
for(int i=0;i<10;i++){
User user = new User(“first name”+i+subject,”last name”+i+subject);
users.add(user);
}
userMap.put(subject,users);
}
I wanted to try this with the Java 8. Just tried something below, but doesn’t look like the right way.
subjects.stream().map((subjectName)->{
List<User> userList = new ArrayList<User>();
for(int i=0;i<10;i++){
User user = new User(“first name”+i+subject,”last name”+i+subject);
userList.add(user);
}
})

subjects.stream()
.map(subjectName -> {
List<User> users = .... create the users;
return new SimpleEntry<>(subjectName, users);
})
.collect(Collectors.toMap(Entry::getKey, Entry::getValue))
This would be one way if you really wanted to do it with java-8 and streams. One improvement would be to have a method that takes a String subjectName and create that Entry for example:
private static Entry<String, List<User>> createEntry(String subjectName) {
List<User> users = .... create the user;
return new SimpleEntry<>(subjectName, users);
}
And use it with:
subjects.stream()
.map(YourClass::createEntry)
.collect(Collectors.toMap(Entry::getKey, Entry::getValue))
Just notice that your loop is the cleanest way to do it IMO

One way to do it with java 8:
Map<String,List<User>> userMap = new HashMap<>();
subjects.forEach(s -> {
for (int i = 0; i < 10; i++)
userMap.computeIfAbsent(s, k -> new ArrayList<>())
.add(new User("first name" + i + subject, "last name" + i + subject));
});

Let's do this one step at a time. First, the inner loop for creating 10 users can be written with streams as:
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
List<User> userList = IntStream.range(0, 10)
.mapToObj(i -> new User("first name" + i + subject, "last name" + i + subject)
.collect(toList());
And the outer loop can be written as
subjects.stream()
.collect(toMap(
subject -> subject, // key for the map is the subject
subject -> ... // whatever should be the value of the map
));
And now we can put it together:
Map<String, List<User>> userMap = subjects.stream()
.collect(toMap(
subject -> subject,
subject -> IntStream.range(0, 10)
.mapToObj(i -> new User("first name" + i + subject, "last name" + i + subject))
.collect(toList())
));

Related

Map first element of stream differently than rest

Is there a way in Java's Stream API to map first element of stream differently than other?
Equivalent of this code:
List<Bar> barList = new ArrayList<>();
for (int i=0; i<fooList.size(); i++) {
Foo foo = fooList.get(i);
Foo modifiedFoo = foo.getModifiedFoo();
if (i == 0) {
barList.add(new Bar(modifiedFoo, false));
}else {
barList.add(new Bar(modifiedFoo, true));
}
}
Stream<Bar> = barList.stream();
Note: I already have a stream setup and I would want some operation after first mapping
fooList.stream()
.map(Foo::getModifiedFoo)
.(May be Some operation here to get different new Bar for first modifiedFoo)
.map(modifiedFoo -> new Bar(modifiedFoo, true));
I would get the first element, create a Stream out of it and apply the needed mappings. Then, I'd take the rest of the list, create a stream out of it and apply the different mappings. Then concat the streams. Something like this:
Stream<Bar> first = Stream.of(fooList.get(0))
.map(Foo::getModifiedFoo)
.map(modifiedFoo -> new Bar(modifiedFoo, false));
Stream<Bar> others = fooList.subList(1, fooList.size()).stream()
.map(Foo::getModifiedFoo)
.map(modifiedFoo -> new Bar(modifiedFoo, true));
Stream<Bar> bars = Stream.concat(first, others).flatMap(s -> s);
Another approach:
Stream<Bar> bars = IntStream.range(0, fooList.size())
.mapToObj(i -> new Bar(fooList.get(i).getModifiedFoo(), i > 0));
This way is succinct and does the job pretty well.
Use an IntStream to iterate over the indices, then mapToObj to create an object for that index, and finally collect into a list:
List<Bar> barList = IntStream.range(0, fooList.size())
.mapToObj(i -> (i == 0 ? new Bar (fooList.get(i), false) :
new Bar(fooList.get(i),true)))
.collect(Collectors.toList());
What would be more readable though, is doing the first item handling outside the loop, and using IntStream starting with 1.
Here is a demo using simple lists.
I can propose two ways but I find your way straighter.
With IntStream such as :
List<Bar> barList = new ArrayList<>();
IntStream.range(0, fooList.size())
.forEach(i->{
if (i == 0) {
barList.add(new Bar(foo, false));
}else {
barList.add(new Bar(foo, true));
}
}
);
It is not a real functional approach (forEach() use and no Collector) because it maintains the current index of the List.
As alternative, you could use a more functional approach but I don't find it straighter either :
List<Bar> barList = IntStream.range(0, fooList.size())
.mapToObj(i->{
Foo foo = fooList.get(i);
if (i == 0) {
return new Bar(foo, false);
}
return new Bar(foo, true));
})
.collect(Collectors.toList());
Although I think the accepted answer is better, here is an alternate approach.
int[] counter = {-1};
Stream<Bar> barListStream = fooList.stream().map(foo -> {
counter[0]++;
return new Bar(mfoo.getModifiedFoo(), counter[0]>0);
}).collect(Collectors.toList()).stream();
You can have an object to hold a flag e.g. AtomicBoolean or AtomicInteger - that you can reset on first or nth iteration (you would need something like AtomicInteger or some Integer holder to reset on nth iteration) e.g. following code using HashMap as the holder class - will print first line of the stream differently than the other lines:
Map<String, Boolean> firstTime = new HashMap<>(Map.of("firstTime", true)); // to make the map modifiable
try (Stream<String> lines = Files.lines(Paths.get(filename), Charset.defaultCharset())) {
lines.forEachOrdered(line -> System.out.println(firstTime.remove("firstTime") != null ? ("firstTime: " + line) : line));
}
Use an AtomicBoolean initially set to true to determine when is the first item.
final AtomicBoolean first = new AtomicBoolean(true);
System.out.println("** Print all numbers 1..10");
IntStream.range(1, 11).forEach(number -> {
System.out.print((first.get() ? "" : ",") + number);
first.set(false);
});
System.out.println();

How to collect map from the Set of objects that has a list using Collectors.toMap

I have class Element with a list, my intended output is like this:
Map<String , List<Element>>
{
1 = [Element3, Element1],
2 = [Element2, Element1],
3 = [Element2, Element1], 4=[Element2]
}
And my input is set of element objects, I used forEach to get the desired outcome, but I'm looking for how to collect it using collectors.toMap. Any inputs are much appreciated
Set<Element> changes = new HashSet();
List<String> interesetList = new ArrayList();
interesetList.add("1");
interesetList.add("2");
interesetList.add("3");
Element element = new Element(interesetList);
changes.add(element);
interesetList = new ArrayList();
interesetList.add("2");
interesetList.add("3");
interesetList.add("4");
element = new Element(interesetList);
changes.add(element);
Map<String, List<Element>> collect2 = new HashMap();
changes.forEach(element -> {
element.getInterestedList().forEach(tracker -> {
collect2.compute(tracker, ( key , val) -> {
List<Element> elementList = val == null ? new ArrayList<Element>() : val;
elementList.add(Element);
return elementList;
});
});
});
class Element {
List<String> interestedList;
static AtomicInteger sequencer = new AtomicInteger(0);
String mName;
public Element(List<String> aList) {
interestedList = aList;
mName = "Element" + sequencer.incrementAndGet();
}
public List<String> getInterestedList() {
return interestedList;
}
#Override
public String toString() {
return mName;
}
}
You can do it by using Collectors.groupingBy instead of Collectors.toMap, along with Collectors.mapping, which adapts a collector to another collector:
Map<String, List<Element>> result = changes.stream()
.flatMap(e -> e.getInterestedList().stream().map(t -> Map.entry(t, e)))
.collect(Collectors.groupingBy(
Map.Entry::getKey,
Collectors.mapping(Map.Entry::getValue, Collectors.toList())));
You need to use the Stream.flatMap method first and then pair the elements of the inner lists with the current Element instance. I did this via the new Java 9's Map.entry(key, value) method. If you're not on Java 9 yet, you could change it to new AbstractMap.SimpleEntry<>(key, value).
After flatmapping, we need to collect instances of Map.Entry. So I'm using Collectors.groupingBy to classify entries by key (where we had previously stored each element of the inner lists, aka what you call tracker in your code). Then, as we don't want to have instances of List<Map.Entry<String, Element>> as the values of the map, we need to transform each Map.Entry<String, Element> of the stream to just Element (that's why I'm using Map.Entry::getValue as the first argument of Collectors.mapping). We also need to specify a downstream collector (here Collectors.toList()), so that the outer Collectors.groupingBy collector knows where to place all the adapted elements of the stream that belong to each group.
A shorter and surely more efficient way to do the same (similar to your attempt) could be:
Map<String, List<Element>> result = new HashMap<>();
changes.forEach(e ->
e.getInterestedList().forEach(t ->
result.computeIfAbsent(t, k -> new ArrayList<>()).add(e)));
This uses Map.computeIfAbsent, which is a perfect fit for your use case.

Filter and Sort internal Map java

I have class Person
private String name;
private int age;
private Map<String, LocalDate> carsBoughWithDate;
You can ignore name and age. The important one here is carsBoughWithDate
Due to some reason I am saving person cars bough in a map with the date
Test Data
Map<String, LocalDate> carsbought = new HashMap<>();
carsbought.put("Toyota", LocalDate.of(2017, 2, 1));
carsbought.put("Corolla", LocalDate.of(2017, 2, 1));
Person john = new Person("John", 22, carsbought);
carsbought = new HashMap<>();
carsbought.put("Vauxhall", LocalDate.of(2017, 1, 1));
carsbought.put("BMW", LocalDate.of(2017, 1, 1));
carsbought.put("Toyota", LocalDate.of(2017, 1, 1));
Person michael = new Person("Michael", 44, carsbought);
List<Person> personList = new ArrayList<>();
personList.add(john);
personList.add(michael);
Output:
[Person{name='John', age=22, carsBoughWithDate={Toyota=2017-02-01, Corolla=2017-02-01}},
Person{name='Michael', age=44, carsBoughWithDate={Vauxhall=2017-01-01, Toyota=2017-01-01, BMW=2017-01-01}}]
Now, I have to find out the person which has bought cars but then sort the person who bought the car earliest on the top in the list
Example: search person who has cars "Toyota" or BMW
This is what I have done
**
System.out.println("Before sort >" + personList);
List<Person> sortedList = Lists.newArrayList();
HashMap<LocalDate, Person> collect = Maps.newHashMap();
for (Person person : personList) {
Map<String, LocalDate> docCarsBoughWithDate = person.getCarsBoughWithDate();
collect.putAll(docCarsBoughWithDate.entrySet().stream()
.filter(map -> Lists.newArrayList("Toyota", "BMW").contains(map.getKey()))
.collect(HashMap::new,
(m, v) -> m.put(
v.getValue(),
person),
HashMap::putAll
));
}
Map<String, List<Person>> collect1 = collect.entrySet().stream().sorted(Map.Entry.comparingByKey()).map(m -> m.getValue()).collect(Collectors.groupingBy(Person::getName));
collect1.keySet().forEach(key -> sortedList.add(collect1.get(key).get(0)));
System.out.println("after sort > " + sortedList
);
This all works
Before sort >
[Person{name='John', age=22, carsBoughWithDate={Toyota=2017-02-01, Corolla=2017-02-01}}, Person{name='Michael', age=44, carsBoughWithDate={Vauxhall=2017-01-01, Toyota=2017-01-01, BMW=2017-01-01}}]
after sort >
[Person{name='Michael', age=44, carsBoughWithDate={Vauxhall=2017-01-01, Toyota=2017-01-01, BMW=2017-01-01}}, Person{name='John', age=22, carsBoughWithDate={Toyota=2017-02-01, Corolla=2017-02-01}}]
I feel this is bit cumbersome. Can I simplify the logic?
Here you go:
List<Person> sortedList = personList.stream() //
.flatMap(p -> p.getCarsBoughWithDate().entrySet().stream() //
.filter(e -> targetCarNames.contains(e.getKey())) // filter the bought cars which are in the target bought cars.
.sorted(Entry.comparingByValue()).limit(1) // sorted and only fetch the entry with earliest bought date.
.map(e -> new SimpleEntry<>(p, e.getValue()))) // composite a new entry with the person and the earliest bought date.
.sorted(Entry.comparingByValue()).map(e -> e.getKey()).collect(toList()); //
First of all, are you sure that "this all works"? I tried your code with your test data with the following additional person:
carsbought = new HashMap<>();
carsbought.put("BMW", LocalDate.of(2017, 2, 1));
Person sally = new Person("Sally", 25, carsbought);
and she overwrote John because she happened to have bought a car at the same date.
Second, the strategy to solve complex problems is to break them down into simpler problems. For example, I would first add a method which determines the first date at which a person bought one of a set of cars:
private Optional<LocalDate> firstDateOf(Person person, Collection<String> cars)
{
return person.getCarsBoughWithDate().entrySet().stream()
.filter(e -> cars.contains(e.getKey()))
.map(Map.Entry::getValue)
.min(Comparator.naturalOrder());
}
This will be the sort key of the people. Then use this method to map each person to the sort key and finally sort the list:
List<Person> sortCarOwners(Collection<Person> people, Collection<String> cars)
{
Map<Person, Optional<LocalDate>> personToDateMap = people.stream()
.collect(Collectors.toMap(p -> p, p -> firstDateOf(p, cars)));
return personToDateMap.entrySet().stream()
.filter(e -> e.getValue().isPresent())
.sorted(Comparator.comparing(e -> e.getValue().get()))
.map(e -> e.getKey())
.collect(Collectors.toList());
}
I don't know if you consider this "less cumbersome", but I hope it helps.

Am I approaching this correctly?

JavaFX 8 – FXML – TableView – TableColumn
I have the following objects
CourseResult
SimpleStringProperty CourseID
SimpleStringProperty CourseName
SimpleStringProperty Grade
SimpleIntegerProperty Credits
ATableRow
SimpleStringProperty StudentID
SimpleStringProperty StudentName
CourseResult[] AResult // Occurs 1 to 20.
In my program
javafx.collections.ObservableList<ATableRow> TableData = javafx.collections.FXCollections.observableArrayList();
I populate this Observable list from the database and I am able to see all the values perfectly in debugger.
I create the Tableview and add columns.
public void createTableForThisSemester(int thisSemester, int numberOfCourses, javafx.collections.ObservableList<AResultRow> TableRows) {
TableView<AResultRow> thisTable = new TableView<>();
TableColumn<AResultRow, String> tcolRollNo = new TableColumn<>("Roll Number");
tcolRollNo.setEditable(false);
tcolRollNo.setPrefWidth(120);
TableColumn<AResultRow, String> tcolName = new TableColumn<>("Student Name");
tcolName.setEditable(false);
tcolName.setPrefWidth(350);
tcolRollNo.setCellValueFactory(cellData -> cellData.getValue().StudentIDProperty());
tcolName.setCellValueFactory(cellData -> cellData.getValue().StudentNameProperty());
boolean xyz = thisTable.getColumns().addAll(tcolRollNo, tcolName);
// TableColumn[] courseColumn = new TableColumn[numberOfCourses];
for (int courseNo = 0; courseNo < numberOfCourses; courseNo++) {
String colName = getASemesterCourse(thisSemester, courseNo).getCourseID();
TableColumn<AResultRow, String> thisColumn = new TableColumn<>(colName);
thisColumn.setPrefWidth(80);
thisColumn.setStyle("-fx-alignment: CENTER; font-weight:bold;");
thisColumn.setCellValueFactory(cellData -> cellData.getValue().courseGradeProperty(courseNo));
boolean retVal = thisTable.getColumns().addAll(thisColumn);
}
// System.out.println("# of Rows in Table [" + thisSemester + "] = " + TableRows.size());
thisTable.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
thisTable.setItems(TableRows);
ScrollPane thisScrollPane = new ScrollPane();
thisScrollPane.setFitToWidth(true);
thisScrollPane.setFitToHeight(true);
thisScrollPane.setMinHeight((theDetails.getHeight() - 25));
thisScrollPane.setMaxHeight((theDetails.getHeight() - 25));
thisScrollPane.setMinWidth((theDetails.getWidth() - 25));
thisScrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS);
Tab thisTab = tabs.getTabs().get(thisSemester);
thisTab.setContent(thisScrollPane);
thisScrollPane.setContent(thisTable);
}
Columns StudentID and Name are perfectly populated. But the Results are not being populated in
thisColumn.setCellValueFactory(cellData -> cellData.getValue().courseGradeProperty(courseNo));
I get this error in Netbeans for the line shown above. "Local Variables referenced from a lambda expression must be final or effectively final".
Remember, the GRADE[courseNo] field is a String and is populated.
How to show this value in the TABLE.
I have been trying various methods, like storing this value in a temp String... etc. etc.

How to sort String in Arraylist

I have an ArrayList where I add customers. What i wnat to do is that i want to sort them, so they appear sorted on Console.
private static ArrayList <Kund> klista = new ArrayList<>();
Kund kundd = new Kund("a","b");
System.out.print("Namn: ");
String namn = scr.next();
System.out.print("Adress: ");
String adress = scr.next();
if (!namnKontroll(namn)){
System.out.println (namn + " " +"har lagts till \n");
klista.add(kundd);
Kund k = new Kund(namn, adress);
klista.add(k);
}else{
System.out.println("Kunden med det namnet finns redan i systemet!");
}
// this is how i add customers to my ArrayList. So now, how it is possible to sort those names in ArrayList. I want to sort them with Collections. thanks
Try use Collections.sort(klista, theComparator). You will need create a Comparator, like this:
public class KundComparator implements Comparator<Kund> {
#Override
public int compare(Kund o1, Kund o2) {
// write comparison logic here
return o1.getID().compareTo(o2.getID());
}
}
Then use the Comparator:
Collections.sort(klista, new KundComparator());
If you are using Java 8, you can do like this:
Collections.sort(klista, (Kund k1, Kund k2) -> k1.getId().compareTo(k2.getId()));

Resources