0

I use to store lists/arrays of repeating objects as maps where key is the object itself and value is its multiplicity.

Say ['d', 'a', 'd', 'd', 'y'] --> {d=3, a=1, y=1} (the order is preserved, FWIW)

I do this both for saving bytes and because manipulating this counting map makes my life easier

Sometimes I do need to revert to list/array form,

say {d=3, a=1, y=1} --> ['d', 'd', 'd', 'a', 'y']

Clearly the original order can't be recreated, that information is lost, but that's unrelevant as I consider equivalent two lists/arrays if they have the same size and share exactly the same elements (including the relative multiplicity)

To get from Map to List I've written the following method :

<T> ArrayList<T> counted2Lst(final LinkedHashMap<T, Integer> map){
    final ArrayList<T> grp=new ArrayList<T>(map.size());  // larger most of the times

    for (final Entry<T, Integer> entry : map.entrySet()) {
        final T obj=entry.getKey();

        for(int i=entry.getValue(); i>0; i--){
            grp.add(obj);
        }
    }

    return grp;
}

Which does what it is expected to do : build an ArrayList out of a Map

I run into problems when I decided to do the same, but getting a T[] (NOT an Object[]) instead of an ArrayList

Here's my first attempt :

<T> T[] counted2Arry(final Map<T, Integer> map){
    final ArrayList<T> tmpLst=counted2Lst(map);     // method defined above

    @SuppressWarnings("unchecked")
    T[] arryT=(T[]) java.lang.reflect.Array.newInstance(tmpLst.get(0).getClass(), tmpLst.size());

    return tmpLst.toArray(arryT);
}

As far as I know, due to type erasure, there's no way at runTime to get the parameterized type of a Collection/Map, so to actually create an array of T I had first to get the Class of the first object stored in tmpLst (granted to be of type T) and then making an array of T via java.lang.reflect.Array.newInstance()

Problem is that if the input map is empty (which is perfectly reasonable and legal case), then there's no object from which detecting the actual Class for T.

In other words, how can I say in Java ?

if(map.size()==0)  {    // or  (tmpLst.size()==0)
    // return a zero-sized T[0] (as opposed to a zero-sized Object[])
}
aliteralmind
  • 18,274
  • 16
  • 66
  • 102
  • By the way, look at how the `toArray` method is implemented in the java collections. Most of the time it's just an `Object[]` cast to a `T[]` because of type erasure and the fact that you won't put anything but `T`s in the array. – Giovanni Botta Mar 17 '14 at 16:33
  • 1
    Do you need an array for some reason? Arrays do not mix well with generics. Returning a List is always more convenient. The client can call `toArray` on it if they want. – Radiodef Mar 18 '14 at 13:57
  • counted2Lst() does return a List, but I wanted to allow returning as an option a T[] (that's what counted2Arry() tries to do). – user3374707 Mar 20 '14 at 08:35
  • ... continue ... List.toArry() does return an Object[] - not a T[] - and List.toArry(T[]) needs to specify the array type, which is the info I was trying to work out (the client programmer might not know that info). As a matter of fact, after a few days of investigation, I've come to the conclusion that my attempt was really naif and that currently Java doesn't allow to do what I want, mainly due to type erasure; I don't know if Java 8, possibly breaking back compatibility, allows that. – user3374707 Mar 20 '14 at 08:45

2 Answers2

2

You should really checkout Guava's Multiset (or SortedMultiset). It basically does exactly what you're doing in your code and more, but within a production proven library.

Here's an example:

char[] chars = new char[]{'d', 'a', 'd', 'd', 'y'};

// unsorted version
ImmutableMultiset.Builder<Character> builder = 
  ImmutableMultiset.builder();
for (char c : chars) builder.add(c);
ImmutableMultiset<Character> mset = builder.build();
System.out.println(new ArrayList<>(mset));

// sorted version
ImmutableSortedMultiset.Builder<Character> sortedBuilder =
  ImmutableSortedMultiset.naturalOrder();
for (char c : chars) sortedBuilder.add(c);
ImmutableSortedMultiset<Character> sortedMset = sortedBuilder.build();
System.out.println(new ArrayList<>(sortedMset));

// how to get an array
Object[] array = mset.toArray();
System.out.println(Arrays.toString(array));

EDIT: I don't recommend writing the method yourself, but this is how ArrayList.toArray is implemented in the Oracle JDK 7:

public <T> T[] toArray(T[] a) {
  if (a.length < size)
    // Make a new array of a's runtime type, but my contents:
    return (T[]) Arrays.copyOf(elementData, size, a.getClass());
  System.arraycopy(elementData, 0, a, 0, size);
  if (a.length > size)
    a[size] = null;
  return a;
}

EDIT 2: added Guava code example above. I realized that there is no way to do what the OP is trying to do unless the map has at least one key or you pass in a (possibly empty) T[] and use the approach used throughout the JDK as described above, or by at least having the Class<T> available (see this SO):

public <T> T[] toArray(Class<T> type) {
  int size = // define size of returned array
  T[] ret = (T[]) Array.newInstance(type, size)
  // fill the array
  return ret;
}
Community
  • 1
  • 1
Giovanni Botta
  • 8,996
  • 5
  • 45
  • 88
  • 1
    (To do this particular thing, you'd just write `new ArrayList(multiset)`.) – Louis Wasserman Mar 17 '14 at 16:28
  • Not sure that toArray(T[]) provided code example can help : the class information of the array T[] does retain information about its parameterized type, whereas that doesn't happen, AFAIK, with Map and ArrayList. About Guava's MultiSet (specificaly HashMultiset) I don't see a method doing what I ask. Indeed there's java.util.AbstractCollection.toArray() --> Object[] and java.util.AbstractCollection.toArray(T[] a) --> T[], but that won't work the way I need : a method returning an E[], but w/o passing any info about the paramaterized type – user3374707 Mar 18 '14 at 09:23
  • I think the approach used in the JDK is the only one possible. That's why the collection library doesn't even attempt at doing what you are trying to. See my updated answer for an example of how to use `Multiset` and `SortedMultiset`. – Giovanni Botta Mar 18 '14 at 13:37
1

You can get the size of the entry set.

if (map.entrySet().size() == 0) { /* do things */ }
jgitter
  • 3,296
  • 1
  • 16
  • 25
  • The problematic part is not getting the size of the map, of it's entrySet (which is the same) or of the derived list, but the /* do things */ part – user3374707 Mar 18 '14 at 08:55