450 lines
14 KiB
C#
450 lines
14 KiB
C#
using System;
|
||
using System.Collections;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Text;
|
||
using System.Threading.Tasks;
|
||
using TrainInfomationProviderService.StationInfo;
|
||
using TrainInfomationProviderService.StationInfo.Entities;
|
||
|
||
namespace TrainInfomationProviderService.TrainInfo.Entities
|
||
{
|
||
public class TrainTransitOnceResultCollection : ICollection<TrainTransitOnceResult>
|
||
{
|
||
List<TrainTransitOnceResult> _list = new List<TrainTransitOnceResult>();
|
||
TrainTransitSearchOptions _options;
|
||
|
||
/// <summary>
|
||
/// 最推荐的几个线路
|
||
/// </summary>
|
||
public List<TrainTransitOnceResult> TopMostRecommandLines { get; private set; }
|
||
|
||
/// <summary>
|
||
/// 日期
|
||
/// </summary>
|
||
public DateTime Date { get; private set; }
|
||
|
||
/// <summary>
|
||
/// 出发城市编码
|
||
/// </summary>
|
||
public string FromCode { get; private set; }
|
||
|
||
/// <summary>
|
||
/// 到达城市编码
|
||
/// </summary>
|
||
public string ToCode { get; private set; }
|
||
|
||
/// <summary>
|
||
/// 出发城市信息
|
||
/// </summary>
|
||
public StationDetailInfo From { get; private set; }
|
||
|
||
/// <summary>
|
||
/// 到达城市信息
|
||
/// </summary>
|
||
public StationDetailInfo To { get; private set; }
|
||
|
||
/// <summary>
|
||
/// 创建 <see cref="TrainTransitOnceResultCollection"/> 的新实例(TrainTransitOnceResultCollection)
|
||
/// </summary>
|
||
/// <param name="options"></param>
|
||
/// <param name="date"></param>
|
||
/// <param name="fromCode"></param>
|
||
/// <param name="toCode"></param>
|
||
public TrainTransitOnceResultCollection(DateTime date, TrainTransitSearchOptions options, string fromCode, string toCode)
|
||
{
|
||
ToCode = toCode;
|
||
FromCode = fromCode;
|
||
Date = date;
|
||
_options = options;
|
||
|
||
From = StationManager.Instance.Storage.Stations.GetValue(fromCode);
|
||
To = StationManager.Instance.Storage.Stations.GetValue(toCode);
|
||
}
|
||
|
||
#region Implementation of IEnumerable
|
||
|
||
/// <summary>
|
||
/// 返回一个循环访问集合的枚举器。
|
||
/// </summary>
|
||
/// <returns>
|
||
/// 可用于循环访问集合的 <see cref="T:System.Collections.Generic.IEnumerator`1"/>。
|
||
/// </returns>
|
||
public IEnumerator<TrainTransitOnceResult> GetEnumerator()
|
||
{
|
||
return _list.GetEnumerator();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 返回一个循环访问集合的枚举数。
|
||
/// </summary>
|
||
/// <returns>
|
||
/// 一个可用于循环访问集合的 <see cref="T:System.Collections.IEnumerator"/> 对象。
|
||
/// </returns>
|
||
IEnumerator IEnumerable.GetEnumerator()
|
||
{
|
||
return GetEnumerator();
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Implementation of ICollection<TrainTransitOnceResult>
|
||
|
||
/// <summary>
|
||
/// 将某项添加到 <see cref="T:System.Collections.Generic.ICollection`1"/> 中。
|
||
/// </summary>
|
||
/// <param name="item">要添加到 <see cref="T:System.Collections.Generic.ICollection`1"/> 的对象。</param><exception cref="T:System.NotSupportedException"><see cref="T:System.Collections.Generic.ICollection`1"/> 为只读。</exception>
|
||
public void Add(TrainTransitOnceResult item)
|
||
{
|
||
if (item == null || !CheckTransitLineAvailable(item))
|
||
return;
|
||
|
||
_list.Add(item);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从 <see cref="T:System.Collections.Generic.ICollection`1"/> 中移除所有项。
|
||
/// </summary>
|
||
/// <exception cref="T:System.NotSupportedException"><see cref="T:System.Collections.Generic.ICollection`1"/> 为只读。</exception>
|
||
public void Clear()
|
||
{
|
||
_list.Clear();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 确定 <see cref="T:System.Collections.Generic.ICollection`1"/> 是否包含特定值。
|
||
/// </summary>
|
||
/// <returns>
|
||
/// 如果在 <see cref="T:System.Collections.Generic.ICollection`1"/> 中找到 <paramref name="item"/>,则为 true;否则为 false。
|
||
/// </returns>
|
||
/// <param name="item">要在 <see cref="T:System.Collections.Generic.ICollection`1"/> 中定位的对象。</param>
|
||
public bool Contains(TrainTransitOnceResult item)
|
||
{
|
||
return _list.Contains(item);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从特定的 <see cref="T:System.Array"/> 索引开始,将 <see cref="T:System.Collections.Generic.ICollection`1"/> 的元素复制到一个 <see cref="T:System.Array"/> 中。
|
||
/// </summary>
|
||
/// <param name="array">作为从 <see cref="T:System.Collections.Generic.ICollection`1"/> 复制的元素的目标的一维 <see cref="T:System.Array"/>。 <see cref="T:System.Array"/> 必须具有从零开始的索引。</param><param name="arrayIndex"><paramref name="array"/> 中从零开始的索引,从此索引处开始进行复制。</param><exception cref="T:System.ArgumentNullException"><paramref name="array"/> 为 null。</exception><exception cref="T:System.ArgumentOutOfRangeException"><paramref name="arrayIndex"/> 小于 0。</exception><exception cref="T:System.ArgumentException">源 <see cref="T:System.Collections.Generic.ICollection`1"/> 中的元素数目大于从 <paramref name="arrayIndex"/> 到目标 <paramref name="array"/> 末尾之间的可用空间。</exception>
|
||
public void CopyTo(TrainTransitOnceResult[] array, int arrayIndex)
|
||
{
|
||
_list.CopyTo(array, arrayIndex);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从 <see cref="T:System.Collections.Generic.ICollection`1"/> 中移除特定对象的第一个匹配项。
|
||
/// </summary>
|
||
/// <returns>
|
||
/// 如果已从 <see cref="T:System.Collections.Generic.ICollection`1"/> 中成功移除 <paramref name="item"/>,则为 true;否则为 false。 如果在原始 <see cref="T:System.Collections.Generic.ICollection`1"/> 中没有找到 <paramref name="item"/>,该方法也会返回 false。
|
||
/// </returns>
|
||
/// <param name="item">要从 <see cref="T:System.Collections.Generic.ICollection`1"/> 中移除的对象。</param><exception cref="T:System.NotSupportedException"><see cref="T:System.Collections.Generic.ICollection`1"/> 为只读。</exception>
|
||
public bool Remove(TrainTransitOnceResult item)
|
||
{
|
||
return _list.Remove(item);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取 <see cref="T:System.Collections.Generic.ICollection`1"/> 中包含的元素数。
|
||
/// </summary>
|
||
/// <returns>
|
||
/// <see cref="T:System.Collections.Generic.ICollection`1"/> 中包含的元素个数。
|
||
/// </returns>
|
||
public int Count { get { return _list.Count; } }
|
||
|
||
/// <summary>
|
||
/// 获取一个值,该值指示 <see cref="T:System.Collections.Generic.ICollection`1"/> 是否为只读。
|
||
/// </summary>
|
||
/// <returns>
|
||
/// 如果 <see cref="T:System.Collections.Generic.ICollection`1"/> 为只读,则为 true;否则为 false。
|
||
/// </returns>
|
||
public bool IsReadOnly { get { return false; } }
|
||
|
||
public TrainTransitSearchOptions Options
|
||
{
|
||
get { return _options; }
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 辅助函数
|
||
|
||
|
||
static int GetMinimalSpace(Train train, bool notRecommand)
|
||
{
|
||
var extraTime = notRecommand ? 60 : 0;
|
||
|
||
switch (train.TrainClass)
|
||
{
|
||
case 'C':
|
||
case 'G':
|
||
return 30 + extraTime;
|
||
case 'D':
|
||
return 30 + extraTime;
|
||
case 'Z':
|
||
return 60 + extraTime;
|
||
case 'T':
|
||
return 60 + extraTime;
|
||
default:
|
||
return 60 + extraTime;
|
||
}
|
||
}
|
||
|
||
|
||
static int GetMaxiumSpace(Train train, bool notRecommand)
|
||
{
|
||
var extraTime = notRecommand ? 60 : 0;
|
||
|
||
switch (train.TrainClass)
|
||
{
|
||
case 'C':
|
||
case 'G':
|
||
return 150 + extraTime;
|
||
case 'D':
|
||
return 150 + extraTime;
|
||
case 'Z':
|
||
return 180 + extraTime;
|
||
case 'T':
|
||
return 180 + extraTime;
|
||
default:
|
||
return 240 + extraTime;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获得比较理想中位时间
|
||
/// </summary>
|
||
/// <param name="train"></param>
|
||
/// <returns></returns>
|
||
static int GetHighQualityTimeSpace(Train train, bool notRecommand)
|
||
{
|
||
var extraTime = notRecommand ? 60 : 0;
|
||
|
||
switch (train.TrainClass)
|
||
{
|
||
case 'C':
|
||
case 'G':
|
||
return 60 + extraTime;
|
||
case 'D':
|
||
return 60 + extraTime;
|
||
case 'Z':
|
||
return 120 + extraTime;
|
||
case 'T':
|
||
return 120 + extraTime;
|
||
default:
|
||
return 120 + extraTime;
|
||
}
|
||
}
|
||
|
||
|
||
#endregion
|
||
|
||
#region 验证车次换乘是否有效
|
||
|
||
|
||
bool CheckTransitLineAvailable(TrainTransitOnceResult item)
|
||
{
|
||
//总共时间
|
||
if (item.First.CalculatedMinutesBase + item.Second.CalculatedMinutesBase > _options.MaxExtraMinutes)
|
||
//时间过久
|
||
return false;
|
||
|
||
//时间衔接
|
||
var arriveFirst = Date.Add(item.First.To.Arrive.Value);
|
||
var leftSecond = arriveFirst.Date.Add(item.Second.From.Left.Value);
|
||
|
||
//早于发车
|
||
if (leftSecond <= arriveFirst)
|
||
return false;
|
||
var space = (int)(leftSecond - arriveFirst).TotalMinutes;
|
||
if (space < GetMinimalSpace(item.First.Train, item.NotRecommand) || space > GetMaxiumSpace(item.Second.Train, item.NotRecommand))
|
||
return false;
|
||
|
||
return true;
|
||
}
|
||
|
||
|
||
#endregion
|
||
|
||
#region 对列表进行二次优化
|
||
|
||
/// <summary>
|
||
/// 二次优化,排除小站等。
|
||
/// </summary>
|
||
internal void SecondaryAnalyze()
|
||
{
|
||
//对于前车进行分组优化
|
||
RemoveSmallStation();
|
||
//排序
|
||
ProcessPriority();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 移除小站
|
||
/// </summary>
|
||
void RemoveSmallStation()
|
||
{
|
||
var groups = this.GroupBy(s => s.First.Train.Id).Select(s => new { s.Key, Array = s.ToArray() }).ToArray();
|
||
foreach (var g in groups)
|
||
{
|
||
if (g.Array.Length < 2)
|
||
continue;
|
||
|
||
Array.Sort(g.Array, (s1, s2) =>
|
||
{
|
||
var st1 = s1.First.To.StopTime.TotalMinutes;
|
||
var st2 = s2.First.To.StopTime.TotalMinutes;
|
||
|
||
if (st1 < st2) return 1;
|
||
if (st1 > st2) return -1;
|
||
return 0;
|
||
});
|
||
foreach (var line in g.Array.Skip(Options.SameLineMaxKeepStations))
|
||
{
|
||
_list.Remove(line);
|
||
}
|
||
}
|
||
|
||
groups = this.GroupBy(s => s.Second.Train.Id).Select(s => new { s.Key, Array = s.ToArray() }).ToArray();
|
||
foreach (var g in groups)
|
||
{
|
||
if (g.Array.Length < 2)
|
||
continue;
|
||
|
||
Array.Sort(g.Array, (s1, s2) =>
|
||
{
|
||
var st1 = s1.Second.From.StopTime.TotalMinutes;
|
||
var st2 = s2.Second.From.StopTime.TotalMinutes;
|
||
|
||
if (st1 < st2) return 1;
|
||
if (st1 > st2) return -1;
|
||
return 0;
|
||
});
|
||
foreach (var line in g.Array.Skip(Options.SameLineMaxKeepStations))
|
||
{
|
||
_list.Remove(line);
|
||
}
|
||
}
|
||
|
||
//如果线路数超过五条,则砍掉时间太多的
|
||
if (Options.StartCutLongRunningCount <= _list.Count)
|
||
{
|
||
//基础时间
|
||
var times = _list.Select(s => s.FirstElapsedTime.Add(s.SecondElapsedTime)).OrderBy(s => s).Min().TotalMinutes;
|
||
var maxTime = times * Options.CutLongRunningRate;
|
||
|
||
Array.ForEach(_list.Where(s => (s.SecondElapsedTime + s.FirstElapsedTime).TotalMinutes > maxTime).ToArray(), _ => _list.Remove(_));
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 分组排序
|
||
|
||
public string[] CityOrder { get; private set; }
|
||
|
||
public int[] TopLinesIndices { get; private set; }
|
||
|
||
void ProcessPriority()
|
||
{
|
||
//对换乘站点计算优先级
|
||
var stData = _list.GroupBy(s => s.First.ToStation).Select(s => new KeyValuePair<StationDetailInfo, TrainTransitOnceResult[]>(s.Key, s.ToArray())).ToList();
|
||
|
||
stData.Sort((x, y) =>
|
||
{
|
||
//前车高铁/动车等,优先推荐
|
||
var fhc1 = x.Value.Count(_ => _.First.Train.IsHighSpeedClass);
|
||
var shc1 = y.Value.Count(_ => _.First.Train.IsHighSpeedClass);
|
||
if (shc1 != fhc1)
|
||
return shc1 > fhc1 ? 1 : -1;
|
||
|
||
//否则推荐后车的高铁/动车
|
||
var fhc2 = x.Value.Count(_ => _.Second.Train.IsHighSpeedClass);
|
||
var shc2 = y.Value.Count(_ => _.Second.Train.IsHighSpeedClass);
|
||
if (shc2 != fhc2)
|
||
return shc1 > fhc1 ? 1 : -1;
|
||
|
||
//如果都米有,则按线路数推荐
|
||
if (x.Value.Length != y.Value.Length)
|
||
return y.Value.Length - x.Value.Length;
|
||
|
||
//线路数也相同。。。。那好像没啥再区别的了
|
||
return 0;
|
||
});
|
||
CityOrder = stData.Select(s => s.Key.Code).ToArray();
|
||
//对应起排序
|
||
var stationWeight = new Dictionary<StationDetailInfo, int>(stData.Count);
|
||
for (var i = 0; i < stData.Count; i++)
|
||
{
|
||
stationWeight.Add(stData[i].Key, i);
|
||
}
|
||
|
||
//分组排序
|
||
_list.Sort((x, y) =>
|
||
{
|
||
//不推荐,按不推荐来排序
|
||
if (x.NotRecommand ^ y.NotRecommand)
|
||
return x.NotRecommand ? 1 : -1;
|
||
|
||
//按始发终到排序
|
||
var wx = (x.First.IsBegin ? 10 : 0) + (x.First.IsEnd ? 10 : 0) + (x.Second.IsBegin ? 10 : 0) + (x.Second.IsEnd ? 10 : 0);
|
||
var wy = (y.First.IsBegin ? 10 : 0) + (y.First.IsEnd ? 10 : 0) + (y.Second.IsBegin ? 10 : 0) + (y.Second.IsEnd ? 10 : 0);
|
||
if (wx != wy)
|
||
return wx < wy ? 1 : -1;
|
||
|
||
//按前车是否是高速车排序
|
||
if (x.First.Train.IsHighSpeedClass ^ y.First.Train.IsHighSpeedClass)
|
||
return x.First.Train.IsHighSpeedClass ? -1 : 1;
|
||
//按后车是否是高速车排序
|
||
if (x.Second.Train.IsHighSpeedClass ^ y.Second.Train.IsHighSpeedClass)
|
||
return x.Second.Train.IsHighSpeedClass ? -1 : 1;
|
||
|
||
//如果到的时间是不推荐的时间
|
||
var xIsTimeNotRecommand = x.FirstTrainArriveDate.Hour < 6 || x.FirstTrainArriveDate.Hour >= 23;
|
||
var yIsTimeNotRecommand = y.FirstTrainArriveDate.Hour < 6 || y.FirstTrainArriveDate.Hour >= 23;
|
||
if (xIsTimeNotRecommand ^ yIsTimeNotRecommand)
|
||
return xIsTimeNotRecommand ? 1 : -1;
|
||
|
||
//如果发车时间是不推荐的时间
|
||
var xIsBeginTimeNotRecommand = x.FirstTrainLeftDate.Hour < 6 || x.FirstTrainLeftDate.Hour >= 23;
|
||
var yIsBeginTimeNotRecommand = y.FirstTrainLeftDate.Hour < 6 || y.FirstTrainLeftDate.Hour >= 23;
|
||
if (xIsBeginTimeNotRecommand ^ yIsBeginTimeNotRecommand)
|
||
return xIsBeginTimeNotRecommand ? 1 : -1;
|
||
|
||
//如果到的时间是不推荐的时间
|
||
var xIsEndTimeNotRecommand = x.SecondTrainArriveDate.Hour < 6 || x.SecondTrainArriveDate.Hour >= 23;
|
||
var yIsEndTimeNotRecommand = y.SecondTrainArriveDate.Hour < 6 || y.SecondTrainArriveDate.Hour >= 23;
|
||
if (xIsEndTimeNotRecommand ^ yIsEndTimeNotRecommand)
|
||
return xIsEndTimeNotRecommand ? 1 : -1;
|
||
|
||
//按总耗时排序
|
||
var ttl1 = x.First.ElapsedTime + x.SecondElapsedTime;
|
||
var ttl2 = y.First.ElapsedTime + y.SecondElapsedTime;
|
||
|
||
if (ttl1 != ttl2)
|
||
return ttl1 < ttl2 ? -1 : 1;
|
||
return 0;
|
||
});
|
||
|
||
TopMostRecommandLines = _list.Take(Options.TopMostRecommandCount).ToList();
|
||
_list.Sort((x, y) =>
|
||
{
|
||
//不推荐,按不推荐来排序
|
||
if (x.NotRecommand ^ y.NotRecommand)
|
||
return x.NotRecommand ? 1 : -1;
|
||
|
||
//车站不同,按车站来排序
|
||
if (x.First.To != y.First.To)
|
||
{
|
||
return stationWeight[x.First.ToStation] - stationWeight[y.First.ToStation];
|
||
}
|
||
|
||
return 0;
|
||
});
|
||
TopLinesIndices = TopMostRecommandLines.Select(s => _list.IndexOf(s)).ToArray();
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
}
|