List iteration producing unexpected results


#1

The following program outputs 289; an extra 6 days that can’t be accounted.

def isLeapYear(year):
    days_per_year = 0
    if year % 4 != 0:
        days_per_year += 365
    elif year % 100 != 0:
        days_per_year += 366
    elif year % 400 != 0:
        days_per_year += 365
    else:
        days_per_year += 366
    print days_per_year
    return days_per_year

def daysBetweenDates(year1, month1, day1):
    daysOfMonths = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ]
    daysInYear = isLeapYear(2018)
    days_to_birthday = day1
    days_to_EOY = 0
    for month in daysOfMonths:
        while month1 != 1:
            days_to_birthday += month
            month1 -= 1
    days_to_EOY = daysInYear - days_to_birthday
    print "Days from January 1st till Birthday: %s" % days_to_birthday
    print "Days from Birthday till December 31st: %s" % days_to_EOY

daysBetweenDates(2012, 10, 10)

However, changing the the while to an if, produces the correct output of 283 days.

def daysBetweenDates(year1, month1, day1):
    daysOfMonths = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ]
    daysInYear = isLeapYear(2018)
    days_to_birthday = day1
    days_to_EOY = 0
    for month in daysOfMonths:
        if month1 != 1:
            days_to_birthday += month
            month1 -= 1
    days_to_EOY = daysInYear - days_to_birthday
    print "January 1 to birthday is %s days." % days_to_birthday
    print "Birthday to December 31 is %s days." % days_to_EOY

daysBetweenDates(2012, 10, 10)

I don’t understand why this is the case. Could someone please help explain? Thanks.


#2
>>> def year_length(year):
  return 366 if not year % 4 and year % 100 or not year % 400 else 365

>>> year_length(2000)
366
>>> year_length(1600)
366
>>> year_length(1800)
365
>>> year_length(1984)
366
>>> 

There are three quantities involved. The days from the date in the first month, itself exclusive, the days of the intervening months, and the days of the terminating month, up to and including the target date. For that we need date information.

In the above, all we really need to know is the length of February. 29 else 28. That can be factored into the table very easily.


#3

Hi @mtf,

Thanks for that. It’s much nicer! That part of the program, however, works okay. It’s the while line that’s producing unexpected output (i.e., 289 days):

for month in daysOfMonths:
        while month1 != 1:
            days_to_birthday += month
            month1 -= 1

But by changing while to if like so:

for month in daysOfMonths:
        if month1 != 1:
            days_to_birthday += month
            month1 -= 1

The correct number of days between January 1 and October 10 (i.e., 283) is returned. And I don’t know why. Could you please help explain this to me?


#4

Python has libraries for this sort of thing, but tackling the problem manually is a good practice and a chance to think things through.

Let’s say one’s birthday is October 10, and today’s date is May 15. So from your list of months we can construct our own list…

d = [(31-15), 30, 31, 31, 30, 10]

Days remaining in May, days in June, July, August and September, days in October.

Now add them up,

print (sum(d))    # 148

If we want to count from January 1, then,

def feb_length(year):
    return 29 if not year % 4 and year % 100 or not year % 400 else 28
d = [(31-1), feb_length(2018), 31, 30, 31, 30, 31, 31, 30, 10]
print (sum(d))    # 282

The first day is excluded from the count, but not the target day, hence 1 less than what you suggest.

The process of computing overlapping years means breaking it down into two lists. Let’s say one’s birthday is April 10 and today’s date is May 15, 2018. First we determine how many days remain in this year, then how many there will be in 2019.

to_eoy = [(31-15), 30, 31, 31, 30, 31, 30, 31]
to_dob = [31, feb_length(2019), 31, 10]
print (sum(to_eoy) + sum(to_dob))    #  330

More simply, we could use a single list and take four slices.

>>> year = 2019
>>> days_in_mon = [31, feb_length(year), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
>>> sum([days_in_mon[5 - 1] - 15] + days_in_mon[5:] + days_in_mon [:3] + [10])
330
>>> 

#5

Thanks, @mtf. You gave me lots of clues to finish this so that it passes all test cases. The approach of simply turning into a sum of days till end of year after date1, and days from start of year till date2 really simplified the problem. Thanks!

Here’s the final code:

# removed following two lines that @mtf wrote in post https://discuss.codecademy.com/t/list-iteration-producing-unexpected-results/325479/4?u=toprank
def febLength(year):
    return 29 if not year % 4 and year % 100 or not year % 400 else 28

def febLength(year):
  febLength = 0
  if year % 4 != 0:
    febLength += 28
  elif year % 100 != 0:
    febLength += 29
  elif year % 400 != 0:
    febLength += 28
  else:
    febLength += 29
  return febLength

def daysBetweenDates(year1, month1, day1, year2, month2, day2):
    daysOfMonths = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ]
    daysOfMonths[1] = febLength(year1)    # if year1 is(n't) a leap year, change Feb to 29(28) days
    # count days from date1 to end of year1
    days_to_end_of_year1 = sum(daysOfMonths[month1-1:]) - day1
    daysOfMonths[1] = febLength(year2)    # if year2 is(n't) a leap year, change Feb to 29(28) days
    # count days from start of year2 to date2
    days_to_end_of_year2 = sum(daysOfMonths[:month2-1]) + day2
    days_between_dates = 0
    # if both dates in same year, subtract sum of days between date2 to EOY from sum of days between date1 to EOY and return result
    if year1 == year2:
        date1_to_EOY = sum(daysOfMonths[month1-1:]) - day1
        date2_to_EOY = sum(daysOfMonths[month2-1:]) - day2
        return date1_to_EOY - date2_to_EOY
    # for each year between year1 & year2, set correct number of days in February with febLength() to calculate days in each year and add to previous totals of days_to_end_of_year1 and year2
    years = year1 + 1
    while years != year2:
        daysOfMonths[1] = febLength(years)
        days_between_dates += sum(daysOfMonths)
        years += 1
    return days_between_dates + days_to_end_of_year1 + days_to_end_of_year2

#6

You did not write this so do not have the right to rename it. Use other writer’s code with attribution, at least, else it makes you a plagiarist, which is a nice word for thief.


#7

Hi @mtf,

My apologies. I didn’t realise I couldn’t use these forum posts to complete the lessons. I changed it back to this:

def febLength(year):
  febLength = 0
  if year % 4 != 0:
    febLength += 28
  elif year % 100 != 0:
    febLength += 29
  elif year % 400 != 0:
    febLength += 28
  else:
    febLength += 29
  return febLength

#8

Nothing of the sort. Use the code but attribute the source. Changing the name and not doing so is the theft about which I speak. If you use the code, keep the name and attribute it back to this topic.

# https://discuss.codecademy.com/t/list-iteration-producing-unexpected-results/325479/3
def feb_length(year):
    return 29 if not year % 4 and year % 100 or not year % 400 else 28

That way it can never come back and stain you with the ink of a plagiarist. We are all creators of a sort. That we understand our neighbour is a blessing.

If you didn’t write it, don’t ever pretend you did. I goes against the rule that if you don’t understand it, don’t use it. Most plagiazers don’t understand the code they are taking credit for. Easily trapped whence forbearance (meaning in this sense, legitimacy as in legitmate title) is obliged.


#9

Hi @mtf,

Thanks for the lesson. I do appreciate it.

I pasted those lines under my existing function name rather than changing each time the function febLength() was called. Now I removed your lines and returned the function to my original code, and documented the changes in this post: List iteration producing unexpected results

I hope this properly shows you’re the owner of those lines and that I am not trying to take credit for your work in this exercise. If I can make it any clearer, please let me know. And thanks again for the help in doing this quiz, and the lesson on not changing naming conventions when using code shared in these forum posts. For future reference, is there a license or something that covers the posts on this forum if not explicitly stated in the thread when it comes to doing these quizzes? I prefer using the forums to solve the problems rather than clicking the “solution” button because I feel like I learn more. If I want to use code shared in a post again to finish a quiz, is linking to the post and copying verbatim sufficient?


#10

It’s not about ownership, or it wouldn’t be posted in a public forum. For your own reputation, just be sure to attribute all code you use, whether from CC or StackOverflow, or SitePoint, or wherever you found it. This lets others track down background information as well as other possible usages.


#11

Hi @mtf,

Most definitely! If I use code found online for an actual product I’m making, I’ll cite all references. Although I’m a long way from doing anything like that as I’ve just started learning how to program with these free online Python courses, which are great! I didn’t think about doing it for these quizzes, though, so thanks again for taking the time to tell me to reference forum post code in these quizzes too.


#12

I still haven’t figured out why the different output from my original question in this thread. Are you able to help explain that to me?


#13

This should apply to all code, not just code you use in production. It will help you to later on track down the original posting in case you have more questions. Don’t let that question be, “Where the heck did I find this?”


#14

I’m not sure I follow your logic. Can you describe what you want those functions to return?


#15

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.