Dynamic order by with java with list - java

I want to sort my list(taken from the db I can't sort using query), but I don't know for what parameters, the parameters are chosen by the user. The following code sorts by first and last name in desc, but how do I create a dynamic one? I have made many attempts, but I have failed.
myList.sort(Comparator.comparing(DTO::getName)
.thenComparing(DTO::getLastName).reversed());

To create a customized order depending on the user params, I suggest something like :
1- Create an enumeration of the possible sorting orders which will be populated by the parameters in the user query, say that, for example, I have 3 orders BY_ID, BY_NAME, BY_LAST_NAME.
2- Then create a list of this enumeration;
3- After that create a map, where the key is the enumeration above, and the value is a comparator.
4- populate the map.
5-Derive a reduced final comparator from both the list of orders you had in (2), and the map in (3).
6- Sort your list by that final comparator.
Putting all the steps together
The enumeration of the sort orders :
public enum SortOrder {
BY_ID,
BY_NAME,
BY_LAST_NAME;
}
The main test method :
public static void main(String[] args) {
/**dummy objects for test purposes**/
DTO firstDTO = new DTO();
firstDTO.setId(1);
firstDTO.setName("John");
firstDTO.setLastName("Doe");
DTO secondDTO = new DTO();
secondDTO.setId(2);
secondDTO.setName("John");
secondDTO.setLastName("Doe");
DTO thirdDTO = new DTO();
thirdDTO.setId(2); // the id is repeated
thirdDTO.setName("Alice");
thirdDTO.setLastName("Bob");
final List<SortOrder> sortOrders = createSortOrdersFromInputParams(/*the params passed as args*/);
final List<DTO> myList = Arrays.asList(firstDTO, secondDTO, thirdDTO); // your list that is supposed to come from a DB
final Map<SortOrder, Comparator<DTO>> map = new EnumMap<>(SortOrder.class);
final Comparator<DTO> defaultComparator = Comparator.nullsLast(Comparator.comparing(DTO::getId)); // you'all always need a default comparator , say by ID
map.put(SortOrder.BY_ID, defaultComparator);
map.put(SortOrder.BY_NAME, Comparator.nullsLast(Comparator.comparing(DTO::getName)));
map.put(SortOrder.BY_LAST_NAME, Comparator.nullsLast(Comparator.comparing(DTO::getLastName)));
final Comparator<DTO> finalComparator = sortOrders.stream()
.map(map::get)
.filter(Objects::nonNull)
.reduce(Comparator::thenComparing)
.orElse(defaultComparator);
myList.sort(finalComparator);
System.out.println(myList);
}
The method that turns parameters of the user input into sort orders
private static List<SortOrder> createSortOrdersFromInputParams(/*params passed as args*/) {
return Arrays.asList(SortOrder.BY_ID, SortOrder.BY_NAME, SortOrder.BY_LAST_NAME);
}
I hope this will respond to your case.

Related

Sorting TreeMaps by value

I'm looking to sort a TreeMap of Customer objects by a specific Customer property.
The TreeMap is defined as so:
private TreeMap<Long,Customer> customerMap = new TreeMap<>();
Long is the type of ID for the customers stored.
I wrote a function to create a new TreeMap and pass a Comparator to its constructor which gets map entries, their values, that compares the specific field.
public Customer[] getCustomersByName() {
TreeMap<Long,Customer> sortByName = new TreeMap<> (
new Comparator<Map.Entry<Long,Customer>>() {
#Override public int compare(Map.Entry<Long,Customer> cus1, Map.Entry<Long,Customer> cus2) {
return cus1.getValue().getLastName().compareTo(cus2.getValue().getLastName());
}
}
);
sortByName.putAll(customerMap);
// sortByName to Customer[] and return.
}
This doesn't work and throws: Cannot infer type arguments for TreeMap<>Java(16778094) at Line 2.
Perhaps, the issue is that the Comparator takes <Map.Entry<K,V>> to compare a TreeMap<K,V> and that is the issue.
How would I fix this though to sort by values but keep the customerMap type as is?
I know that TreeMaps are only sorted by keys. Is there a better data structure for this job perhaps so that I could store a bunch of Customer objects and sort them by different Customer properties without the operation being too expensive (not polynomial preferably)?
Set up a second TreeMap, using the customers last name as the key:
TreeMap<String,Customer> sortByName = new TreeMap<>();
TreeMap<Long,Customer> sortByID = new TreeMap<>();
----------------
sortByName.put(customer.getLastName(), customer);
sortByID.put(new Long(customer.getID()), customer);
----------------
return sortByName.values().toArray( new Customer[sortByName.size()] );
'''
This is rather easy with streams:
Customer[] cust =
customerMap.values()
.stream()
.sorted(Comparator.comparing(Customer::getName))
.toArray(Customer[]::new);
You only need to sort the values according to your example, so why bother sorting the TreeMap in reverse, when the only thing you care about is a sorted (by name) Customer[]?

Create list of an element of an Object from another list of Objects

The model:
public class MyModel{
private int id;
private String name;
....
....
//getters and setters
}
I have a list of MyModel object:
//First Object to be added to list
MyModel myModelObject1 = new MyModel();
myModelObject1.setId(1);
myModelObject1.setName("abc");
//Second Object to be added to list
MyModel myModelObject2 = new MyModel();
myModelObject1.setId(2);
myModelObject1.setName("pqr");
List<MyModel> myModelList = new ArrayList<MyModel>();
myModelList.add(myModelObject1);
myModelList.add(myModelObject2);
I want to get a list of names present in the MyModel List i.e. I want to create a list of names (List<String> in this case) from myModelList. So, I want my list to have:
{"abc", "pqr"}
There is always a way to iterate and create another list but is there any better way to do that? (Not necessarily to be efficient but if it can be done in a line using streams, foreach e.t.c.).
EDIT:
The answers worked for me but I have some modifications in my use case: If I want to add a condition that only name which contains character 'a' should be added to the list and I want to add a logger message to debug for each element then how should I approach this?
I tried doing the following using a method (charAPresent()) which checks that if the passed String contains character 'a' but it didn't work:
List<String> modelNameList = myModelList.stream()
.map(model -> {
if (charAPresent(model.getName)) {
model.getName()
}
})
.collect(Collectors.toList());
Let me know if I am missing something.
Using Java 8 Stream:
List<String> modelNameList = myModelList.stream()
.map(Model::getName)
.collect(Collectors.toList());
Model::getName is called as method reference. It equivalents to model -> model.getName()
You can use streams and map your object to its name and collect to a list as:
List<String> names = myModelList.stream()
.map(MyModel::getName)
.collect(Collectors.toList());
There is always a way to iterate and create another list but is there
any better way to do that
Even with the use of streams, you would have to iterate the complete collection. There is no better way of doing it than iterating the complete collection.
You can use Stream.map() to get only the names of your model. Use Stream.filter() to get only the names matching your charAPresent() method. To log the entries before collecting you can use Stream.peek():
List<String> modelNameList = myModelList.stream()
.map(Model::getName) // map only the name
.filter(YourClass::charAPresent) // filter the items
.peek(System.out::println) // print all items which are in the filter
.collect(Collectors.toList());
You can also use foreach like this:
public static List<String> modelNames(List<MyModel> myModelList)
List<String> list = new ArrayList<String>();
for(MyModel mm : myModelList) {
if(mm.getName().contains("a") {
list.add(mm.getName());
}
}
return list;
}

How can I obtain the list of values for a given key and add the list of values to a newly created list?

So I'm going crazy with this one. This is for an assignment and can't seem to get this to work at all!!
I have the following HashMap:
HashMap<String, ArrayList<Team>> teams;
(Team being another class to obtain the details of the teams)
What I need to be able to do is get the List of teams for the Key(String) from the above HashMap, and assign the List to a local variable I have declared:
List<Team> results = teams.get(division);
But this is where I get stuck. I have no idea how I'm suppose to complete this task.
As a further note "division" is the Key used in the HashMap. The ArrayList is a list of teams that belong to the division.
I have tried the below, which does not compile at all. Really not sure how I can get this to work!!
public void recordResult(String division, String teamA, String teamB, int teamAScore, int teamBScore)
{
List<Team> results = teams.get(division);
for (String i : teams.keySet())
{
results = new ArrayList<Team>();
results.add();
}
}
**You can ignore the arguments after the "String division". These will be used later.
Iterate over the entrySet() of the Map. Now you can fetch each List for that specific key and proceed further. Something like:
for (Entry<String, ArrayList<Team>> entry : teams.entrySet()) {
// extract the value from the key using `teams.get(entry.getKey())`
// proceed further with the value obtained
}

JAVA : Best performance-wise method to find an object stored in hashMap

I have a bunch of objects stored in hashMap<Long,Person> i need to find the person object with a specific attribute without knowing its ID.
for example the person class:
public person{
long id;
String firstName;
String lastName;
String userName;
String password;
String address;
..
(around 7-10 attributes in total)
}
lets say i want to find the object with username = "mike". Is there any method to find it without actually iterating on the whole hash map like this :
for (Map.Entry<Long,Person> entry : map.entrySet()) {
if(entry.getValue().getUserName().equalsIgnoreCase("mike"));
the answers i found here was pretty old.
If you want speed and are always looking for one specific attribute, your best bet is to create another 'cache' hash-map keyed with that attribute.
The memory taken up will be insignificant for less than a million entries and the hash-map lookup will be much much faster than any other solution.
Alternatively you could put all search attributes into a single map (ie. names, and ids). Prefix the keys with something unique if you're concerned with collisions. Something like:
String ID_PREFIX = "^!^ID^!^";
String USERNAME_PREFIX = "^!^USERNAME^!^";
String FIRSTNAME_PREFIX = "^!^FIRSTNAME^!^";
Map<String,Person> personMap = new HashMap<String,Person>();
//add a person
void addPersonToMap(Person person)
{
personMap.put(ID_PREFIX+person.id, person);
personMap.put(USERNAME_PREFIX+person.username, person);
personMap.put(FIRSTNAME_PREFIX+person.firstname, person);
}
//search person
Person findPersonByID(long id)
{
return personMap.get(ID_PREFIX+id);
}
Person findPersonByUsername(String username)
{
return personMap.get(USERNAME_PREFIX+username);
}
//or a more generic version:
//Person foundPerson = findPersonByAttribute(FIRSTNAME_PREFIX, "mike");
Person findPersonByAttribute(String attr, String attr_value)
{
return personMap.get(attr+attr_value);
}
The above assumes that each attribute is unique amongst all the Persons. This might be true for ID and username, but the question specifies firstname=mike which is unlikely to be unique.
In that case you want to abstract with a list, so it would be more like this:
Map<String,List<Person>> personMap = new HashMap<String,List<Person>>();
//add a person
void addPersonToMap(Person person)
{
insertPersonIntoMap(ID_PREFIX+person.id, person);
insertPersonIntoMap(USERNAME_PREFIX+person.username, person);
insertPersonIntoMap(FIRSTNAME_PREFIX+person.firstname, person);
}
//note that List contains no duplicates, so can be called multiple times for the same person.
void insertPersonIntoMap(String key, Person person)
{
List<Person> personsList = personMap.get(key);
if(personsList==null)
personsList = new ArrayList<Person>();
personsList.add(person);
personMap.put(key,personsList);
}
//we know id is unique, so we can just get the only person in the list
Person findPersonByID(long id)
{
List<Person> personList = personMap.get(ID_PREFIX+id);
if(personList!=null)
return personList.get(0);
return null;
}
//get list of persons with firstname
List<Person> findPersonsByFirstName(String firstname)
{
return personMap.get(FIRSTNAME_PREFIX+firstname);
}
At that point you're really getting into a grab-bag design but still very efficient if you're not expecting millions of entries.
The best performance-wise method I can think of is to have another HashMap, with the key being the attribute you want to search for, and the value being a list of objects.
For your example this would be HashMap<String, List<Person>>, with the key being the username. The downside is that you have to maintain two maps.
Note: I've used a List<Person> as the value because we cannot guarantee that username is unique among all users. The same applies for any other field.
For example, to add a Person to this new map you could do:
Map<String, List<Person>> peopleByUsername = new HashMap<>();
// ...
Person p = ...;
peopleByUsername.computeIfAbsent(
p.getUsername(),
k -> new ArrayList<>())
.add(p);
Then, to return all people whose username is i.e. joesmith:
List<Person> matching = peopleByUsername.get("joesmith");
Getting one or a few entries from a volatile map
If the map you're operating on can change often and you only want to get a few entries then iterating over the map's entries is ok since you'd need space and time to build other structures or sort the data as well.
Getting many entries from a volatile map
If you need to get many entries from that map you might get better performance by either sorting the entries first (e.g. build a list and sort that) and then using binary search. Alternatively you could build an intermediate map that uses the attribute(s) you need to search for as its key.
Note, however, that both approaches at least need time so this only yields better performance when you're looking for many entries.
Getting entries multiple times from a "persistent" map
If your map and its valuies doesn't change (or not that often) you could maintain a map attribute -> person. This would mean some effort for the initial setup and updating the additional map (unless your data doesn't change) as well as some memory overhead but speeds up lookups tremendously later on. This is a worthwhile approach when you'd do very little "writes" compared to how often you do lookups and if you can spare the memory overhead (depends on how big those maps would be and how much memory you have to spare).
Consider one hashmap per alternate key.
This will have "high" setup cost,
but will result in quick retrieval by alternate key.
Setup the hashmap using the Long key value.
Run through the hashmap Person objects and create a second hashmap (HashMap<String, Person>) for which username is the key.
Perhaps, fill both hashmaps at the same time.
In your case,
you will end up with something like HashMap<Long, Person> idKeyedMap and HashMap<String, Person> usernameKeyedMap.
You can also put all the key values in the same map,
if you define the map as Map<Object, Person>.
Then,
when you add the
(id, person) pair,
you need to also add the (username, person) pair.
Caveat, this is not a great technique.
What is the best way to solve the problem?
There are many ways to tackle this as you can see in the answers and comments.
How is the Map is being used (and perhaps how it is created). If the Map is built from a select statement with the long id value from a column from a table we might think we should use HashMap<Long, Person>.
Another way to look at the problem is to consider usernames should also be unique (i.e. no two persons should ever share the same username). So instead create the map as a HashMap<String, Person>. With username as the key and the Person object as the value.
Using the latter:
Map<String, Person> users = new HashMap<>();
users = retrieveUsersFromDatabase(); // perform db select and build map
String username = "mike";
users.get(username).
This will be the fastest way to retrieve the object you want to find in a Map containing Person objects as its values.
You can simply convert Hashmap to List using:
List list = new ArrayList(map.values());
Now, you can iterate through the list object easily. This way you can search Hashmap values on any property of Person class not just limiting to firstname.
Only downside is you will end up creating a list object. But using stream api you can further improve code to convert Hashmap to list and iterate in single operation saving space and improved performance with parallel streams.
Sorting and finding of value object can be done by designing and using an appropriate Comparator class.
Comparator Class : Designing a Comparator with respect to a specific attribute can be done as follows:
class UserComparator implements Comparator<Person>{
#Override
public int compare(Person p1, Person p2) {
return p1.userName.compareTo(p2.userName);
}
}
Usage : Comparator designed above can be used as follows:
HashMap<Long, Person> personMap = new HashMap<Long, Person>();
.
.
.
ArrayList<Person> pAL = new ArrayList<Person>(personMap.values()); //create list of values
Collections.sort(pAL,new UserComparator()); // sort the list using comparator
Person p = new Person(); // create a dummy object
p.userName="mike"; // Only set the username
int i= Collections.binarySearch(pAL,p,new UserComparator()); // search the list using comparator
if(i>=0){
Person p1 = pAL.get(Collections.binarySearch(pAL,p,new UserComparator())); //Obtain object if username is present
}else{
System.out.println("Insertion point: "+ i); // Returns a negative value if username is not present
}

Create lists with java

I am very new in java. I want to create a kind of complex array. I think its called list/collection/map...
My data looks like
-item
ref:GH987
size:22
date:1992
-item
ref:98KJ
size:27
date:2000
-item
ref:ZXJ212
size:24
date:1999
I do not prefer to create an item Class and store the 3 instance in an array.
I found something called Map but it really confused me and I don't understand how could I access the values after creating the data. Could you help me how to start with this ?
final Map<String, List<String>> data = new LinkedHashMap<String, List<String>>();
data.put("item", new LinkedList<String>());
You have to create an Item class, that's the whole point of OOP!
Very minimal example:
public class Item {
public String ref;
public int size;
public int date;
public Item(String ref, int size, int date) {
this.ref = ref;
this.size = size;
this.date = date;
}
}
Then it's just a List<Item> and you can access each part with myList.get(i).ref etc:
List<Item> l = new ArrayList<>();
l.add(new Item("GH987", 22, 1992));
l.add(new Item("98KJ", 27, 2000));
...
for (Item it : l)
System.out.println("Ref: "+item.ref+", size: "+item.size+", date: "+item.date);
Now, if you really want to use a Map to store each attribute, you have to think what would be your unique key. Let's suppose it's ref, which is a String:
Map<String,Integer> sizes = new LinkedHashMap<>(); // LinkedHashMap keeps the insert order
Map<String,Integer> dates = new LinkedHashMap<>();
sizes.put("GH987", 22);
dates.put("GH987", 1992);
sizes.put("98KJ", 27);
dates.put("98KJ", 2000);
then it's difficult to access all members as they're not bundled in a single instance:
String ref = "GH987";
System.out.println("Ref: "+ref+", size: "+sizes.get(ref)+", date: "+dates.get(ref))
Here you should realize that if a Map hasn't been updated, it will return null on the value and you'll have to handle consistency yourself. It is also a pain to create so many objects just to store single attributes, which in your case are Number subclasses (e.g. Integer) instead of native types, which are far more efficient.
So do yourself a favor, and create your Item class. Then you can use a Map to quickly access a particular item based on its key, which looks like the ref member:
myMap.put(ref, new Item(ref, size, date));
Item it = myMap.get(ref);
...
Yes you can choose Map with a class and having it's reference as a key for ex
Map<String,Item> map = new HashMap<>();
Assuming reference is unique. You can store values like
map.put(item.getReference(),item);

Categories