1

I've seen a lot of nice solutions here about how to work with ArrayList and HashMaps, but the point is I still can't solve my problem.

So, the idea is there are few people that drink beer, wine and cola. So, it looks like that (for example):

Steve wine
Steve cola
Ben cola
Frank wine
Ben cola
Ben cola
Frank wine

In the end I need to count how many glasses of each drink each of them drank. So, the answer should look like that:

Steve wine 1
Steve cola 1
Ben cola 3
Frank wine 2

My idea was to put to create an object Person(String name, String drink). Then I put all the persons to ArrayList. After that I have created HashMap and wanted to add there a new Person if key doesn't exist, and to increment to 1 if key already exists.

    Map<Person, Integer> map = new HashMap<Person, Integer>();

    for (Person p : persons)
    {
        if (map.containsKey(p)) {
            map.put(p, map.get(p)+1);
        } else {
            map.put(p,1);
        }
   }

It doesn't work. It just returns me the result like this:

 Steve wine 1
 Steve cola 1
 Ben cola 1
 Frank wine 1
 Ben cola 1
 Ben cola 1
 Frank wine 1

So, as I understand that should be some other trick here. Maybe you could also tell any other ideas of how to count the glasses of the drinks instead of using HashMap? Many thanks!

Boris
  • 404
  • 1
  • 7
  • 20
  • 10
    Did you override hashCode and equals in Person class? – Eran Sep 12 '17 at 06:22
  • 2
    Possible duplicate of [HashSet does not seem to realize that two objects are the same](https://stackoverflow.com/questions/3692426/hashset-does-not-seem-to-realize-that-two-objects-are-the-same) – Ferrybig Sep 12 '17 at 06:29

8 Answers8

7

Overwrite hashcode and equals method in your Person class

anukumarrag
  • 111
  • 1
5

If you can use Java 8 streams here is one clever solution :

    List<Person> people = Arrays.asList(new Person("Steve", "wine"), new Person("Steve", "cola"),
            new Person("Ben", "cola"), new Person("Ben", "cola"), new Person("Steve", "wine"),
            new Person("Steve", "wine"));

    Map<Person, Long> map = people.stream()
            .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));

Edit : To decrease the code you can import the methods statically like this:

import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.counting;
import static java.util.function.Function.identity;

and then the code look like this:

Map<Person, Long> map = people
                .stream()
                .collect(groupingBy(identity(), counting()));
Petar Petrov
  • 586
  • 2
  • 10
  • 28
  • Very nice solution just in 1 line of code! But honestly I don't understand what exactly is going inside this line of code. – Boris Sep 12 '17 at 07:06
2

You are making a mistake by storing Person Object as a Key.

You must store the person name, a String as Key and it will work fine.

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

    for (Person p : persons)
    {
        if (map.containsKey(p.getName())) {
            map.put(p.getName(), map.get(p)+1);
        } else {
            map.put(p.getName(),1);
        }
   }
Karan Sharma
  • 2,353
  • 4
  • 28
  • 46
  • Its a `Map`, isn't that fine then? – Naman Sep 12 '17 at 06:26
  • No, it is not,, you have two choices, 1. use string (name) as a key. or. 2. Use Person as key but overirde the hashcode() and equals() method in the Person class. – Karan Sharma Sep 12 '17 at 06:28
  • @Seelenvirtuose please check again , its updated. and kindly revert your -1. instead of correcting you are downvoting an answer. – Karan Sharma Sep 12 '17 at 06:28
  • @Seelenvirtuose kindly check the first sentence, it is corrected. So it would be better if you could take -1 back. – Karan Sharma Sep 12 '17 at 06:31
  • The point that it's about the name of the man and his drink. If I use just his name, I guess the information about drink will be lost. – Boris Sep 12 '17 at 06:44
  • @Boris no, because the object Person has name and drink name stored in it, so it won't. – Karan Sharma Sep 12 '17 at 06:59
  • @Karan ok, now I got the idea. You get the Person object, but use just the Name of it. Thanks! Also good solution! – Boris Sep 12 '17 at 07:08
  • @Boris Thank you !! This way, you do not have to override the hashcode and equals method. Please upvote if it helped. – Karan Sharma Sep 12 '17 at 09:20
1

Overriding equals and hascode method in your Person class is the solution for your problem.

assuming you have Person class with parameter name and drink , then you could use some IDE like eclipse to generate hashcode and equals method for you

see below code:

public class Person {
      private String name;
      private String drink;

      public String getName() {
            return name;
      }

      public void setName(String name) {
            this.name = name;
      }

      public String getDrink() {
            return drink;
      }

      public void setDrink(String drink) {
            this.drink = drink;
      }

      @Override
      public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((drink == null) ? 0 : drink.hashCode());
            result = prime * result + ((name == null) ? 0 : name.hashCode());
            return result;
      }

      @Override
      public boolean equals(Object obj) {
            if (this == obj)
                  return true;
            if (obj == null)
                  return false;
            if (getClass() != obj.getClass())
                  return false;
            Person other = (Person) obj;
            if (drink == null) {
                  if (other.drink != null)
                        return false;
            } else if (!drink.equals(other.drink))
                  return false;
            if (name == null) {
                  if (other.name != null)
                        return false;
            } else if (!name.equals(other.name))
                  return false;
            return true;
      }
}
Raju Sharma
  • 2,496
  • 3
  • 23
  • 41
  • Yes, now it works. Thanks! I'm new to Java programming and I've never tried overriding HashCode method before. I guess this is a good and more general solution for any kind of problem like that. – Boris Sep 12 '17 at 06:59
1

You have to override equals and hashCode method in Person class. Below is the sample code:

class Person {

  private String name;
  private String drink;

  public Person(String name, String drink) {
    super();
    this.name = name;
    this.drink = drink;
  }

  @Override
  public int hashCode() {

    return this.getName().hashCode();
  }

  @Override
  public boolean equals(Object obj) {

    if (obj == this)
        return true;

    if (!(obj instanceof Person)) {
        return false;
    }
    Person person = (Person) obj;

    return person.getName().equals(this.name);
  }
  ....getters and setters
  ....toString method
}

After this, if you try to run your code it will work for sure for me output for below code was

    Map<Person, Integer> map = new HashMap<>();

    for (Person p : persons)
    {
        if (map.containsKey(p)) {
            map.put(p, map.get(p)+1);
        } else {
            map.put(p,1);
        }
   }

   for(Map.Entry<Person, Integer> person : map.entrySet()){
       System.out.println(person.getKey()+"  "+person.getValue());
   }

Output:

Person [name=Steve, drink=wine]  2
Person [name=Ben, drink=cola]  3
Person [name=Frank, drink=wine]  2

Hope it will help you.

SachinSarawgi
  • 2,632
  • 20
  • 28
1

Key should be unique in hash map or Dictionary (C#). This case while inserting key itself need to combine name and drink. Giving the solution in C# here. Hope it helps.

public class Person
{
    public string Name { get; set; }
    public string Drink { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        List<Person> persons = new List<Person>();
        persons.Add(new Person() { Name = "Steve", Drink = "Tea" });
        persons.Add(new Person() { Name = "Bell", Drink = "Milk" });
        persons.Add(new Person() { Name = "Bell", Drink = "Milk" });
        persons.Add(new Person() { Name = "Bell", Drink = "Milk" });
        persons.Add(new Person() { Name = "Steve", Drink = "Milk" });
        Dictionary<string, int> output = new Dictionary<string, int>();
        foreach(var p in persons)
        {
            string key = p.Name + ":" + p.Drink;
            if(output.ContainsKey(key))
            {
                output[key]++;
            }
            else
            {
                output.Add(key,1);
            }
        }
        foreach(var k in output)
        {
            string[] split = k.Key.Split(':');
            Console.WriteLine(string.Format("{0} {1} {2}", split[0],split[1],k.Value.ToString()));
        }
    }
}
JyothiJ
  • 315
  • 1
  • 11
  • It's not in the language the OP works in - that's not going to help him, considering that in Java the implementation is different. – Assafs Sep 12 '17 at 07:27
0
int count = Collections.frequency("your collection", "Your Value");

I meant to say like that:

ArrayList<String> list = new ArrayList<>();
list.add("Steve wine");
list.add("Steve cola");
list.add("Ben cola");
list.add("Frank wine");
list.add("Ben cola");
list.add("Ben cola");
list.add("Frank wine");

System.out.println(Collections.frequency(list, "Steve wine"));

System.out.println(Collections.frequency(list, "Ben cola"));
  • What is "Your Value"? I don't know the names of the people or what they drink beforehand. – Boris Sep 12 '17 at 07:15
  • input would be 'Steve wine' –  Sep 12 '17 at 07:28
  • This won't work as I don't know what I have in the list. Imagine that this is an information provided by the bartender (it's just an example) as a text file. And around 200 guys were drinking something during the night. So, I can't really use frequency as I don't know the names to the drinks that were drunk. – Boris Sep 12 '17 at 09:38
  • @Boris if you are looking for frequency then you must have any properties of input to get the count. Lets say in your example if bartender is looking for frequency then he should know either drink or drinker. Even if you have file for data then you have to have data in any collection. –  Sep 12 '17 at 11:43
  • Yes, maybe it's also the solution to take every member of the collection one by one and use frequency method. You're right. Thanks. – Boris Sep 12 '17 at 11:59
0

First I would recommend you to take care of your namings. What is really the key in your case is an order not a person (since Steve cola and Steve wine is different you shouldn't name it as person).

After that: containsKey will use the hashcode method which will be inherited from the Object class if you don't override it in your class. The Object class's hashcode probably will provide different hashcode for your instances so you should override it in your class e.g. you can create an acceptable hashcode method if you concat the name and beverage (which is your unique identifier for an order) and invoke a hashcode method on that string.

Zsolt V
  • 517
  • 3
  • 8