10

I have a Hashmap which may contain wildcards (*) in the String.

For instance,

HashMap<String, Student> students_;

can have John* as one key. I want to know if JohnSmith matches any elements in students_. There could be several matches for my string (John*, Jo*Smith, etc). Is there any way I can get a list of these matches from my HashMap?

Is there another object I could be using that does not require me to iterate through every element in my collection, or do I have to suck it up and use a List object?

FYI, my collection will have less than 200 elements in it, and ultimately I will want to find the pair that matches with the least amount of wildcards.

Sarah
  • 245
  • 2
  • 6
  • 12
  • 2
    Hashing functions in general are constructed in a way that minor changes (eg: `John SmitH` to `John Smith`) yield totally different hashes. – NullUserException Sep 30 '11 at 13:06
  • Why don't you want to iterate? It's not that bad (especially with less than 200 elements) , and ultimately any other solution will likely involve something similar in terms of performance. – Guillaume Sep 30 '11 at 13:07
  • At less than 200 elements, just do linear search over `entrySet()` and evaluate your wildcard against each key. If it would have been much more, I'd suggested an (embedded) database and a `LIKE` query. – Philipp Reichart Sep 30 '11 at 13:08
  • Thank you guys. The list is not huge but I may have to do the get frequently. I was curious if there was was a more efficient way do this but perhaps not – Sarah Sep 30 '11 at 13:39
  • Possible duplicate of [Partial search in HashMap](https://stackoverflow.com/questions/6713239/partial-search-in-hashmap) – Justinas Jakavonis May 29 '19 at 20:21

3 Answers3

3

It's not possible to achieve with a hasmap, because of the hashing function. It would have to assign the hash of "John*" and the hash of "John Smith" et al. the same value.

You could make it with a TreeMap, if you write your own custom class WildcardString wrapping String, and implement compareTo in such a way that "John*".compareTo("John Smith") returns 0. You could do this with regular expressions like other answers have already pointed out.

Seeing that you want the list of widlcard matchings you could always remove entries as you find them, and iterate TreeMap.get()'s. Remember to put the keys back once finished with a name.

This is just a possible way to achieve it. With less than 200 elements you'll be fine iterating.

UPDATE: To impose order correctly on the TreeSet, you could differentiate the case of comparing two WildcardStrings (meaning it's a comparation between keys) and comparing a WildcardString to a String (comparing a key with a search value).

Community
  • 1
  • 1
Xavi López
  • 27,550
  • 11
  • 97
  • 161
  • Thank you, Xavi. For a list of 200 do you think there will be any performance benefit to using a TreeSet? – Sarah Sep 30 '11 at 14:00
  • 2
    Creating a compareTo(string) method in the WildCardString class would break the contract of the compareTo method because: `wildCardString.compareTo(string)` may not be the opposite sign or `string.compareTo(wildCardString)`. Additionally it's recommended that compareTo is consistent with equals. – Jim Sep 30 '11 at 14:04
2

You can use regex to match, but you must first turn "John*" into the regex equivalent "John.*", although you can do that on-the-fly.

Here's some code that will work:

String name = "John Smith"; // For example
Map<String, Student> students_ = new HashMap<String, Sandbox.Student>();

for (Map.Entry<String, Student> entry : students_.entrySet()) {
    // If the entry key is "John*", this code will match if name = "John Smith"
    if (name.matches("^.*" + entry.getKey().replace("*", ".*") + ".*$")) {
        // do something with the matching map entry
        System.out.println("Student " + entry.getValue() + " matched " + entry.getKey());
    }
}
Bohemian
  • 412,405
  • 93
  • 575
  • 722
  • @Guillaume no he specifically did **NOT** say that. Specifically, he said: *Is there another object I could be using that does not require me to iterate through every element in my collection, **or do I have to suck it up and use a List object** ?*. I answered the question by confirming the *OR* part. – Bohemian Sep 30 '11 at 20:28
  • 1
    Bohemian, you sophist :-) I agree with your answer anyway. I don't see why he doesn't want to iterate. – Guillaume Sep 30 '11 at 21:32
0

You can just iterate your Map without converting it into a list, and use the String matches function, wih uses a regexp.

If you want to avoid the loop, you can use guava like this

@Test
public void hashsetContainsWithWildcards() throws Exception {
Set<String> students = new HashSet<String>();
students.add("John*");
students.add("Jo*Smith");
students.add("Bill");

Set<String> filteredStudents = Sets.filter(students, new Predicate<String>() {
  public boolean apply(String string) {
    return "JohnSmith".matches(string.replace("*", ".*"));
  }
});

assertEquals(2, filteredStudents.size());
assertTrue(filteredStudents.contains("John*"));
assertTrue(filteredStudents.contains("Jo*Smith"));

}