I have two list of objects.
members: List,
membersHistory: List
Both of these objects have an "id" field. I want to create a list by joining both of these lists by making sure the "id" field of one is associated to the other object that have the same id. Both, object have different data but they are for specific member. Just need to pair them somehow to create a collection.
I started out something like below. But, I think I need to map them by "id" first before zipping them. Thank you!
members.zip(membersHistory).mapIndexed {_, pair ->
val (member, memberHistory) = pair
}
Here are a couple of ways to create a list of pairs of the items.
For each item in the first list, find a corresponding item in the second list with the same idea and pair them if found. mapNotNull will cause it to skip items that have no match in the second list.
val combination: List<Pair<Member, MemberHistory>> = members
.mapNotNull { member -> memberHistories.firstOrNull { it.id == member.id }?.let { member to it } }
To do this in O(n), you can create a map with the IDs as keys from one of the sources lists using associateBy.
val memberHistoryById = memberHistories.associateBy { it.id }
val combination = members.mapNotNull { member -> memberHistoryById[member.id]?.let { member to it } }
Presumably, though it hasn't been stated explicitly in the question, the id is the ID of a Member, so there will be no two objects in the members list with the same id value.
For quick lookup of Member by ID, I'd recommend creating 2 maps:
// Examples in Java
Map<Integer, Member> memberById = members.stream()
.collect(Collectors.toMap(Member::getId, Function.identity()));
Map<Integer, List<History>> memberHistoryById = membersHistory.stream()
.collect(Collectors.groupingBy(History::getId));
Those are both good to keep around, but if you want the Member and the History together, you can then create a combined map, keyed by the Member object. Assuming the natural order of Member is not the ID, we need a custom key, which we can do with TreeMap.
Map<Member, List<History>> historyByMember = memberHistoryById.entrySet().stream()
.collect(Collectors.toMap(e -> memberById.get(e.getKey()),
Map.Entry::getValue,
(a,b) -> a/*this is never called*/,
() -> new TreeMap(Comparator.comparingInt(Member::getId)));
The Answer by Andreas is a good one, using a map to associate a member object with its matching history object.
Write a class
Alternatively, you could create a class to bind the two objects together.
In Java 16 and later, a record might do. A record is a brief way to write a class whose main purpose is to communicate data transparently and immutably. The compiler implicitly creates the constructor, getters, equals & hashCode, and toString. A record can be declared locally or separately.
record MemberWithHistory ( Member member , History history ) {}
Loop your list of members.
For each member, find a matching history. We can do this easily with streams. Make a stream of your history objects, filtering for one whose member identifier matches the identifier of the nth member. An Optional is returned carrying the history object if found. Otherwise, if no matching history is found, the optional carries nothing.
If the optional does indeed have a found history object, instantiate a new MemberWithHistory record object. Collect that new record by adding to our results list named mwhs.
Here is some untested code to get your started.
List< MemberWithHistory > mwhs = new ArrayList<>( members.size() ) ;
for( Member member : members )
{
Optional< History > historyOptional = histories.stream().filter( history -> history.memberId.equals( member.id ) ).findAny() ;
if( historyOptional.isPresent() )
{
MemberWithHistory mwh = new MemberWithHistory( member , historyOptional.get() ) ;
mwhs.add( mwh ) ;
}
}
If you had a large number of items, searching by way of a stream repeatedly might become inefficient. Sorting and possibly deleting from a copy of the histories might be more efficient. But I would not bother for small data size or occasional use.
Related
I am new to Drools and i am struggling to find the solution for below problem:
I have a list of Account class:
class Account {
private String name;
private float amount;
}
I want to group the list of Account using name in drools.
For example:
Account a = new Account("Science", 100);
Account b = new Account("Science", 200);
List<Account> list = new ArrayList<String>();
list.add(a);
list.add(b);
Now I need a drool rule that should group the elements inside the list using name and provide the list that will have "Science, 300".
Please suggest.
As Ironluca points out in the comments, you are using the wrong tool for the job. The correct solution would be to use Java streams; even a simple for-loop would solve your problem.
Of course, since you can use a hammer on a screw, you can do this with Drools. I will include an example rule and an explanation, though it will be much less efficient than a stream-based solution (or even a for-loop.)
I will assume this model, with appropriate getters, setters, and an all-args constructor:
class Account {
String name;
int quantity;
}
Then this rule will populate an 'output' list with the "grouped" accounts. I've declared this 'output' list as a global, which isn't best-practice but none of this is recommended anyway since it's not the appropriate tool for the job.
global List $output;
rule "Consolidate duplicates"
when
$inputs: List()
Account( $name: name ) from $inputs
$duplicates: List()
from collect( Account( name == $name ) from $inputs )
$total: Number()
from accumulate( Account( $value: quantity ) from $duplicates, sum( $value ) )
then
$output.add(new Account($name, $total));
end
The first part of the rule identifies the subset of Account instances in the input list which share the same name. I use collect because this is a simple subset of the original input. The next step is to use accumulate to sum up the quantities from those duplicates. Then we add a new instances to the output list on the rule's RHS with the consolidated quantity and the shared name. Note that the rule works the same for Accounts which have unique names; there is no constraint on length for $duplicates, and the accumulation would just be the one identified quantity.
Modifying $inputs in-place is also do-able, but tricky, since you have to worry about concurrent modification exceptions. The much cleaner implementation is to use
Of course this is way more complicated than it has to be. Java 8 streams provide a groupingBy collector which allows you to trivially create a map by a property within the object:
Map<String, List<Account>> accountsByName = accounts.stream()
.collect(groupingBy(Account::getName));
You could then easily transform this by iterating over the valueset.
This other question goes into detail for other 'summation of duplicates in a stream' scenarios: Java 8 stream sum entries for duplicate keys
as per the requirement , i have to create a list of custom objects which holds details like id,name,address in a collection(array,list,map etc..)How to traverse the list of collection in order to fetch the unique records based on the id?
I have created a List Object which holds different collection varity like arraylist,set,map etc...After once the custom object values are assigned to List it is converted to Object class object.For example , i have created a custom object in the form of hashmap with key as 1 and value as custom object of id=1,name=aaa,address=delhi and assigned the custom object to the List and i'm repeating the same process for custom object creation using arraylist and set as well..how to traverse the List object inorder to the user details uniquely based on id?
List<Collection> customobjectlist
customobjectlist.add(list)
customobjectlist.add(set)
customobjectlist.add(map)
here each list,set and map holds 5 custom objects.I have to traverse the customobjectlist in order to fetch the user details uniquely based on ID?
Map is not a Collection in Java, so your code:
List<Collection> customobjectlist
customobjectlist.add(list)
customobjectlist.add(set)
customobjectlist.add(map)
is not valid if your variable map is an instance of a subclass of the Map interface.
Second issue, you should type your Collection (example: List<Collection<MyObject>>), else you'll get Object from the collections, you'll have to check instanceof every time, it's really not a good thing !
So if you type your Collection and add only Collection<MyObject> (not Map) to customobjectlist (List, Set, Queue, Stack, Vector,...) you have to loop on collections to find your object:
for (Collection coll : customobjectlist) {
for (MyObject obj: coll) {
if (theIdImLookingFor.equals(obj.getId()) {
// find it
}
}
}
It's not the only way to do it, in Java 8+, you can use the stream API :
customobjectlist.stream()
.flatMap(Collection::stream)
.findFirst(obj -> theIdImLookingFor.equals(obj.getId()))
I am currently writing code which contains an arraylist. This arraylist includes data which is name, lastname, job and id. I need to seperate the data into different arraylists. Currently i am using the method which is shown below.
for (int i = 0; i < details.size(); i = i + 4) {
names.add(details.get(i));
lastname.add(details.get(i + 1));
job.add(details.get(i + 2));
id.add(details.get(i+3));
}
I want to know if there is a better way of doing this. The initial arraylist can be very long, and i dont know if there are any issues with this method.
You asked: "I want to know if there is a better way of doing this". There is a better way.
You should consider creating a class called Record that contains the data (name, last name, job, and ID), and create an ArrayList. Then, instead of using index locations (and potentially grab the wrong data item), you could use the Record getter methods to get the data item you need (and perhaps store it in a different list).
Step 1: Create a Record class:
public class Record
{
private String firstName;
private String lastName;
private String job;
private String id;
// TODO add constructor(s), getters and setters
}
Step 2: Create a list of Records (this is an better alternative that create a list having the information in different index locations. That way, each set of name, last name, job, and ID will be self-contained which is way better than disjointed in different index locations in a list.
ArrayList<Record> records = new ArrayList<Record>();
Step 3: Instead of using index locations (and potentially grab the wrong data item), you could use the Record getter methods to get the data item you need (and perhaps store it in a different list).
ArrayList<String> names = new ArrayList<String>();
ArrayList<String> jobs = new ArrayList<String>();
...
names.add(records.getLastName() + ", " + records.getFirstName());
jobs.add(records.getJob());
Alternatively, and maybe a better solution, you could use a Map to store this information. For example, a job ID could be the key in a Map that returns a job description and who has been assigned to perform it. Job IDs have to be unique. Adding IDs to a list can be duplicated, because the List interface doesn't restrict entering duplicate data. If you use a Map, they keys are guaranteed to be unique. The value being returned from the Map could be a Record object (or some other kind) that contains the name of the person and the job the person is responsible for. Since values can be duplicates, you can have a person performing multiple jobs, which is probably what you want to do. To use a Map:
Map<String, Record> jobs = new HashMap<String, Record>(); //This record class doesn't have ID in it.
jobs.put("ABC123", new Record("John", "Doe", "Fix Drywall");
jobs.put("321CBA", new Record("Bill", "Smith", "Install Light Fixtures");
A few things to consider if using a Map. If you try to make a new entry with an existing key, the old one will be overwritten.
jobs.put("ABC123", new Record("John", "Doe", "Fix Drywall");
jobs.put("ABC123", new Record("Bill", "Smith", "Install Light Fixtures"); //Overwrote the previous entry because key is the same
If you want to change the key for an existing value, you must obtain the value, store temporarily, remove the old record, and make a new entry with the old temp value:
jobs.put("ABC123", new Record("John", "Doe", "Fix Drywall");
Record rec = jobs.remove("ABC123"); // gets the record and removes old entry
jobs.put("321CBA", rec); // new job ID for old record
The main issue is that your details can have missing data. For example it has the size=5. Then your method will crush with IndexOutOfBounds. Your details list should contain a Person object which has all the details you want and then just use them to fill other lists.
The main performance kill will be the add operation since it will have to grow the data structure over time. Since you know details.size() you should initialize the other arraylists with details.size()/4.
You should also check that details.size() % 4 == 0 before the for loop. If not it means your data is somehow wrong and you will run for sure into an IndexOutOfBounds.
Just for correctness you should write i < details.size()+3 as your condition, since you will access element i+3 in the for body. You should always check for i < details.size()+x do it like this if you ever access i+x in the body. (for the largest x there will be in the body)
I have an ArrayList of HashMap key-value pairs which looks like
ArrayList<HashMap<String, String>> myList =
new ArrayList<HashMap<String, String>>();
I understand that I can iterate through these items and find a match, but this seems to be an expensive task. Is there any other way to get an element without iterating?
My ArrayList has values like
[{Father Name=a, Mother Name=b, Child Name=c, Reg No=1, Tag ID=1},
{Father Name=p, Mother Name=q, Child Name=r, Reg No=2, Tag ID=2},
{Father Name=x, Mother Name=y, Child Name=z, Reg No=3, Tag ID=3}]
Based on RegNo, I wish to get Father Name, Mother Name and Child Name without iterating individual items.
Without iterating you will need to store your HashMap in another HashMap with key Reg No. Though I'd recommend using a Family object or something similar: HashMap<Integer, Family> registration (that's the beauty of OO-languages :) )
class Family {
String father;
String mother;
String child;
// constructor getters setters
}
Map<Integer, Family> registration = new HashMap(); // note this is a JDK7 future
//Map<Integer, Family> registration = new HashMap<Integer, Family>(); // the 'old' way
registration.put(regNo, new Family("Jack", "Mary", "Bastard"));
Family family = registration.get(regNo);
String father = family.getFather();
since you are storing hashes in list, that means order remain constant. So that mean you can create another array to store the Reg No in same order, and then search reg no in that array and based on searched value index you can get the other values.
Iterating is O(n), but you want the access to your structure to be faster... This means storing objects in a ordered manner ( -> O(log(n)) usually) or using another hash ( -> O(1)).
Or this, or you "hide" the iteration, but this would solve the problem only esthetically (something like getElementsByTagName in xml).
In any case you'll probably have to alter your structures, especially if you want to be able to have faster access for every field (father/mother/child/tag) and not just 'reg no'.
Maybe another solution could be storing plain data in a hash with a keypair like (primary key, data), duplicating the PK for every field in your HashMap, but this not only implies searching a valid primary key, there could be the problem of the size of the hash.
I am looking for an effective way to create a list/map etc out of the below two lists, which I can use to get both current and past status of a rule.
List<Boolean> rulesCurrentStatus = new ArrayList<Boolean>(); // 3 Rules: false/true meaning if the rule passed of failed
rulesCurrentStatus.add(false);
rulesCurrentStatus.add(true);
rulesCurrentStatus.add(false);
List<Boolean> rulesPreviousStatus = new ArrayList<Boolean>(); // Previous state of the above 3 rules.
rulesPreviousStatus.add(true);
rulesPreviousStatus.add(true);
rulesPreviousStatus.add(false);
You can use Map with key of String type and value of Boolean type. You can differentiate between current and previous value using the key. e.g. Store all current values with key something similar C#1,C#2 and store previous values with key something similar P#1, P#2 etc.
if I understood you right, you want to get the status history of a rule.
then maybe this could help:
Map<Rule( or ruleName as String), List<Boolean>>
the key in that map is the rule object or e.g. a String indicate which rule. the value is a list (ArrayList for example), stores the status history. for example:
{"rule1":[True, False,True] //1st,2nd,3rd(current) status
"rule2":[True,False]
...
}
thus if you want to get the whole status-history of a rule by
List<Boolean> history = map.get("someRule")
then you could add new status, or get certain status by playing with the List.
if you only need pre and current, you could declare the List with initial capacity.