Light12306/TrainInfomationProviderService/TrainInfo/Entities/TrainTransitOnceResultCollection.cs
2015-12-03 19:35:30 +08:00

450 lines
14 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 ? 180 : 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 ? 120 : 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 Tuple<StationDetailInfo, TrainTransitOnceResult[]>(s.Key, s.ToArray())).ToList();
stData.Sort((x, y) =>
{
//前车高铁/动车等,优先推荐
var fhc1 = x.Item2.Count(_ => _.First.Train.IsHighSpeedClass);
var shc1 = y.Item2.Count(_ => _.First.Train.IsHighSpeedClass);
if (shc1 != fhc1)
return shc1 > fhc1 ? 1 : -1;
//否则推荐后车的高铁/动车
var fhc2 = x.Item2.Count(_ => _.Second.Train.IsHighSpeedClass);
var shc2 = y.Item2.Count(_ => _.Second.Train.IsHighSpeedClass);
if (shc2 != fhc2)
return shc2 > fhc2 ? 1 : -1;
//如果都米有,则按线路数推荐
if (x.Item2.Length != y.Item2.Length)
return y.Item2.Length - x.Item2.Length;
//线路数也相同。。。。那好像没啥再区别的了
return string.CompareOrdinal(x.Item1.PyFull, y.Item1.PyFull);
});
CityOrder = stData.Select(s => s.Item1.Code).ToArray();
//对应起排序
var stationWeight = new Dictionary<StationDetailInfo, int>(stData.Count);
for (var i = 0; i < stData.Count; i++)
{
stationWeight.Add(stData[i].Item1, 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
}
}