[C#] 判斷集合陣列裡的日期(或數字)是否連續遞增n次
前言
最近幫客戶做一個類似行事曆的功能,其中有個需求是「員工不得連續排休3天」
.net不知道有沒有內建函數可以判斷,太常寫CRUD功能,很久沒寫演算法邏輯的需求,讓我想了一個月,某次跑步中忽然靈感來臨,才解出來
這解法得靠遞迴才行
實作
Console專案
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplicationRepeat
{
class Program
{
static void Main(string[] args)
{
//員工從畫面上行事曆輸入想要的排休日
DateTime dtInput = new DateTime(2014, 4, 25);
//要檢查的日期集合(員工已排定的休假日),資料來源其實是DB
List<DateTime> dtList = new List<DateTime>() {
new DateTime(2014,4,4), new DateTime(2014,4,1) ,
new DateTime(2014,4,26),new DateTime(2014,4,27)
};
dtList.Add(dtInput);//想要的排休日加入集合
//排序 由小到大
dtList = dtList.OrderBy(m=>m).ToList();//由小排到大,方便底下迴圈走訪
//※例如:2014/4/1=>2014/4/2可以,但2014/4/1=>2014/4/2=>2014/4/3就不行
long allowContinueCount = 1;//允許連續次數
long currentContinueCount = 0;//目前連續次數
List<DateTime> dtContinues = new List<DateTime>();//哪些日期連續n次的記錄器
for(int i=0;i<dtList.Count;i++)//走訪要檢查的集合
{
bool isContinue = isContinueNumber(dtList, i, allowContinueCount, currentContinueCount);
if (isContinue)//符合連續n次
{//記錄下來...
dtContinues.Add(dtList[i]);
}
}
if (dtContinues.Count>0)
{//有連續n次的日期
//印出連續的日期
Console.Write("連續n次的日期:");
foreach (DateTime dtContinue in dtContinues)
{
Console.Write(dtContinue.ToString("yyyy/MM/dd")+"、");
}//end foreach
Console.WriteLine("");//換行
}
else
{
Console.WriteLine("無連續n次日期");
}
Console.ReadKey();
}
/// <summary>
/// 集合是否連續N次
/// </summary>
static bool isContinueNumber(List<DateTime> dtList, int index, long allowContinueCount, long currentContinueCount)
{
//結果
bool result = false;
DateTime dtCurrent = dtList[index];//被檢查的日期
index++;//下一索引
if (index<dtList.Count)//下一個index是合法的
{
if (dtList[index].Subtract(dtCurrent).Days==1)
{//日期連續了
//累加連續次數
currentContinueCount++;
if (currentContinueCount > allowContinueCount)//超過允許的連續次數
{
//條件符合連續次數
result = true;
}
else
{//繼續比對
result = isContinueNumber(dtList, index, allowContinueCount, currentContinueCount);
}
}
}
return result;
}
}
}
執行結果:
使用時需留意一個地方,假設員工正在排本月(2014/4)的班,為了檢查員工想排休的日期和下個月或上個月跨月份的已排休日是否也連續了(要檢查2014/3/30=>2014/3/31=>2014/4/1和2014/4/30=>2014/5/1=>2014/5/2的可能性)
要把上個月和下個月該名員工已排休的日期也加入至被檢查的集合(dtList變數)裡
本範例該集合只有4月份
※2014/4/29 追記客戶進階需求:
因為客戶認為員工的主管可以幫員工連續排休,所以在員工上系統親自排休時會發生一種詭異情況=>上個月被主管排連續休假,員工這個月明明還沒排連續休假,程式便告知整個日期有連續(因為一起檢查上個月+下個月)
解法為對dtContinues變數再篩選
dtContinues = dtContinues.Where(m=>m.Month==員工正在排的月份).ToList();//因為員工目前正在排本月份,本月份以外的連續日期無視
之後再帶入範例程式的if檢查即可
if(dtContinues.Count>0)
{
//alert(“您這個月排的日期xxx造成連續排休”);
}
既然知道連續n次日期的解法,相對地連續n次數字的解法也順便寫出來(雖然還沒碰到要判斷數字連續n次的需求)
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplicationRepeat
{
class Program
{
static void Main(string[] args)
{
//要檢查的數字集合
List<long> lList = new List<long>() {
2,4,7,8,9,11,12,17,18,19,21
};
//排序 由小到大
lList = lList.OrderBy(m => m).ToList();//由小排到大,方便底下迴圈走訪
//※例如:7=>8可以,但7=>8=>9就不行
long allowContinueCount = 1;//允許連續次數
long currentContinueCount = 0;//目前連續次數
List<long> lContinues = new List<long>();//哪些日期連續n次的記錄器
for(int i=0;i<lList.Count;i++)//走訪要檢查的集合
{
bool isContinue = isContinueNumber(lList, i, allowContinueCount, currentContinueCount);
if (isContinue)//符合連續n次
{//記錄下來...
lContinues.Add(lList[i]);
}
}
if (lContinues.Count>0)
{//有連續n次的數字
//印出連續的數字
Console.Write("連續n次的數字:");
foreach (long lContinue in lContinues)
{
Console.Write(lContinue + "、");
}//end foreach
Console.WriteLine("");//換行
}
else
{
Console.WriteLine("無連續n次數字");
}
Console.ReadKey();
}
/// <summary>
/// 集合是否連續N次
/// </summary>
static bool isContinueNumber(List<long> lList, int index, long allowContinueCount, long currentContinueCount)
{
//結果
bool result = false;
long lCurrent = lList[index];//被檢查的數字
index++;//下一索引
if (index<lList.Count)//下一個index是合法的
{
if (lList[index]-lCurrent==1)
{//數字連續了
//累加連續次數
currentContinueCount++;
if (currentContinueCount > allowContinueCount)//超過允許的連續次數
{
//條件符合連續次數
result = true;
}
else
{//繼續比對
result = isContinueNumber(lList, index, allowContinueCount, currentContinueCount);
}
}
}
return result;
}
}
}
執行結果:
2014/4/30 晚上追記
這樣雖然解決客戶的需求了,但回頭看看文章標題……乍看之下,好像要抓出各日期的連續次數來判斷
所以這邊再提供另一種演算法求出各日期連續的次數(也是可以用來解決客戶需求)
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication11
{
class Program
{
static void Main(string[] args)
{
//員工從畫面上行事曆輸入想要的排休日
DateTime dtInput = new DateTime(2014, 4, 25);
//要檢查的日期集合(員工已排定的休假日),資料來源其實是DB
List<DateTime> dtList = new List<DateTime>() {
new DateTime(2014,4,4), new DateTime(2014,4,1) ,
new DateTime(2014,4,26),new DateTime(2014,4,27),new DateTime(2014,5,1)
};
dtList.Add(dtInput);//想要的排休日加入集合
Dictionary<DateTime, long> result = Query集合中各日期連續的次數(dtList);
//※例如:2014/4/1=>2014/4/2可以,但2014/4/1=>2014/4/2=>2014/4/3就不行
long allowContinueCount = 1;//允許連續次數
//印出結果
foreach (KeyValuePair<DateTime, long> date in result)
{
Console.WriteLine(date.Key.ToString("yyyy/MM/dd") + "的連續次數:" + date.Value + ",此日期是否造成連續排休:" + (date.Value > allowContinueCount));
}
Console.ReadKey();
}
public static Dictionary<DateTime, long> Query集合中各日期連續的次數(List<DateTime> checkdates)
{
//排序 由小到大
checkdates = checkdates.OrderBy(m => m).ToList();//由小排到大,方便底下迴圈走訪
Dictionary<DateTime, long> dtContinueCounts = new Dictionary<DateTime, long>();//各日期的連續次數回傳資料
for (int i = 0; i < checkdates.Count; i++)//走訪要檢查的集合
{
long currentContinueCount = 0;//目前連續次數
//計算此日期的連續次數
long Count = ContinueCount(checkdates, i, currentContinueCount);
dtContinueCounts.Add(checkdates[i], Count);
}
return dtContinueCounts;
}
/// <summary>
/// 取得某日期開始的連續次數
/// </summary>
private static long ContinueCount(List<DateTime> checkdates, int index, long currentContinueCount)
{
DateTime dtCurrent = checkdates[index];//被檢查的日期
index++;//下一索引
if (index < checkdates.Count)//下一個index是合法的
{
if (checkdates[index].Subtract(dtCurrent).Days == 1)
{//日期連續了
//累加連續次數
currentContinueCount++;
//繼續往下比對
currentContinueCount = ContinueCount(checkdates, index, currentContinueCount);
}
}
return currentContinueCount;
}
}
}
執行結果:
數字版(同樣最多允許連續一次的情況):
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplicationRepeat
{
class Program
{
static void Main(string[] args)
{
//要檢查的數字集合
List<long> lList = new List<long>() {
2,7,9,10,11,13,15,16,18,20,21,22
};
//排序 由小到大
lList = lList.OrderBy(m => m).ToList();//由小排到大,方便底下迴圈走訪
long currentContinueCount = 0;//目前連續次數
Dictionary<long,long> lContinueCounts = new Dictionary<long,long>();//各數字的連續次數資料
for (int i = 0; i < lList.Count; i++)//走訪要檢查的集合
{
//計算此數字的連續次數
long Count = ContinueCount(lList, i, currentContinueCount);
lContinueCounts.Add(lList[i], Count);
}
//※例如:9=>10可以,但9=>10=>11就不行
long allowContinueCount = 1;//允許連續次數
//印出結果
foreach (KeyValuePair<long,long> number in lContinueCounts)
{
Console.WriteLine("數字" + number.Key + "的連續次數:" + number.Value + ",此數字是否被允許:" + (number.Value <= allowContinueCount));
}
Console.ReadKey();
}
/// <summary>
/// 取得某數字的連續次數
/// </summary>
static long ContinueCount(List<long> lList, int index,long currentContinueCount)
{
long lCurrent = lList[index];//被檢查的數字
index++;//下一索引
if (index < lList.Count)//下一個index是合法的
{
if (lList[index]- lCurrent == 1)
{//數字連續了
//累加連續次數
currentContinueCount++;
//繼續比對
currentContinueCount = ContinueCount(lList, index, currentContinueCount);
}
}
return currentContinueCount;
}
}
}
數字版執行結果: