Generating Hierarchy using Maps and path variable - java

I've got a family class that pulls data from a postgres database. The class looks like something like this:
#Entity
public class Family(){
#Id
private long id;
private String firstName;
private String lastName;
private Long parentId;
private4 String familyPath;
private List<Family> children;
//getters and setters
In the database, I have their relation to each other stored as a period-delimited string. So for example, if Bob is the child of Sue, the tree column would look like: "bob.sue". This path is stored as part of the family object in the familyPath variable.
CLARIFICATION
familyPath is a path based on unique IDs for each row in the database. So a path may look like "1.2.3" where the last number is the current row.
"1.2.4" is another potential path. so rows with IDs 3 and 4 are children of 2, etc.
In my code I query the database for all family members in the data, so I have a set of every member of the family in the database. My goal is to generate a set of all family members as a hierarchical structure using this initial, flat set. So, in the end if I call getChildren on Bob, I get a list back containing Sue and any other children.
My Solution:
First, I iterate through my list of families, and find what I call the root members -- those at the top level of the family path -- and remove them into a separate list. So now I have a list of top level family members, an a list of everyone else.
Then, for each member in the top level list, I call the following recursive method:
private Family familyTree(Family root, List<Family> members) {
List<Family> children = new ArrayList<>();
for (Family f : members) {
if (isChildOf(f, root)) {
children.add(familyTree(f, resources));
}
}
root.setChildren(children);
return root;
}
private boolean isChildOf(Family a, Family b) {
String pCPath = a.getFamilyPath();
String pPPath = b.getFamilyPath();
return pCPath.indexOf('.') >= 0
&& pCPath.substring(0, pCPath.lastIndexOf('.')).equals(pPPath);
}
and save the output to a list. This generates the desired results.
My Question
However, I feel that this recursive method is very expensive (n^2). I'm thinking that there may be a more efficient way to generate this hierarchy using sets, maps and the Family object's familyPath variable, But i keep getting stuck in multiple iterative loops. Does anyone have any thoughts?

Option 1 - Single pass
private Family familyTree(Family root, List<Family> members) {
Map<Long, List<Family>> parentMap = new HashMap<>();
// Assuming root is not contained in members
root.children = new ArrayList<>();
parentMap.put(root.id, root.children);
// Assign each member to a child list
for (Family member : members) {
// Put the family member in the right child list
Long parentId = member.getParentId();
List<Family> parentChildren = parentMap.get(parentId);
if (parentChildren == null) {
parentChildren = new ArrayList<>();
parentMap.put(parentId, parentChildren);
}
parentChildren.add(member);
// Get or create the child list of the family member
List<Family> ownChildren = parentMap.get(member.id);
if (ownChildren == null) {
ownChildren = new ArrayList<>();
parentMap.put(member.id, ownChildren);
}
member.children = ownChildren;
}
return root;
}
private Long getParentId() {
// left as an exercise...
}
Option 1.b - Single pass over all members including roots
private List<Family> familyTree(List<Family> members) {
List<Family> roots = new ArrayList<>();
Map<Long, List<Family>> parentMap = new HashMap<>();
// Assign each member to a child list
for (Family member : members) {
// Put the family member in the right child list
Long parentId = member.getParentId();
if (parentId == null) {
// a root member
roots.add(member);
} else {
// a non-root member
List<Family> parentChildren = parentMap.get(parentId);
if (parentChildren == null) {
parentChildren = new ArrayList<>();
parentMap.put(parentId, parentChildren);
}
parentChildren.add(member);
}
// Get or create the child list of the family member
List<Family> ownChildren = parentMap.get(member.id);
if (ownChildren == null) {
ownChildren = new ArrayList<>();
parentMap.put(member.id, ownChildren);
}
member.children = ownChildren;
}
return roots;
}
Option 2 - Add a reference to the parent
Your Family class should have a private Family parent attribute. You will then be able to do a single query per family "level". That is:
get all children of Sue
get all children of people from (1) and assign them to the proper parent
etc.
Option 3 - Nested Set Model of Hierarchies
The database schema can be modified to retrieve whole sub-trees in a single query. The trick is to give each tree node a "left" and a "right" value. These values establish a range for the "left" and "right" values of a node's children.
Selecting a full tree can then be done like this:
SELECT child.id, ...
FROM family AS child, family AS parent
WHERE child.lft BETWEEN parent.lft AND parent.rgt
AND parent.id = 111
ORDER BY child.lft;
There are many other hierarchical operations which can be done very easily with such a schema. See this post and Joe Celko's Trees and Hierarchies in SQL for Smarties for more information.
Finally, your model only considers a single parent for each family member which seems strange.

Related

How to convert a collection of categories into a tree structure

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.

Java recursion doesn't return all values

I have two columns in a table called Folder and Parent. And in my service I have a method called FolderService.getAllParents(s) - where (s) is a string. However a folder can also be in the parent column and I m trying to do a recursive formula to get all the folders and their respective parents.
For example Documents (parent) and Film (folder) also Film (parent) and Reviews(folder) ,Reviews(parent) and name(folder) and so on. I want to get all the parents and folder according to the string searched. Here's the recursive formula that i m stuck on:
public ArrayList<String> getParents(String s){
ArrayList<String> mn = new ArrayList<String>();
for(FolderManager y:FolderService.getAllParents(s)){
if(y.getParent().isEmpty()){
mn.add(y.getFolder());
return mn;
}else{
return getParents(y.getFolder());
}
}
return mn;
}
I m not getting any errors but I m only getting till
Documents (parent) - Film (folder) Film (parent) - Reviews(folder)
First of all, if I understood you correctly, your FolderService.getAllParents() will always return one object (because one child can have only one parent, isn't it?).
Then I don't think recursion is needed here. It can be done with a simple loop:
List<String> getParents(FolderManager fm) {
List<String> parents = new ArrayList<>();
while (fm.getParent() != null) {
parents.add(fm.getParent().getFolder());
fm = fm.getParent();
}
return parents;
}

Merging list of two objects to create a third object on the basis of common attribute

I have two different csv files having data on two different entities and I have to merge two different csv files to create one on the basis of sql join type equijoin and left join.
so I have created first entity as class name Customer having attributes:
int CustomerId ;
String CustomerName;
int OrderId;
And List of object of this class like:
Customer c1 = new Customer(CustomerId, CustomerName, OrderId);
1 million objects..
List<Customer> cust = new ArrayList<>();
cust.add(c1);
cust.add(c2);
so on to make list of 1 million object.
Similarly, I have created class of second entity Order having attributes:
int orderId;
String orderName;
Date orderdate;
Order o1 = new Order(orderId, orderName, orderdate);
so on 1 million object
List<Oder> order = new ArrayList<>();
Now I need to merge both the object on the basis of orderId and generate third object having result class having all the attributes from both the classes described above.
Please suggest me solution using java stream 8 to map both the streams of list to create inner join and left join type example in the third new result class.
Aside from the getters, your Customer class should have the following method:
public boolean orderMatch(Order order) {
//fixed the attribute name so it would be in camelCase
return orderId == order.getId();
}
Of course, this implies that Order has a getId() getter method to get its id attribute.
Finally, you'll need a CustomerExtended class.
class CustomerExtended {
int customerId ;
String customerName;
Order customerOrder;
public CustomerExtended(Customer customer, Order order) {
customerId = customer.getId();
customerName = customer.getName();
customerOrder = order;
}
}
Now, you can create a Function which would search for the corresponding Order and append it to a Customer:
Function<Customer,CustomerExtended> extendCustomer = (c) ->{
//I used the more descriptive name orderList instead of o1.
Optional<Order> order = orderList.stream()
.filter(c::orderMatch)
.findFirst();
if(order.isPresent()) {
return new CustomerExtended(c,order.get());
}
return null;
};
And then you can apply it to the Customer list through a map.
List<CustomerExtended> newCustomerList = customerList.stream()
.map(c -> extendCustomer.apply(c))
.collect(Collectors.toList());
EDIT: A few last notes
There should be some check that the ID numbers are not duplicate either when adding the objects to the lists, or when the lists are populated.
For semantic purposes, The Customer object as it is should be renamed CustomerOrder or be separated into an object only for customer info and an object which would store the relation between customer and order.
The case where an order is not found should be better handled and throw an exception.

Database tree to java

I want to move a tree from database in java.
First I get ResultSet from DB:
String selectTree = "select id, id_parent, text from tree ";
ResultSet resultSet = statement.executeQuery(selectTree);
resultSet.last();
sizeOfSelect = resultSet.getRow();
resultSet.beforeFirst();
list = new ArrayList<Node>(sizeOfSelect);
while(resultSet.next()) {
list.add(new Node(resultSet.getInt(1),
resultSet.getInt(2),
resultSet.getString(3)));
}
Now I have an array of nodes:
class Node<T> {
private int id;
private int parent_id;
private String text;
Node(int _id, int _parentId, String _text) {
this.id = _id;
this.parent_id = _parentId;
this.text = _text;
}
//getters & setters here
How can I obtain the same tree in java?
Could you tell me the line of thought, please.
I have made progress in solution.
I turning array of RawNode in map thus:
for(RawNode rawNode : tmpList) {
tmpMap.put(rawNode.getId(), dependsId(rawNode.getId()));
}
Where method dependsId() is:
private List<RawNode> dependsId(int id) {
List<RawNode> tmpDependList = new ArrayList<>();
for(RawNode rawNode : tmpList) {
if(rawNode.getParentId() == id) {
tmpDependList.add(rawNode);
}
}
return tmpList;
}
What should I do next? How transform this map in array of Node with parent and children?
Right now, your "raw" Nodes look like this:
class RawNode {
private int id;
private int parent_id;
private String text;
[ hint: I renamed that class declaration; and removed the <T> part which doesn't make any sense given the fact that you have nothing generic in that class; at least from what you showing ]
and ideally, that would mean that there is exactly ONE Node object for each ID. In other words; over time, you created objects like:
RawNode rootWithoutParent = new Node(0, -1, "root");
RawNode firstChildParent = new Node(1, 0, "root");
And you have all these objects in an array/list. Now you could transform those RawNodes, into "real" Nodes, and that one could look like:
class Node {
private int id;
private Node parent;
private List<Node> children = new ArrayList<>();
private String text;
Translation could be done in multiple iterations.
First, you create one Node per RawNode (and for later, we remember specific information in two maps):
List<Node> nodes = new ArrayList<>();
Map<Node, RawNode> rawNodesByNode = new HashMap<>();
Map<Integer, Node> nodesById = new HashMap<>();
for(RawNode rawNode : tmpList) {
Node node = new Node();
node.setId(rawNode.getId());
node.setText(rawNode.getText());
rawNodesByNode.put(node, rawNode);
nodesById.put(rawNode.getId(), node);
}
What is missing?
First, the "link" from childs to their parent.
for(Node node : nodes) {
RawNode raw = rawNodesByNode.get(node);
int parentId = raw.getParentId();
Node parent = nodesById.get(parentId);
node.setParent(parent);
}
Second ("optional") the "link" from parents to all childs:
for(Node node : nodes) {
Node parent = node.getParent(); // should be != null by now
parent.addChild(node);
}
with:
void addChild(Node node) {
children.add(node);
}
( please note: the children list is created directly when you do a Node.new() call; but at that point, it is empty. And beyond that: I didn't put my code into a compiler; it is meant as "pseudo code", to get you going; and give some insights into the required steps. There is also a certain potential for "bugs" - conditions where you need special checking to handle cases like the "root node" which doesn't have a parent; in other words: don't just copy&paste my code: understand what it is supposed to do and work from there).
Hope that gives you enough ideas to get you going. And please note: all of this is just "work"; there is no "rocket science" involved; so the essential part is that you now engage and understand what you are about to do. So resist the urge to ask yet-another question; instead: start working.
Well, if you want tree, your Node definitely lacks some links to other nodes(for parent at least), i'd suggest replacing parent_id in Node to Node parentNode, and set it according to parent_id from database, for example add them all to map at first, mapped by Map<Integer, Node> someMap, where integer is id, then when iterate trhough each and do:
list.add(new Node(resultSet.getInt(1),
someMap.get(resultSet.getInt(2)),
resultSet.getString(3)));
oh, and don't forget to check if that parent id is not null.
I don't really use result set often, but i suppose it would be better to move them from there to some temporary list.
Really sorry, don't have time to write everything as it should be, but i hope you've got my idea

How to create dynamic tree data structure in Java

specifically I need to represent the following:
The tree at any node can have an arbitrary number of children
Each parent node (after the root) is just a String (whose children are also Strings)
I need to be able to get parent and list out all the children (some sort of list or array of Strings) given an input string representing a given node
Dynamically populating the tree structure based on reference relationship between parent and child.
Example given is I have one member1 sponsor another member2, and member2 sponsor member 3 and so and so for. Already have the table records relationship
Is there an available structure for this ???
My data is from DB or a List, I will loop through the information with the name and the relation to determine if the node is a root, parent or a child.
So during the loop, I found a child, I need a reference to the parent so that I can compare the child relation to the parent before adding the child to its parent.
The closest code I found .
public class TreeNode<T> implements Iterable<TreeNode<T>> {
T data;
TreeNode<T> parent;
List<TreeNode<T>> children;
public TreeNode(T data) {
this.data = data;
this.children = new LinkedList<TreeNode<T>>();
}
public TreeNode<T> addChild(T child) {
TreeNode<T> childNode = new TreeNode<T>(child);
childNode.parent = this;
this.children.add(childNode);
return childNode;
}
// other features ...
}
Sample usage:
TreeNode<String> root = new TreeNode<String>("root");
{
TreeNode<String> node0 = root.addChild("node0");
TreeNode<String> node1 = root.addChild("node1");
TreeNode<String> node2 = root.addChild("node2");
{
TreeNode<String> node20 = node2.addChild(null);
TreeNode<String> node21 = node2.addChild("node21");
{
TreeNode<String> node210 = node20.addChild("node210");
}
}
}
This is what I have done so far. The parent will get overwritten by the latest entry so hence I am unable to retrieve what I have added previously .
public static TreeNode<String> getSet1() throws IOException {
BufferedReader reader = new BufferedReader(new FileReader("test.txt"));
String line;
while ((line = reader.readLine()) != null) {
String[] items = line.split(":");
String name = items[0];
String parent = items[1];
String type = items[2];
if (parent.equalsIgnoreCase("-") && type.equalsIgnoreCase("mainparent")) {
root = new TreeNode<String>(name);
} else if (type.equalsIgnoreCase("ChildParent") && parent.equalsIgnoreCase(root.toString())) {
childParent = root.addChild(name);
} else if (type.equalsIgnoreCase("Child") && parent.equalsIgnoreCase(childParent.toString())) {
child = childParent.addChild(name);
}
}
return root;
}
Your diagram indicates a tree of arbitrary depth, but your code only handles grandparent -> parent -> child relationships (with a single grandparent at the root).
I would ignore the type, as all you need is the name of a person and the name of their parent. If the parent name is a dash, you know you have the root.
Now for each person, you need to get the parent node already in the tree (assuming parents come before children in the list - if that's not the case, the problem becomes significantly more complex, as you would have to store orphaned persons temporarily and for each new person see if they are the parent of an orphaned person).
In order to get the parent by name, you should store each person you have already processed in a second data structure, parallel to the tree. The second data structure should make it easy to look someone up by name. Maps, and in particular Hashtables, are ideal for this. This is how it works:
Map processedPersonsMap=new Hashtable<String, TreeNode<String>>();
For each person, you store them in the map, indexed by their name:
TreeNode<String> person=...;
processedPersonsMap.put(person.getData(), person);
When you read in a new person and their parent's name is not a dash, you look up the parent:
String parentName=items[1];
TreeNode<String> parent=processedPersonsMap.get(parentName);
In this way, no matter how deep the tree is, you will always find the right parents. However, keep in mind that this requires a valid input file where each child comes after their parent, and which does not contain circular references or missing parents.
If those conditions are not met, you have to handle them explicitly.

Categories