Java, searching within a list of objects? - java

I'm a bit lost on the way to make this happen the fastest. I have a large list of objects that have basic variable attributes (with getters / setters) and I need to do a search in this list to find the objects within the list that match a given parameter
I have found how to do a regular list search but I need to, for example search for the value of the result of doing a call getName() for each object in the list and get objects that have a result that matches my input.
Something like below where the third argument is the result of the method call and the second is what I am trying to find.
int index = Collections.binarySearch(myList, "value", getName());
Any advice is appreciated

If you just as a one-off operation need to find the object(s) whose getName() is a particular value, then there's probably not much magic possible: cycle through the list, call getName() on each object, and for those that match, add them to your list of results.
If getName() is an expensive operation and there's some other way of a-priori working out if a given object definitely won't return a matching value, then obviously you can build in this 'filtering' as you cycle through.
If you frequently need to fetch objects for a given getName(), then keep an index (e.g. in a HashMap) of [result of getName()->object -> list of matches]. You'll need to decide how and if you need to keep this "index" in synch with the actual list.
See also the other proposition to use binarySearch() but to keep the list maintained. This way, inserts are more expensive than with a map and unsorted list, but if inserts are infrequent compared to lookups, then it has the advantage of only needing to maintain one structure.

Take a look at the binarySearch that takes a comparator:
public static int binarySearch(List list,
T key,
Comparator c)
So you would do something like:
class FooComparator
implements Comparator<Foo>
{
public int compare(T a, T b)
{
return (a.getName().compareTo(b.getName());
}
}
int index = Collections.binarySearch(myList, "value", new FooComparator());
You will need to first sort the list of course (Collections.sort takes a Comaprator as well...).

I know anonymous inner classes are not fashion anymore, but while Java 8 arrives, you can create something like this:
1.- Create a search method that iterates the collection and pass an object that tells you if your object is to be returned or not.
2.- Invoke that method and create an anonymous inner class with the criteria
3.- Get the new list in separate variable.
Something like this:
result = search( aList, new Matcher(){ public boolean matches( Some some ) {
if( some.name().equals("a")) {
return true;
}
}});
Here's a working demo:
import java.util.*;
class LinearSearchDemo {
public static void main( String ... args ) {
List<Person> list = Arrays.asList(
Person.create("Oscar", 0x20),
Person.create("Reyes", 0x30),
Person.create("Java", 0x10)
);
List<Person> result = searchIn( list,
new Matcher<Person>() {
public boolean matches( Person p ) {
return p.getName().equals("Java");
}});
System.out.println( result );
result = searchIn( list,
new Matcher<Person>() {
public boolean matches( Person p ) {
return p.getAge() > 16;
}});
System.out.println( result );
}
public static <T> List<T> searchIn( List<T> list , Matcher<T> m ) {
List<T> r = new ArrayList<T>();
for( T t : list ) {
if( m.matches( t ) ) {
r.add( t );
}
}
return r;
}
}
class Person {
String name;
int age;
String getName(){
return name;
}
int getAge() {
return age;
}
static Person create( String name, int age ) {
Person p = new Person();
p.name = name;
p.age = age;
return p;
}
public String toString() {
return String.format("Person(%s,%s)", name, age );
}
}
interface Matcher<T> {
public boolean matches( T t );
}
Output:
[Person(Java,16)]
[Person(Oscar,32), Person(Reyes,48)]

To do this in a more scalable way, without simply iterating/filtering objects, see this answer to a similar question: How do you query object collections in Java (Criteria/SQL-like)?

If the objects are immutable (or you at least know their names won't change) you could create an index using a HashMap.
You would have to fill the Map and keep it updated.
Map map = new HashMap();
map.put(myObject.getName(), myObject);
... repeat for each object ...
Then you can use map.get("Some name"); to do lookup using your index.

One library I'm familiar with is Guava -- you can compose its Predicate to pull out items from an Iterable. There's no need for the collection to be pre-sorted. (This means, in turn, that it's O(N), but it's convenient.)

Related

The most efficient method to find an element in Set

I have the Person class:
public class Person implements Comparable<Person> {
private int id;
#Override
public int hashCode() {
return id;
}
#Override
public boolean equals(Object obj) {
Person other = (Person) obj;
return id == other.id;
}
#Override
public int compareTo(Person o) {
return Integer.compare(id, o.id);
}
}
And I have TreeSet of persons.
I need to implement method findPersonById(int id) in TreeSet.
I made it this way:
public Person find(int id) {
List<Person> personList = new ArrayList(idTreeSet);
Person pattern = new Person(id);
int index = Collections.binarySearch(personList, pattern);
return index < 0 ? null : personList.get(index);
}
Now the efficient of the find method is O(n) because it needs to copy all of elements from TreeSet to ArrayList.
But is there more efficient way to implement this method?
I don't need a Map. I'm interesed to resolve it without Maps.
Since you are prepared to allocate a temporary Person object, you can do it like this:
public Person find(int id) {
Person temp = new Person(id);
Person candidate = idTreeSet.ceiling(temp);
return temp.equals(candidate) ? candidate : null;
}
This is O(logN).
Note that we only create one temporary object here. If we use tailSet or subSet we will be creating at least second one; i.e. the NavigableSet returned by the tailSet or subSet call. (Looking under the hood of the TreeSet implementation, it looks like more will be created.)
If you don't need the properties of a TreeSet then using a HashMap<Integer, Person> or a HashSet<Person> would give you O(1) lookup. But in the latter case, you need change your Person class to satisfy the equals / hashCode contract.
Because TreeSet is a NavigableSet, you can use TreeSet.subSet, which leverages knowledge about the order of the elements to extract a range of elements as close as possible to the element you are interested in:
Person pattern = new Person(id);
return
// Get the Persons between pattern (inclusive) and pattern (inclusive).
// In other words: all the Persons with id equal to the input,
// of which there are zero or one.
idTreeSet.subSet(pattern, true, pattern, true).stream()
.findFirst()
.orElse(null);
Map<Integer, Person> personsById = new HashMap<>();
Would definitely be fastest, though not Tree based. A LinkedHashMap for order of insert would allow some order.
It is the more rugged solution.

How can I consume for each elements of an optional list?

I have a List which can be null;
List<T> list; // may or may not null
I want to process for each element with a consumer.
So far, I do.
ofNullable(list)
.map(List::stream)
.ifPresent(stream -> stream.forEach(e -> {}));
or
ofNullable(eventDataList).ifPresent(v -> v.forEach(e -> {}));
Is there any easy or concise way to do this?
To avoid ugly null checking, use orElse(Collections.emptyList())
Optional.ofNullable(eventDataList)
.orElse(Collections.emptyList())
.forEach(e -> {});
With static imports, it's pretty concise:
ofNullable(eventDataList).orElse(emptyList()).forEach(e -> {});
Technically, if (list != null) { list.stream().forEach(e -> {...}); } is both shorter and more efficient in terms of CPU/memory usage than your variants.
Architecturally, if you have control over initialization of the list and its usage, it's often better to use either Collections.emptyList() instead of null (if the logic of your program allows) or make the list Optional from the very beginning. That would save you from necessity to make checks or create Optionals every time you want to use the list.
If you require to do something with every value in the list and say return a value then ifPresent will not work. Rather you can do something like below. In my example the optional list contains a user defined object Person which has a few attributes. I am iterating over the list and concatenating the values of a specific attribute and returning it.
public static class Person
{
String name;
int age;
public Person(final String name, final int age)
{
this.name = name;
this.age = age;
}
public String getName()
{
return name;
}
public int getAge()
{
return age;
}
}
public static void main(String[] args)
{
Person a = new Person("Alice", 1);
Person b = new Person("Bob", 2);
List<Person> personList = Lists.newArrayList(a, b);
String concatNames = Optional.of(personList).map(people -> people.stream().map(Person::getName).collect(Collectors.joining(" "))).orElse(null);
System.out.println("Names: "+concatNames);
}
I'm not sure that you can make it more concise. However, if you are frequently using the construct of looping over a nullable list and consuming each element, you could make a small class which does just that:
public class ListConsumer {
public static <H> Consumer<List<H>> of(Consumer<H> consumer) {
return hs -> hs.forEach(consumer);
}
}
You can then consume each element in a list as follows (e.g. print all Strings in list):
List<String> list = Arrays.asList("A", "B", "C");
Consumer<String> consumer = System.out::println;
Optional.ofNullable(list).ifPresent(ListConsumer.of(consumer));

Compare two sets of different types

I'm looking for a way to tell if two sets of different element types are identical if I can state one-to-one relation between those element types. Is there a standard way for doing this in java or maybe guava or apache commons?
Here is my own implementation of this task. For example, I have two element classes which I know how to compare. For simplicity, I compare them by id field:
class ValueObject {
public int id;
public ValueObject(int id) { this.id=id; }
public static ValueObject of(int id) { return new ValueObject(id); }
}
class DTO {
public int id;
public DTO(int id) { this.id=id; }
public static DTO of(int id) { return new DTO(id); }
}
Then I define an interface which does the comparison
interface TwoTypesComparator<L,R> {
boolean areIdentical(L left, R right);
}
And the actual method for comparing sets looks like this
public static <L,R> boolean areIdentical(Set<L> left, Set<R> right, TwoTypesComparator<L,R> comparator) {
if (left.size() != right.size()) return false;
boolean found;
for (L l : left) {
found = false;
for (R r : right) {
if (comparator.areIdentical(l, r)) {
found = true; break;
}
}
if (!found) return false;
}
return true;
}
Example of a client code
HashSet<ValueObject> valueObjects = new HashSet<ValueObject>();
valueObjects.add(ValueObject.of(1));
valueObjects.add(ValueObject.of(2));
valueObjects.add(ValueObject.of(3));
HashSet<DTO> dtos = new HashSet<DTO>();
dtos.add(DTO.of(1));
dtos.add(DTO.of(2));
dtos.add(DTO.of(34));
System.out.println(areIdentical(valueObjects, dtos, new TwoTypesComparator<ValueObject, DTO>() {
#Override
public boolean areIdentical(ValueObject left, DTO right) {
return left.id == right.id;
}
}));
I'm looking for the standard solution to to this task. Or any suggestions how to improve this code are welcome.
This is what I would do in your case. You have sets. Sets are hard to compare, but on top of that, you want to compare on their id.
I see only one proper solution where you have to normalize the wanted values (extract their id) then sort those ids, then compare them in order, because if you don't sort and compare you can possibly skip pass over duplicates and/or values.
Think about the fact that Java 8 allows you to play lazy with streams. So don't rush over and think that extracting, then sorting then copying is long. Lazyness allows it to be rather fast compared to iterative solutions.
HashSet<ValueObject> valueObjects = new HashSet<>();
valueObjects.add(ValueObject.of(1));
valueObjects.add(ValueObject.of(2));
valueObjects.add(ValueObject.of(3));
HashSet<DTO> dtos = new HashSet<>();
dtos.add(DTO.of(1));
dtos.add(DTO.of(2));
dtos.add(DTO.of(34));
boolean areIdentical = Arrays.equals(
valueObjects.stream()
.mapToInt((v) -> v.id)
.sorted()
.toArray(),
dtos.stream()
.mapToInt((d) -> d.id)
.sorted()
.toArray()
);
You want to generalize the solution? No problem.
public static <T extends Comparable<?>> boolean areIdentical(Collection<ValueObject> vos, Function<ValueObject, T> voKeyExtractor, Collection<DTO> dtos, Function<DTO, T> dtoKeyExtractor) {
return Arrays.equals(
vos.stream()
.map(voKeyExtractor)
.sorted()
.toArray(),
dtos.stream()
.map(dtoKeyExtractor)
.sorted()
.toArray()
);
}
And for a T that is not comparable:
public static <T> boolean areIdentical(Collection<ValueObject> vos, Function<ValueObject, T> voKeyExtractor, Collection<DTO> dtos, Function<DTO, T> dtoKeyExtractor, Comparator<T> comparator) {
return Arrays.equals(
vos.stream()
.map(voKeyExtractor)
.sorted(comparator)
.toArray(),
dtos.stream()
.map(dtoKeyExtractor)
.sorted(comparator)
.toArray()
);
}
You mention Guava and if you don't have Java 8, you can do the following, using the same algorithm:
List<Integer> voIds = FluentIterables.from(valueObjects)
.transform(valueObjectIdGetter())
.toSortedList(intComparator());
List<Integer> dtoIds = FluentIterables.from(dtos)
.transform(dtoIdGetter())
.toSortedList(intComparator());
return voIds.equals(dtoIds);
Another solution would be to use List instead of Set (if you are allowed to do so). List has a method called get(int index) that retrieves the element at the specified index and you can compare them one by one when both your lists have the same size. More on lists: http://docs.oracle.com/javase/7/docs/api/java/util/List.html
Also, avoid using public variables in your classes. A good practice is to make your variables private and use getter and setter methods.
Instantiate lists and add values
List<ValueObject> list = new ArrayList<>();
List<DTO> list2 = new ArrayList<>();
list.add(ValueObject.of(1));
list.add(ValueObject.of(2));
list.add(ValueObject.of(3));
list2.add(DTO.of(1));
list2.add(DTO.of(2));
list2.add(DTO.of(34));
Method that compares lists
public boolean compareLists(List<ValueObject> list, List<DTO> list2) {
if(list.size() != list2.size()) {
return false;
}
for(int i = 0; i < list.size(); i++) {
if(list.get(i).id == list2.get(i).id) {
continue;
} else {
return false;
}
}
return true;
}
Your current method is incorrect or at least inconsistent for general sets.
Imagine the following:
L contains the Pairs (1,1), (1,2), (2,1).
R contains the Pairs (1,1), (2,1), (2,2).
Now if your id is the first value your compare would return true but are those sets really equal? The problem is that you have no guarantee that there is at most one Element with the same id in the set because you don't know how L and R implement equals so my advise would be to not compare sets of different types.
If you really need to compare two Sets the way you described I would go for copying all Elements from L to a List and then go through R and every time you find the Element in L remove it from the List. Just make sure you use LinkedList instead of ArrayList .
You could override equals and hashcode on the dto/value object and then do : leftSet.containsAll(rightSet) && leftSet.size().equals(rightSet.size())
If you can't alter the element classes, make a decorator and have the sets be of the decorator type.

In java, is there a way to search for a specific field of a class stored in a linked list?

I wrote a class that is to be stored in a linkedlist, with 3 fields in the class. One of these fields is a String, which I would like to search for in the linked list.
Example
LinkedList
Obj1
String name = "first";
int age = 2;
int size = 4;
Obj2
String name = "second";
int age = 3;
int size = 6;
Obj3
String name = "third";
int age = 5;
int size = 8;
If this is the linkedlist storing these three objects with the given fields, is there a way to search the linked list for the object with the name "second"?
You can search for an item in the list by iteration
// Iterate over each object within the list
for(YourClass obj : yourLinkedList) {
// Check if the object's name matches the criteria, in this case, the name
// of the object has to match "second"
if (obj.name.equals("second")) {
// If we are within this block, it means that we found the object that has
// its name set as "second".
return obj;
}
}
You could also make a method to make things more elegant
public YourClass findByName(String name) {
for(YourClass obj : yourLinkedList) {
if (obj.name.equals(name)) {
return obj;
}
}
return null;
}
And use it the following way
YourClass object = findByName("second");
The easiest way to do this would be to of course, iterate through each element in the collection, checking if it matched your filter condition, and selecting the matches found. However this gets tedious the more times you need to do it, and the more complex your filter condition is. I would recommend utilizing pre-existing libraries to get the task done efficiently. Here is an example using Google-Collections:
final List<SomeObj> listObjs = Arrays.asList(
new SomeObj("first", 2, 4), new SomeObj("second", 3, 6),
new SomeObj("third", 5, 8));
final Iterable<SomeObj> filtered = Iterables.filter(listObjs,
new Predicate<SomeObj>() {
#Override
public boolean apply(final SomeObj obj) {
return "second".equals(obj.getName());
}
});
for (final SomeObj obj : filtered) {
System.out.println(obj);
}
The code shown would select all objects in the list with a name property of "second". Obviously, the predicate doesn't have to be an anonymous inner class - if you needed to reuse it you would just break it out to a standalone class.
Here's another way to implement a Comparator (just in case it helps).
I find it's easier to understand if you implement the Comparator explicitly:
class PersonAgeComparator implements Comparator<Person> {
#Override
public int compare(Person p1, Person person2) {
return p1.getAge().compareTo(p2.getAge());
}
}
You might use the above like this:
Comparator ageComparator = new PersonAgeComparator();
List<Person> personList = // populate list somehow
Person fourYearOld = new Person();
fourYearOld.setAge(4);
for (Person p : personList) {
if (ageComparator.compare(fourYearOld, p) == 0) {
System.out.println(p.getName() + " is 4 years old");
}
}
This doesn't make much sense for this simple example.
It would be ideal if you had several complicated ways to compare people (by height, by adjusted income, by how many states they've lived in, etc...).
Take a look at the java.util.Comprator interface. You can write a method that iterates over a List and uses a comparator to find the one you are after.
Something like (not compiled):
for(final T value : list)
{
if(comparator.compare(value, desired) == 0)
{
// match
}
}
In your comparator you have it perform whatever comparison you want.
Here is a working example:
public class JavaApplication4
{
public static void main(String[] args)
{
final List<Data> list;
final List<Data> a;
final List<Data> b;
list = new ArrayList<Data>();
list.add(new Data("Foo", 1));
list.add(new Data("Bar", 10));
list.add(new Data("Car", 10));
a = find(list,
new Data("Bar", 0),
new Comparator<Data>()
{
#Override
public int compare(final Data o1,
final Data o2)
{
return (o1.name.compareTo(o2.name));
}
});
b = find(list,
new Data(null, 10),
new Comparator<Data>()
{
#Override
public int compare(final Data o1,
final Data o2)
{
return (o1.count - o2.count);
}
});
System.out.println(a.size());
System.out.println(b.size());
}
private static List<Data> find(final List<Data> list,
final Data desired,
final Comparator<Data> comprator)
{
final List<Data> results;
results = new ArrayList(list.size());
for(final Data data : list)
{
if(comprator.compare(desired, data) == 0)
{
results.add(data);
}
}
return (results);
}
private static class Data
{
private final String name;
private final int count;
Data(final String nm,
final int c)
{
name = nm;
count = c;
}
}
}
And here is a generic version of the find method. Using this method you would never have to write the find method again, using a method that embeds the logic for matching in the iteration code means that you would have to re-write the iteration logic for each new set of matching logic.
private static <T> List<T> find(final List<T> list,
final T desired,
final Comparator<T> comprator)
{
final List<T> results;
results = new ArrayList(list.size());
for(final T value : list)
{
if(comprator.compare(desired, value) == 0)
{
results.add(value);
}
}
return (results);
}
You can go through it and get it done or there's another way.
You need to override the equals method in your class (and the hashcode method as well).
After you override the equals to your desire, in this case to compare the names, create a new object with the same name and call the remove(Object o) method of the LinkedList and get the object.
You should note that with this approach you objects equality will be defined by name and that the entry will be removed from the LinkedList

How to get index of an item in java.util.Set

I know the differences between Set and List(unique vs. duplications allowed, not ordered/ordered, etc). What I'm looking for is a set that keeps the elements ordered(that's easy), but I also need to be able to recover the index in which an element was inserted. So if I insert four elements, then I want to be able to know the order in which one of them was inserted.
MySet<String> set = MySet<String>();
set.add("one");
set.add("two");
set.add("three");
set.add("four");
int index = set.getIndex("two");
So at any given moment I can check if a String was already added, and get the index of the string in the set. Is there anything like this, or I need to implement it myself?
After creating Set just convert it to List and get by index from List:
Set<String> stringsSet = new HashSet<>();
stringsSet.add("string1");
stringsSet.add("string2");
List<String> stringsList = new ArrayList<>(stringsSet);
stringsList.get(0); // "string1";
stringsList.get(1); // "string2";
A small static custom method in a Util class would help:
public static <T> int getIndex(Set<T> set, T value) {
int result = 0;
for (T entry:set) {
if (entry.equals(value)) return result;
result++;
}
return -1;
}
If you need/want one class that is a Set and offers a getIndex() method, I strongly suggest to implement a new Set and use the decorator pattern:
public class IndexAwareSet<T> implements Set {
private Set<T> set;
public IndexAwareSet(Set<T> set) {
this.set = set;
}
// ... implement all methods from Set and delegate to the internal Set
public int getIndex(T entry) {
int result = 0;
for (T entry:set) {
if (entry.equals(value)) return result;
result++;
}
return -1;
}
}
you can extend LinkedHashSet adding your desired getIndex() method. It's 15 minutes to implement and test it. Just go through the set using iterator and counter, check the object for equality. If found, return the counter.
One solution (though not very pretty) is to use Apache common List/Set mutation
import org.apache.commons.collections.list.SetUniqueList;
final List<Long> vertexes=SetUniqueList.setUniqueList(new LinkedList<>());
it is a list without duplicates
https://commons.apache.org/proper/commons-collections/javadocs/api-3.2.2/index.html?org/apache/commons/collections/list/SetUniqueList.html
How about add the strings to a hashtable where the value is an index:
Hashtable<String, Integer> itemIndex = new Hashtable<>();
itemIndex.put("First String",1);
itemIndex.put("Second String",2);
itemIndex.put("Third String",3);
int indexOfThirdString = itemIndex.get("Third String");
you can send your set data to a new list
Java ArrayList<String> myList = new ArrayList<>(); myList.addAll(uniqueNameSet); myList.indexOf("xxx");

Categories

Resources