I need a multimap where keys are case insensitive strings and values
are lists (as opposed to sets). Using the 20071022 package.
Can you get this particular combination?
TreeMultimap allows you to specify a comparator for keys, so you can
get case insensitive keys, but this class treats values as sets, not
as lists.
Any other options?
If not, maybe adding something like a TreeListMultimap would make
sense?
Thanks,
Marius
> I need a multimap where keys are case insensitive strings and values
> are lists (as opposed to sets). Using the 20071022 package.
>
> Can you get this particular combination?
>
> TreeMultimap allows you to specify a comparator for keys, so you can
> get case insensitive keys, but this class treats values as sets, not
> as lists.
<soapbox>
I can't let this go without issuing a blanket caution against ever
using TreeMap, TreeSet, TreeMultimap, TreeMultiset, etc. with a
comparator that is inconsistent with equals().
I would love to be able to tell you the exact 37 things that break
badly when you do this, but I'm still discovering new ones every day.
Can you just convert all your keys to lowercase? Then simply use
ArrayListMultimap. If you don't want to lose the knowledge of what
casing you first saw a particular string in, you could keep that in a
separate map.
If this doesn't work for you, let's keep discussing it.
>
> Any other options?
>
> If not, maybe adding something like a TreeListMultimap would make
> sense?
>
> Thanks,
> Marius
>
>
> >
>
--
Kevin Bourrillion @ Google
go/javalibraries
google-collections.googlecode.com
google-guice.googlecode.com
jsr-310.dev.java.net
For String keys, can you use String.CASE_INSENSITIVE_ORDER? This
definitely is not consistent with String.equals() (and
equalsIgnoreCase does not count).
I have seen TreeMap used with String.CASE_INSENSITIVE_ORDER.
>
> I would love to be able to tell you the exact 37 things that break
> badly when you do this, but I'm still discovering new ones every day.
A few examples would help.
>
> Can you just convert all your keys to lowercase? Then simply use
> ArrayListMultimap. If you don't want to lose the knowledge of what
> casing you first saw a particular string in, you could keep that in a
> separate map.
Probably I can get away with forcing everything to lowercase, not 100%
sure yet. Would need to subclass ArrayListMultimap to force keys to
lowercase.
Keeping a separate map is a posibility, but I would prefer to keep
things simple.
> If this doesn't work for you, let's keep discussing it.
As a general question though, doesn't it make sense to have
ListMultimaps where you can specify a key comparator/order?
Marius
> > I can't let this go without issuing a blanket caution against ever
> > using TreeMap, TreeSet, TreeMultimap, TreeMultiset, etc. with a
> > comparator that is inconsistent with equals().
>
> For String keys, can you use String.CASE_INSENSITIVE_ORDER? This
> definitely is not consistent with String.equals() (and
> equalsIgnoreCase does not count).
>
> I have seen TreeMap used with String.CASE_INSENSITIVE_ORDER.
Right, that's the quintessential example of how to begin the process
of shooting yourself in the foot. :-)
> > I would love to be able to tell you the exact 37 things that break
> > badly when you do this, but I'm still discovering new ones every day.
>
> A few examples would help.
For starters, what would you suppose the following code does?
static void test(String... elts) {
Set<String> s = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
s.addAll(Arrays.asList("a", "b"));
s.removeAll(Arrays.asList(elts));
System.out.println(s);
}
public static void main(String[] args) {
test("A");
test("A", "C");
}
Run it, and I'm sure you'll be surprised. True, this a bug (in
AbstractSet.java), so you could say, "well, they should just fix that,
then I'd be fine" -- but read AbstractSet.java and see if you can
figure out what the bug is! I sure as hell couldn't, it's very
subtle.
I will come up with more and better cautionary tales when I'm less
short on time....
> > Can you just convert all your keys to lowercase? Then simply use
> > ArrayListMultimap. If you don't want to lose the knowledge of what
> > casing you first saw a particular string in, you could keep that in a
> > separate map.
>
> Probably I can get away with forcing everything to lowercase, not 100%
> sure yet. Would need to subclass ArrayListMultimap to force keys to
> lowercase.
Why would you need to subclass it to do this? Why don't you just, you
know, change your usages of "put(k, v)" to "put(k.toLowerCase(), v)"?
> Keeping a separate map is a posibility, but I would prefer to keep
> things simple.
Yes, simple is much better than not-simple, when both options are
correct and bug-proof.
> > If this doesn't work for you, let's keep discussing it.
>
> As a general question though, doesn't it make sense to have
> ListMultimaps where you can specify a key comparator/order?
Oh, I forgot to mention that you can indeed do this!
ListMultimap<String, Foo> multimap = Multimaps.newListMultimap(
Maps.newTreeMap(),
new Supplier<List<Foo>>() {
protected List<Foo> get() {
return Lists.newArrayList();
}
});
It's not pretty, and you can't subclass it (but again, you shouldn't
need to), but it should work.
Yes, it was a surprise.
Tried several tests, for both TreeSet and TreeMap, using Java 5 and 6.
It seems that only removeAll is affected, but it is clear that things
can definitely go wrong.
As far as you know, is there a bug report for this issue?
> > Probably I can get away with forcing everything to lowercase, not 100%
> > sure yet. Would need to subclass ArrayListMultimap to force keys to
> > lowercase.
>
> Why would you need to subclass it to do this? Why don't you just, you
> know, change your usages of "put(k, v)" to "put(k.toLowerCase(), v)"?
You have to call toLowerCase whenever you use a key. At some point, me
or someone else, will forget to do that.
> > As a general question though, doesn't it make sense to have
> > ListMultimaps where you can specify a key comparator/order?
>
> Oh, I forgot to mention that you can indeed do this!
>
> ListMultimap<String, Foo> multimap = Multimaps.newListMultimap(
> Maps.newTreeMap(),
> new Supplier<List<Foo>>() {
> protected List<Foo> get() {
> return Lists.newArrayList();
> }
> });
>
> It's not pretty, and you can't subclass it (but again, you shouldn't
> need to), but it should work.
I did not notice newListMultimap, thanks.
The code above did not work though, and I am not sure how to make it work.
Here is the compiler error:
<K,V>newListMultimap(java.util.Map<K,java.util.Collection<V>>,com.google.common.base.Supplier<?
extends java.util.List<V>>) in com.google.common.collect.Multimaps
cannot be applied to
(java.util.TreeMap<java.lang.Comparable,java.lang.Object>,<anonymous
com.google.common.base.Supplier<java.util.List<java.lang.String>>>)
I also had to make the get method public, instead of protected.
Thanks,
Marius
> As far as you know, is there a bug report for this issue?
Yes... still open... don't have the # handy.
> > > Probably I can get away with forcing everything to lowercase, not 100%
> > > sure yet. Would need to subclass ArrayListMultimap to force keys to
> > > lowercase.
> >
> > Why would you need to subclass it to do this? Why don't you just, you
> > know, change your usages of "put(k, v)" to "put(k.toLowerCase(), v)"?
>
> You have to call toLowerCase whenever you use a key. At some point, me
> or someone else, will forget to do that.
Huh. Just how widely are you exposing this thing? I would expect it
to be private within a class that had a simple method in it to do
put(k.toLowerCase(), v) and I'd always use that method when putting.
If you want to expose it as something people all over can see, even
then subclassing is not required; you could, for example, use a
ForwardingListMultimap and override put() and putAll()....
> > ListMultimap<String, Foo> multimap = Multimaps.newListMultimap(
> > Maps.newTreeMap(),
> > new Supplier<List<Foo>>() {
> > protected List<Foo> get() {
> > return Lists.newArrayList();
> > }
> > });
> >
> > It's not pretty, and you can't subclass it (but again, you shouldn't
> > need to), but it should work.
>
> I did not notice newListMultimap, thanks.
>
> The code above did not work though, and I am not sure how to make it work.
Once again, where is the compile button in GMail?? I gotta go ask
them to do that...
I see, yeah, we're really pushing javac to its limits. It helps if
you create a temporary variable to hold the newTreeMap().
Yes, that did the trick. Thanks.
Marius