Binary search Java list with key extractor - java

Say I have:
class Item {
int id;
int type;
}
I can do this:
List<Item> items;
Item itemToFind;
Comparator<Item> itemComparator;
Collections.binarySearch(items, itemToFind, itemComparator);
However, say that instead of the whole object, I'm only given a property of the object, say type for the above example. Assuming the list is sorted by that property, is there a standard method in Java or some established library to do something to this effect:
List<Item> items;
Function<Item, Integer> typeExtractor = Item::getType;
int typeToFind;
Comparator<Integer> typeComparator = Integer::compare;
binarySearch(items, typeExtractor, typeToFind, typeComparator);
without additional overhead (e.g. converting List<Item> to List<Integer> to call Collections.binarySearch on or similar)?

Your problem is that in a Collection<T> the binary search implementation of java allows to search only for items of type T. In order to search for another type which is a part of T, you can do the following:
Wrap the other type inside T, in your case this should look like this:
List<Item> items;
int typeToFind;
Item itemToFind = new Item(/* random value for id */ 0, typeToFind);
int index = binarySearch(items, itemToFind , (a, b) -> a.getType() - b.getType());
Some important notes to add here:
- the comparison of items should depend only and only on `type`, otherwise you may end up with some nasty bugs;
- the list of items should be sorted. The sorting should depend only and only on `type`(basically using the same comparator as before)
Create a new list from the initial list:
List<Items> items;
int typeToFind
int index = binarySearch(items.stream.map(item -> item.getType()).collect(Collectors.toList()), itemToFind);
As far as I know, the standard library for Java does not provide binary search implementation with a comparator for key equality. If these options does not satisfy you, probably you should search for a library or implement your own search.

The comparator still would be Comparator<Item>. What you would change is the implementation of the comparator to evaluate upon the type instead of the id.
Comparator<Item> comparator = new Comparator<Item>(){
public int compare(Item a, Item b)
{
return a.getType() - b.getType();
}
}
Item, would need to have the getter for type or the attribute made public. The same if using the id.
However, not sure how you suggest I would call
Collections.binarySearch
The usage doesn't change (what changes is how the comparison is done inside the comparator object):
Item itemToFind = new Item();
itemToFind.setType(typeToFind);
Collections.binarySearch(items, itemToFind, comparator );
After some thought on the subject:
An alternative to use an Item as a needle is to base the Comparator on an Interface that Item and the needle implement.
An interface to return int values:
public interface Intgettable{
public int getInt();
}
Item should have to implement this interface:
public class Item implements Intgettable{
private int id;
private int type;
public void setId(int id){
this.id = id;
}
public void setType(int type){
this.type = type;
}
public int getId(){
return id;
}
public int getType(){
return type;
}
public int getInt(){
return type;
}
}
The key to search will be an Intgettable which can be created:
1 - Using a class that extends Intgettable.
public static class MyItemKey implements Intgettable{
private int value;
public MyItemKey(int v){
this.value = v;
}
#Override
public int getInt(){
return value;
}
}
MyItemKey typeToFind = new MyItemKey(6);
2 - As an anonymous class inside the method.
Intgettable typeTofind = new Intgettable(){
private int value = 6;
public int getInt(){
return value;
}
};
3 - Or using the lambda version:
Intgettable typeTofind = ()->{return 6;};
The Comparator will be:
Comparator<Intgettable> comparator = new Comparator<Intgettable>(){
public int compare(Intgettable a, Intgettable b){
return a.getInt() - b.getInt();
}
};
And finally use it in the binary search:
Collections.binarySearch(items, typeToFind, comparator );

Related

How to create a Queue which accepts any Datatype?

How to insert both String's and Integers in the same Queue ?
Please look at the below program, I have created two objects (q, q1). Can we insert strings and integers in one Queue?
import java.util.*;
public class Solution {
public void myMethod() {
Queue<Integer> q=new PriorityQueue<>();
Queue<String> q1=new PriorityQueue<>();
q.add(3);
q1.add("Eswar");
System.out.println(q);
System.out.println(q1);
}
public static void main(String...args) {
Solution s=new Solution();
s.myMethod();
}
}
You can:
Queue<Object> q=new PriorityQueue<>();
But you have to care about correct types now.
You can do it without generics, but its not recommended as you can have runtime exceptions:
List list = new ArrayList();
list.add("Hi");
String s = (String) list.get(0);
It is not suggested to have two different types in the same Collection with Generics using Object or RawType as it might lead to runtime errors,
Option 1 - Try to keep the Integer also in String representation If it fills your purpose,
Queue<String> q1=new PriorityQueue<>();
q1.add(String.valueOf(1));
q1.add("Eswar");
System.out.println(q1);
Option 2 - Create your own type which contains both and manage in through enum if only you have to use PriorityQueue. Please note you have to add your own Comparator,
Enum for DataType
enum DataType{
INTEGER,
STRING
}
Your Own DataType class
class MyData implements Comparable<MyData>{
private Integer intValue;
private String stringValue;
private DataType type;
public MyData(Integer intValue) {
this.intValue=intValue;
type=DataType.INTEGER;
}
public MyData(String stringValue) {
this.stringValue=stringValue;
type=DataType.STRING;
}
public Integer getIntValue() {
return intValue;
}
public String getStringValue() {
return stringValue;
}
public DataType getType() {
return type;
}
#Override
public String toString() {
return type==DataType.INTEGER?intValue.toString():stringValue;
}
#Override
public int compareTo(MyData data) {
//Add your comparator here based on your criteria
return 0;
}
}
And the implementation
Queue<MyData> q1=new PriorityQueue<>();
q1.add(new MyData(1));
q1.add(new MyData("Eswar"));
System.out.println(q1);
Using a Queue<Object> you'll be able to add any type you want, becauseObject is a supertpe of every one. But you cannot use any implementation, like PriorityQueue because it requires a sort on the elements, and different types are not comparable together. An ArrayDeque would be good for that.
Queue<Object> q = new ArrayDeque<>();
q.add(5);
q.add("Foo");
q.add(5d);
Workable Demo

I can't iterate through TreeSet

I have to add User identified by his id into set and in runtime all users form that set have to be sorted by this id.
I've created TreeSet added some User objects and tried to iterate through it.
Here is my attempt:
//irrelevant code removed
TreeSet<User> userSet = new TreeSet<User>();
userSet.add(new User(2));
userSet.add(new User(1));
userSet.add(new User(3));
Iterator<User> iterator = userSet.iterator();
while (iterator.hasNext()) {
System.out.print(iterator.next() + " ");
}
I wrote class User where is one of the fields id and constructor have id as parameter.
public class User {
private int id;
// irrelevant code removed
public User(int id) {
this.id = id;
}
// irrelevant code removed
public String toString() {
return id + "";
}
}
When i run this code I get ClassCastException.
Here is the stacktrace:
Exception in thread "main" java.lang.ClassCastException: OrderedUsers$User cannot be cast to java.lang.Comparable
at java.util.TreeMap.compare(TreeMap.java:1188)
at java.util.TreeMap.put(TreeMap.java:531)
at java.util.TreeSet.add(TreeSet.java:255)
at OrderedUsers.<init>(OrderedUsers.java:9)
at Main.main(Main.java:6)
What I am doing wrong?
You are on the right way when you decided to use TreeSet because with TreeSet you can get ordered output. But...
Note that if you use TreeSet, because of TreeSet is sorted you have to implement Comparable.
When you implement Comparable you will get what you expected.
I suggest that you perform changes like this:
public class User implements Comparable<User> {
private int id;
// irrelevant code removed
public User(int id) {
this.id = id;
}
// irrelevant code removed
public String toString() {
return id + "";
}
#Override
public int compareTo(User u) {
return id - u.id;
}
}
Either pass a custom Comparator to TreeSet constructor or implement Comparable in your model class
TreeSet maintains sorted order and it needs to know how Users can be compared
Here's the statement at TreeMap.java 1188:
return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2)
: comparator.compare((K)k1, (K)k2);
So if comparator is null, then it will try to cast the set member to a (Comparable). If your class doesn't implement Comparable, this will cause the ClassCastException you saw. comparator is non-null only if you call the TreeMap constructor that provides a Comparator (or if you copy it from another SortedMap that already has a comparator).
TreeSet internally stores the object by performing comparasions with the existing ones similar to Binary Search Tree (actually its a Red-Black tree). So you need to implement interface Comparable in User or provide a custom Comparator to the TreeSet.
If you dont want the user objects to be stored in a sorted order I would recommend using ArrayList.
Method 1 :
public class User implements Comparable<User>
{
public int compare(User u)
{
if( u == null)
return 1;
return id - u.id;
}
}
Method 2 :
public class CompareUsers implements Comparator<User>
{
public int compareTo(User a, User b)
{
if(a == null)
return -1;
if(b == null)
return 1;
return a.id - b.id;
}
}
// Create an instance of this comparator class and pass to the TreeSet
// during initialization.
TreeSet<User> userSet = new TreeSet<User>(new CompareUsers());

How to implement a Map with array list as keys

I have HashMap with ArrayList as key and value as Integer, how can I get value from specific key.
Map< List<Object>,Integer> propositionMap=new HashMap<List<Object>,Integer>();
my key are:[Brand, ID], [Launch, ID], [Model, ID], [Brand, UserModelNoMatch], [ProducerPrice, UserModelMatch], [ProducerPrice, ID]]
my values are:[3, 5, 4, 2, 1, 6]
In my program in several time in different place i need to find a specific value for the specific key. i do not want to use for loop evry time to get value.
how can i do that?
Putting aside that this is a bad idea (as described in the comments), you don't need to do anything special:
List<Object> list = new ArrayList<Object>();
// add objects to list
Map<List<Object>,Integer> propositionMap = new HashMap<List<Object>,Integer>();
propositionMap.put(list, 1);
Integer valueForList = propositionMap.get(list); // returns 1
You can get the same value when constructing a list independently:
List<Object> list2 = new ArrayList<Object>();
// add the same objects (by equals and by hashcode) to list2 as to list
Integer valueForList = propositionMap.get(list2); // returns 1
But you need to be careful not to change the list after you use it as a key in the map!
list.add(new Object());
Integer valueForList = propositionMap.get(list); // likely returns null
Again, it's very likely a bad idea.
Seeing as how you want the same behavior, I strongly recommend using an interface with classes.
public interface Proposition
{
public int getID();
}
public class Brand implements Proposition
{
private int id;
public Brand(int _id_)
{
this.id = _id_;
}
public int getID()
{
return this.id;
}
}
public class Launch implements Proposition
{
private int id;
public Launch(int _id_)
{
this.id = _id_;
}
public int getID()
{
return this.id;
}
}
public class ProducerPrice implements Proposition
{
private int id;
private int UserModelMatch;
public ProducerPrice(int _id_, int _UserModelMatch_)
{
this.id = _id_;
this.UserModelMatch = _UserModelMatch_;
}
public int getID()
{
return this.id;
}
public int getUserModelMatch()
{
return this.UserModelMatch;
}
}
And then using a hashmap for proposition objects
Map<Integer, Proposition> propositionMap = new HashMap<Integer, Proposition>();
Proposition newprop = new ProducerPrice(6, 1);
propositionMap.put(newprop.getID(), newprop);
Proposition someprop = propositionMap.get(6);
if (someprop instanceof ProducerPrice)
{
ProducerPrice myprodprice = (ProducerPrice)someprop;
// rest of logic here
}
You can get the value as usual way :
propositionMap.get(arrayListN)
until you modify the list itself after adding.

Multiple Field Collection Sort

I have a class Arraylist contains value
String word, String expandedword, double confidence, double support
I want to sort the arraylist based on the confidence, and then based on the support.
I have succeed sort the arraylist based on confidence, but i failed to make a new method to sort the arraylist based on the support
this is my code to sort it based on confidence
public class ExpandedTerm implements Comparable<ExpandedTerm> {
String word;
String expandedWord;
double support;
double confidence;
public ExpandedTerm (String word,String expandedWord, double confidence,double support){
this.word = word;
this.expandedWord = expandedWord;
this.support = support;
this.confidence = confidence;
}
public String getWord(){
return word;
}
public String expandedWord(){
return expandedWord;
}
public Double getSupport(){
return support;
}
public Double getConfidence(){
return confidence;
}
#Override
public int compareTo(ExpandedTerm conf) {
return new Double(this.confidence).compareTo(new Double(conf.confidence));
}
I failed to make another method like compareTo, to sort it based on the support value.
How to sort it first by the confidence, and then make another method to sort it by the support value?
User Comparator for this. As compaarble provide functionality to sort on single type basis.
here is link where you found when to use comparable and comapartor
http://iandjava.blogspot.in/2012/10/comparable-and-comparator.html
Use multiple comparartor
one for confidence
public class ConfidanceComparator implements Comparator<ExpandedTerm> {
#Override
public int compare(final ExpandedTerm o1, final ExpandedTerm o2) {
return new Double(o1.confidence).compareTo(new Double(o2.confidence));
}
}
one for support
public class SupportComparator implements Comparator<ExpandedTerm> {
#Override
public int compare(final ExpandedTerm o1, final ExpandedTerm o2) {
return new Double(o1.support).compareTo(new Double(o2.support));
}
}
and use Collections.sort(<List>, <comparator>) adn you will get the desired list.
THis is only required when you want to sort either on confidance basis or support basis.
But if you required then first sort on confidance basis and if confidance is equal then check for support basis. then comparable is sufficient and is
public int compareTo(ExpandedTerm conf) {
int compare = new Double(this.confidence).compareTo(new Double(conf.confidence));
if (compare == 0) {
compare = new Double(this.support).compareTo(new Double(conf.support));
}
return compare;
}
Try this code for your compareTo method:
#Override
public int compareTo(ExpandedTerm other) {
Double thisConfidence = new Double(getConfidence());
Double otherConfidence = new Double(other.getConfidence());
int compare = thisConfidence.compareTo(otherConfidence);
if (compare == 0) {
Double thisSupport = new Double(getSupport());
Double otherSupport = new Double(other.getSupport());
compare = thisSupport.compareTo(otherSupport);
}
return compare;
}
Basically only compare "support" if "confidence" is equal.
I saw your reply that you want to sort once and sort again except differently, so I'm assuming you want to add a custom Comparator when you sort. Is this what you're looking for?
public static void main(String[] args) {
ExpandedTerm term1 = new ExpandedTerm("a", "b", 1, 4);
ExpandedTerm term2 = new ExpandedTerm("c", "d", 3, 2);
List<ExpandedTerm> list = new ArrayList();
list.add(term1);
list.add(term2);
Collections.sort(list);
System.out.println(list);
Collections.sort(list, new Comparator<ExpandedTerm>() {
#Override
public int compare(ExpandedTerm o1, ExpandedTerm o2) {
return new Double(o2.confidence).compareTo(new Double(o1.confidence));
}
});
System.out.println(list);
}
Here's the output
[ExpandedTerm#2eeb3c84, ExpandedTerm#55d2162c]
[ExpandedTerm#55d2162c, ExpandedTerm#2eeb3c84]
Some other tips: make sure you implement the toString(), hashCode(), and equals() functions for your ExpandedTerm. These are essential for debugging and also for use in other Collections like HashMap, etc.

Write Java Comparator

I have created a Vector object to store data in Table object as Vector<Table>. Vector<Table> contains components as below.
[Vector<Record> records, String tableName, String keyColumnName, int recordCount, int columnCount]
I need to sort tableName in above Vector to my own order and return Vector<Table> with sorted tableNames for other processes.
I have wrote method as below.
private Vector<Table> orderTables(Vector<Table> loadTables) {
List<String> tableNames = new ArrayList<String>();
for (Table table : loadTables) {
String tblName = table.getTableName();
tableNames.add(tblName);
}
Collections.sort(tableNames, new MyComparable());
return null;
}
But I have no idea about how to write Comparator to this. My own sort order is stored in .properties file. I can read it and get value. But I have no idea about how to compare it.
How could I do it?
Before clarification
You need to write a Comparator for Table objects that delegates to the tableName's comparator:
new Comparator<Table>() {
#Override public int compare(Table one, Table two) {
return one.getTableName().compareTo(two.getTableName());
}
}
Note that this will consider Tables that have the same name to be equal. This can mess things up if you put these tables in a HashMap or HashSet. To avoid this, you can detect this case and return one.hashCode() - two.hashCode() if the table names are the same.
Guava's ComparisonChain is a convenient way to write such multi-stage comparisons:
new Comparator<Table>() {
#Override public int compare(Table one, Table two) {
return ComparisonChain.start()
.compare(one.getTableName(), two.getTableName())
.compare(one.hashCode(), two.hashCode())
.result();
}
}
After clarification
Okay, the question is to impose a predefined sorting order rather than sorting the Tables by name. In that case, you need to make a Comparator that is aware of the ordering defined in the .properties file.
One way to achieve this is to initialize a mapping of table names to sorting order indices, and refer that mapping during the comparison. Given the property value:
SORT_ORDER = SALES,SALE_PRODUCTS,EXPENSES,EXPENSES_ITEMS
The mapping should look like:
{
SALES: 0,
SALE_PRODUCTS: 1,
EXPENSES: 2,
EXPENSES_ITEMS: 3
}
Here's what the comparator would look like:
private static class PredefinedOrderComparator implements Comparator<Table> {
public PredefinedOrderComparator() {
// Initialize orderIndex here
}
private final Map<String, Integer> orderIndex;
#Override public int compare(Table one, Table two) {
return orderIndex.get(one.getTableName()) - orderIndex.get(two.getTableName());
}
}
To populate orderIndex from the property value, you need to:
Get the comma-separated list using getProperty() as you mentioned
Split that value on comma (I recommend using Guava's Splitter, but String.split or others will work too)
Initialize a new HashMap<String, Integer> and an int index = 0
Iterate through the split tokens, map the current token to index and increment index
Note the implicit assumption that none of the table names have a comma in it.
public class MyComparable implements Comparator<Table>{
#Override
public int compare(Table table1, Table table2) {
return (table1.getTableName().compareTo(table2.getTableName());
}
}
make sure that you have overridden the hashcode and equals in Table class to achieve this.
I wrote you a very simple example on how to work with a Comparator. If you create a class called Main, copy paste below contents in it, compile and run it, you can see what's going on.
A comparator just needs to implement an interface. For this it needs to implement one method (public int compare(T arg0, T arg1). There you specify how a collection will get sorted; in this case according to the alfabet.
I hope this helps you.
import java.util.*;
public class Main {
public static void main(String[] args) {
System.out.println("Start\n");
List<Item> items = new ArrayList<Item>();
for(String s : new String[]{"mzeaez", "xcxv", "hjkhk", "azasq", "iopiop"}) {
items.add(createItem(s));
}
System.out.println("Items before sort:");
System.out.println(Item.toString(items));
Collections.sort(items, new ItemComparator());
System.out.println("Items after sort:");
System.out.println(Item.toString(items));
System.out.println("End");
}
private static Item createItem(String s) {
Item item = new Item();
item.setS(s);
return item;
}
}
class Item {
private String s;
public String getS() {
return s;
}
public void setS(String s) {
this.s = s;
}
#Override
public String toString() {
return "Item: " + s;
}
public static String toString(Collection<Item> items) {
String s = "";
for(Item item : items) {
s += item + "\n";
}
return s;
}
}
class ItemComparator implements Comparator<Item> {
#Override
public int compare(Item item1, Item item2) {
return item1.getS().compareTo(item2.getS());
}
}

Categories