DarkT
DarkT

Reputation: 179

Finding the youngest and oldest dogs by date in Java

I put the ints that I made for the Month, Day, and Year together using the toString method. and then brought it to the main program.

public class Dog
{
    private String dogName;
    private int dogMonth;
    private int dogDay;
    private int dogYear;


public Dog(String name, int month, int day, int year)
{
    dogName  = name; 
    dogMonth = month;
    dogDay = day;
    dogYear = year;
} 
public String getName()
{
    return dogName;
}

public String toString()
{
    return String.format("%d/%d/%d", dogMonth, dogDay, dogYear);
}

Then I tried to find which is the lowest. but I don't know if I need a calendar API.

  while(sc.hasNextLine())
  {

    // read a line from the input file via sc into line
        line = sc.nextLine();       

     StringTokenizer stk = new StringTokenizer(line);
     String name = stk.nextToken();
     int month = Integer.parseInt(stk.nextToken());
     int day = Integer.parseInt(stk.nextToken());
     int year = Integer.parseInt(stk.nextToken());


     Dog list = new Dog(name, month, day, year);

     dogs.add(list);
   }
   sc.close();


   String lowDate= dogs.get(0).toString();
   String lowName = dogs.get(0).getName();

   for (int i = 0; i< dogs.size(); i++)
   {
     String newLow= dogs.get(i).toString();
     String newName = dogs.get(i).getName();
     if(lowDate>newLow)
     {
       lowDate = newLow;
       lowName= newName;
     }
  System.out.println("The youngest dog is"+lowName+lowDate);

How would I figure out the youngest and oldest?

For example:

File:

Dog#1 12 25 2005

Dog#2 7 15 2003

Dog#3 9 24 2005

Dog#4 1 1 2001

Outcome:

The oldest is Dog#4 1/1/2001

The youngest is Dog#1 12/25/2005

Upvotes: 2

Views: 3099

Answers (6)

Basil Bourque
Basil Bourque

Reputation: 338326

java.time

To represent a date-only value, without time-of-day, and without time zone, use java.time.LocalDate class. Make that class the type of the birth date field on the Dog class.

If the purpose of your class is to transparently communicate shallowly-immutable data, define the class as record.

Here is your entire Dog class, just one short line.

record Dog ( String name , LocalDate birthDate ) {}

After collecting year-month-day input from the user, instantiate a LocalDate. Pass to the constructor of your Dog class.

final List < Dog > dogs = new ArrayList <> () ;
…
dogs.add ( new Dog ( "Fido" , LocalDate.of ( y , m , d ) ) ) ;

The LocalDate objects know how to sort themselves. Pass a Comparator to specify using the birthDate field.

dogs.sort ( Comparator.comparing ( Dog :: birthDate ) );

Full example:

final List < Dog > dogs = new ArrayList <> ( );

dogs.add ( new Dog ( "Fido" , LocalDate.of ( 2024 , Month.FEBRUARY , 24 ) ) );
dogs.add ( new Dog ( "Alfy" , LocalDate.of ( 2020 , Month.MARCH , 20 ) ) );
dogs.add ( new Dog ( "Fluffy" , LocalDate.of ( 2022 , Month.APRIL , 22 ) ) );

dogs.sort ( Comparator.comparing ( Dog :: birthDate ) );

Report each dog with its calculated age.

LocalDate today = LocalDate.now ( ZoneId.of ( "America/Edmonton" ) );
dogs.forEach ( dog -> System.out.println ( "Dog: " + dog.name ( ) + " Age: " + Period.between ( dog.birthDate ( ) , today ) ) );

When run:

Dog: Alfy Age: P4Y9M19D
Dog: Fluffy Age: P2Y8M17D
Dog: Fido Age: P10M15D

You asked for the oldest and youngest dogs. To find those, ask for the first and last elements of the sorted list.

System.out.println ( "Oldest: " + dogs.getFirst ( ) );
System.out.println ( "Youngest: " + dogs.getLast ( ) );

When run:

Oldest: Dog[name=Alfy, birthDate=2020-03-20]
Youngest: Dog[name=Fido, birthDate=2024-02-24]

This code ignores ties, when two or more dogs are born on the same day. To handle that, to report multiple oldest or youngest dogs, you’d need another approach. For example, a multimap of birthdate to collection of dogs sharing that date.

Upvotes: 1

Basil Bourque
Basil Bourque

Reputation: 338326

Use Date-Time Data Types

Yes, if you are working with date-time data you should use date-time types (classes).

Avoid java.util.Date

Unfortunately, the old date-time clases bundled with Java (java.util.Date, .Calendar, and so on) are notoriously troublesome. Avoid them. Among many other problems, they lack a way to represent a date-only without time-of-day and time zone which is what you need.

Instead use either the Joda-Time library or the java.time package bundled with Java 8 (inspired by Joda-Time).

Comparators

As described in other answers, we must implement a comparator to reach inside the Dog object to retrieve and then compare his/her birthdate.

To actually compare the birthdates, we call the LocalDate comparison methods: isBefore, isAfter, and isEqual.

Joda-Time

Like java.time, Joda-Time includes a LocalDate class to represent date-only values without any time-of-day nor time zone.

Here is some example code in Joda-Time 2.4.

package com.example.jodatimeexperiment;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.joda.time.LocalDate;

public class Dog
{
    // Member variables.
    String name = null;
    LocalDate birthDate = null;

    // Constructor
    public Dog( String nameArg , int yearArg , int monthArg , int dayOfMonthArg )
    {
        this.name = nameArg;
        this.birthDate = new LocalDate( yearArg , monthArg , dayOfMonthArg );
    }

    @Override
    public String toString()
    {
        return "Dog{" + "name=" + name + ", birthDate=" + birthDate + '}';
    }

    // Main method, to run this program.
    public static void main( String[] args )
    {
        List<Dog> list = new ArrayList<>();
        list.add( new Dog( "Alpha" , 2005 , 12 , 25 ) );
        list.add( new Dog( "Beta" , 2003 , 7 , 15 ) );
        list.add( new Dog( "Gamma" , 2005 , 9 , 24 ) );
        list.add( new Dog( "Delta" , 2001 , 1 , 1 ) );
        Collections.sort( list , new AgeComparator() );
        System.out.println( "Oldest: " + list.get( 0 ) ); // First item in list.
        System.out.println( "Youngest: " + list.get( list.size() - 1 ) ); // Last item in list.
    }

}

// Implement a Comparator.
class AgeComparator implements Comparator<Dog>
{

    @Override
    public int compare( Dog dog1 , Dog dog2 )
    {
        if ( dog1.birthDate.isAfter( dog2.birthDate ) ) {
            return 1;
        } else if ( dog1.birthDate.isBefore( dog2.birthDate ) ) {
            return -1;
        } else if ( dog1.birthDate.isEqual( dog2.birthDate ) ) {
            return 0;
        } else {
            // FIXME: Handle this supposedly impossible error condition.
            // Programmer made a mistake in logic or a typo in comparisons above.
            return -1; // Arbitrary choice of value to satisfy compiler.
        }

    }
}

When run.

Oldest: Dog{name=Delta, birthDate=2001-01-01}
Youngest: Dog{name=Alpha, birthDate=2005-12-25}

Time Zone

Probably not a part of your homework assignment, but FYI, the example code above assumes all the birthdate data represents the same time zone. If the birthdates are not representative of the same time zone, ideally you would pass in a time zone or offset from UTC.

Upvotes: 0

Kulu Limpa
Kulu Limpa

Reputation: 3541

I suggest you use a Comparer to sort a collection of dogs. Have a look at the javadoc of java.util.Comparator and java.lang.Comparable

The integers dogDay, dogMonth and dogYear are parts of the representation of a single concept, the dog's birthday. So instead of letting the dog have those three integers separately, I would advise you to define a date-like class that contains those.

class Dog {
  String dogName;
  Date dogBirthday;

Now, you can define an ordering on your date-like class. You can do this by implementing the Comparable interface.

class Date implements Comparable<Date> {
  final int day;
  final int month;
  final int year; 
  public Date(int day, int month, int year) {
    /* could check for invalid dates, e.g., 
    if (day < 1 || day > 31 || month < 1 || ...) {
      throw new InvalidArgumentException("Invalid date");
    } */
    this.day = day; this.month = month; this.year = year;
  }
  public int compareTo(Date other) {
    // Dates are comparable in lexicographic order
    int result = Integer(year).compareTo(Integer(other.year);
    if (result == 0) { result = Integer(month).compareTo(Integer(other.month)); }
    if (result == 0) { result = Integer(day).compareTo(Integer(other.day)); }
    return result;
  }
}

Having an ordering on dates, you can sort a collection of dogs by their birhtday.

Collections.sort(dogs, 
  new Comparator<Dog>{
    int compare(Dog d1, Dog d2) { return d1.dogBirthday.compareTo(d2.dogBirthday);}
  };)

Note that if you intend to implement Comparable, you should also override java.lang.Object's equals and hashCode method (though, you are not bound by contract to do so).

A side note on the Date class I defined: It is always good to reuse existing code, so definig your own Date class may not be the best thing to do. However, I would advise you not to use java.util.Date, as this class is completely broken. If I am not mistaken, one of its main issues is that it is mutable, which is basically as bad as having String being mutable: You would need to make a lot of defensive copies or your code may break in potentially hard to debug ways. If you intent to make more extensive use of dates and you are using Java 8, then use the new Date and Time library. If you are using an older version of Java, I suggest you have a look at Joda time.

Upvotes: 2

AlexWei
AlexWei

Reputation: 1103

1. add field Calendar to your dog class to replace the month,day,year field

2. use Calendar.setTime(Date )to initiate

3. use Calendar.after or Calendar.before to compare

Upvotes: 0

Nick Humrich
Nick Humrich

Reputation: 15755

Normally, I would recommend to always use a calender when working with dates. This is because dates get complicated really fast, like the 29th of February. However for this specific case, you dont need a calender API.

The problem you have currently is you are converting the dates to Strings, then comparing the strings. DONT DO THIS. Comparing strings is bad (it actually doesnt work the way you would expect).

You already have the numbers, so why dont you use them. Just put getter methods for month, day, and year in your dog class.

Then all you need to compare the dogs is a simple "find the smallest number" method. Find the smallest of the year. If that doesnt work, then than the month, if that doesnt work, than the day.

public Dog youngestDog(List<Dog> dogs) {
    youngest = new Dog("Fake", 12, 31, 9999);
    for (dog : dogs) {
        if (dog.getYear() < youngest.getYear()) {
            youngest = dog;
        }
        else if (dog.getYear() == youngest.getYear()) {
            if (dog.getMonth() < youngest.getMonth()) {
                youngest = dog;
            }
            if (dog.getMonth() == youngest.getMonth()) {
                if (dog.getDay() <= youngest.getDay()) {
                    youngest = dog;
                }
            }
         }
    }
    return youngest;
}

Note: Ideally you would actually want to use a comparator or implement comparable, but that is more advanced and probably way beyond what you are trying to learn.

Upvotes: 1

5gon12eder
5gon12eder

Reputation: 25409

You could use a calendar / date function from the standard library. But it turns out that your question can also be answered easily without it:

  • If the year dog A was born is less than the year dog B was born then dog A is older.
  • If dog A and B are born in the same year and the month dog A was born is less than the month dog B was born then dog A is older.
  • If dog A and B are born in the same month of the same year and the day dog A was born is less than the day dog B was born then dog A is older.
  • If dog A and B are born at the same day in the same month of the same year then they are of equal age (give or take 24 hours).

Upvotes: 2

Related Questions