Efficiently comparing two lists of objects in Java - java

I'm currently looking through two very large lists of Peak Objects, by overriding the equals method and looping through the two lists, comparing every peak to every other peak. Is there a more efficient way of doing this? My lists can be ~10,000 elements, which means up to 10000 * 10000 comparisons.
The code for my peak object:
public class Peak extends Object{
private final SimpleIntegerProperty peakStart;
private final SimpleIntegerProperty peakEnd;
private final SimpleIntegerProperty peakMaxima;
private final SimpleIntegerProperty peakHeight;
private final SimpleIntegerProperty peakWidth;
private final SimpleStringProperty rname;
public Peak(int peakStart, int peakEnd, int peakMaxima, int peakHeight, String rname) {
this.peakStart = new SimpleIntegerProperty(peakStart);
this.peakEnd = new SimpleIntegerProperty(peakEnd);
this.peakMaxima = new SimpleIntegerProperty(peakMaxima);
this.peakHeight = new SimpleIntegerProperty(peakHeight);
this.peakWidth = new SimpleIntegerProperty(peakEnd - peakStart);
this.rname = new SimpleStringProperty(rname);
}
public String getRname() {
return rname.get();
}
public SimpleStringProperty rnameProperty() {
return rname;
}
public int getPeakWidth() {
return peakWidth.get();
}
public int getPeakHeight() {
return peakHeight.get();
}
public int getPeakStart() {
return peakStart.get();
}
public int getPeakEnd() {
return peakEnd.get();
}
public int getPeakMaxima() {
return peakMaxima.get();
}
#Override
public String toString() {
return "Peak{" +
"peakStart= " + peakStart.get() +
", peakEnd= " + peakEnd.get() +
", peakHeight= " + peakHeight.get() +
", rname= " + rname.get() +
'}';
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Peak peak = (Peak) o;
if (!peakMaxima.equals(peak.peakMaxima)) return false;
return rname.equals(peak.rname);
}
#Override
public int hashCode() {
int result = peakMaxima.hashCode();
result = 31 * result + rname.hashCode();
return result;
}
}
And my loop for comparing the objects is here.
List<Peak> interestingPeaks = new ArrayList<>();
if(peakListOne != null && peakListTwo != null){
for(Peak peak : peakListOne){
for(Peak peak2 : peakListTwo){
if(peak.equals(peak2)){ //number one, check the rnames match
if((peak2.getPeakHeight() / peak.getPeakHeight() >= 9) || (peak.getPeakHeight() / peak2.getPeakHeight() >= 9)){
interestingPeaks.add(peak);
}
}
}
}
}
return interestingPeaks;
The code is basically matching the position of the maxima, and the rname , which is just a String. Then appending the peak to the interestingPeaks list if the height of one is a factor of 9x larger than the other.

Appreciate that if the two lists were sorted by maxima and name, you could simply make a single linear pass down both lists, and compare items side by side. If the two lists were in fact completely equal, then you would never find a pair from the two lists which were not equal.
List<Peak> p1;
List<Peak> p2;
p1.sort((p1, p2) -> {
int comp = Integer.compare(p1.getPeakMaxima(), p2.getPeakMaxima());
return comp != 0 ? comp : p1.getRname().compareTo(p2.getRname());
});
// and also sort the second list
Now we can just walk down both lists and check for a comparison failure:
for (int i=0; i < p1.size(); ++i) {
if (!p1.get(i).equals(p2.get(i))) {
System.out.println("peaks are not equal");
break;
}
}
This reduces an O(N^2) operation to one which is O(N*lgN), which is the penalty for doing both sorts (the final walk down the list is O(N), and would be negligible with either approach).

Related

How to choose pass instance method to use to a method in Java

I have a method that sorts a List by different criteria and returns the name (an instance variable) of the one with maximum value. In case more than one instance is having the maximum, all of their names should be concatenated.
Let's say I have Class A as follows.
Class A {
...
String getName(){...}
int getValue1() {...}
int getValue2() {...}
...
int getValueN() {...}
...
}
I have a List<A> listToSort. I would normally sort this list as listToSort.sort(Comparator.comparing(A::getValue1)) or listToSort.sort(Comparator.comparing(A::getValue2)), so on and so forth. Then get the ones sharing the maximum value.
In a method I believe this should be done as:
String getMaxString (Comparator c) {
listToSort.sort(c);
...
}
and send Comparator.comparing(A.getValueX) as parameter to call it with different methods. (X here indicates an arbitrary number for the getValue function).
However, I need to also return other instances sharing the same values
I will need to pass the Class methods to my method and call on instances as:
String getMaxString (Comparator c) {
listToSort.sort(c);
int maxValue = listToSort.get(listToSort.size() - 1).getValueX();
String maxString = listToSort.get(listToSort.size() - 1).getName();
for (int i = listToSort.size() - 2; i >= 0; i--) {
if (listToSort.get(i).getValueX()() == maxValue) {
maxString += ", " + listToSort.get(i).getName();
}
}
return maxString;
}
How would I pass this method to call on instances here? Or do I need to consider another way?
Edit:
I have a list of Courses as List<Course> mylist where a course can be simplified as:
Class Course {
private String name;
private int capacity;
private int students;
...
//bunch of getters.
}
My task is to return Strings for the course(es) with maximum capacity, the course(es) with maximum registered students, the course(es) with most difficulty, the maximum filled percentage, the course(es) with the maximum number of TAs etc...
Edit 2:
As requested in the comment section.
List of
Course a (name "a", capacity 10, students 5)
Course b (name "b", capacity 20, students 5)
Course c (name "c", capacity 30, students 0)
Sorting based on capacity should return "c"
Sorting based on students should return "a b"
You can pass the getter method and create the Comparator in getMaxString:
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.function.Function;
public class Foo {
static class AClass {
private final String name;
private final int value1;
private final int value2;
String getName() { return name; }
int getValue1() { return value1; }
int getValue2() { return value2; }
public AClass(String name, int value1, int value2) {
this.name = name;
this.value1 = value1;
this.value2 = value2;
}
}
static String getMaxString(Function<AClass,Integer> f, List<AClass> listToSort) {
listToSort.sort(Comparator.comparing(f));
int maxValue = f.apply(listToSort.get(listToSort.size() - 1));
String maxString = listToSort.get(listToSort.size() - 1).getName();
for (int i = listToSort.size() - 2; i >= 0; i--) {
if (f.apply(listToSort.get(i)) == maxValue) {
maxString += ", " + listToSort.get(i).getName();
}
}
return maxString;
}
public static void main(String[] args) {
List<AClass> list = new ArrayList<>();
list.add(new AClass("a", 1,2));
list.add(new AClass("b", 1,2));
list.add(new AClass("c", 2,1));
list.add(new AClass("d", 2,1));
System.out.println(getMaxString(AClass::getValue1, list));
System.out.println(getMaxString(AClass::getValue2, list));
}
}
As Tim Moore suggested above, it isn't necessary to sort the list (which has cost O(n*log n)), we can traverse it twice:
static String getMaxString2(ToIntFunction<AClass> f, List<AClass> listToSort) {
int maxValue = listToSort.stream().mapToInt(f).max().orElseThrow();
return listToSort.stream()
.filter(a -> maxValue == f.applyAsInt(a))
.map(AClass::getName)
.collect(Collectors.joining(", "));
}
Note that you should test your code with an empty list.
It's useful to look at the type signature for Comparator.comparing, because it sounds like you want to do something similar:
static <T,U extends Comparable<? super U>> Comparator<T> comparing(Function<? super T,? extends U> keyExtractor)
The interesting part is the type of keyExtractor. Roughly speaking, it's a function from the type of the object you're comparing, to the type of the field you want to use for the comparison. In our case, these correspond to the A class and Integer. Because these types are fixed in this example, you can declare a method with a signature like this:
String getMaxString(Function<A, Integer> property)
With the existing algorithm, it can be used this way:
String getMaxString(Function<A, Integer> property) {
listToSort.sort(Comparator.comparing(property));
int maxValue = property.apply(listToSort.get(listToSort.size() - 1));
String maxString = listToSort.get(listToSort.size() - 1).getName();
for (int i = listToSort.size() - 2; i >= 0; i--) {
if (listToSort.get(i).getValueN() == maxValue) {
maxString += ", " + listToSort.get(i).getName();
}
}
return maxString;
}
However, it isn't necessary or efficient to sort the entire list in order to determine the maximum elements, as this can be determined by iterating through the list once:
String getMaxString(Function<A, Integer> property) {
int maxValue = Integer.MIN_VALUE;
StringBuilder maxString = new StringBuilder();
for (A element : listToSort) {
int currentValue = property.apply(element);
if (currentValue > maxValue) {
// there is a new maximum, so start the string again
maxString = new StringBuilder(element.getName());
maxValue = currentValue;
} else if (currentValue == maxValue) {
// equal to the existing maximum, append it to the string
if (maxString.length() > 0) {
maxString.append(", ");
}
maxString.append(element.getName());
}
// otherwise, it's less than the existing maximum and can be ignored
}
return maxString.toString();
}
Either way, you can call it using the same method reference syntax:
getMaxString(A::getValueN)
Time complexity O(n) - only one iteration through the dataset.
Hope it'll help.
If something will be unclear fill free to raise a question.
Main
public class MaxClient {
public static void main(String[] args) {
Comparator<A> comp = Comparator.comparingInt(A::getVal1);
List<A> items = List.of(new A(1, 8), new A(2, 8), new A(5, 8), new A(5, 27), new A(3, 8));
items.stream()
.collect(new GetMax(comp))
.forEach(System.out::println);
}
}
Custom collector GetMax
public class GetMax implements Collector <A, Deque<A>, Deque<A>> {
private final Comparator<A> comp;
public GetMax(Comparator<A> comp) {
this.comp = comp;
}
#Override
public Supplier<Deque<A>> supplier() {
return ArrayDeque::new;
}
#Override
public BiConsumer<Deque<A>, A> accumulator() {
return (stack, next) -> {
if (!stack.isEmpty() && comp.compare(next, stack.peekFirst()) > 0) stack.clear();
if (stack.isEmpty() || comp.compare(next, stack.peekFirst()) == 0) stack.offerLast(next);
};
}
#Override
public BinaryOperator<Deque<A>> combiner() {
return (stack1, stack2) -> {
if (stack1.isEmpty()) return stack2;
if (stack2.isEmpty()) return stack1;
if (comp.compare(stack1.peekFirst(), stack2.peekFirst()) == 0) {
stack1.addAll(stack2);
}
return stack1;
};
}
#Override
public Function<Deque<A>, Deque<A>> finisher() {
return stack -> stack;
}
#Override
public Set<Characteristics> characteristics() {
return Set.of(Characteristics.UNORDERED);
}
}
Class A that I used for testing purposes
public class A {
private int val1;
private int val2;
public A(int val1, int val2) {
this.val1 = val1;
this.val2 = val2;
}
public int getVal1() {
return val1;
}
public int getVal2() {
return val2;
}
#Override
public String toString() {
return "A val1: " + val1 + " val2: " + val2;
}
}
OUTPUT
A val1: 5 val2: 8
A val1: 5 val2: 27
Thanks for posting the information I requested. Here is what I came up with.
Create a list of Course objects
List<Course> list = List.of(
new Course("a", 10, 5),
new Course("b", 20, 5),
new Course("c", 30, 0));
Stream the methods and apply them to the list
List<String> results = Stream.<Function<Course, Integer>>of(
Course::getCapacity,
Course::getStudents)
.map(fnc-> getMaxString(fnc, list))
.toList();
results.forEach(System.out::println);
print the results
c
a b
I wrote a simple method that takes a method reference and list and finds the maximum. It does not do any sorting.
allocate a list to hold the names
set the maximum to the lowest possible
iterate thru the list applying the method.
if the value is greater than the current max replace it and clear the current list of names.
otherwise, if equal, add a new name.
once done, return the formatted string.
static String getMaxString(Function<Course, Integer> fnc,
List<Course> list) {
List<String> result = new ArrayList<>();
int max = Integer.MIN_VALUE;
for (Course obj : list) {
int val = fnc.apply(obj);
if (val >= max) {
if (val > max) {
result.clear();
}
max = val;
result.add(obj.getName());
}
}
return String.join(" ", result);
}

finding the largest attribute value in class and assigning the order

I have an ArrayList of a custom class named 'Team'.
Every object of 'Team' has an attribute of 'setsWon' with a value assigned.
Team also has an attribute of 'rank', that is set to null. I want to find the 'Team' with the largest number of 'setsWon', and assign 'rank' to 1, and then find the second largest 'setsWon' value, and assign its 'rank' to 2, and so on.
I have attempted, but i receive a null pointer exception, and its because I am only able to assign a rank to the largest 'setsWon' Team, and not any others.
this is my attempt:
// create temporary team list
ArrayList<Team> temp = new ArrayList<>();
// teams is my ArrayList of 'Team'
for (Team t : teams.getTeamList()){
temp.add(t);
}
for (int i = 0; i < teams.getTeamList().size(); i++){
int max = 0;
for (Team t : temp){
if(t.getSetsWon() > max){
max = t.getSetsWon();
}
}
if(temp.get(i).getSetsWon() == max){
temp.get(i).setRank(i);
temp.remove(i);
}
}
Team class:
public class Team {
private String teamName;
private ArrayList<Player> playerList = new ArrayList<>();
private int matchesPlayed;
private int matchesWon;
private int setsWon;
private int rank;
public Team(String teamName){
this.teamName = teamName;
// these values are set to 0 for another purpose I have in my project
this.matchesPlayed = 0;
this.matchesWon = 0;
this.setsWon = 0;
}
public String getTeamName() {
return teamName;
}
public void setTeamName(String teamName) {
this.teamName = teamName;
}
public void setPlayerList(ArrayList<Player> playerList) {
this.playerList = playerList;
}
public ArrayList<Player> getPlayerList() {
return playerList;
}
public void add (Player player){
playerList.add(player);
}
public void setMatchesPlayed(int matchesPlayed) {
this.matchesPlayed = matchesPlayed;
}
public int getMatchesPlayed() {
return matchesPlayed;
}
public void setMatchesWon(int matchesWon) {
this.matchesWon = matchesWon;
}
public int getMatchesWon() {
return matchesWon;
}
public void setSetsWon(int setsWon) {
this.setsWon = setsWon;
}
public int getSetsWon() {
return setsWon;
}
public void setRank(int rank) {
this.rank = rank;
}
public int getRank() {
return rank;
}
public String toString(){
return String.format("%s", teamName);
}
}
Instead of iterating over your list to find out which team has the largest setsWon and then again to find the second largest and so on.. I would just simply sort the list (or a copy of it) by setsWon and use the index (or index + 1 as indices start by 0) as rank:
List<Team> teams = // your teams ..
List<Team> copy = new ArrayList<>(teams);
copy.sort(new Comparator<Team>(){
#Override
public int compare(Team t1, Team t2) {
return t2.setsWon - t1.setsWon;
}
});
for(Team x : teams){
x.setRank(copy.indexOf(x)+1);
}
You can of course sort your original list instead of making a copy if order dose not matter in your original list
Just one thing to be careful of with Sorting - as a rule of thumb you want a Comparison method that is stable.. i.e. it always results in the comparison of two objects giving the same ordering (regardless of which object is compared first).
This comparator is un-stable - because it gives the same result when setsWon is the same on both objects. But that means if t1 is first, then when sorted it'll stay first, and t2 will stay second. BUT if you had a different input order for the sort the end result could be different (so different Teams appear in different orders - because setsWon may not uniquely provide an order).
new Comparator<Team>() {
#Override
public int compare(Team t1, Team t2) {
return t2.setsWon - t1.setsWon; // Whoooaaa I'm not safe !!
}
}
THE general safe way to resolve this is to fall back to a secondary, or tertiary, or N'ary sort after that.
i.e.
new Comparator<Team>(){
#Override
public int compare(Team t1, Team t2) {
final int comp = t2.setsWon - t1.setsWon;
if (0 == comp) {
return t1.teamName.compare(t2.teamName); // What if these are the same? [do again until only the same object gives 0]
}
return comp;
}
}
For the purposes of this have ignored nulls - in production you need a strategy for them too (null.setsWon is probably not what you want).

How to calculate value of math expression and check user answer?

Any help or advice would be greatly appreciated. I'm dooing a simple game which generates ten different, random questions. The questions can be composed from 2, 3 or 4 integers.Something like this: 552 − 4 − 101, 102 / 3 / 3, 589 − 281, 123 + 56 + 2.
The question will be displayed in a textview and then the user can take a guess, entering values into an edittext and then upon clicking a key on a custom keypad, it will check the answer, and then display the next question until it reaches 10 questions. I have a problem with imputing the answer from the code i have. No matter what i do here i cant input the answer to the randomly generated expression.
public enum Operator {
PLUS("+"), MINUS("-"), MULTIPLIER("*"), DIVIDER("/");
private String displayValue;
private Operator(String displayValue) {
this.displayValue = displayValue;
}
public String getDisplayValue() {
return displayValue;
}}
public class Question{
private List<QuestionElement> questionElements;
public Question(int sizeOfQuestionElemets) {
questionElements = new ArrayList<QuestionElement>(sizeOfQuestionElemets);
}
public void addElement(QuestionElement questionElement) {
questionElements.add(questionElement);
}
public List<QuestionElement> getElements() {
return questionElements;
}
public int size() {
return questionElements.size();
}
#Override
public String toString() {
StringBuilder sb = new StringBuilder();
for (QuestionElement questionElement : questionElements) {
sb.append(questionElement);
}
return sb.toString().trim();
}
}
public class QuestionElement {
private int value;
private Operator operator;
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public Operator getOperator() {
return operator;
}
public void setOperator(Operator operator) {
this.operator = operator;
}
#Override
public String toString() {
return value + (operator == null ? "" : " " + operator.getDisplayValue()) + " ";
}
}
public class RandomQuestions {
static QuestionElement q = new QuestionElement();
private static final int NUMBER_OF_QUESTIONS = 10;
private static final int MIN_QUESTION_ELEMENTS = 2;
private static final int MAX_QUESTION_ELEMENTS = 2;
private static final int MIN_QUESTION_ELEMENT_VALUE = 1;
private static final int MAX_QUESTION_ELEMENT_VALUE = 20;
private final Random randomGenerator = new Random();
public List<Question> getGeneratedRandomQuestions() {
List<Question> randomQuestions = new ArrayList<>(NUMBER_OF_QUESTIONS);
int randomQuestionElementsCapacity = getRandomQuestionElementsCapacity();
Question question = new Question(randomQuestionElementsCapacity);
for (int j = 0; j < randomQuestionElementsCapacity; j++) {
boolean isLastIteration = j + 1 == randomQuestionElementsCapacity;
QuestionElement questionElement = new QuestionElement();
questionElement.setValue(getRandomQuestionElementValue());
questionElement.setOperator(isLastIteration ? null
: Operator.values()[randomGenerator.nextInt(Operator.values().length)]);
question.addElement(questionElement);
}
randomQuestions.add(question);
return randomQuestions;
}
private int getRandomQuestionElementsCapacity() {
return getRandomIntegerFromRange(MIN_QUESTION_ELEMENTS, MAX_QUESTION_ELEMENTS);
}
private int getRandomQuestionElementValue() {
return getRandomIntegerFromRange(MIN_QUESTION_ELEMENT_VALUE, MAX_QUESTION_ELEMENT_VALUE);
}
private int getRandomIntegerFromRange(int min, int max) {
return randomGenerator.nextInt(max - min + 1) + min;
}
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
RandomQuestions questionGenerator = new RandomQuestions();
List<Question> randomQuestions = questionGenerator.getGeneratedRandomQuestions();
for (Question question : randomQuestions) {
System.out.println(""+ question+"=?");
int answer = input.nextInt();
if (answer == q.getValue()) {
System.out.println("CORRECT");
}else{
System.err.println("STILL NOT WORKING");
}
}
}
}
In your main() you are printing question, reading an answer from the user and then comparing the answer to q.getValue(). q is a question element that is not related to question and always has value 0. So the trick is to answer 0 no matter what the question is, then the program will print CORRECT. :-)
I haven’t found anywhere in your code where you are calculating the correct value of the math expression. This would probably be a good first step towards checking whether the user has indeed entered the correct result.
Calculating the correct result is not really trivial if we insist on taking operator precedence into account. 4 + 3 * 2 should be 10 (not 14). I believe that reading about the Shunting-yard algorithm should get you some of the way. It’s an algorithm for parsing a math expression, which is only the first step towards calculating its value, but still a first step.
I suggest that the object-oriented approach will be that the Question object knows how to check an answer. Here is an implementation of the algorithm, simplified to the four operators, but extended to actually do the calculation:
public boolean checkAnswer(int answer) {
// calculate correct answer
// use shunting yard algorithm
Deque<Integer> outputQueue = new ArrayDeque<>();
Deque<Operator> operatorStack = new ArrayDeque<>();
for (QuestionElement element : questionElements) {
outputQueue.push(element.getValue());
Operator op = element.getOperator();
if (op != null) {
while (!operatorStack.isEmpty() && op.getPrecedence() <= operatorStack.peek().getPrecedence()) {
int operand2 = outputQueue.pop();
int operand1 = outputQueue.pop();
outputQueue.push(operatorStack.pop().apply(operand1, operand2));
}
operatorStack.push(op);
}
}
while (!operatorStack.isEmpty()) {
int operand2 = outputQueue.pop();
int operand1 = outputQueue.pop();
outputQueue.push(operatorStack.pop().apply(operand1, operand2));
}
int result = outputQueue.pop();
assert outputQueue.isEmpty();
return answer == result;
}
You notice that I have put some new demands on your Operator enum too. It has a precedence. And the + operator must know how to do addition (through its apply method), and similarly for the other operators:
PLUS("+", 1) {
#Override
public int apply(int operand1, int operand2) {
return operand1 + operand2;
}
},
// etc.
public abstract int apply(int operand1, int operand2);
and so on. 1 is the precedence; * and / have higher precedence, for example 2.
Now in main() you just need to write:
if (question.checkAnswer(answer)) {
If you decide to explain to the user that strict left-to-right evaluation is applied, it’s getting somewhat simpler:
public boolean checkAnswer(int answer) {
// calculate correct answer
// do left to right calculation
int result = questionElements.get(0).getValue();
for (int elementIndex = 1; elementIndex < questionElements.size(); elementIndex++) {
Operator op = questionElements.get(elementIndex - 1).getOperator();
result = op.apply(result, questionElements.get(elementIndex).getValue());
}
return answer == result;
}
The operators still need to have the apply method, but they no longer need the precedence.

loop to get closest object to input value

I have this Martian class:
public abstract class Martian implements Cloneable {
int id;
public Martian(int id) {
this.id = id;
}
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
public int getId() {
return id;
}
public boolean equals(Object o){
if( o != null);
return this.getId() == ((Martian)o).getId();
}
public abstract void speak();
public String toString(){
return "Martian" + getId();
}
}
and a MartianManager class:
public class MartianManager {
private ArrayList<Martian> martians;
private ArrayList<Martian> teleporters;
public void addMartian(Martian m) {
martians.add(m);
if(m instanceof GreenMartian)
teleporters.add(m);
}
//public Object clone() {
public Martian getMartianClosestToID(int id) {
}
public void groupSpeak() {
for(Martian m : martians) {
m.speak();
}
}
public void groupTeleport(String dest) {
for (Martian m : martians){
if (m instanceof GreenMartian)
((GreenMartian) m).teleport(dest);
}
}
//public obliterateTeleporters()
//removeMartian(int id)
}
In the MartianManager class I have a method getMartianClosestToId() which returns the martian with the id closest to the input id. My question basically is what would be the simplest logic to use in a loop to do this, or is their an easier way to do it such as compareTo which I don't know if compare would even work in a situation like this.
This isn't the simplest, but in a lot of cases it will be the fastest.
If you're willing to keep your list of martians always sorted by id (which is easy to do if you aren't adding martians very often, you can just sort when you add them), you can do this:
Comparator<Martian> compareById = new Comparator<Martian>() {
public int compare(Martian a, Martian b) {
return Integer.compare(a.getId(), b.getId());
}
}
Then you can use a binary search on your list to find the spot where it would go in the list if it were to be inserted.
int location = Collections.binarySearch(martians, idToGetClosestTo, compareById);
Now at this point, you're going to have where it should go, and you'll have one of five options:
the location has the id you're looking for. If so, return martians.get(location);
the location provided wasn't in the list, meaning you're looking for something lower than the lowest, or higher than the highest.
2.a. lower than the lowest: return the lowest, martians.get(0);
2.b. higher than the highest: return the highest, martians.get(martians.length()-1);
the location is higher than the id you're looking for. (it won't be lower, otherwise you would have been given a result 1 lower than what you did!) So look at martians.get(location) and martian.get(location - 1) and see which one you're closest to and return the appropriate one.
This has an expensive upfront cost (sorting) but after you have it sorted, you can use binary search which is very cheap to find the closest martian very fast every time.
If you're going to be adding very often, then I recommend adding the new martians to the end and flagging your collection as unsorted, then only sorting when you're about to find one.
public Martian getMartianClosestToID(int id) {
if(!martiansAreSorted) Collections.sort(martians,compareById);
int loc = Collections.binarySearch(martians,id,compareById);
if(loc >= 0) return martians.get(loc); // found exact match
// we know loc is negative because it wasn't found - read the docs
loc = -loc;
if(loc == 0) return martians.get(0);
if(loc == martians.size()) return martians.get(loc - 1);
Martian high = martians.get(loc);
Martian low = martians.get(loc - 1);
int highid = high.getId();
int lowid = low.getId();
int highdiff = Math.abs(id - highid);
int lowdiff = Math.abs(id - lowid);
if(highdiff < lowdiff) return high;
return low;
}
Something like this ought to work.
Whether this is exactly what you want depends on some assumptions. Are the IDs unique? Can a Martian be closest to itself? What if there are two equally close Martians? Or no other Martians? I've assumed that the 'id' argument may be one of the IDs in the collection - and that you don't want that one.
But the biggest question is: what does 'closest' mean? The concept of 'closeness' doesn't usually apply to IDs.
public Martian getMartianClosestToId(int id) {
Martian closest = null;
int leastDist = -1;
for(Martian m : martians) {
int mId = m.getId();
if(mId == id)
continue; // Skip the Martian with the same id.
int d = Math.abs(mId - id);
if(leastDist == -1 || d < leastDist) {
leastDist = d;
closest = m;
}
}
return closest;
}
I haven't compiled/tested this - you may need to fix typos/errors.
This I think is simpler than the other answers so far (OP requested the "simplest logic"):
public Martian getMartianClosestToID(int id)
{
if (martians == null || martians.isEmpty())
return null;
Martian result = martians.get(0);
for (Martian m : martians)
if (Math.abs(id - m.getId()) < Math.abs(id - result.getId()))
result = m;
return result
}

search with multiple parameters, java collection choice advise

What data structure should I use in the case described below:
I have a simple bean:
public class Points {
private String name;
private String address;
private int phone;
private int coord1;
private int coord2;
//getters+setters
}
I would like to create several beans and store them in some sort of data structure.
And be able to search with two parameters - name and address.
For example, user types in "7" - and it gives him back several object,
which name or address contains this character?
What data structure should i use and how do i search through it?
If it is important, I actually need this to implement into my android app -
i would like to search through my points on the map
Also I do not want to create a database so far, as there are only 20 of them.
Thank you very much in advance.
Try java's collection, e.g. hashmap. Although I ran this on PC, for 10000 items, with search
returned 3440 results, it took 76ms.
class Points {
String name;
String address;
int phone;
int coord1;
int coord2;
// getters+setters
};
class PointsIdentifier {
private String name;
private String address;
public PointsIdentifier(String name, String address) {
this.name = name;
this.address = address;
}
public boolean contains(String seq) {
return name.contains(seq) || address.contains(seq);
}
#Override
public boolean equals(Object obj) {
Points other = (Points) obj;
return name.equals(other.name) && address.equals(other.address);
}
#Override
public int hashCode() {
return name.hashCode() + address.hashCode();
}
};
class PointsCollection {
private Map<PointsIdentifier, Points> map;
public PointsCollection() {
map = new HashMap<PointsIdentifier, Points>();
}
public void add(Points p) {
map.put(new PointsIdentifier(p.name, p.address), p);
}
public List<Points> findIdsContaining(String seq) {
List<Points> resultList = new ArrayList<Points>();
for (Entry<PointsIdentifier, Points> entry : map.entrySet()) {
if (entry.getKey().contains(seq)) {
resultList.add(entry.getValue());
}
}
// optionally cache result
return resultList;
}
}
public class Question_11881630 {
public static void main(String[] args) {
PointsCollection places = createCollection(10000);
System.out.println("Collection created");
String seq = "1";
System.out.format("Searching for: \"%s\"\n", seq);
List<Points> verifySearch = verifySearch(places, seq);
//show(verifySearch);
}
private static void show(List<Points> verifySearch) {
int i = 1;
for (Points p : verifySearch) {
System.out.println(i + ": " + p.name + ", " + p.address);
i++;
}
}
private static List<Points> verifySearch(PointsCollection places, String seq) {
long start = System.currentTimeMillis();
List<Points> searchResult = places.findIdsContaining(seq);
System.out.println("Search results: " + searchResult.size());
long end = System.currentTimeMillis();
System.out.println("Operation time: " + formatTime(end - start));
return searchResult;
}
private static String formatTime(long elapsed) {
return elapsed + " miliseconds";
}
private static PointsCollection createCollection(int number) {
PointsCollection coll = new PointsCollection();
while (number > 0) {
coll.add(createSamplePoint(number));
number--;
}
return coll;
}
private static Points createSamplePoint(int number) {
Points p = new Points();
p.name = "VeryVeryLongName: " + number;
p.address = "VeryVeryLongLongAddress: " + number;
p.coord1 = 123;
p.coord2 = 456;
return p;
}
}
A trie seems a good fit. It is an efficient data structure to find all strings with a certain prefix.
If you want to use one of the existing java collections instead, you can use a TreeSet, and its floor() method to get the element before the needed prefix - and then start iterating the set while it still matches.
If you are looking for search by substring, and not only prefix - you might want to use a suffix tree instead.
An (inefficient) alternative that uses java's existing containers - is to store all substrings of your keys in a Set or a Map, but it will require quadric amount of space.

Categories

Resources