使用Lists和Linq编写高效的代码

时间:2013-06-04 00:09:32

标签: c# performance linq

我创建了一个快速控制台应用程序,它创建了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; }
}

基本上我想了解最佳实践,以便从我在代码中添加的两个示例查询中获得最佳性能。我已经阅读了一些关于使用相交的性能相关材料,但我不确定哪种以及什么时候最好用来获得最佳性能。列表有点OTT(大小明智),但它使示例两个更有趣的运行。

3 个答案:

答案 0 :(得分:3)

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

对于两个你可以做得更好。你走了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();

然后,一旦我们看到这一点,我们应该对自己说,嘿,这看起来像hash join!然后,我们可以更清楚地写出来:

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

任何时候你看到模式嵌套循环,匹配外部,内部你应该考虑加入!

答案 1 :(得分:3)

第一个示例查询很好,您可能无法改进其性能。

在第二个查询中,您可以使用联接:

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();

联接只枚举每个列表一次,因此复杂度为O(M + N),而不是O(M * N)

答案 2 :(得分:0)

杰森和托马斯的具体答案假设(非常合理地给出了你的问题的表达方式),在获得数据之前你不知道问题。

我想我已经指出,如果您确实知道要询问数据的问题,那么当您将年轻人和老年人添加到列表中时,您可以继续运行聚合,以便随时准备好答案。

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