I'm fairly new to the Java 8 Streams API stuff, but I've decided to use it for a new piece of functionality but have hit a brick wall!
I have a bunch of MenuItem objects:
MenuItem parent1 = new MenuItem(0L, "Code Parent", "Description Parent");
MenuItem item1 = new MenuItem(1L, "Code1", "Description1");
MenuItem item2 = new MenuItem(2L, "Code2", "Description2");
MenuItem item3 = new MenuItem(3L, "Code3", "Description3");
MenuItem item4 = new MenuItem(4L, "Code4", "Description4");
I also have a bunch of MenuHierarchy objects which represent the hierarchical relationships between MenuItem (parent/child). This model is fixed as is, so have to work with what I've got.
Constructor - MenyHierarchy(id, parent, child, displayOrder)
MenuHierarchy hierarchy1 = new MenuHierarchy(1L, null, parent1);
MenuHierarchy hierarchy2 = new MenuHierarchy(2L, parent1, item1, 1);
MenuHierarchy hierarchy3 = new MenuHierarchy(3L, item1, item2, 2);
MenuHierarchy hierarchy4 = new MenuHierarchy(4L, item2, item3, 3);
MenuHierarchy hierarchy5 = new MenuHierarchy(5L, item3, item4, 4);
MenuHierarchy objects will a null parent are considered root nodes.
Now using the streams API I want to transform this relationship into a Tree like structure using a MenuNode entity that I've created:
public class MenuNode implements GenericNode<MenuItem> {
private MenuItem data;
private List<GenericNode<MenuItem>> children;
public MenuNode(MenuItem data) {
this.data = data;
this.children = new ArrayList<GenericNode<MenuItem>>();
}
// Getters, setters
}
I'll explain what I have so far:
/* This is the list of Root Menus (Menus which have no parent) */
List<MenuNode> rootNodes = new ArrayList<>();
List<MenuHierarchy> hierarchyList = Arrays.asList(hierarchy1, hierarchy2, hierarchy3, hierarchy4, hierarchy5);
/* This first stream adds a new root MenuNode object to the above list ordered by the hierarchy display order */
hierarchyList.parallelStream()
.filter((h) -> Objects.isNull(h.getParentMenu()))
.sorted((h, i) -> h.getDisplayOrder().compareTo(i.getDisplayOrder()))
.map(MenuHierarchy::getChildMenu)
.forEachOrdered((i) -> rootNodes.add(new MenuNode(i)));
/* This second one is where i've sort of failed...
What i need this to do is iterate over the menu hierarchies and for each non-root one
add it to the MenuNode children collection where MenuNode.data == MenyHeirarchy.parentMenu
Resulting in a tree of MenuItems...
*/
hierarchyList.stream()
.filter((h) -> Objects.nonNull(h.getParentMenu()))
.sorted((h, i) -> h.getDisplayOrder().compareTo(i.getDisplayOrder()))
.forEachOrdered((h) -> {
rootNodes.stream()
.filter((n) -> n.getData().equals(h.getParentMenu()))
.forEach((n) -> {
n.getChildren().add(new MenuNode(h.getChildMenu()));
});
});
As you can see this doesn't work properly at the moment as it doesn't represent all of the hierarchy... I'm not sure if this is even possible with streams?
Any ideas will be greatly recommended.
Do I understand correctly that you want to end up with a bunch of MenuNode objects that represent the menu? If that's the case, I think it would be easier to premake all the node objects and then populate their children list.
// first we make all the nodes and map them to ID
Map<Long, MenuNode> nodes = hierarchies.stream()
.map(MenuHierarchy::getChildMenu)
.collect(toMap(MenuItem::getId, MenuNode::new));
// and now we go over all hierarchies and add children to appropriate node
hierarchies.stream()
.filter(h -> h.getParent() != null)
.sorted(comparing(MenuHierarchy::getDisplayOrder))
.forEach(h -> {
long parentId = h.getParentMenu().getId();
long childId = h.getChildMenu().getId();
nodes.get(parentId).getChildren().add(nodes.get(childId))
});
Alternatively, the second part can be written by going over nodes first. The advantage of doing it this way is that you can make the child list in MenuNode immutable. The downside is that you might find the idea of repeatedly iterating over all hierarchies distasteful (even though it shouldn't matter for any realistic menu size):
nodes.values().forEach( node ->
node.setChildren(
hierarchies.stream()
.filter(h -> h.getParentMenu().getId() == node.getData().getId())
.sorted(comparing(MenuHierarchy::getDisplayOrder))
.map(MenuHierarchy::getChildMenu)
.map(MenuItem::getId)
.map(nodes::get)
.collect(toList())
)
);
And, for completeness sake, you can group up the hierarchies for the same parent using streams and rewrite the second part like this:
hierarchies.stream()
.filter(h -> null != h.getParent())
.collect(
groupingBy(h->g.getParentMenu().getId(), toList())
) // now we have a map of parent Ids to list of MenuHierarchy for that parent
.forEach( (parentId, children) ->
nodes.get(parentId)).setChildren(
children.stream()
.sorted(comparing(MenuHierarchy::getDisplayOrder))
.map(h -> nodes.get(h.getChildMenu().getId()))
.collect(toList())
)
);
You decide what's clearer to you.
Edit: I wasn't sure if hierarchy id is always the same as the child id. If it is, the code can be simplified a bit.
Related
I have two lists of objects, RemoteList and LocalList.
While my app is offline, remote users can be adding and deleting objects from the RemoteList while local users are editing the LocalList.
When the app becomes connected again, it should sync the two lists.
Marble Diagram showing how the lists should be merged
For example, here are the object ids in each list before syncing:
RemoteList: 0, 1, 2 (deleted), 3 (new), 4 (new)
LocalList: 0, 1 (delete), 2, 3 (new)
When syncing, the ids have to be changed to prevent having two separate objects with the same id.
MergedList: 0, 3, 4, 5 (local id 3 was changed to 5)
I'm experimenting with RxJava, so a quick point in the right direction would be great. Merge? Fork? A combination? Maybe using Filter and adding a field to Object like isSynced?
Get each list in the form of Single<List<X>>, where X is a record that indicates what happened to a particular item, and zip them together. Now you can walk both lists and do your combination logic synchronously:
record Item(int id, boolean added, boolean deleted) { }
Single<List<Item>> remoteListSource = ...
Single<List<Item>> localListSource = ...
Single.<List<Item>>zip(remoteListSource, localListSource,
(remoteList, localList) -> {
Set<Integer> deleted = new HashSet<>();
// collect deleted ids
remoteList.stream()
.filter(item -> item.deleted)
.forEach(item -> deleted.add(item.id));
localList.stream()
.filter(item -> item.deleted)
.forEach(item -> deleted.add(item.id));
Set<Integer> duplicate = new HashSet<>();
List<Item> result = new ArrayList<>();
// go through both lists, ignoring deleted ids
Stream.concat(remoteList.stream(), localList.stream())
.filter(item -> !deleted.contains(item.id))
.forEach(item -> {
// id already seen?
if (duplicate.contains(item.id)) {
// if this is an addition, generate new item
if (item.added) {
item = new Item(Collections.max(duplicate) + 1, true, false);
duplicate.add(item.id);
result.add(item);
} else {
// else we have an unmodified item already in result
// nothing to do
}
} else {
// was not a duplicate, remember it
duplicate.add(item.id);
result.add(item);
}
});
return result;
})
.subscribe(combinedList -> { /* ... */ });
public class Parent {
String name;
List<Child> children;
}
public class Child {
String childName;
}
I have these 2 classes by which my data set becomes as below:
List<Parent> parents = new ArrayList<>();
List<Child> _child1 = new ArrayList<>();
_child1.add(new B("p1c1"));
_child1.add(new B("p1c2"));
_child1.add(new B("p1c3"));
Parent p1 = new Parent("p1", _child1);
parents.add(p1);
List<Child> _child2 = new ArrayList<>();
_child2.add(new B("p2c1"));
Parent p2 = new Parent("p2", _child2);
parents.add(p2);
can there be some data by which we can convert this as below:
parents[
parent1 {"p1", ["p1c1"]}, parent1 {"p1", ["p1c2"]}, parent1 {"p1", ["p1c3"]},
parent2 {"p2", ["p2c1"]}
]
I want to break the nested list into singleton nested list, primary aim is to achieve this with streams api from java 8, also can you suggest the best approach for avoiding concurrentModification exception?
This should do:
parents.stream()
.flatMap(parent -> parent.getChildren()
.stream()
.map(child -> new SimpleEntry<>(parent, child)))
.map(pair -> new Parent(pair.getKey().getName(),
Arrays.asList(pair.getValue())))
.collect(Collectors.toList());
It's just creating a parent/child pair for each child, and then creating a new parent for each of those pairs, with the single child added to children field. That then gets collected into a Parent list.
I am doing a tree selector use p:multiSelectListbox. I have a collection of categories, and I tried to convert categories to a component supported structure(categories is unordered).
Here is my category bean:
public class Category {
private String id;
private String pid; //parentId
private String name;
private String value;
//getter and setter
}
This is my convert method:
public List<SelectItem> tree(List<Category> categories) {
Map<Category, SelectItem> map = new HashMap<>();
for (Category node : categories) {
//Check if current category is leaf node, if true new SelectItem, else new SelectItemGroup
SelectItem item;
if (categories.stream().noneMatch(n -> node.getId().equals(n.getPid()))) {
item = new SelectItem(node.getValue(), node.getName());
} else {
item = new SelectItemGroup(node.getName());
}
map.put(node, item);
}
//the result return
//items just add the root level, and child level add into it's parent level
List<SelectItem> items = new ArrayList<>();
categories.forEach(node -> {
//get parent category of current's
SelectItem item = map.get(categories.stream().filter(n -> n.getId().equals(node.getPid())).findFirst().orElse(null));
//parent category is not exists, it's mean current category is root level
if (item == null) {
items.add(map.get(node)); //add root
} else {
SelectItemGroup parentGroup = (SelectItemGroup) item;
SelectItem[] selectItems = parentGroup.getSelectItems();
List<SelectItem> selectItemList = new ArrayList<>();
if (selectItems != null) selectItemList.addAll(Arrays.asList(selectItems));
//add current category into it's parent's children
selectItemList.add(map.get(node));
parentGroup.setSelectItems(selectItemList.toArray(new SelectItem[0]));
}
});
return items;
}
When the categories's size is less than 10000, he works very well; if the size is greater than 20000, it becomes very slow. Does anyone know a more efficient way?
These codes will have a O(n^2) time complexity:
categories.forEach(node -> { //get parent category of current's SelectItem item = map.get(categories.stream().filter(n -> n.getId().equals(node.getPid())).findFirst().orElse(null))
Making a tree structure from a list of categories, according to the pid&id relationship can be done with a O(n) time complexity using HashMap. Baic ideas described below.
Traverse the list of categories and put the pid&id mapping into a HashMap, time complexity:O(n). in the map we have(pid:List of children ids) as the Map.Entity. Now we actually have the tree structure already.
What we do next is to traverse the tree demonstrated via the hashmap, and get the result. It is this can be done either the recursion way which has been given by #Ken Bekov, or using an iterative way. The traveral procidure also takes O(n) time.
So the whole solution's time complexity is O(n). When the n is lagre, say 20000, it should be much faster than your original solution.
There are two entities:
class GiftCertificate {
Long id;
List<Tag> tags;
}
class Tag {
Long id;
String name;
}
There is a list
List<GiftCertificate>
which contains, for example, the following data:
<1, [1, "Tag1"]>, <2, null>, <1, [2, "Tag2"]>. (It does not contain a set of tags, but only one tag or does not have it at all).
I need to do so that in the result it was this:
<1, {[1," Tag1 "], [2," Tag2 "]}>, <2, null>. I mean, add to the set of the first object a tag from the third GiftCertificate and at the same time delete the 3rd one. I would like to get at least some ideas on how to do this. it would be nice to use stream.
Probably not the most effective way, but it might help
private List<GiftCertificate> joinCertificates(List<GiftCertificate> giftCertificates) {
return giftCertificates.stream()
.collect(Collectors.groupingBy(GiftCertificate::getId))
.entrySet().stream()
.map(entry -> new GiftCertificate(entry.getKey(), joinTags(entry.getValue()))).collect(Collectors.toList());
}
private List<Tag> joinTags(List<GiftCertificate> giftCertificates) {
return giftCertificates.stream()
.flatMap(giftCertificate -> Optional.ofNullable(giftCertificate.getTags()).stream().flatMap(Collection::stream))
.collect(Collectors.toList());
}
You can do what you want with streams and with the help of a dedicated custom constructor and a couple of helper methods in GiftCertificate. Here's the constructor:
public GiftCertificate(GiftCertificate another) {
this.id = another.id;
this.tags = new ArrayList<>(another.tags);
}
This just works as a copy constructor. We're creating a new list of tags, so that if the list of tags of either one of the GiftCertificate instances is modified, the other one won't. (This is just basic OO concepts: encapsulation).
Then, in order to add another GiftCertificate's tags to this GiftCertificate's list of tags, you could add the following method to GiftCertificate:
public GiftCertificate addTagsFrom(GiftCertificate another) {
tags.addAll(another.tags);
return this;
}
And also, a helper method that returns whether the list of tags is empty or not will come in very handy:
public boolean hasTags() {
return tags != null && !tags.isEmpty();
}
Finally, with these three simple methods in place, we're ready to use all the power of streams to solve the problem in an elegant way:
Collection<GiftCertificate> result = certificates.stream()
.filter(GiftCertificate::hasTags) // keep only gift certificates with tags
.collect(Collectors.toMap(
GiftCertificate::getId, // group by id
GiftCertificate::new, // use our dedicated constructor
GiftCertificate::addTagsFrom)) // merge the tags here
.values();
This uses Collectors.toMap to create a map that groups gift certificates by id, merging the tags. Then, we keep the values of the map.
Here's the equivalent solution, without streams:
Map<Long, GiftCertificate> map = new LinkedHashMap<>(); // preserves insertion order
certificates.forEach(cert -> {
if (cert.hasTags()) {
map.merge(
cert.getId(),
new GiftCertificate(cert),
GiftCertificate::addTagsFrom);
}
});
Collection<GiftCertificate> result = map.values();
And here's a variant with a slight performance improvement:
Map<Long, GiftCertificate> map = new LinkedHashMap<>(); // preserves insertion order
certificates.forEach(cert -> {
if (cert.hasTags()) {
map.computeIfAbsent(
cert.getId(),
k -> new GiftCertificate(k)) // or GitCertificate::new
.addTagsFrom(cert);
}
});
Collection<GiftCertificate> result = map.values();
This solution requires the following constructor:
public GiftCertificate(Long id) {
this.id = id;
this.tags = new ArrayList<>();
}
The advantage of this approach is that new GiftCertificate instances will be created only if there's no other entry in the map with the same id.
Java 9 introduced flatMapping collector that is particularly well-suited for problems like this. Break the task into two steps. First, build a map of gift certificate IDs to list of tags and then assemble a new list of GiftCertificate objects:
import static java.util.stream.Collectors.flatMapping;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toList;
......
Map<Long, List<Tag>> gcIdToTags = gcs.stream()
.collect(groupingBy(
GiftCertificate::getId,
flatMapping(
gc -> gc.getTags() == null ? Stream.empty() : gc.getTags().stream(),
toList()
)
));
List<GiftCertificate> r = gcIdToTags.entrySet().stream()
.map(e -> new GiftCertificate(e.getKey(), e.getValue()))
.collect(toList());
This assumes that GiftCertificate has a constructor that accepts Long id and List<Tag> tags
Note that this code deviates from your requirements by creating an empty list instead of null in case there are no tags for a gift certificate id. Using null instead of an empty list is just a very lousy design and forces you to pollute your code with null checks everywhere.
The first argument to flatMapping can also be written as gc -> Stream.ofNullable(gc.getTags()).flatMap(List::stream) if you find that more readable.
Is there any way to get a TreeView branch by name?
For example, if I have a TreeView menu like so:
TreeItem<String> root, branch;
root = new TreeItem<>();
root.setExpanded(true);
branch = makeBranch("Chicken", root);
makeBranch("Hen", branch);
Here is the makeBranch() method:
public TreeItem<String> makeBranch(String s, TreeItem<String> parent){
TreeItem<String> item = new TreeItem<>(s);
item.setExpanded(true);
parent.getChildren().add(item);
return item;
}
Now if I want to get a branch, I can do:
branch.getChildren().get(a_number); // get using index
But is there anyway I can get a branch by its name rather than value?
So something like:
branch.getChildren().get("the name of leaf or branch");
Since TreeItem.getChildren() returns a ObservableList<TreeItem<T>>, you can use the methods of List to find the child you're looking for, e.g. using Stream to filter the content:
final String value = "the name of leaf or branch";
Optional<TreeItem<String>> nodeOptional = branch.getChildren().stream()
.filter(
(child)-> child.getValue().equals(value)
)
.findFirst();
if (nodeOptional.isPresent()) {
TreeItem<String> item = nodeOptional.get();
// do something with item
} else {
// no child with specified value was found
}
Note that you only search the direct children of branch that way. If you want to find arbitrary descendants of branch, you have to do a tree search.