I am trying to determine whether times ranges are overlapping on a CRUD page but am stuck.
I can get it working with two time ranges using the code below but I need it working for 3 as well.
public static bool IsOverLapping(ConfigureViewModel viewModel)
{
bool status = false;
var times = viewModel.Periods.OrderBy(x => x.StartTime.TimeOfDay).ToList();
for (var i = 0; i <= times.Count - 2; i++)
{
if (times[i].StartTime.TimeOfDay <= times[i + 1].EndTime.TimeOfDay)
{
if (times[i + 1].StartTime.TimeOfDay >= times[i].EndTime.TimeOfDay)
status = false;
else
return true;
}
else
return true;
}
return status;
}
The data comes in as DateTime values which is why I have only looked at the 'TimeOfDay' value. The image shows the layout of the CRUD page.
This is actually trickier than it seems, as you need to handle time periods that wrap across midnight.
Using some extension methods, you can make it straight forward.
First, determine if a time is between two others:
public static bool Between(this TimeSpan aTime, TimeSpan startTime, TimeSpan endTime) => (startTime <= endTime) ? (startTime < aTime && aTime < endTime)
: (startTime < aTime || aTime < endTime);
Then create a special version using the Period class for the range:
public static bool Between(this TimeSpan aTime, Period aPeriod) => aTime.Between(aPeriod.StartTime.TimeOfDay, aPeriod.EndTime.TimeOfDay);
Finally create a test for if one range overlaps a second range (note this is asymmetric):
public static bool Overlaps(this Period aPeriod, Period bPeriod) => aPeriod.StartTime.TimeOfDay.Between(bPeriod) || aPeriod.EndTime.TimeOfDay.Between(bPeriod);
Now go through all the ranges and check if any range overlaps another range:
public static bool IsOverLapping(this List<Period> periods) {
var periodCount = periods.Count;
for (int j1 = 0; j1 < periodCount; ++j1)
for (int j2 = 0; j2 < periodCount; ++j2)
if (j1 != j2 && periods[j1].Overlaps(periods[j2]))
return true;
return false;
}
Finally you can use the method in your ConfigureViewModel method:
public static bool IsOverLapping(ConfigureViewModel viewModel)
{
bool status = false;
var times = viewModel.Periods.OrderBy(x => x.StartTime.TimeOfDay).ToList();
return times.IsOverLapping();
}
I think it might be simpler than it sounds. If you have period1 and period2, they are NOT overlapping if period1.Start > period2.End or if period1.End < period2.Start. If neither of these are true, then we know that they are overlapping:
I made this a static method on the Period class:
public class Period
{
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
public static bool AreOverlapping(Period first, Period second)
{
if (first == null || second == null) return false;
// These two conditions define "overlapping" and must be true
return first.StartTime <= second.EndTime &&
first.EndTime >= second.StartTime;
}
}
Then this should simplify the logic in your method that detects if there are any overlapping periods in a group:
public static bool DoAnyOverlap(List<Period> periods)
{
if (periods == null || periods.Count < 2) return false;
var ordered = periods.OrderBy(p => p.StartTime).ToList();
for (var i = 0; i < ordered.Count - 1; i++)
{
if (Period.AreOverlapping(ordered[i], ordered[i + 1])) return true;
}
return false;
}
If for some reason you cannot modify the Period class, the logic can easily be incorporated into the DoAnyOverlap method:
public static bool DoAnyOverlap(List<Period> periods)
{
if (periods == null || periods.Count < 2) return false;
var ordered = periods.Where(p => p != null).OrderBy(p => p.StartTime).ToList();
for (var i = 0; i < ordered.Count - 1; i++)
{
if (ordered[i].StartTime <= ordered[i + 1].EndTime &&
ordered[i].EndTime >= ordered[i + 1].StartTime)
{
return true;
}
}
return false;
}
Try this:
var periods = new[]
{
new { start = TimeSpan.Parse("10:00"), end = TimeSpan.Parse("14:00") },
new { start = TimeSpan.Parse("16:00"), end = TimeSpan.Parse("17:00") },
new { start = TimeSpan.Parse("13:00"), end = TimeSpan.Parse("15:00") },
};
bool overlapping =
Enumerable
.Range(0, periods.Length)
.SelectMany(i =>
Enumerable
.Range(i + 1, periods.Length - i - 1),
(i, j) => new { A = periods[i], B = periods[j] })
.Any(x => !(x.B.start >= x.A.end || x.B.end <= x.A.start));
It'll work with DateTime too.
Assuming ConfigureViewModel class looks like that:
class ConfigureViewModel
{
...
public List<Period> Periods { get; set; }
}
class Period
{
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
}
You can use Time Period Library for .NET:
PM> Install-Package TimePeriodLibrary.NET
And IsOverLapping mathod may look as simple as that:
public static bool IsOverLapping(ConfigureViewModel vm, int numberOfOverlaps)
{
var ranges = vm.Periods.Select(q => new TimeRange(q.StartTime, q.EndTime));
TimePeriodCollection periodCollection = new TimePeriodCollection(ranges);
TimePeriodIntersector<TimeRange> intersector = new TimePeriodIntersector<TimeRange>();
return intersector.IntersectPeriods(periodCollection).Count > numberOfOverlaps;
}
Related
I have list of object and i want to filter this to between to date.
I write function for this if date is between to dates return true else return false.
This is my function.
private bool IsDateBetween(DateTime date, string min, string max) {
bool resultMin = true;
bool resultMax = true;
if (!string.IsNullOrEmpty(min))
{
resultMin = date > Convert.ToDateTime(min);
}
if (!string.IsNullOrEmpty(max)) {
resultMax = date < Convert.ToDateTime(max);
}
return resultMin&&resultMax;
}
and this is how i call function with linq.
var policiesT = policies.Where(x=>IsDateBetween(x.StartofInsurance,startDateS,startDateF));
startDateS and startDateF is string of datetime and i checked that values.
They have value when i run the code.
But problem is when i debug the function min and max values always coming null.
This is the image before the calling function
This is the image when function is running.
Why that values caming null can u helpp me. Thanks
This maybe not an answer but in comments would be a mess. This sample works fine for me:
void Main()
{
var policies = new List<Policy> {
new Policy {StartofInsurance=new DateTime(2021,1,1)},
new Policy {StartofInsurance=new DateTime(2021,6,21)},
new Policy {StartofInsurance=new DateTime(2021,7,13)},
};
string startDateS = "01/09/2021";
string startDateF = "07/01/2021";
var policiesT = policies.Where(x => IsDateBetween(x.StartofInsurance, startDateS, startDateF));
foreach (var p in policiesT)
{
Console.WriteLine(p.StartofInsurance);
}
}
private bool IsDateBetween(DateTime date, string min, string max)
{
bool resultMin = true;
bool resultMax = true;
if (!string.IsNullOrEmpty(min))
{
resultMin = date > Convert.ToDateTime(min);
}
if (!string.IsNullOrEmpty(max))
{
resultMax = date < Convert.ToDateTime(max);
}
return resultMin && resultMax;
}
public class Policy
{
public DateTime StartofInsurance { get; set; }
}
And returns the correct policy:
6/21/2021 12:00:00 AM
I'm trying to recreate python code which uses pandas (this one) in C# with Deedle. Basically, I'm struggling with two things.
I want to use the Rsi(this IReadOnlyList<OHLCV> source... overload while calculating my indicators. Right now I'm using List<decimal>.
How can I convert that loc into C#?
dataframe.loc[
(
qtpylib.crossed_above(dataframe['ema20'], dataframe['ema50']) &
(dataframe['ha_close'] > dataframe['ema20']) &
(dataframe['ha_open'] < dataframe['ha_close']) # green bar
),
'buy'] = 1
The questioned method is called PopulateIndicators in my example below. I want to replace the foreach loop with something like the loc above as I mentioned above and make the code look a bit more like the python code (e.g. splitting it to three methods, etc.).
Fully testable code
using Binance.Net;
using Binance.Net.Enums;
using Deedle;
using System;
using System.Collections.Generic;
using System.Linq;
namespace DeedleFrameTests
{
public class OHLCV
{
public DateTime Timestamp { get; set; }
public decimal Open { get; set; }
public decimal High { get; set; }
public decimal Low { get; set; }
public decimal Close { get; set; }
public decimal Volume { get; set; }
}
public static partial class IndicatorExtensions
{
private static IReadOnlyList<decimal?> FixIndicatorOrdering(IReadOnlyList<decimal> source, int outBegIdx, int outNbElement)
{
var outValues = new List<decimal?>();
var validItems = source.Take(outNbElement);
for (int i = 0; i < outBegIdx; i++)
outValues.Add(null);
foreach (var value in validItems)
outValues.Add(value);
return outValues;
}
public static IReadOnlyList<decimal?> Rsi(this IReadOnlyList<OHLCV> source, int period = 14)
{
var rsiValues = new decimal[source.Count];
var closes = source.Select(e => e.Close).ToArray();
var result = TALib.Core.Rsi(closes, 0, source.Count - 1, rsiValues, out int outBegIdx, out int outNbElement, period);
if (result == TALib.Core.RetCode.Success)
{
return FixIndicatorOrdering(rsiValues.ToList(), outBegIdx, outNbElement);
}
throw new Exception("Could not calculate RSI.");
}
public static IReadOnlyList<decimal?> Rsi(this List<decimal> source, int period = 14)
{
var rsiValues = new decimal[source.Count];
var result = TALib.Core.Rsi(source.ToArray(), 0, source.Count - 1, rsiValues, out int outBegIdx, out int outNbElement, period);
if (result == TALib.Core.RetCode.Success)
{
return FixIndicatorOrdering(rsiValues.ToList(), outBegIdx, outNbElement);
}
throw new Exception("Could not calculate RSI.");
}
}
public class TestStrategy
{
public enum TradeAdvice
{
Buy,
Sell,
Hold
}
public List<TradeAdvice> PopulateIndicatorsNoDeedle(List<OHLCV> candles)
{
var result = new List<TradeAdvice>();
var rsiPeriod = 2;
var rsi = candles.Rsi(rsiPeriod);
for (int i = 0; i < candles.Count; i++)
{
if (rsi[i] < 45 && rsi[i] > rsi[i - 1])
result.Add(TradeAdvice.Buy);
else if (rsi[i] > 70)
result.Add(TradeAdvice.Sell);
else
result.Add(TradeAdvice.Hold);
}
return result;
}
public List<TradeAdvice> PopulateIndicators(Frame<int, string> frame)
{
var closes = frame.GetColumn<decimal>("Close").Values.ToList();
var indicator = closes.Rsi(2);
frame.AddColumn("rsi", indicator);
var rsi = frame.GetColumn<decimal>("rsi").Realign(Enumerable.Range(0, closes.Count)).FillMissing(0m);
var rsiShifted = frame.GetColumn<decimal>("rsi").Shift(1).Realign(Enumerable.Range(0, closes.Count)).FillMissing(0m);
var result = new List<TradeAdvice>();
for (int i = 0; i < closes.Count; i++)
{
if (rsi[i] < 45 && rsi[i] > rsiShifted[i])
result.Add(TradeAdvice.Buy);
else if (rsi[i] > 70)
result.Add(TradeAdvice.Sell);
else
result.Add(TradeAdvice.Hold);
}
return result;
}
}
public class Program
{
public static void Main(string[] args)
{
var candles = new BinanceClient().Spot.Market.GetKlines("TRXUSDT", KlineInterval.OneMinute).Data?
.Select(x => new OHLCV
{
Timestamp = x.OpenTime,
Open = x.Open,
High = x.High,
Close = x.Close,
Low = x.Low,
Volume = x.BaseVolume
}).ToList();
var frame = Frame.FromRecords(candles);
var strategy = new TestStrategy();
var advices = strategy.PopulateIndicatorsNoDeedle(candles);
var advices2 = strategy.PopulateIndicators(frame);
var areEqual = Enumerable.SequenceEqual(advices, advices2);
Console.ReadLine();
}
}
}
Edit
I got working the first part of the question only (closes from List<decimal> to List<OHLCV>). This is the question that helped me Convert a Deedle Dataframe into a C# List of Custom Class.
The rest of the question remains still unsolved.
public List<TradeAdvice> PopulateIndicators(Frame<int, string> df)
{
var closes = df.Rows.Select(row =>
new OHLCV
{
Timestamp = row.Value.GetAs<DateTime>("Timestamp"),
Open = row.Value.GetAs<decimal>("Open"),
High = row.Value.GetAs<decimal>("High"),
Low = row.Value.GetAs<decimal>("Low"),
Close = row.Value.GetAs<decimal>("Close"),
Volume = row.Value.GetAs<decimal>("Volume")
}).Observations.Select(e => e.Value).ToList();
//var closes = df.GetColumn<decimal>("Close").Values.ToList();
var indicator = closes.Rsi(2);
df.AddColumn("rsi", indicator);
var rsi = df.GetColumn<decimal>("rsi").Realign(Enumerable.Range(0, closes.Count)).FillMissing(0m);
var rsiShifted = df.GetColumn<decimal>("rsi").Shift(1).Realign(Enumerable.Range(0, closes.Count)).FillMissing(0m);
var result = new List<TradeAdvice>();
for (int i = 0; i < closes.Count; i++)
{
if (rsi[i] < 45 && rsi[i] > rsiShifted[i])
result.Add(TradeAdvice.Buy);
else if (rsi[i] > 70)
result.Add(TradeAdvice.Sell);
else
result.Add(TradeAdvice.Hold);
}
return result;
}
There is a form that each user is required to complete, it has 4 fields: date, bill code, amount and currency. Bill code has a drop down menu with a lot of options of 4 that options are valid (Health, Travel, Meal, Hotel). Bill code field cannot be left blank, and it should take only one of these 4 options. A user make 4 entries with each of the 4 bill codes. If user enters only Health and Travel, an error message should fire that Meal and Hotel records need to be added. This is what I got so far:
public bool ValidateBillCode(bills billArray[][])
{
for(int i = 0; i < billArray.Length; i++)
{
for(int j = 0; j < billArray[0].Length; j++)
{
if(billArray[i][j].IndexOf("Health") >= 0 ||
billArray[i][j].IndexOf("Travel") >= 0 ||
billArray[i][j].IndexOf("Meal") >= 0||
billArray[i][j].IndexOf("Hotel") >= 0)
{
return true;
}
else
{
return false;
}
}
}
}
But it doesn't make sure that all four of these are entered, and I'm not sure how to make an error message that would tell the user which of the four are missing. I will appreciate any help with this.
I believe you don't need any 2-dimensional or jagged array. You need to define a struct like:
public struct Bill
{
public string Date;
public string BillCode;
public string Amount;
public string Currency;
}
Then your ValidateBillCode() becomes like this:
public bool ValidateBillCode(Bill[] billArray)
{
bool healthEntered = false;
bool travelEntered = false;
bool mealEntered = false;
bool hotelEntered = false;
for (int i = 0; i < billArray.Length; i++)
{
if (billArray[i].BillCode == "Health")
healthEntered = true;
else if (billArray[i].BillCode == "Travel")
travelEntered = true;
else if (billArray[i].BillCode == "Meal")
mealEntered = true;
else if (billArray[i].BillCode == "Hotel")
hotelEntered = true;
}
return healthEntered && travelEntered && mealEntered && hotelEntered;
}
But this is just a very simplistic approach. For a more appropriate solution, you'd better to use class instead of struct, use enum for BillCode, DateTime for Date, and double for Amount.
I'm currently doing my current project and I had a problem. Here's what the project needs to do:
Find the maximum and the minimum temperature from a certain range of date. The range of the date will be inputted by the user.
So, I make a form as the main menu for inputting the items and finding the maximum and minimum value (both in the new form). I also make a class to store the items:
public class TempDate
{
public double Temp { get; set; }
public DateTime Date { get; set; }
}
In the first form, just call it FormAddData, from here items will be stored into the list using a textbox and here's the code:
private void buttonSubmit_Click(object sender, EventArgs e)
{
FormMenu formMenu = (FormMenu)this.Owner;
DateTime date = dateTimePickerDate.Value.Date;
double temp = double.Parse(textBoxTemp.Text);
TempDate tempDate = new TempDate();
tempDate.Date = date;
tempDate.Temp = temp;
formMenu.listOfTempDate.Add(tempDate);
listBoxInfo.Items.Add(date + "\t" + temp + "°C");
}
In the second form that called FormMaxMinRange. In this form, I use two DateTimePicker the first one for the starting date and the second for the ending date. From here I need to make a button that will select all the items from the range that I used from starting and ending date. Here's my code:
private void buttonMaxMin_Click(object sender, EventArgs e)
{
FormMenu formMenu = (FormMenu)this.Owner;
DateTime start = dateTimePickerStart.Value.Date;
DateTime end = dateTimePickerEnd.Value.Date;
int highest = 0;
double max = formMenu.listOfTempDate[0].Temp;
int lowest = 0;
double min = formMenu.listOfTempDate[0].Temp;
for (int i = 1; i < formMenu.listOfTempDate.Count; i++)
{
if (formMenu.listOfTempDate[i].Date >= start
&& formMenu.listOfTempDate[i].Date <= end)
{
if (formMenu.listOfTempDate[i].Temp > max)
{
highest = i;
max = formMenu.listOfTempDate[i].Temp;
}
if (formMenu.listOfTempDate[i].Temp < min)
{
lowest = i;
min = formMenu.listOfTempDate[i].Temp;
}
}
}
listBoxMaxMin.Items.Add("");
listBoxMaxMin.Items.Add("Lowest temp: " + min + ", on " + formMenu.listOfTempDate[lowest].Date);
listBoxMaxMin.Items.Add("Highest temp: " + max + ", on " + formMenu.listOfTempDate[highest].Date);
}
Here's the main form that i declared the class (which include the list):
public partial class FormMenu : Form
{
public List<TempDate> listOfTempDate = new List<TempDate>();
public FormMenu()
{
InitializeComponent();
}
private void fromCertainRangeToolStripMenuItem_Click(object sender, EventArgs e)
{
FormMaxMinRange formMaxMinRange = new FormMaxMinRange();
formMaxMinRange.Owner = this;
formMaxMinRange.ShowDialog();
}
}
But, the problem is, the minimum value was not selected inside the range of selection. Also I want the max and min value was printed in the listbox. Sorry for the long and weird question. I hope someone can understand what I means with this question to complete my project. Thank you.
See this code snippet.
You can use Linq to select the reduced list (with Start/Enddate) and order it by Temp. Now you can easy select the first (min) and the last (max) object.
List<TempDate> loTempDateList = new List<TempDate>()
{
new TempDate() {Date = DateTime.Now.AddDays(-10), Temp = 10.01 },
new TempDate() {Date = DateTime.Now.AddDays(-5), Temp = 20.01 },
new TempDate() {Date = DateTime.Now.AddDays(-3), Temp = 30.01 },
new TempDate() {Date = DateTime.Now, Temp = 40.01 }
};
DateTime ldStart = DateTime.Now.AddDays(-6);
DateTime ldEnd = DateTime.Now.AddDays(-1);
var loDateList = loTempDateList.Where(item => item.Date <= ldEnd && item.Date >= ldStart)
.OrderBy(item => item.Temp);
TempDate loMin = loDateList.First();
TempDate loMax = loDateList.Last();
Console.WriteLine("{0}: {1} with max temp", loMax.Date, loMax.Temp);
Console.WriteLine("{0}: {1} with min temp", loMin.Date, loMin.Temp);
Output (for today):
9/26/2017 3:17:09 PM: 30.01 with max temp
9/24/2017 3:17:09 PM: 20.01 with min temp
Update (with your variable names):
Copy this under DateTime end = dateTimePickerEnd.Value.Date;in your Form
var loDateList = listOfTempDate.Where(item => item.Date <= end && item.Date >= start)
.OrderBy(item => item.Temp);
TempDate loMin = loDateList.FirstOrDefault();
TempDate loMax = loDateList.LastOrDefault();
if (loMin != null && loMax != null)
{
listBoxMaxMin.Items.Add("");
listBoxMaxMin.Items.Add("Lowest temp: " + loMin.Temp + ", on " + loMin.Date);
listBoxMaxMin.Items.Add("Highest temp: " + loMax.Temp + ", on " + loMax.Date);
}
I would suggest you use Linq Max and Min methods.
// filter out only the dates in the range you need
var items = formMenu.listOfTempDateWhere(
item => ((TempDate)item).Date >= start && ((TempDate)item).Date <= end
);
// get the maximum value
var max = items.Max(item => item.Temp);
// get the minimum value
var min = items.Min(item => item.Temp);
Just remember to add using System.Linq on the top of your .cs file
try this online
If you don't like a LINQ approach (I never use LINQ, for some, possibly invalid reason, I think it's evil), you can override the List class and extend it with methods of your own.
public class TempDataList<T> : List<TempData>
{
public TempDataList() : base()
{
}
public TempDataList(IEnumerable<TempData> collection) : base(collection)
{
}
public TempData GetMaxTemp(DateTime startDate, DateTime endDate)
{
TempData highestTempData = null;
for (int i = 0; i < this.Count; i++)
{
if (this[i].Date >= startDate && this[i].Date <= endDate)
{
if (highestTempData == null || this[i].Temp > highestTempData.Temp)
{
highestTempData = this[i];
}
}
}
return highestTempData;
}
public TempData GetMinTemp(DateTime startDate, DateTime endDate)
{
TempData lowestTempData = null;
for (int i = 0; i < this.Count; i++)
{
if (this[i].Date >= startDate && this[i].Date <= endDate)
{
if (lowestTempData == null || this[i].Temp < lowestTempData.Temp)
{
lowestTempData = this[i];
}
}
}
return lowestTempData;
}
}
And fill the extended list and call the methods:
TempDataList<TempData> tempDataList = new TempDataList<TempData>();
tempDataList.Add(new TempData(10, DateTime.UtcNow));
tempDataList.Add(new TempData(20, DateTime.UtcNow));
tempDataList.Add(new TempData(15, DateTime.MinValue));
tempDataList.Add(new TempData(25, DateTime.MaxValue));
Console.WriteLine(tempDataList.GetMaxTemp(DateTime.UtcNow.AddDays(-1), DateTime.UtcNow.AddDays(1)).Temp);
Console.WriteLine(tempDataList.GetMinTemp(DateTime.UtcNow.AddDays(-1), DateTime.UtcNow.AddDays(1)).Temp);
I have a simple class called Team, that looks like this:
public class Team
{
public Team ParentTeam;
public string Name;
}
So it has a Name and a reference to another team that is its Parent Team.
I now have a list of Teams that I am getting back from a function
List<Team> list = GetTeamsList();
Given, a few assumptions:
All teams have a ParentTeam except one (the top team)
Every team returned in the list is part of the same hierarchy and its only a single hierarchy (no 2 teams at the same "level")
I now need to take the results of this function and order the list by the hierarchy
So imagine we have the following team information:
|| Team Name || Parent Team Name ||
||-----------||------------------||
|| Team A || Team B ||
|| Team B || Team C ||
|| Team C || Team D ||
|| Team D || null ||
but the GetTeamsList() function returns the teams in any random order. For example, it might come back list this:
var teamA = GetTeamA();
var teamB = GetTeamB();
var teamC = GetTeamC();
var teamD = GetTeamD();
List<Team> list = new List() { teamD, teamA, teamB, teamC };
where I need to reorder this list so it looks like this:
List<Team> list = new List() { teamA, teamB, teamC, teamD };
How could I reorder a list into the "correct" order based on the team hierarchy?
Several of the solutions given so far are correct, and all of them are at least quadratic in the number of teams; they will be inefficient as the number of teams grows large.
Here's a solution which is (1) linear, (2) shorter, and (3) easier to understand than some of the other solutions so far:
static IEnumerable<Team> SortTeams(IEnumerable<Team> teams)
{
var lookup = teams.ToDictionary(t => t.ParentTeam ?? new Team());
var current = teams.Single(t => t.ParentTeam == null);
do
yield return current;
while (lookup.TryGetValue(current, out current));
}
This produces the sequence in the reverse of the order you want, so put a Reverse on the end of the call if you want it in the other order:
Console.WriteLine(String.Join(" ", SortTeams(teams).Reverse().Select(t => t.Name)));
The "dummy" team is there because a dictionary does not allow a key to be null.
This is my suggestion:
public class Team
{
public Team ParentTeam;
public string Name;
int Level
{
get
{
int i = 0;
Team p = this.ParentTeam;
while (p != null)
{
i++;
p = p.ParentTeam;
}
return i;
}
}
static IEnumerable<Team> Sort(IEnumerable<Team> list)
{
return list.OrderBy(o => o.Level);
}
}
Of course, if there are Teams with equal level, you might use another criteria to sort them.
This should work:
static IEnumerable<Team> GetOrdered(IEnumerable<Team> teams)
{
var set = teams as HashSet<Team> ?? new HashSet<Team>(teams);
var current = teams.First(t => t.Parent == null);
while (set.Count > 1)
{
yield return current;
set.Remove(current);
current = set.First(t => t.Parent == current);
}
yield return set.Single();
}
This gives you the reversed order, so you should call Reverse() to get the order you are asking for.
We can find the ascendants of the null team, defining an extension
public static IEnumerable<Team> FindAscendants(this IEnumerable<Team> l, Team from)
{
Team t = l.FirstOrDefault(x =>
(x.ParentTeam?.Name ?? "").Equals(from?.Name ?? ""));
return new List<Team>() { t }.Concat(t != null ?
l.FindAscendants(t) : Enumerable.Empty<Team>());
}
and reverse the order of the null team's ascendants
list.FindAscendants(null).Reverse().Skip(1)
Edit
Alternative version of the extension with yield return
public static IEnumerable<Team> FindAscendants(this IEnumerable<Team> l, Team from)
{
Team t = l.FirstOrDefault(x =>
(x.ParentTeam?.Name ?? "").Equals(from?.Name ?? ""));
yield return t;
if (t != null)
foreach (Team r in l.FindAscendants(t))
{
yield return r;
}
}
Edit 2
In terms of the most efficient solution, a dictionary is the key.
As you can see now, there is no longer need to reverse the order.
So an optimized version would be
public static IEnumerable<Team> FindDescendandOptimized(this List<Team> l, Team from)
{
int count = l.Count;
var dic = l.ToDictionary(x => x.ParentTeam?.Name??"");
Team start = dic[from?.Name??""];
Team[] res = new Team[count];
res[count - 1] = start;
for (int i = count - 2; i >= 0; i--)
{
start = dic[start.Name];
res[i] = start;
}
return res;
}
with a test case and usage
List<Team> list = new List<Team>();
Team team = new Team();
team.Name = "0";
list.Add(team);
for (int i = 1; i < 200000; i++)
{
team = new Team();
team.Name = i.ToString();
team.ParentTeam = list.Last();
list.Add(team);
}
list.Reverse();
Console.WriteLine("Order List of " + list.Count +" teams");
Console.WriteLine("order is " + (TestOrder(list) ? "ok" : "ko"));
list.Shuffle();
Console.WriteLine("Shuffled List");
Console.WriteLine("order is " + (TestOrder(list) ? "ok" : "ko"));
DateTime start = DateTime.Now;
var res = list.FindDescendandOptimized(null);
list = res.ToList();
DateTime end = DateTime.Now;
Console.WriteLine("Reordered List");
Console.WriteLine("order is " + (TestOrder(list) ? "ok" : "ko"));
Console.WriteLine("Benchmark ms: " + (end - start).TotalMilliseconds);
Console.ReadLine();
where the test check is
static bool TestOrder(List<Team> list)
{
int tot = list.Count;
for (int i = 0; i < tot; i++)
{
if (!list[i].Name.Equals((tot-i-1).ToString()))
{
return false;
}
}
return true;
}
Edit 3
A final consideration, maybe obvious.
The absolutely most efficient way would have been to define a child team.
public class Team
{
public string Name;
public Team ParentTeam;
public Team ChildTeam;
}
appropriately filled like below
team.ParentTeam = list.Last();
list.Last().ChildTeam = team;
to enable an immediate reordering
DateTime start = DateTime.Now;
var res = list.OrderByChild(); //list.FindDescendandOptimized(null);
list = res.ToList();
DateTime end = DateTime.Now;
Console.WriteLine("Reordered List");
with a direct link
public static IEnumerable<Team> OrderByChild(this List<Team> l)
{
int count = l.Count;
Team start = l.First(x => x.ParentTeam == null);
Team[] res = new Team[count];
res[count - 1] = start;
for (int i = count - 2; i >= 0; i--)
{
start = start.ChildTeam;
res[i] = start;
}
return res;
}