使用列表和Linq,编写高效的代码

本文关键字:高效 代码 列表 Linq | 更新日期: 2023-09-27 18:16:36

我创建了一个快速控制台应用程序,它创建了10000个年轻人和10000个老年人,并将它们添加到两个单独的列表中。然后执行一些查询以获得基于个性的信息。

class Program
{
    static void Main(string[] args)
    {
        private Random random = new Random();
        private List<Person> youngerPersons = new List<Person>();
        private List<Person> olderPersons = new List<Person>();
        private long samePersonalityMatches = 0;
        for (int i = 0; i < 10000; i++)
        {
            youngerPersons.Add(new Person(RandomString(10), DateTime.Now.ToString(), RandomString(4), random.Next(10, 50),(Person.PersonalityType)random.Next(0, 4), i));
        }
        for (int i = 0; i < 10000; i++)
        {
            olderPersons.Add(new Person(RandomString(10), DateTime.Now.ToString(), RandomString(4), random.Next(51, 120),(Person.PersonalityType)random.Next(0, 4), i));
        }
        //Example query 1
        foreach (Person person in youngerPersons.Where(w => w.Id > 4567 && w.Age > 70))
        {
            Console.WriteLine(person.Id);
        }
        //Example query  2
        foreach (Person person in youngerPersons)
        {
            foreach (Person olderPerson in olderPersons)
            {
                if (person.Personality == olderPerson.Personality)
                {
                    samePersonalityMatches++;
                }
            }
        }
        Console.WriteLine("Number of matches: " + samePersonalityMatches);
    }
    private static Random random = new Random((int)DateTime.Now.Ticks);
    private static string RandomString(int size)
    {
        StringBuilder builder = new StringBuilder();
        char ch;
        for (int i = 0; i < size; i++)
        {
            ch = Convert.ToChar(Convert.ToInt32(Math.Floor(26 * random.NextDouble() + 65)));
            builder.Append(ch);
        }
        return builder.ToString();
    }
}
internal class Person
{
    public enum PersonalityType
    {
        Funny = 0,
        Boring = 1, 
        Serious = 2,
        Grumpy = 3, 
        Normal = 4
    }
    public Person(string name, string dateofbirth, string nickname, int age, PersonalityType personalityType, int id)
    {
        this.Name = name;
        this.Dateofbirth = dateofbirth;
        this.Nickname = nickname;
        this.Age = age;
        this.Id = id;
        this.Personality = personalityType;
    }
    public string Name { get; set; }
    public string Dateofbirth { get; set; }
    public string Nickname { get; set; }
    public int Age { get; set; }
    public int Id { get; set; }
    public PersonalityType Personality { get; set; }
}

基本上,我想了解最佳实践,以获得我放入代码中的两个示例查询的最大性能。我已经阅读了一些关于使用intersect的性能相关材料,但我不确定使用哪种以及何时使用最好以获得最佳性能。列表有点太大了(大小方面),但它使示例2运行起来更有趣。

使用列表和Linq,编写高效的代码

一个很好,非常接近最优,我会让它像你有它(记住,程序员的时间比机器的时间更昂贵)。

对于两个,你可以做得更好。你遍历olderPersons列表的次数太多了,让我们看看能不能把它减少到一次遍历。

Dictionary<Personality, int> dict =
    youngerPersons.GroupBy(p => p.Personality)
                  .ToDictionary(g => g.Key, g => g.Count());
long samePersonalityMatches =
    olderPersons.Select(
                     q => dict.ContainsKey(q.Personality) ?
                     dict[q.Personality] : 0
                )
                .Sum();

然后,一旦我们看到这个,我们应该对自己说,嘿,这看起来像一个哈希连接!然后,我们可以写得更清楚:

long samePersonalityMatches = 
    youngerPersons.Join(
                      olderPersons,
                      p => p.Personality,
                      q => q.Personality,
                      (p, q) => null
                  )
                  .Count();

任何时候你看到模式嵌套循环,匹配超过外部,内部你应该考虑一个连接!

第一个示例查询很好,您可能无法做任何事情来提高其性能。

在第二个查询中,可以使用join:

samePersonalityMatches =
    youngerPersons.Join(olderPersons,
                        p => p.Personality,
                        p => p.Personality,
                        (left, right) => null) // the result doesn't really matter,
                                               // since we just want the count
                  .Count();

或者如果您喜欢查询语法:

samePersonalityMatches =
    (from y in youngerPersons
     join o in olderPersons
         on y.Personality equals o.Personality
     select null).Count();

一个join只枚举每个列表一次,所以复杂度是O(M + N),而不是O(M * N)

Jason和Thomas的具体答案假设(考虑到你的问题的措辞方式,这是相当合理的)你在获得数据之前不知道这些问题。

我只是想指出,如果你知道你要问你的数据的问题,你可以继续运行聚合,当你把年轻人和老年人添加到你的列表,这样答案总是准备好了。

这与选择在数据库中构建特定索引的动机类似。