Get the Distinct and count values from a field of an object in a Collection

5.8k Views Asked by At

I have a List of Pin objects (List<Pin>) where the Pin class has the following attributes:

String pinNumber, String pinType, Date insertDate

I would like to get a HashMap with <String pinNumber, int count> that have the distinct pinNumber telling me how many distinct pinNumber are in the List<Pin> and a count of each.

So the way I know of to do this is to:

  • Iterate through the List<Pin>
  • Check if the HashMap contains already the key value of the pinNumber and:
  • Increase it or add it if it does not exist.

I would like to do the same for each of the fields from the Pin object.

I am sure there should be an easier way to do this?

Maybe Guava has something simpler?

4

There are 4 best solutions below

0
On

Here is one possible solution if you didn't want to rely on another library and wanted to maintain backward compatibility with older JVMs. It isn't the best, or the easiest to use, but it does work.

FrequencyUtil.java

import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;

public class FrequencyUtil
{
    private static FrequencyUtil SINGLETON;

    private static FrequencyUtil getInstance()
    {
        if (FrequencyUtil.SINGLETON == null)
        {
            FrequencyUtil.SINGLETON = new FrequencyUtil();
        }

        return FrequencyUtil.SINGLETON;
    }

    public static <X> Map<X, Integer> frequency(final Collection<X> objects, final Comparator<X> comparator)
    {
        Map<ComparatorWrapper<X>, Integer> frequencies = new HashMap<ComparatorWrapper<X>, Integer>();

        for (X object : objects)
        {
            ComparatorWrapper<X> wrapper = FrequencyUtil.getInstance().new ComparatorWrapper<X>(object, comparator);
            Integer count = frequencies.get(wrapper);
            frequencies.put(wrapper, (count == null) ? 1 : count + 1);
        }

        // unwrap the frequencies
        Map<X, Integer> frequenciesRaw = new HashMap<X, Integer>();

        for (ComparatorWrapper<X> wrapper : frequencies.keySet())
        {
            frequenciesRaw.put(wrapper.getObject(), frequencies.get(wrapper));
        }

        return frequenciesRaw;
    }

    private class ComparatorWrapper<Z>
    {
        private Z object;
        private Comparator<Z> comparator;

        ComparatorWrapper(final Z object, final Comparator<Z> comparator)
        {
            this.object = object;
            this.comparator = comparator;
            return;
        }

        public Z getObject()
        {
            return this.object;
        }

        @Override
        public int hashCode()
        {
            return 0;
        }

        @SuppressWarnings("unchecked")
        @Override
        public boolean equals(Object obj)
        {
            if ((obj == null) || !(obj instanceof ComparatorWrapper))
            {
                return false;
            }

            return this.comparator.compare(this.object, ((ComparatorWrapper<Z>) obj).getObject()) == 0;
        }
    }
}

FrequencyTest.java

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Comparator;
import java.util.List;
import java.util.Map;

public class FrequencyTest
{
    public void test()
    {
        List<Pin> pins = new ArrayList<Pin>();

        Pin pin1 = new Pin();
        Pin pin2 = new Pin();
        Pin pin3 = new Pin();

        pin1.setPinType("typeA");
        pin2.setPinType("typeB");
        pin3.setPinType("typeA");

        pin1.setPinNumber("50");
        pin2.setPinNumber("50");
        pin3.setPinNumber("80");

        pin1.setInsertDate(Calendar.getInstance().getTime());
        pin2.setInsertDate(Calendar.getInstance().getTime());
        pin3.setInsertDate(Calendar.getInstance().getTime());

        pins.add(pin1);
        pins.add(pin2);
        pins.add(pin3);

        Comparator<Pin> pinTypeComparator = new Comparator<Pin>()
        {
            @Override
            public int compare(final Pin o1, final Pin o2)
            {
                return o1.getPinType().compareTo(o2.getPinType());
            }
        };

        Comparator<Pin> pinNumberComparator = new Comparator<Pin>()
        {
            @Override
            public int compare(final Pin o1, final Pin o2)
            {
                return o1.getPinNumber().compareTo(o2.getPinNumber());
            }
        };

        Comparator<Pin> insertDateComparator = new Comparator<Pin>()
        {
            private SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");

            @Override
            public int compare(final Pin o1, final Pin o2)
            {
                return this.sdf.format(o1.getInsertDate()).compareTo(this.sdf.format(o2.getInsertDate()));
            }
        };

        Map<Pin, Integer> pinTypeFrequency = FrequencyUtil.frequency(pins, pinTypeComparator);
        Map<Pin, Integer> pinNumberFrequency = FrequencyUtil.frequency(pins, pinNumberComparator);
        Map<Pin, Integer> insertDateFrequency = FrequencyUtil.frequency(pins, insertDateComparator);

        System.out.println("pinTypeFrequency");
        for (Pin pin : pinTypeFrequency.keySet())
        {
            System.out.println(pin.getPinType() + ": " + pinTypeFrequency.get(pin));
        }

        System.out.println();
        System.out.println("pinNumberFrequency");
        for (Pin pin : pinNumberFrequency.keySet())
        {
            System.out.println(pin.getPinNumber() + ": " + pinNumberFrequency.get(pin));
        }

        System.out.println();
        System.out.println("insertDateFrequency");
        for (Pin pin : insertDateFrequency.keySet())
        {
            System.out.println(pin.getInsertDate().toString() + ": " + insertDateFrequency.get(pin));
        }
    }

    public static void main(String[] args)
    {
        try
        {
            new FrequencyTest().test();
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
        }

        System.exit(0);
    }
}

Pin.java

import java.util.Date;

public class Pin
{
    private String pinNumber;
    private String pinType;
    private Date insertDate;

    public String getPinNumber()
    {
        return pinNumber;
    }

    public void setPinNumber(String pinNumber)
    {
        this.pinNumber = pinNumber;
    }

    public String getPinType()
    {
        return pinType;
    }

    public void setPinType(String pinType)
    {
        this.pinType = pinType;
    }

    public Date getInsertDate()
    {
        return insertDate;
    }

    public void setInsertDate(Date insertDate)
    {
        this.insertDate = insertDate;
    }
}

output

pinTypeFrequency typeB: 1 typeA: 2

pinNumberFrequency 80: 1 50: 2

insertDateFrequency Mon Jun 22 12:09:19 EDT 2015: 3

Just for fun and historical reference, a Java 1.2 version:

FrequencyUtil12.java

import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class FrequencyUtil
{
    private static FrequencyUtil SINGLETON;

    private static FrequencyUtil getInstance()
    {
        if (FrequencyUtil.SINGLETON == null)
        {
            FrequencyUtil.SINGLETON = new FrequencyUtil();
        }

        return FrequencyUtil.SINGLETON;
    }

    public static Map frequency(final Collection objects, final Comparator comparator)
    {
        Map frequencies = new HashMap();

        Iterator iter = objects.iterator();
        while (iter.hasNext())
        {
            Object object = iter.next();
            ComparatorWrapper wrapper = FrequencyUtil.getInstance().new ComparatorWrapper(object, comparator);
            Integer count = (Integer) frequencies.get(wrapper);
            frequencies.put(wrapper, (count == null) ? 1 : count + 1);
        }

        // unwrap the frequencies
        Map frequenciesRaw = new HashMap();

        Iterator keys = frequencies.keySet().iterator();
        while (keys.hasNext())
        {
            ComparatorWrapper wrapper = (ComparatorWrapper) keys.next();
            frequenciesRaw.put(wrapper.getObject(), frequencies.get(wrapper));
        }

        return frequenciesRaw;
    }

    private class ComparatorWrapper
    {
        private Object object;
        private Comparator comparator;

        ComparatorWrapper(final Object object, final Comparator comparator)
        {
            this.object = object;
            this.comparator = comparator;
            return;
        }

        public Object getObject()
        {
            return this.object;
        }

        public int hashCode()
        {
            return 0;
        }

        public boolean equals(Object obj)
        {
            if ((obj == null) || !(obj instanceof ComparatorWrapper))
            {
                return false;
            }

            return this.comparator.compare(this.object, ((ComparatorWrapper) obj).getObject()) == 0;
        }
    }
}
5
On

Even Simpler implementation:

public static void main(String[] args) {

    List<Pin> pinList = new ArrayList<Pin>();

    // Add employee to list
    pinList.add(new Pin("1234", "local", null));
    pinList.add(new Pin("2345", "extra", null));
    pinList.add(new Pin("3456", "extra", null));
    pinList.add(new Pin("1234", "local", null));

    Map<String, Integer> mapPinNumber = new HashMap<String, Integer>();

    for (Pin pin : pinList) {
        Integer cnt = mapPinNumber.get(pin.getPinNumber());
        mapPinNumber.put(pin.getPinNumber(), (cnt == null) ? 1 : ++cnt);
    }
    printMap(mapPinNumber);

    Map<String, Integer> mapPinType = new HashMap<String, Integer>();

    for (Pin pin : pinList) {
        Integer cnt = mapPinType.get(pin.getPinType());
        mapPinType.put(pin.getPinType(), (cnt == null) ? 1 : ++cnt);
    }
    printMap(mapPinType);
}

private static void printMap(Map<String, Integer> map) {
    String key;
    int value;

    for (Map.Entry<String, Integer> entry : map.entrySet()) {
        key = entry.getKey();
        value = entry.getValue();
        System.out.println(key + ": " + value);

    }
}
0
On

If you have the possibility to use Java 8 (and since what you want to do basically sounds like a "group by" operation), this can be solved in an elegant way using the new Stream API (as hinted by user vallismortis):

import static java.util.stream.Collectors.counting;
import static java.util.stream.Collectors.groupingBy;

import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;

public class Main {

    public static void main(String[] args) {
        List<Pin> pins = Arrays.asList(
                new Pin("PIN-1", "T1", new Date()),
                new Pin("PIN-1", "T2", new Date()),
                new Pin("PIN-1", "T3", new Date()),
                new Pin("PIN-2", "T2", new Date()),
                new Pin("PIN-2", "T2", new Date()),
                new Pin("PIN-3", "T2", new Date())

        );
        Map<String, Long> map = pins.stream().collect(groupingBy(Pin::getPinNumber, counting()));
        System.out.println("map = " + map);
    }
}

class Pin {
    String pinNumber;
    String pinType;
    Date insertDate;

    public Pin(String pinNumber, String pinType, Date insertDate) {
        this.pinNumber = pinNumber;
        this.pinType = pinType;
        this.insertDate = insertDate;
    }

    public String getPinNumber() {
        return pinNumber;
    }

    public String getPinType() {
        return pinType;
    }

    public Date getInsertDate() {
        return insertDate;
    }
}

Output:

map = {PIN-1=3, PIN-3=1, PIN-2=2}
0
On

You don't need Guava for this. You can use standard Java 8 features. One way is with streams, however they aren't a good fit if you need to compute counts for more than one field. Instead, you could use the Map.merge method:

Map<String, Integer> byNumber = new HashMap<>();
Map<String, Integer> byType = new HashMap<>();
Map<Date, Integer> byInsertDate = new HashMap<>();

listOfPins.forEach(pin -> {
        byNumber.merge(pin.getPinNumber(), 1, Integer::sum);
        byType.merge(pin.getPinType(), 1, Integer::sum);
        byInsertDate.merge(pin.getInsertDate(), 1, Integer::sum);
    });

This has the advantage that it can be accomplished in only one iteration over listOfPins, while with streams, you'd need one pass for each field.