中天面试试卷
Not so long ago, I was given a job interview task. I was to write a function which deduces the day of the standard 7-day week of an imaginary calendar, provided I know how often leap years happen, if at all, how many months their year has, and how many days each month has.
不久前,我接受了工作面试任务。 如果我知道leap年发生的频率(如果有的话),一年中有多少个月以及每月中有多少天,我将编写一个函数来推导出假想日历的标准7天工作日。
This is a fairly common introductory job-interview task, and in this article I'll be solving and explaining the math behind it. I'm no Rainman so feel free to throw simplifications and corrections at me – I'm sure my way is one of the needlessly complex ones. This article will be presenting two ways of approaching the problem – one that allows you to mentally do this on-the-fly (impress your friends, I guess?), and one that is more computer friendly (fewer lines of code, larger numbers).
这是一个相当常见的介绍工作面试的任务,在本文中,我将解决和解释其背后的数学原理。 我不是Rainman,所以可以随意对我进行简化和更正–我确信我的方法是不必要的复杂方法之一。 本文将介绍两种解决问题的方法-一种使您能够在脑海中即时地做到这一点(我想打动您的朋友?),另一种对计算机更友好(更少的代码行,更大的数字) )。
The definition of the calendar I was given went as follows:
我得到的日历的定义如下:
each year has 13 months 每年有13个月 each even month has 21 days, and each odd month has 22 每个偶数月有21天,每个奇数月有22天 the 13th month lacks a day every leap year 第十三个月每个leap年都缺少一天 a leap year is any year divisible by 5 leap年是可以除以5的任何年份 each week has 7 days: Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday 每周有7天:星期日,星期一,星期二,星期三,星期四,星期五,星期六The task went as follows:
任务如下:
Given that the first day of the year 1900 was Monday, write a function that will print the day of the week for a given date. Example, for input {day: 17, month: 11, year: 2013} the output is "Saturday".
假设1900年的第一天是星期一,则编写一个函数,该函数将打印给定日期的星期几。 例如,对于输入{day:17,month:11,year:2013},输出为“ Saturday”。
Throughout the rest of the article, I will be using the following date format: dd.mm.yyyy, because it's what makes sense.
在本文的其余部分中,我将使用以下日期格式:dd.mm.yyyy,因为这很有意义 。
Before starting out on any brainy venture, it's important to have a proper environment set up in order to avoid wasting time on things that could have been prepared in advance. I always recommend you venture into coding job interview tasks with a revved up development environment, ready to test your code at a moment's notice.
在开始任何有头脑的冒险之前,重要的是要建立一个适当的环境,以避免浪费时间在本可以预先准备的事情上。 我总是建议您冒险使用经过改进的开发环境来编写工作面试任务,随时准备测试您的代码。
Create a new folder containing two subfolders: classes, and public. Yes, this is a throwaway task that can be solved in a simple procedural function, but I like to be thorough. You'll see why.
创建一个包含两个子文件夹的新文件夹: classes和public 。 是的,这是一项可以一次性完成的任务,可以通过一个简单的程序功能来解决,但我想很彻底。 您会明白为什么。
In the classes subfolder, create an empty PHP class called CalendarCalc.php. In the public subfolder, create a file called index.php with the following content:
在classes子文件夹中,创建一个名为CalendarCalc.php的空PHP类。 在public子文件夹中,创建一个名为index.php的文件,其内容如下:
<?php require_once '../classes/CalendarCalc.php'; echo "Hello";If you can open this in your browser and "Hello" is displayed, you're ready to get started.
如果您可以在浏览器中打开它并显示“ Hello”,则准备开始。
To make things easier to verify and visualize, I've created a demo method which prints out the entire calendar from 1.1.1900. to 22.13.2013. This will allow us to easily check if our calculation function works. First, though, initialize the class like so:
为了使事情更易于验证和可视化,我创建了一个演示方法,该方法可以打印出1.1.1900以来的整个日历。 至2013年13月22日。 这将使我们能够轻松检查我们的计算功能是否有效。 但是,首先,像这样初始化类:
<?php class CalendarCalc { /** @var array */ protected $aDays = array('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'); /** @var int Cached number of days in a week, to avoid recounts. This way, we can alter the week array at will */ protected $iNumDays; /** @var int Array index of starting day (1.1.1900.), i.e. Monday = 1*/ protected $iStartDayIndex; /** @var int */ protected $startYear = 1900; /** @var int */ protected $leapInterval = 5; /** @var array Array gets populated on instantiation with date params. This is to make params accessible to every alternative calculation method. */ protected $aInput = array(); public function __construct($day, $month, $year) { $this->iNumDays = count($this->aDays); $this->iStartDayIndex = array_search('Monday', $this->aDays); $this->aInput = array('d' => $day, 'm' => $month, 'y' => $year); } public function demo() { } }Let's explain the protected properties.
让我们解释一下受保护的属性。
$aDays is an array of days. Defining it made sure each day of the week has a numeric index assigned to it – something of utmost importance when determining the day of the week later on. We cache its length with the $iNumDays property. This allows us to expand the days array later on, if we so choose – another task might ask for the same calculation, but might mention that the week has more or less than 7 days.
$aDays是几天的数组。 定义它可以确保为一周中的每一天分配了一个数字索引-在确定一周中的某天之后,这一点至关重要。 我们使用$iNumDays属性缓存其长度。 如果允许的话,这使我们可以在以后扩展days数组–另一个任务可能要求相同的计算,但可能会提到一周的时间少于或等于7天。
$iStartDayIndex is the index of Monday (in this case), because the start day (1.1.1900.) is defined as Monday in the task description. When we have the index of the start day, we can use it in tandem with a calculated offset to get the real day of the week. You'll understand what I mean in a bit.
$iStartDayIndex是星期一的索引(在这种情况下),因为在任务描述中将开始日期(1.1.1900。)定义为星期一。 当我们有了开始日的索引时,我们可以将其与计算出的偏移量结合使用,以获得星期的实际日期。 您很快就会明白我的意思。
$aInput is an array to hold the input values. When we instantiate the CalendarCalc, we pass in the date values for which we want to know the day of the week. This property stores those values, making them available for every alternative calc method we think up, thus making sure we don't need to forward them along, or even worse, repeat them in another function call. The logic of $aInput, $iStartDayIndex and $iNumDays is in the __construct method.
$aInput是一个保存输入值的数组。 当我们实例化CalendarCalc时,我们传入了我们想知道星期几的日期值。 此属性存储这些值,使它们可用于我们考虑的每个替代calc方法,从而确保我们不需要转发它们,甚至更不用说在另一个函数调用中重复它们。 $aInput , $iStartDayIndex和$iNumDays的逻辑在__construct方法中。
The other properties are self-explanatory.
其他属性是不言自明的。
Now, populate the demo() method with the following content:
现在,使用以下内容填充demo()方法:
public function demo() { $demoYear = $this->startYear; $totalDays = 0; while ($demoYear < 2014) { echo "<h2>$demoYear</h2><table>"; $demoMonth = 1; while ($demoMonth < 14) { echo "<tr><td colspan='7'><b>Month $demoMonth</b></td></tr>"; echo "<tr><td>Monday</td><td>Tuesday</td><td>Wednesday</td><td>Thursday</td><td>Friday</td><td>Saturday</td><td>Sunday</td></tr>"; $dayCount = ($demoMonth % 2 == 1) ? 22 : 21; $dayCount = ($demoMonth == 13 && $demoYear % 5 == 0) ? 21 : $dayCount; $demoDay = 1; echo "<tr>"; while ($demoDay <= $dayCount) { $index = ++$totalDays % 7; if ($demoDay == 1) { for ($i = 0; $i < $index-1; $i++) { echo "<td></td>"; } if ($index == 0 || $index == 7) { $i = 6; while ($i--) { echo "<td></td>"; } } } echo "<td>$demoDay</td>"; if ($index == 0) { echo "</tr><tr>"; } $demoDay++; } echo "</tr>"; $demoMonth++; } echo "</table><hr />"; $demoYear++; } }Don't bother trying to understand this method – it's entirely unimportant. It's only there to help us verify our work, and is in fact partially based on the second solution we'll be presenting in this article.
不必费力尝试理解这种方法–完全不重要。 它只是在帮助我们验证我们的工作,实际上部分是基于我们将在本文中介绍的第二种解决方案。
Change the contents of the index.php file to:
将index.php文件的内容更改为:
<?php require_once '../classes/CalendarCalc.php'; $cc = new CalendarCalc(17, 11, 2013); $cc->demo();…and open it in the browser. You should see a calendar output not unlike the one in the image below:
…并在浏览器中将其打开。 您应该看到一个日历输出,与下图中的输出相同:
We now have a way to check the results against the truth (notice that the date 17.11.2013. is indeed Saturday).
现在,我们有一种方法可以根据真实情况检查结果(请注意,2013年11月17日确实是星期六)。
The mental way to do this calculation is actually quite simple. First, we need the number of leap years between the base date, and our given date. 1900 is divisible by 5 and is itself a leap year. The number of leaps is thus the difference in years between the input date and the base date, divided by 5, rounded down (only fully elapsed years count, naturally), plus one for 1900. Create a new method in CalendarCalc called calcFuture and give it this content:
实际上,进行这种计算的思路很简单。 首先,我们需要从基准日期到给定日期的of年数。 1900被5整除,本身就是a年。 因此,跳数是输入日期与基准日期之间的年差,除以5,四舍五入(自然而然,只有完全经过的年计数),再加上1900。在CalendarCalc创建一个称为calcFuture的新方法,该内容:
$iLeaps = floor(($this->aInput['y'] - $this->startYear) / $this->leapInterval + 1);We were also told that each even month has 21 days, and each odd month has 22:
我们还被告知,每个偶数月都有21天,每个奇数月都有22天:
1 => 22 2 => 21 3 => 22 4 => 21 5 => 22 6 => 21 7 => 22 8 => 21 9 => 22 10 => 21 11 => 22 12 => 21 13 => 22 (or 21 on leap years)
1 => 22 2 => 21 3 => 22 4 => 21 5 => 22 6 => 21 7 => 22 8 => 21 9 => 22 10 => 21 11 => 22 12 => 21 13 = > 22(或leap年为21)
The total number of days in their year is, thus, 280, or 279 on leap years. If we take the modulo of 280 % 7, we get 0, because 280 is divisible by 7. On leap years, the modulo is 6.
因此,一年中的总天数为280天,即leap年为279天。 如果我们采用280%7的模,则得到0,因为280可被7整除。在leap年,模为6。
This means that every year of that calendar begins on the same day, except on leap years, when it begins on the day that precedes the previous year's first day. Thus, if 1.1.1900. was Monday:
这意味着该日历的每一年都在同一天开始,但leap年除外,而leap年则在前一年的第一天之前的那一天开始。 因此,如果是1.1.1900。 是星期一:
1.1.1901. is Monday 1.1.1901。 是星期一 1.1.1902. is Sunday 1.1.1902。 是星期天 1.1.1903. is Sunday 1.1.1903。 是星期天 1.1.1904. is Sunday 1.1.1904。 是星期天 1.1.1905. is Saturday 1.1.1905。 是星期六 1.1.1906. is Saturday 1.1.1906。 是星期六 etc… 等等…According to this, we can calculate the number of day moves until our input year. Seeing as we know we have 23 leaps until the input date (2013), we moved back a day 23 times. The modulo of 23 % 7 is 2, meaning we came full circle 3 times and then two more days (this is the offset) – 1.1.2013. was Saturday. Check on the demo calendar and see for yourself.
据此,我们可以计算到输入年份为止的天数。 众所周知,在输入日期(2013年)之前,我们有23次飞跃,因此我们将一天移回了23次。 23%7的模为2,这意味着我们来了3次整圈,然后又过了两天(这是偏移)– 2013年1月。 是星期六。 查看演示日历并亲自查看。
Let's put this into code. After the "leaps" line above, add the following:
让我们将其放入代码中。 在上面的“ leaps”行之后,添加以下内容:
$iOffsetFromCurrent = $iLeaps % $this->iNumDays; $iNewIndex = $this->iStartDayIndex - $iOffsetFromCurrent; if ($iNewIndex < 0) { $iFirstDayInputYearIndex = $this->iStartDayIndex + $this->iNumDays - $iOffsetFromCurrent; } else { $iFirstDayInputYearIndex = $iNewIndex; }First, we calculate the offset. Then, we calculate the new index of the days array, which varies depending on whether or not the new index is positive. This gives us the day of the week on which our input year starts.
首先,我们计算偏移量。 然后,我们计算days数组的新索引,该索引取决于新索引是否为正。 这为我们提供了输入年份的星期几。
We also know that each month X with 21 days makes the next month Y start on the same day as month X did, because 21 % 7 = 0. During odd months, however, the starting day moves ahead by one (22 % 7 = 1). So if Month 1 started with Saturday, Month 2 starts with Sunday, Month 3 with Sunday, Month 4 with Monday, and so on. We conclude that every odd month that passed since the start of the year until our input date's month has advanced the day index by 1. We're in month 11, so there were 5 odd months. The new offset is +5 or in our case, the 11th month of 2013 begins on Thursday. Let's put it into code immediately under the previous lines.
我们还知道,每个X具有21天的月份使下个月Y与X月份在同一天开始,因为21%7 =0。但是,在奇数月中,起始日期向前移动了一个(22%7 = 1)。 因此,如果第1个月从星期六开始,第2个月从星期日开始,第3个月从星期日开始,第4个月从星期一开始,依此类推。 我们得出的结论是,从年初开始直到输入日期的月份过去的每个奇数月都使日指数提前了1。我们在第11个月,所以有5个奇数月。 新的偏移量为+5,在我们的情况下为2013年的第11个月,从周四开始。 让我们将其直接放在前面几行的代码中。
$iOddMonthsPassed = floor($this->aInput['m'] / 2); $iFirstDayInputMonthIndex = ($iFirstDayInputYearIndex + $iOddMonthsPassed) % $this->iNumDays;All that's left now is to see how far removed from the beginning of the month our input date's day is.
现在剩下的就是看我们输入日期的月份与月初相距多远。
$iTargetIndex = ($iFirstDayInputMonthIndex + $this->aInput['d']-1) % $this->iNumDays; return $this->aDays[$iTargetIndex];We add the day number minus one (because the day hasn't passed yet!) and modulo it with 7, the number of days. The number we get is our target index, reliably giving us Saturday.
我们将天数减去一(因为天还没有过去!),然后将天数乘以7。 我们得到的数字是我们的目标指数,可靠地给了我们星期六。
From the top now, the whole calcFuture method of CalendarCalc goes like this:
从顶部开始, CalendarCalc的整个calcFuture方法如下所示:
/** * A more "mental" way of calculating the day of the week * @return mixed */ public function calcFuture() { $iLeaps = floor(($this->aInput['y'] - $this->startYear) / $this->leapInterval + 1); $iOffsetFromCurrent = $iLeaps % $this->iNumDays; $iNewIndex = $this->iStartDayIndex - $iOffsetFromCurrent; if ($iNewIndex < 0) { $iFirstDayInputYearIndex = $this->iStartDayIndex + $this->iNumDays - $iOffsetFromCurrent; } else { $iFirstDayInputYearIndex = $iNewIndex; } $iOddMonthsPassed = floor($this->aInput['m'] / 2); $iFirstDayInputMonthIndex = ($iFirstDayInputYearIndex + $iOddMonthsPassed) % $this->iNumDays; $iTargetIndex = ($iFirstDayInputMonthIndex + $this->aInput['d']-1) % $this->iNumDays; return $this->aDays[$iTargetIndex]; }A perhaps simpler approach is just calculating the number of days that have elapsed since the base date, modulo that by 7 and get the offset that way. There's not many people who can calculate numbers of this magnitude on the fly, though, and that's why it's more machine-friendly.
也许更简单的方法是只计算自基准日期以来经过的天数,将其取模7,然后以这种方式获取偏移量。 但是,没有多少人可以即时计算出如此数量的数字,这就是为什么它对机器更友好的原因。
Again, we need leaps:
同样,我们需要飞跃:
public function calcFuture2() { $iTotalDays = 0; $iLeaps = floor(($this->aInput['y'] - $this->startYear) / $this->leapInterval + 1); }Then, take years into account first. That's 280 times number of elapsed years, minus number of leaps to account for lost days, plus one because the current year is still ongoing.
然后,首先考虑几年。 那是经过的年数的280倍,减去弥补损失天数的跃迁数,再加上因为当前年份仍在进行的1。
$iTotalDays = (280 * ($this->aInput['y'] - $this->startYear)) - $iLeaps + 1;Then, we add in the days by summing up all the elapsed months.
然后,我们通过汇总所有过去的月份来添加天数。
$iTotalDays += floor($this->aInput['m'] / 2) * 21 + floor($this->aInput['m'] / 2) * 22;Finally, we add the days of the input date, again minus one because the current day hasn't passed yet:
最后,我们添加输入日期的天数,再减去一,因为当前日期尚未过去:
$iTotalDays += $this->aInput['d'] - 1; return $this->aDays[$iTotalDays % $this->iNumDays];Dead simple, no?
死简单,不是吗?
To see a live example of this calculation, please check here. You can browse the containing directory at that URL to see the files, or you can download the complete source code of that demo site, along with the final CalendarCalc class, from Github. The repo/demo has slightly more code than presented in this article – some html5boilerplate was used to make it more organized and to enable ajax requests to check the dates as you enter them, so you don't need to reload the screen and regenerate the calendar every time you check for a date.
要查看此计算的实时示例,请在此处检查。 您可以浏览该URL所在的目录以查看文件,也可以从Github下载该演示站点的完整源代码以及最终的CalendarCalc类。 repo / demo的代码比本文中介绍的代码略多–使用了一些html5boilerplate使其更加井井有条,并使ajax请求能够在输入日期时检查日期,因此您无需重新加载屏幕并重新生成每次您检查日期时的日历。
If you have alternative solutions or suggestions for improvement, please leave them in the comments below – like I said I'm no math wiz and I welcome the opportunity to learn more. For example, one should take corner cases into account – edge dates, or dates in the past require more modifications to the original algorithm. I'll leave that up to you. Feel free to submit a pull request and you'll get a shout out in the article!
如果您有替代解决方案或改进建议,请在下面的评论中保留它们-就像我说的我不是数学家一样,我欢迎有机会了解更多信息。 例如,应该考虑到极端情况–边缘日期,或者过去的日期需要对原始算法进行更多修改。 我留给你。 随时提交请求请求,您将在本文中大喊大叫!
Hope you enjoyed this and learned something new! Good luck in your interviews!
希望您喜欢这个并学到新的东西! 祝您采访愉快!
翻译自: https://www.sitepoint.com/php-job-interview-task-day-week-calculation/
中天面试试卷
相关资源:jdk-8u281-windows-x64.exe