Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Return 2 counted values from one function

Tags:

java

I want to create a function which returns two counted values. The values are counted by iterating over a for-loop.

For example, I have an array of persons (male, female, adults, children) and I only want to find the amount of boys (child + male) and the amount of women (adult + female).

In the last few years I've been writing in javascript and this is how I would have done it in javascript.

function countBoysAndWomen() {
    var womenCounter = 0;
    var boysCounter = 0;

    for (var p of persons) {
        if (p.isAdult() && p.isFemale()) womenCounter++;
        else if (p.isChild() && p.isMale()) boysCounter++;
    }

    return {amountOfWomen: womenCounter, amountOfBoys: boysCounter};
}

Now my problem is, how do I return such an object in java? Do I need to create a new class? What would you call that class?

Isn't that extremely inefficient, having a complete class only for this one small purpose?

What if I wanted to count a different pair of values? Would I have to create another entirely new class?

Is this below actually the best way of creating such a function?

private Counter countBoysAndWomen() {
     Counter counter = new Counter();

     for (Person p:persons) {
         if (p.isBoy()) counter.addBoy();
         else if (p.isWomen()) counter.addWoman();
     }

     return counter;
}

Of course, another option would be to seperate the function into the functions "countBoys()" and "countWomen()" but then I'd have to iterate over the array twice and that wouldn't be optimal right?

like image 610
IceRevenge Avatar asked Sep 26 '19 19:09

IceRevenge


3 Answers

You can call below method from your main function or any method. it will return a map of desired value. in below code i am assuming that persons is a list. but if person is an array, change size() with length method.

public HashMap<String,Integer>  countBoysAndWomen() {

        int womenCounter = 0;
        int boysCounter = 0;
        HashMap<String,Integer> hm = new HashMap<>();
        for (int i = 0 ; i < persons.size() ; i++ ) {

            if (p.isAdult() && p.isFemale()){
              womenCounter++;
            }
            else if (p.isChild() && p.isMale()) {
            boysCounter++;
            }
        }
         hm.put("women",womenCounter );
         hm.put("boy" , boysCounter);

         return hm;
     }

for retrieving hashmap value :-

 for (Map.Entry<String,String> entry : hm.entrySet())  
            System.out.println("Key = " + entry.getKey() + 
                             ", Value = " + entry.getValue()); 
    } 
like image 94
SSP Avatar answered Nov 14 '22 04:11

SSP


While there are some good Answers, especially the one by ash, they address the specific question raised in the Title about returning two values. However, you raised two other questions that show the larger problem.

Premature Optimization

Isn't that extremely inefficient, having a complete class only for this one small purpose?

Always go with clean design first. Do not build an initial design on some imaginary possible performance issue or on excessive worry about efficiency. Processors do two to four billion instructions per second nowadays, so we can afford a little bit of inefficiency. Making code clear, easy to read, easy to debug, and easy to modify is almost always more important than efficiency.

On top of that, programmers of all caliber are notoriously bad at predicting performance and bottlenecks. Do not muddy your design with compromises until you have a proven measurable bottleneck of some significance.

What if I wanted to count a different pair of values? Would I have to create another entirely new class?

That question points to the clumsiness of your trying to combine queries that should be separate.

Let's write some code. First, the Person class.

Enum

Notice that we have nested two enums, Gender & Maturity. If not familiar, see Oracle Tutorial. The enum facility in Java is much more useful, flexible, and powerful than in other languages. Using strings as value flags is clumsy and error-prone. The compiler cannot help you with typos. In contrast, the compiler can help you with enums, bringing type-safety to your code while ensuring valid values.

Bonus benefits to using enums: Very little memory used, and very fast to execute.

package work.basil.example;

import java.util.Objects;
import java.util.UUID;

public class Person
{
    public enum Gender
    {
        FEMALE, MALE
    }

    public enum Maturity
    {
        ADULT, CHILD
    }

    // Members
    private UUID id;
    private Gender gender;
    private Maturity maturity;

    // Constructor
    public Person ( UUID id , Gender gender , Maturity maturity )
    {
        Objects.requireNonNull ( id );
        Objects.requireNonNull ( gender );
        Objects.requireNonNull ( maturity );

        this.id = id;
        this.gender = gender;
        this.maturity = maturity;
    }

    // Accessors

    public UUID getId ( )
    {
        return id;
    }

    public Gender getGender ( )
    {
        return gender;
    }

    public Maturity getMaturity ( )
    {
        return maturity;
    }

    // Object overrides

    @Override
    public String toString ( )
    {
        return "Person{" +
                "id=" + id +
                ", gender=" + gender +
                ", maturity=" + maturity +
                '}';
    }

    @Override
    public boolean equals ( Object o )
    {
        if ( this == o ) return true;
        if ( o == null || getClass () != o.getClass () ) return false;
        Person person = ( Person ) o;
        return id.equals ( person.id );
    }

    @Override
    public int hashCode ( )
    {
        return Objects.hash ( this.id );
    }
}

Write a method countPeople that takes a List of Person objects, along with your pair of criteria (gender & maturity).

We can pass any combination of gender and maturity. This design continues to work even as you add values to your enums, such as Gender.UNKNOWN and Maturity.ADOLESCENT.

private Integer countPeople ( List < Person > people , Person.Gender gender , Person.Maturity maturity )
{
    Objects.requireNonNull ( people );
    Objects.requireNonNull ( gender );
    Objects.requireNonNull ( maturity );

    Integer count = 0;
    for ( Person person : people )
    {
        if ( ( person.getGender ().equals( gender ) ) && ( person.getMaturity ().equals( maturity ) ) )
        {
            count = ( count + 1 );
        }
    }
    return count;
}

And, write some code to exercise that counting code.

The List.of syntax is new in Java 9 and later, creating an unmodifiable List object of an indeterminate concrete class in one simple line of code.

List < Person > people = List.of (
        new Person ( UUID.randomUUID () , Person.Gender.FEMALE , Person.Maturity.CHILD ) ,
        new Person ( UUID.randomUUID () , Person.Gender.FEMALE , Person.Maturity.ADULT ) ,
        new Person ( UUID.randomUUID () , Person.Gender.MALE , Person.Maturity.ADULT ) ,
        new Person ( UUID.randomUUID () , Person.Gender.FEMALE , Person.Maturity.CHILD ) ,
        new Person ( UUID.randomUUID () , Person.Gender.MALE , Person.Maturity.ADULT ) ,
        new Person ( UUID.randomUUID () , Person.Gender.MALE , Person.Maturity.CHILD ) ,
        new Person ( UUID.randomUUID () , Person.Gender.FEMALE , Person.Maturity.ADULT )
);

Integer women = this.countPeople ( people , Person.Gender.FEMALE , Person.Maturity.ADULT );
Integer boys = this.countPeople ( people , Person.Gender.MALE , Person.Maturity.CHILD );

Report.

System.out.println ( "people = " + people );
System.out.println ( "women = " + women );
System.out.println ( "boys = " + boys );

people = [Person{id=1ac225e6-f21c-49f5-82d5-8e0f289f16e0, gender=FEMALE, maturity=CHILD}, Person{id=333828cc-48e6-4d0c-9937-66f3168445bd, gender=FEMALE, maturity=ADULT}, Person{id=4d37bc08-1e1f-4806-8d84-dc314b6b2cd8, gender=MALE, maturity=ADULT}, Person{id=0cd2a38a-5b01-4091-9cb2-c0284739aa70, gender=FEMALE, maturity=CHILD}, Person{id=36d9af87-3cbb-44bc-bf03-67df45a5d8c8, gender=MALE, maturity=ADULT}, Person{id=2fef944a-79c9-4b29-9191-4bc694b58a4d, gender=MALE, maturity=CHILD}, Person{id=ffc8f355-9a4b-47c3-8092-f64b6da87483, gender=FEMALE, maturity=ADULT}]

women = 2

boys = 1


Streams

We could get fancy and use streams in place of that for loop. Not necessarily better in this case, but more fun.

We are going to call Stream::count. This returns a long, so change our use of 32-bit integer to 64-bit long. Or else you could cast the long to an integer.

Calling List::stream generates a stream of the Person objects held in the list.

A Predicate object holds our test for gender and our test for maturity. The call to Stream::filter applies the predicate to select objects. These get counted by a call to Stream::count.

private Long countPeople ( List < Person > people , Person.Gender gender , Person.Maturity maturity )
{
    Objects.requireNonNull ( people );
    Objects.requireNonNull ( gender );
    Objects.requireNonNull ( maturity );

    Predicate < Person > predicate = ( Person person ) -> ( person.getGender ().equals ( gender ) && person.getMaturity ().equals ( maturity ) );
    Long count = people.stream ().filter ( predicate ).count ();
    return count;
}

Tip: Performance with very large lists of people might be better if you went the next step, to parallelize the stream.


Databases

If this data were coming from a database, you should be doing this work within that database, rather than on the Java side. Database engines such as Postgres are highly optimized to do just this kind of selecting, comparing, sorting, and counting work.

like image 1
Basil Bourque Avatar answered Nov 14 '22 03:11

Basil Bourque


Rather than solutions using Map, I would propose you create a private class in which you can store the result:

private class People {
    public int boys = 0;
    public int women = 0;
}

The class can be used as an inner class of where your countBoysAndGirls method is located. Rather than making the method return Map, you can make it return People (I couldn't come up with a better name for the People class).

Generally, placing items in a map isn't really great as the keys can vary and get out of sync with what you expect in other places in your code. Making a private class with actual variables, ensures compile-time safety and is semantically much more powerfull.

like image 1
ImJustACowLol Avatar answered Nov 14 '22 02:11

ImJustACowLol