Range of values - how to determine without if loop?

I need to find the sun-sign of a given person based on his age.

For eg,

Capricorn   December 22 – January 20
Aquarius    January 21 – February 18
Pisces      February 19 – March 19
Aries       March 20 – April 19
Taurus      April 20 – May 20
Gemini      May 21 – June 20
Cancer      June 21 – July 22
Leo         July 23 – August 22
Virgo       August 23 – September 22
Libra       September 23 – October 22
Scorpio     October 23 – November 21
Sagittarius November 22 – December 21

I wrote this code,

 public enum Months
 {
     January = 1, February, March, April, May, June, July, August, September, October, November, December,
 }

var person = new Person(name:"mady", age:20, dateTime: new DateTime(2011,09,16));
if (person.DOB.Month == (int)Months.December) 
{
    if (person.DOB.Day >= 22)
        return "Capricorn";
    else
        return "Sagittarius";
} ...
....
....
....

The IF statements grow consistently and might become a nightmare if tomorrow the list grows.

Is there an elegant way of finding out the Sunsign ? Enumerable or Range in .NET doesn't seem to fit this case or is this the only way of writing the code ?

Upvotes: 1

Views: 244

Answers (6)

David Božjak
David Božjak

Reputation: 17627

Create a class StarSign:

class StarSign
{
    public readonly string Name;
    public readonly DateTime StartDate;
    public readonly DateTime EndDate;

    public bool Contains(DateTime date);
}

Add all the star signs to a collection StarSigns. Then for any given DateTime date (of the person) do

foreach (var sign in StarSigns)
{
    if (sign.Contains(date))
    {
        Console.WriteLine("I am a: " + sign.Name);
            break;
    }
}

Edit, responding to your comment:

The Contains function can easily compare dates, just make sure you ignore the year:

public bool Contains(DateTime date)
{
    DateTime startNoYear = new DateTime(1904, StartDate.Month, StartDate.Day);
    DateTime endNoYear = new DateTime(1904, EndDate.Month, EndDate.Day);
    DateTime dateNoYear = new DateTime(1904, date.Month, date.Day);

    return dateNoYear >= startNoYear && dateNoYear <= endNoYear;
}

So yes, if you have many many StarSigns, this will affect performance. Normaly you will only have 12, and since you know you are dealing with a closed set, you can afford to do it this way.

When it comes to optimization, you will also want to store startNoYear and endNoYear and not calculate them each time you run Contains. Calculate them in the constructor; I'm only doing it in the method so it's easier to understand. Even faster would be to work on DateTime properties directly and avoid creating new DateTime objects altogether. As far as this example goes, I opt for simplicity over optimization.

Upvotes: 6

Thiago Romam
Thiago Romam

Reputation: 439

The David Božjak's answer is a great choice.

I think the class could be abstract and implemented for every sign. Also the StartDate, EndDate and the date pass as parameter need to ignore the year. I made in this way:

public abstract class StarSign
{
    public readonly string Name;
    public readonly DateTime StartDate;
    public readonly DateTime EndDate;

    protected StarSign(string name, DateTime startDate, DateTime endDate)
    {
        Name = name;
        StartDate = startDate;
        EndDate = endDate;
    }

    public virtual bool Contains(DateTime date)
    {
        date = new DateTime(1, date.Month, date.Year);
        return date >= StartDate && date <= EndDate;
    }
}

public class AquariusStarSign : StarSign
{
    public AquariusStarSign()
        : base("Aquarius", new DateTime(1, 1, 21), new DateTime(1, 2, 18))
    {
    }
}

public class CapricornStarSign : StarSign
{
    public CapricornStarSign()
        : base("Capricorn", new DateTime(1, 12, 21), new DateTime(1, 1, 20))
    {
    }

    public override bool Contains(DateTime date)
    {
        if (date.Month == StartDate.Month)
            return date.Day >= StartDate.Day;

        if (date.Month == EndDate.Month)
            return date.Day <= EndDate.Day;

        return false;
    }
}

Upvotes: 0

dimis164
dimis164

Reputation: 130

linq is good.. just use your list... have a look at this one

    public class sing
{
    public string singName {
        get { return _singName; }
        set { _singName = value; }
    }

    private string _singName;
    public DateTime singStart {
        get { return _singStart; }
        set { _singStart = value; }
    }

    private DateTime _singStart;
    public DateTime singEnd {
        get { return _singEnd; }
        set { _singEnd = value; }
    }

    private DateTime _singEnd;


    public void findSing(System.DateTime usersDate)
    {
        List<sing> ListOfSings = new List<sing>();

        sing scorpio = new sing();
        System.DateTime startD = new System.DateTime(1910, 10, 23);
        System.DateTime endD = new System.DateTime(1910, 11, 21);
        scorpio.singName = "scorpio";
        scorpio.singStart = startD;
        scorpio.singEnd = endD;
        ListOfSings.Add(scorpio);
        //' ....etc all the others

        dynamic hismonth = usersDate.Month;
        dynamic hisDay = usersDate.Day;

        System.DateTime fixedDate = new System.DateTime(1910, hismonth, hisDay);

        dynamic q = (from i in ListOfSings where i.singStart >= fixedDate && i.singEnd <= fixedDatei).ToList;

        MessageBox.Show("your sing is: " + q.FirstOrDefault.singName);

    }
}

Upvotes: 0

Heinzi
Heinzi

Reputation: 172270

Note that you can compare dates:

if (new DateTime(2012, 1, 1) < new DateTime(2012, 2, 1)) ...

Thus, I would suggest that

  • you normalize the DOB to a given leap year (e.g. 1904)
  • and then simply use date comparisons:

    DateTime dob = new DateTime(1904, person.DOB.Month, person.DOB.Day);
    
    if (dob >= new DateTime(1904, 12, 21))
        return "Aquarius";
    else if (dob >= new DateTime(1904, 11, 22))
        return "Sagittarius";
    else if (dob >= new DateTime(1904, 10, 23))
        return "Scorpio";
    ...
    else
        return "Aquarius";
    

An obvious improvement would be to create a List<Tuple<DateTime, String>> and iterate through that. However, since the dates are very unlikely to change in the next hundred years, hardcoding them in the if conditions might suffice.

Upvotes: 3

Christian Sauer
Christian Sauer

Reputation: 10899

May you could build a small Dictionary of sun-sign, which stors the Name of the sun sign as key and it's timespan as value. The timespan would be the first to the last date. Then there a standard time function to tell if the persons DOB is in the timespan. Mayb you need to strip out the year of birth, but that should be easy.

As a final touch you could use linq:

var sunsigns as Dictionary<string, TimeSpan>();
// adding sun-signs here

var sunsign = (from s in sunsigns where (methodToTellIfItsinRange(s)) select s).first();

Upvotes: 0

vborutenko
vborutenko

Reputation: 4443

You can use switch statement

switch (person.DOB.Month)
 {
   .....
   case 12:
      if (day >= 22) return "Capricorn"; else return "Sagittarius";
      break;
   .......

 }

Upvotes: 1

Related Questions