In C#, how can I order this list of objects by which item is greater?

心已入冬 提交于 2020-01-05 03:45:08

问题


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:

  1. All teams have a ParentTeam except one (the top team)
  2. 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?


回答1:


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.




回答2:


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.




回答3:


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.




回答4:


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;
    }


来源:https://stackoverflow.com/questions/42058132/in-c-how-can-i-order-this-list-of-objects-by-which-item-is-greater

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!