ArrayList Retrieve object by Id - java

Suppose I have an ArrayList<Account> of my custom Objects which is very simple. For example:
class Account
{
public String Name;
public Integer Id;
}
I want to retrieve the particular Account object based on an Id parameter in many parts of my application. What would be best way of going about this ?
I was thinking of extending ArrayList but I am sure there must be better way.

It sounds like what you really want to use is a Map, which allows you to retrieve values based on a key. If you stick to ArrayList, your only option is to iterate through the whole list and search for the object.
Something like:
for(Account account : accountsList) {
if(account.getId().equals(someId) {
//found it!
}
}
versus
accountsMap.get(someId)
This sort of operation is O(1) in a Map, vs O(n) in a List.
I was thinking of extending ArrayList but I am sure there must be
better way.
Generally speaking, this is poor design. Read Effective Java Item 16 for a better understanding as to why - or check out this article.

Java Solution:
Account account = accountList.stream().filter(a -> a.getId() == YOUR_ID).collect(Collectors.toList()).get(0);
Kotlin Solution 1:
val index = accountList.indexOfFirst { it.id == YOUR_ID }
val account = accountList[index]
Kotlin Solution 2:
val account = accountList.first { it.id == YOUR_ID }

A better way to do this would be to use a Map.
In your case, you could implement it in the following way
Map<account.getId(), account>
you can use the "get" method to retrieve the appropriate account object.
accountMap.get(id);

Assuming that it is an unordered list, you will need to iterate over the list and check each object.
for(int i = 0; i < sizeOfList; i++) {
list.get(i).equals(/* What you compare against */)
}
There's also the other for syntax:
for(Account a : accountList)
You could put this loop into a helper method that takes an Account and compares it against each item.
For ordered lists, you have more efficient search options, but you will need to implement a search no matter what.

You must use the Map for example:
private Map<String, int> AccountMap;
for (String account : accounts )
AccountMap.put(account, numberofid);

ArrayList does not sort the elements contained. If you want to look for a single element in an ArrayList, you're going to need to loop through the list and compare each one to the value you're looking for.
Account foundAccount;
for(Account a : accountList){
if(a.Id == targetID){
foundAccount = a;
break;
}
}
if(foundAccount != null){
//handle foundAccount
}
else{
//not found
}
Alternatively, you can use a more intelligent data structure which does sort and keep information on the data contianed.
You'll want to research the Map interface, specifically the HashMap implementation. This lets you store each element in an order tied to a certain key. So you could place each of your objects in a HashMap with the Id as the key, and then you can directly ask the HashMap if it has an object of a certain key or not.

Extending ArrayList is almost never a good solution to your problem. This is a base Java implementation of List, which allows you to store objects in a specific order, and retrieve them by their index.
If you want to be able to index elements using an unique identifier, you may have a look into Map, and its implementation HashMap.
It could help you to solve your problem, by using a Map<Integer, Account>.
Inserting objects: map.put(id, account) instead of list.add(account)
Retrieving objects: map.get(id)
This will be the fastest implementation. But, if you cannot change this, you can still iterate through your ArrayList and find the right account:
for (Account acc : accounts) {
if (acc.getId() == yourId) {
return acc;
}
}
throw new NoSuchElementException();

Related

Sorting by, which is better - hashmap, treemap, custom implementation

I have an ArrayList of Subjects and they have parameter Date, I want to group them into sorted array (maybe not array, but still sorted structure) of Day objects, so every Day will have parameter Date and object will contain only subjects with this date. So the thing I wanna do is somehow group them by date and then get them. I saw implementations of grouping by using HashMap, but then I have grouped structure but not sorted, so after that I should convert this to ArrayList for example. Or maybe I should use TreeMap, which will do the same but give me back sorted structure, or maybe best way is simply write my own sorter which will get ArrayList<Subject> and return ArrayList<Day>. Also I can use LinkedHashMap which will work too
So now I have no idea what is better and what should I choose? Important thing is that most likely I will not put new values or delete values from structure, I will only get them.
UPD: If I use map then Date will be key and Day object will be value.
By saying "get them" I meant iterate through them.
All this I'm doing in order to fill my UI elements with this info so most likely I will not search something in my structure later
Here's what I think you are asking for, but hopefully my answer can help even if it's not exactly it:
fast lookup using a Day as the key
the result of that lookup should be sorted (i.e. multiple times of the same day are ordered)
the possibility to see all subjects sorted by their Day
Here's one option. Use a Map that associates a Day to a sorted list of Subjects, so Map<Day, List<Subject>>. Since you don't need to add to it, you can build your mapping at the start and then sort it before you do any lookups. Here's an outline:
Map<Day, List<Subject>> buildMap(List<Subject> subjects) {
Map<Day, List<Subject>> map = new HashMap<Day, List<Subject>>();
// create a list of subjects for each day
for (Subject subject : subjects) {
if (!map.containsKey(subject.getDate().getDay())) {
map.put(subject.getDate().getDay(), new ArrayList<Subject>());
}
map.get(subject.getDate().getDay()).add(subject);
}
// go through and sort everything now that you have grouped them
for (Day day : map.keySet()) {
Collections.sort(map.get(day));
}
return map;
}
If you also need to be able to 'get' every entry sorted throughout the map, you could maintain a sorted list of days. Like so:
List<Day> buildSortedDaysList(Map<Day, List<Subject>> map) {
List<Day> sortedDays = new ArrayList<Day>(map.keySet());
// again, many ways to sort, but I assume Day implements Comparable
Collections.sort(sortedDays);
return sortedDays;
}
You could then wrap it in a class, of which I recommend you create a better name:
class SortedMapThing {
Map<Day, List<Subject>> map;
List<Day> orderedDays;
SortedMapThing(List<Subject> subjects) {
map = buildMap(subjects);
orderedDays = buildSortedDaysList(map);
}
List<Subject> getSubject(Day day) {
return map.get(day);
}
List<Subject> getAllSubjects() {
List<Subject> subjects = new ArrayList<Subject>();
for (Day day : orderedDays) {
subjects.addAll(map.get(day));
}
return subjects;
}
}
This implementation puts the work up front and gives you efficient lookup speed. If I misunderstood your question slightly, you should be able to adjust it accordingly. If I misunderstood your question entirely...I will be sad. Cheers!

Fastest and optimized way to search for value in a List<T>

I have a List<Person> persons = new ArrayList<Person>(), the size of this list is 100+. I want to check whether a particular personID object is contained in this list or not. Currently I am doing it in this way :
for(Person person : persons)
{
for(Long pid : listOfIDs)
{
if(person.personid == pid)
{
// do somthing
}
else
{
// do somthing
}
} // end of inner for
}
But I don't want to traverse through the persons list for each element in listOfIDs. I thought of taking HashMap of Person with personid as the key and Person object as value. So that I can only traverse through listOfIDs and check for contains()
Is there any other way to do it?
Your implementation with nested loops will not scale well if the lists get long. The number of operations you will do is the product of the length of the two lists.
If at least one of your lists is sorted by ID, you can use binary search. This will be an improvement over nested loops.
Building a Map is a good idea and will scale well. Using this technique, you will iterate over the list of Persons once to build the map and then iterate over the list of IDs once to do the lookups. Make sure that you initialize the size of the HashMap with the number of Persons (so you don't have to rehash as you put the Persons into the Map). This is a very scalable option and does not require that either list be sorted.
If BOTH lists happen to be sorted by ID, you have another attractive alternative: jointly walk down the two lists. You will start at the beginning of both lists and move forward in the list with the smallest ID. If the IDs are equal, then you do your business logic for having found the person with that ID and step forward in both lists. As soon as you get to the end of either list, you are done.
Java's Collections provides a binary search which is very fast but it assumes you are searching for a member of the list. You could implement your own using your ID criteria:
Collections.sort(persons, (p1, p2) -> p1.personID - p2.personID);
if (binarySearch(persons, id)) {
...
}
boolean binarySearch(List<Person> personList, Long id) {
if (personList.empty())
return false;
long indexToTest = personList.size() / 2;
long idToTest = personList.get(indexToTest).personID;
if (idToTest < id)
return binarySearch(personList.subList(indexToTest + 1, personList.size());
else if (idToTest > id)
return binarySearch(personList.subList(0, indexToTest));
else
return true;
}
If you don't want to sort your list then you could copy it to a sorted list and search on that: for large lists that would still be much faster than iterating through it. In fact that's pretty similar to keeping a separate hash map (though a hash map could be faster).
If you must iterate, then you can at least use a parallel stream to take advantage of multiple cores if you have them:
if (persons.parallelStream().anyMatch(p -> p.personID == id)) {
...
}

Implementing search based on 2 fields in a java class

I am trying to present a simplified version of my requirement here for ease of understanding.
I have this class
public class MyClass {
private byte[] data1;
private byte[] data2;
private long hash1; // Hash value for data1
private long hash2; // Hash value for data2
// getter and setters }
Now I need to search between 2 List instances of this class, find how many hash1's match between the 2 instances and for all matches how many corresponding hash2's match. The 2 list will have about 10 million objects of MyClass.
Now I am planning to iterate over first list and search in the second one. Is there a way I can optimize the search by sorting or ordering in any particular way? Should I sort both list or only 1?
Best solution would be to iterate there is no faster solution than this. You can create Hashmap and take advantage that map does not add same key but then it has its own creation overload
sort only second, iterate over first and do binary search in second, sort O(nlogn) and binary search for n item O(nlogn)
or use hashset for second, iterate over first and search in second, O(n)
If you have to check all the elements, I think you should iterate over the first list and have a Hashmap for the second one as said AmitD.
You just have to correctly override equals and hashcode in your MyClass class. Finally, I will recomend you to use basic types as much as possible. For example, for the first list, instead of a list will be better to use a simple array.
Also, at the beginning you could select which of the two lists is the shorter one (if there's a difference in the size) and iterate over that one.
I think you should create a hashmap for one of the lists (say list1) -
Map<Long, MyClass> map = new HashMap<Long, MyClass>(list1.size());//specify the capacity
//populate map like - put(myClass.getHash1(), myClass) : for each element in the list
Now just iterate through the second list (there is no point in sorting both) -
int hash1MatchCount = 0;
int hash2MatchCount = 0;
for(MyClass myClass : list2) {
MyClass mc = map.get(myClass.getHash1());
if(mc != null) {
hash1MatchCount++;
if(myClass.getHash2() == mc.getHash2) {
hash2MatchCount++;
}
}
}
Note: Assuming that there is no problem regarding hash1 being duplicates.

Adding items to empty List at specific locations in java

Is there any way I can make the below code work without commenting the 3rd line.
List<Integer> list = new ArrayList<Integer>();
list.add(0,0);
//list.add(1,null);
list.add(2,2);
I want to add items to list at specific locations. But if I don't change the index to Nth position I am not being able to add at Nth as told in this answer.
I can't use a map because I don't want to miss a value when the keys are same. Also adding null values to a list for large lists will be an overhead. When there is a collision I want the item to take the next position(nearest to where it should have been).
Is there any List implementation that shifts index before it tries to add the item?
Use something like a MultiMap if your only concern is not "missing a value" if the keys are the same.
I'm not sure how doing a shift/insert helps if I understand your problem statement--if the "key" is the index, inserting will lose the same information.
You can use Vector and call setSize to prepopulate with null elements.
However, your comment about the overhead of the nulls speaks to an associative container as the right solution.
This still smells like you should be using a Map. Why not use a Map<Integer, List<Integer>>?
something like,
private Map<Integer, List<Integer>> myMap = new HashMap<Integer, List<Integer>>();
public void addItem(int key, int value) {
List<Integer> list = myMap.get(key);
if (list == null) {
list = new ArrayList<Integer>();
myMap.put(key, list);
}
list.add(value);
}
public List<Integer> getItems(int key) {
return myMap.get(key);
}
Well, There are a couple of ways I would think to do this, if you are not adding items too frequently, then it might be a good idea to simply do a check to see if there is an item at that location before adding it.
if(list.get(X) == null)
{
list.add(X,Y);
}
Otherwise if you are going to be doing this too often...then I would recommend creating your own custom List class, and extending ArrayList or whatever you are using, and simply override the add method, to deal with collisions.

removing duplicates from an arraylist

I am trying to remove duplicate objects from an arraylist
see code below:
ArrayList<Customer> customers=new ArrayList<Customer>();
for(int i=0;i<accounts.size();i++){
customers.add(accounts.get(i).getCustomer());
}
for(int i=0;i<customers.size();i++){
for(int j=i+1;j<customers.size();j++){
if(customers.get(i).getSocialSecurityNo().compareTo(customers.get(j).getSocialSecurityNo())==0){
if(customers.get(i).getLastName().compareToIgnoreCase(customers.get(j).getLastName())==0){
if(customers.get(i).getFirstName().compareToIgnoreCase(customers.get(j).getFirstName())==0){
customers.remove(j);
}
}
}
}
}
However, it seems that the last object in the list is not being processed. Perhaps someone can pinpoint the error
Try adding j--; after removing an item. That will reindex for you and solve your issue.
The basic flaw is that since the ListArray is mutable, once you remove one element your indexes have to be readjusted.
if(customers.get(i).getFirstName().compareToIgnoreCase(customers.get(j).getFirstName())==0){
customers.remove(j--);
}
also try subtracting one from your i loop:
for(int i=0;i<customers.size()-1;i++){
for(int j=i+1;j<customers.size();j++){
public static void removeDuplicates(ArrayList list) {
HashSet set = new HashSet(list);
list.clear();
list.addAll(set);
}
override equals and hashcode appropriatley
custormers = new ArrayList(new HashSet(customers))
ensure the equals and hashmethod are correctly implemented
The code below worked for me. Give it a try. You can manipulate the compare method to suit your taste
ArrayList customers = .....;
Set customerlist = new TreeSet(new Comparator(){
#Override
public int compare(Customer c1, Customer c2) {
return c1.getSocialSecurityNo().compareTo(c2.getSocialSecurityNo());
}
});
customerlist.addAll(customers);
customers.clear();
customers.addAll(customerlist);
It's your int j=i+1 that causes trouble. You need to test with the last value of the customers list for each iteration.
Before you add them to the list in the above loop, why don't you check
if(!cutomers.contains(accounts.get(i).getCustomer())
{
//add them if it doesn't contain
}
It should save you from doing the second loop
Edit: Need to override the equals method.
So, about doing this right:
Your Customer objects should have an equals() and hashCode() method, which do the comparison. (Or you simply would have only one Customer object for each customer, which would mean your data model would have to be adjusted. Then the default hashCode/equals would do.)
If you have this, you can replace your three nested ifs with one:
if(customers.get(i).equals(customers.get(j)) {
customers.remove(j);
}
This would not yet solve your problem, but make it easier to have a clearer look on it. If
you look at which objects are compared to which others, you will see that after each removal
of an object from the list, the next one has the same index as the one which you just removed,
and you will not compare the current object to it. As said, j-- after the removal will solve this.
A more performant solution would be using a Set (which is guaranteed not to contain duplicates).
In your case, a HashSet<Customer> or LinkedHashSet<Customer> (if you care about the order)
will do fine.
Then your whole code comes down to this:
Set<Customer> customerSet = new HashSet<Customer>();
for(Account acc : accounts){
customerSet.add(acc.getCustomer());
}
List<Customer> customers = new ArrayList<Customer>(customerSet);
If you don't really need a list (i.e. indexed access), ommit the last line and simply
use the set instead.
My first thought was to use Sets, as others have mentioned. Another approach would be to use Java's version of the foreach, instead of using indexes. A general approach:
public static ArrayList removeDuplicates(ArrayList origList) {
ArrayList newList = new ArrayList();
for (Object m : origList) {
if (!newList.contains(m)) {
newList.add(m);
}
}
return newList;
}
In testing, I just used Strings; I'd recommend inserting Customer into the code where appropriate for type safety.

Categories

Resources