2015-07-21 1 views
1

У меня есть класс, как это:нахождения последовательных моделей объектов в виде списка с особыми свойствами

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# земли.

Я слышал, как люди говорили о F #, но не имеют практического опыта с этим. Кроме того, я думаю, что «сопоставление шаблонов», о котором я говорю, немного больше, чем некоторые из simple string-pattern-matching. Я продолжаю работать.

ответ

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() для результатов> 60 и отрегулировать differenceLimit соответственно

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 баллов в списке.

UPDATE:

Поскольку вы обновили свой пост Я понимаю ваш вопрос лучше. Вот обновленный ответ:

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+. Наконец, мы проверяем, что минимальная оценка произошла в день до того, как максимум произошел.