在具有特定属性的列表中查找对象的顺序模式

时间:2015-07-21 23:05:23

标签: c# linq pattern-matching sequence

我有一个这样的课程:

public class TestResults
{
public String TestName {get;set;}
public Int32 StudentID {get;set;}
public Decimal Score {get;set;}
public Date TestTaken {get;set;}
}

所以有些物品看起来像这样:

test.TestName = "Big Important Test";
test.StudentID = 17;
test.Score = 0.75M;
test.TestTaken = "1/1/2015";

tests.add(test);

test.TestName = "Big Important Test";
test.StudentID = 12;
test.Score = 0.89M;
test.TestTaken = "1/1/2015";

tests.add(test);

test.TestName = "Sneaky Pop Quiz in Chemistry";
test.StudentID = 17;
test.Score = 0.97M;
test.TestTaken = "2/1/2015";

tests.add(test);

test.TestName = "Sneaky Pop Quiz in Chemistry";
test.StudentID = 17;
test.Score = 0.97M;
test.TestTaken = "2/1/2015";

tests.add(test);

我想要确定的是“对于每个学生,向我展示他们的分数大幅跳跃的学生?” I asked a similar question a while back in the dba.stackexchange.com world并使用了LEAD函数,但现在我想将逻辑移到C#中。

因此,我想要编写的具体问题是(作为示例):

  

告诉我从60%和70%的范围跳到学生的学生   90范围。

我知道我可以写一个rat's nest of loops and branching logic,但想知道是否有更优雅,更全面的方法来识别LINQ / C#land中的模式序列。

我听说人们谈论F#,但没有实际经验。另外,我认为我所谈论的“模式匹配”比我持续运行的simple string-pattern-matching中的一些涉及更多。

2 个答案:

答案 0 :(得分:1)

您可以这样做:

const decimal differenceLimit = 0.05M;

var studentIdsWithJump = tests.GroupBy (g => g.StudentID)
    .Where(g => g.OrderBy(c => c.Score)
                .GroupAdjacentBy((first, second) => 
                        first.Score + differenceLimit < second.Score
                ).Count() > 1
    )
    .Select(g => g.Key);

使用辅助方法from here

public static class LinqExtensions
{
   public static IEnumerable<IEnumerable<T>> GroupAdjacentBy<T>(this IEnumerable<T> source, Func<T, T, bool> predicate)
   {
       using (var e = source.GetEnumerator())
       {
           if (e.MoveNext())
           {
               var list = new List<T> { e.Current };
               var pred = e.Current;
               while (e.MoveNext())
               {
                   if (predicate(pred, e.Current))
                   {
                       list.Add(e.Current);
                   }
                   else
                   {
                       yield return list;
                       list = new List<T> { e.Current };
                   }
                   pred = e.Current;
               }
               yield return list;
           }
       }
   }
}

这为您提供了所有范围的跳转。如果你想缩小它,你可以添加一个.Where()得分&gt; 60,并相应调整differenceLimit

答案 1 :(得分:1)

您可以使用LINQ来获得答案。以下是您可以执行此操作的示例:

var scores = tests.GroupBy(t => t.StudentID)
    .Select(g => new { StudentID = g.Key, Min = g.Min(i => i.Score), Max = g.Max(i => i.Score) })
    .Where(s => s.Max - s.Min > .20M);

foreach(var score in scores)
    Console.WriteLine("Student: {0} Jump: {1}", score.StudentID, score.Max - score.Min);

LINQ语句首先按StudentID分组。接下来,它将每个组的StudentID和Min和Max分数投影到新的匿名类型。最后,应用where条件,该条件仅返回“得分大跳跃”的项目。我定义“得分大跳”,因为最高得分和最低得分之间的差异大于.20。

注意:即使学生在列表中的分数超过2分,此代码也会起作用。

更新:

由于您更新了帖子,我更了解您的问题。这是一个更新的答案:

var scores = tests.GroupBy(t => t.StudentID)
    .Select(g => new { StudentID = g.Key, Min = g.OrderBy(i => i.Score).First(), Max = g.OrderByDescending(i => i.Score).First() })
    .Where(s => (s.Min.Score >= .60M & s.Min.Score < .80M) & s.Max.Score >= .90M & s.Min.TestTaken < s.Max.TestTaken);

foreach(var score in scores)
    Console.WriteLine("Student: {0} Jump: {1}", score.StudentID, score.Max.Score - score.Min.Score);

这使用了类似的方法,但不是记录匿名类型的最小和最大分数,而是记录具有最小分数和最大分数的TestResults实例。在where where子句中,我们检查具有最小分数的TestResults是否在60-80范围内。我们检查具有最高分数的TestResults是否在90+范围内。最后,我们检查最小分数是否发生在最大分数发生之前的日期。

相关问题