15

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

Rick
  • 16,612
  • 34
  • 110
  • 163

6 Answers6

10

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...).

TofuBeer
  • 60,850
  • 18
  • 118
  • 163
  • And of course if the list is added to, you need to add in the appropriate place also based on this comparator so that the list is maintained in order. If additions are infrequent, this could be a better solution than with a map as you only maintain one structure. – Neil Coffey Mar 03 '11 at 23:42
  • In particular, this only works if you sort the list first using the same Comparator you'll use for search. – Vance Maverick Mar 03 '11 at 23:43
10

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.

Neil Coffey
  • 21,615
  • 7
  • 62
  • 83
  • So using the hashmap would make this faster? The data will rarely change in the list of objects (like only once a week), I have already done it the way you suggested initially but am not happy with the speed so basically I am trying to figure a way to make this faster.. thanks – Rick Mar 04 '11 at 00:11
  • If it changes in frequently sort it only when it changes. I would use a Map myself rather than a binary search. It is probably faster to use the Map, but only one way to find out :-) – TofuBeer Mar 04 '11 at 00:15
  • I am thinking if I just put the "foreignkey" that I need in a Map like `Map ` this would make if faster since there is basically a foreign key field where I only need to check values if it matches that, this would make it faster than just cycling through the entire list, correct? – Rick Mar 04 '11 at 00:22
  • 2
    On paper, the map is faster, and more scalable. Essentially, the time to retrieve, whatever that is on your system, will be constant however many elements with a map; with the binary search, the time will increase by some constant amount for each doubling of the number of elements. But if either is fast enough for your purposes, then it comes down to which structure is easier to manage for you. – Neil Coffey Mar 04 '11 at 02:48
  • I think you would really do many maps, eg Map for the getName() and Map for the getAge etc... you might even want to consider some sort of in memory database like hsqldb for this if you really are doing lots of lookups of different "keys". – TofuBeer Mar 04 '11 at 03:56
9

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)]
OscarRyz
  • 196,001
  • 113
  • 385
  • 569
  • This is cool and I plussed you, but in practice it's not much more concise than the "verbose" code.. for(n:list){if(n.getAge()>16 t.add(n);} is shorter than the anonymous class in some cases.. – Mark D Nov 29 '11 at 19:40
2

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)?

Community
  • 1
  • 1
npgall
  • 2,979
  • 1
  • 24
  • 24
1

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.

Matthew
  • 44,826
  • 10
  • 98
  • 87
0

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.)

Vance Maverick
  • 812
  • 6
  • 4