Passing objects through streams and flatmaps - java

I'm dealing with Java 8 streams and I wondering if I could resolve this problem in a fancy way.
That's my scenario:
Suppose I have a list of parties and inside each element I have the names of the members. I want to iterate over the list and make a new one with the names and which party they belong to.
My first approach was:
#Test
public void test(){
Party firstParties = new Party("firstParty",Lists.newArrayList("Member 1","Member 2","Member 3"));
Party secondParty = new Party("secondParty",Lists.newArrayList("Member 4","Member 5","Member 6"));
List<Party> listOfParties = Lists.newArrayList();
listOfParties.add(firstParty);
listOfParties.add(secondParty);
List<Elector> electors = new ArrayList<>();
listOfParties.stream().forEach(party ->
party.getMembers().forEach(memberName ->
electors.add(new Elector(memberName,party.name))
)
);
}
class Party {
List<String> members = Lists.newArrayList();
String name = "";
public Party(String name, List<String> members) {
this.members = members;
this.name = name;
}
public List<String> getMembers() {
return members;
}
}
class Elector{
public Elector(String electorName,String partyName) {
}
}
In my second approach I tried to use maps an flatmap's operations:
#Test
public void test(){
Party firstParty = new Party("firstParty",Lists.newArrayList("Member 1","Member 2","Member 3"));
Party secondParty = new Party("secondParty",Lists.newArrayList("Member 4","Member 5","Member 6"));
List<Party> listOfParties = Lists.newArrayList();
listOfParties.add(firstParty);
listOfParties.add(secondParty);
List<Elector> people = listOfParties.stream().map(party -> party.getMembers())
.flatMap(members -> members.stream())
.map(membersName -> new Elector(membersName, party.name)) #Here is my problem variable map doesn't exist
.collect(Collectors.toList());
}
The problem is I can't access to the party object inside the map operation.
So the question again is Can I do in a more functional way? (like the second approach)
Thanks!

You decomposed too much into individual operations:
List<Elector> people = listOfParties.stream()
.flatMap(party -> party.getMembers().stream()
.map(membersName -> new Elector(membersName, party.name)))
.collect(Collectors.toList());
This works by moving both map steps into the flatMap step, where only the second one survives, now being applied to the returned “substream”. As pointed out in the comments of your question, you need some kind of Pair type to map the “substream” elements to, but your Elector type fulfills exactly that, as it is constructed using the two values you are interested in. So there is no need to map to a generic Pair(member,party) just to map to Elector afterwards.

To keep everything readable I would add a helper method in the Party class (or a static method somewhere else) to get a Stream<Elector>:
public Stream<Elector> electors() {
return getMembers().stream().map(member -> new Elector(member, name));
}
// Alternatively
public static Stream<Elector> electors(final Party p) {
return p.getMembers().stream().map(member -> new Elector(member, p.name));
}
And then just use that in your flatmap
final List<Elector> people = listOfParties.stream()
.flatMap(Party::electors)
.collect(Collectors.toList());

Related

Split a method into two separate ones and reuse the same list across the invocations

I have a method as follows (simplified for clarity and brevity):
public static List<Person> getPersons(String a, int b, DataSetX dataSetX, DataSetY dataSetY) {
List<Person> persons = new ArrayList<>();
if(dataSetX != null) {
while (dataSetX.hasNext()) {
// create an instance of person with very specific attributes set
persons.add(processX(dataSetX.next()));
}
}
if(dataSetY != null) {
while (dataSetY.hasNext()) {
// create an instance of person with very specific attributes set
persons.add(processY(dataSetY.next()));
}
}
return persons;
}
The method in reality is a bit more complicate than this (doing a bit more processing using also the a and b variables) but overall this is the method structure.
I was thinking to split this method into 2, one method for dealing with DataSetX and the other with DataSetY.
I was thinking to structure it as follows:
public static List<Person> getPersons(String a, DataSetX dataSetX, List<Person> persons) {
if(dataSetX != null) {
while (dataSetX.hasNext()) {
// create an instance of person with very specific attributes set
persons.add(processX(dataSetX.next()));
}
}
return persons;
}
I would then call the methods as follows:
List<Person> persons = getPersons(a, dataSetX, new ArrayList<Person>());
getPersons(a, dataSetX, persons);
// now I can use persons list with the result of both present
With this approach I reuse the same list and don't need to concat 2 different lists from 2 different methods if I just created the list inside the methods and returned.
On the other side it looks kind of weird and possibly error prone.
Is there a way to be able to split the function and avoid creating multiple lists and merging them (as I need 1 list in the end).
Is there some design pattern suited for this?
In my view, this method is not simple and future user of this method will be surprised that this method adds
data to List<Person> persons. Why? As method is named getPersons. In my view, people will be think that this method will just return data, not add new data to List<Person> persons.
So, it is better to be consistent with naming of method and do just one thing in method. So I would just read data in method:
public static List<Person> getPersons(String a, DataSetX dataSetX) {
List<Person> persons = new ArrayList<>();
if(dataSetX != null) {
while (dataSetX.hasNext()) {
// create an instance of person with very specific attributes set
persons.add(processX(dataSetX.next()));
}
}
return persons;
}
and the second method should look like the above method:
public static List<Person> getPersons(String a, DataSetY dataSetX) {
List<Person> persons = new ArrayList<>();
if(dataSetX != null) {
while (dataSetX.hasNext()) {
// create an instance of person with very specific attributes set
persons.add(processX(dataSetX.next()));
}
}
return persons;
}
In addition, this method can be placed in repository layer. Read more about Repository pattern.
And then you can create a collection and add these data from two methods. I am not Java guy, however, I think
it can be done like this:
List<Person> overallPersons = new ArrayList<>();
Collections.addAll(list, getPersons(String a, DataSetX dataSetX));
Collections.addAll(list, getPersons(String a, DataSetY dataSetX));
Read more how to add multiple items to collection in Java
In case if your datasets can be transformed in Stream you can try something like this:
Stream<Object> streamX = dataSetX.stream().map(e -> processX(e));
Stream<Object> streamY = dataSetY.stream().map(e -> processY(e));
List<Person> persons= Stream.concat(streamX , streamY).collect(Collectors.toList());

How can I make a new HashMap that won't change the one I've copied from?

I have a HashMap called the Item Database, which stores data about all of the items.
However, these items can have modifiers (in this case, the stat multiplier is important). Whenever I change an item to just one specific item drop, it ends up changing the base item from the HashMap.
For example, whenever a player creates a Katana, it does something like this.
HashMap<String, CustomItem> db = new HashMap<String, CustomItem>();
db.putAll(ItemDatabase.database);
CustomItem ci = db.get("KATANA");
From there, modifiers are applied via a getBukkitItem function on the CustomItem ci, basically multiplying a lot of the stats on that CustomItem and applying it.
baseHealth = (int) ((abbaseHealth / 100.0) * multiplier);
and other stats like that.
However, whenever I make changes to this new CustomItem, it also applies to the ItemDatabase hashmap. This means that whenever somebody makes another Katana, those multiplied stats become the new base stats to be multiplied.
TL;DR Whenever I'm changing a variable I got from a HashMap (db), that change also applies to the HashMap (itemdb). This happens even if the HashMap (db) it's from, is a copy of another HashMap (itemdb)
I have tried the method above, and using .clone() on a HashMap and casting it back to HashMap. Unfortunately I'm not really sure what else to try.
you should create a new object of deep clone. Using orika framework like below.
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
#Test
public void t() {
Map<Integer, User> map = new HashMap<>();
User one = new User();
one.setName("one");
System.out.println(one);
User two = new User();
two.setName("two");
System.out.println(two);
map.put(1,one);
map.put(2,two);
TypeBuilder<Map<Integer,User>> typeBuilder = new TypeBuilder<Map<Integer,User>>() {
};
Type<Map<Integer,User>> type = typeBuilder.build();
Map<Integer,User> copyMap = mapperFactory.getMapperFacade().mapAsMap(map, type,type);
System.out.println(copyMap.get(1));
System.out.println(copyMap.get(2));
}
You need to make new CustomItem instances. If you only make a copy of the Map, you’re just copying the references stored in the Map; they’ll still refer to the same CustomItem instances.
You can make this easier by adding a copy constructor or clone() method to CustomItem. Example of a copy constructor:
public class CustomItem {
public CustomItem(CustomItem other) {
this.name = other.name;
this.baseHealth = other.baseHealth;
this.multiplier = other.multiplier;
// Don't want two instances to refer to the same List!
this.inventoryList = new ArrayList<>(other.inventoryList);
// etc.
}
}
Example of a clone() method:
public class CustomItem
implements Cloneable {
#Override
public CustomItem clone()() {
try {
CustomItem copy = (CustomItem) super.clone();
// Don't want two instances to refer to the same List!
copy.inventoryList = new ArrayList<>(copy.inventoryList);
// etc.
return copy;
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
}
Once you have a way to copy CustomItem instances, you need to use it in your new Map:
Map<String, CustomItem> newMap = new HashMap<>();
for (Map.Entry<String, CustomItem> entry : db) {
String key = entry.getKey();
CustomItem value = entry.getValue()
value = value.clone();
newMap.put(key, value);
}
A shorter way:
Map<String, CustomItem> newMap = new HashMap<>(db);
newMap.replaceAll((k, v) -> v.clone());

How to create arraylist without one element from another arraylist

I needed to create an arraylist without an element of another arraylist, but I need this new arraylist to keep updating. For example, an element of the old arraylist is removed, also remove in the new one.
But I did not want to remove the element of the two arraylist, only the old one, so as not to have much code
(My method "showPeople" is updated every 1 second)
My code:
ArrayList<Person> personList = new ArrayList<>();
private void method(){
personList.add(new People("Name"))
}
private void showPeople(){
ArrayList<Person> newPersonList =
new ArrayList<>(personList.stream()
.filter(person -> !person.getName().equals("Test"))
.collect(Collectors.toList()))
for (int i = 0; i < newPersonList.size(); i++){
gui.show(newPersonList.get(i).getName());
}
}
The problem is that when I create the new arraylist and remove an item from the old one, the new one does not update
You're making multiple copies of your list; instead, do something like:
List<Person> filterPeople(List<Person> people, #NotNull String name) {
return people.stream()
.filter(person -> !name.equals(person.getName()))
.collect(Collectors.toList());
}
If you're uncomfortable with the lack of guarantees on the the shape of the List, you can be explicit:
.collect(Collectors.toCollection(ArrayList::new));
It's still unclear what you're asking, however. I suggest you provide a minimal, complete, and verifiable example.
If you want the list without the element to keep updating, you can create a view of the list by extending AbstractList.
The API documentation contains instructions as to the methods you would need to override. If you don't want the list to be modifiable through the view, all you need to do is to override the get and size methods:
class ListView extends AbstractList<String> {
private final List<String> delegate; // Initialize in constructor.
public T get(int i) {
int pos = delegate.indexOf("Test");
if (pos < 0 || i < pos) return delegate.get(i);
return delegate.get(i + 1);
}
public int size() {
return delegate.size() - (delegate.contains("Test") ? 1 : 0);
}
}
This will repeatedly search for the "Test" element, because there is no way for the view to know if the delegate list has been updated underneath it.
Here's a handy method:
private static <T> List<T> CopyListWithoutItem(List<T> listToCopy, T itemToNotCopy) {
return listToCopy.stream().filter(item -> !item.equals(itemToNotCopy)).collect(Collectors.toList());
}
You can use that: List<String> elements = list.stream().distinct().collect(Collectors.toList());
That will remove duplicates.

Is it possible to replace nested loops on different collections with Stream API

I was wondering if it's possible to rewrite nested for loops using java.utils.stream in Java 8?
Here is a sample data type I'm working with:
class Folder {
private final String name;
private final Integer itemCound;
Folder(String name, Integer itemCount) {
this.name = name;
this.itemCount = itemCount;
}
public String getName() { return this.name; }
public Integer getItemCount() { return this.itemCount; }
}
Here's code in action:
List<Folder> oldFolders = new ArrayList<>();
List<Folder> newFolders = new ArrayList<>();
// Fill folder collections with sample data...
oldFolders.add(new Folder("folder1", 2));
oldFolders.add(new Folder("folder2", 4));
newFolders.add(new Folder("folder1", 0));
newFolders.add(new Folder("folder2", 100));
// This part should be rewrited using streams
for (Folder newFolder : newFolders) {
for (Folder oldFolder : oldFolders) {
if (newFolder.getName().equals(oldFolder.getName())
&& !newFolder.getItemCount().equals(oldFolder.getItemCount())) {
// do stuff...
}
}
}
P.S: I've seen other questions on SO, but all of them had 1 collection or a collection with it's own nested collection instead of two different collections like in my example.
Thanks in advance!
That not much of an improvement to be fair unless if you can parallelize the first iteration (commented in this example)
List<String> oldList = new ArrayList<>();
List<String> newList = new ArrayList<>();
oldList
//.stream()
//.parallel()
.forEach(s1 ->
newList
.stream()
.filter(s2 -> s1.equals(s2)) //could become a parameter Predicate
.forEach(System.out::println) //could become a parameter Consumer
);
Replacing the if with a filter and his Predicate then executing a method on it.
This would give a solution that can be dynamic providing different Predicate and Consumer to the filter and forEach method. That would be the only reason to work on this conversion.
Yeah you can:
newFolders.forEach((newFolder) -> {
oldFolders.forEach((oldFolder) -> {
if (newFolder.getName().equals(oldFolder.getName())
&& !newFolder.getItemCount().equals(oldFolder.getItemCount())) {
// do stuff...
}
})
})
EDIT: But as #Kayaman mentions in the comments below this is not necessarily better than just using nested for loops.
This is a good read for when you should and shouldn't consider using streams:
In Java, what are the advantages of streams over loops?
Try with this:
newFolders.stream().filter(newFolder ->
oldFolders
.stream()
.anyMatch(oldFolder->
newFolder.getName().equals(oldFolder.getName()) &&
!newFolder.getItemCount().equals(oldFolder.getItemCount())
)).forEach(folder -> {
//dostuff
});

How to copy a List of key value except 1 key in Java?

I have 1 List of key:value pair primaryList in Java, now i want to copy complete list except one key date in primaryListExceptDate. Can anyone help me on this? I know we can do it using for loop but i want to know is there any other efficient way of doing it?
So as I understand you, you have a list of Record objects that keep pairs of values as key, value!?
Then you can use Stream api to do what you want. Something like:
List<Record> primaryListExceptDate = primaryList.stream()
.filter(record -> !record.getKey().equals(unwantedDateInstance))
.collect(Collectors.toList());
That will give you a new list without the Record with that unwanted date.
UPDATE: You asked for a Vector example.
I made this test which works fine, d2 is removed. Vector implements List so it can be cast. Collectors doesn't have a toVector method since Vector is outdated:
public class Testa {
public static void main(String[] args) {
Date d1 = new Date(100,1,2);
Date d2 = new Date(101,2,3);
Date d3 = new Date(102,3,4);
Date test = new Date(101,2,3);
Vector<Record> primaryList = new Vector<>();
primaryList.add(new Record(d1, new Object()));
primaryList.add(new Record(d2, new Object()));
primaryList.add(new Record(d3, new Object()));
List<Record> primaryListExceptDate = primaryList.stream()
.filter(record -> !record.getKey().equals(test))
.collect(Collectors.toList());
primaryListExceptDate.forEach(r -> System.out.println(r.getKey().toString()));
}
static class Record {
Date key;
Object value;
public Record(Date k, Object v) {
this.key = k;
this.value = v;
}
public Date getKey() {
return key;
}
}
}
Try a foreach construct to copy data into another List with an if to exclude the object that not interest to you. You may think it's not such an efficient way, but most of the methods the API offer have O(n) complexity.
I think it's the simplest method. You also could use List proper methods to copy the List and then remove the object, but this could be a bit more onerous if you look at performance.
Anyway, I suggest you to use Map Collection: it comes to rescue of you when using a key:value pair and it's very efficient! List is useless in this case.
I don't know if it's more efficient but you could first make a copy of the list and then iterate over it with an iterator and remove the entry with key 'date'.
EDIT: something like this:
List<Record> primaryList = ...;
List<Record> primaryListExceptDate = new ArrayList<>(primaryList );
Iterator<Record> it = primaryListExceptDate.iterator();
while(it.hasNext()) {
Record record = it.next();
if (record.getKey().equals("date")) {
it.remove();
}
}

Categories