How can I order these json objects using LINQ?

With data like this (as a side note, these individual components in a json file are rightly called what - records? items? elements? objects? jsonpiecesparts?):

[
  {
    "WeekOfAssignment": "2016-04-04T00:00:00",
    "TalkType": 1,
    "StudentID_FK": 32,
    "AssistantID_FK": 0,
    "CounselPoint": 10,
    "HasBeenEmailed": false,
    "SlipHasBeenPrinted": false
  },
  {
    "WeekOfAssignment": "2016-04-11T00:00:00",
    "TalkType": 1,
    "StudentID_FK": 43,
    "AssistantID_FK": 0,
    "CounselPoint": 39,
    "HasBeenEmailed": false,
    "SlipHasBeenPrinted": false
  },
  {
    "WeekOfAssignment": "2016-04-11T00:00:00",
    "TalkType": 2,
    "StudentID_FK": 44,
    "AssistantID_FK": 40,
    "CounselPoint": 12,
    "HasBeenEmailed": false,
    "SlipHasBeenPrinted": false
  },
  {
    "WeekOfAssignment": "2016-04-11T00:00:00",
    "TalkType": 4,
    "StudentID_FK": 4,
    "AssistantID_FK": 24,
    "CounselPoint": 23,
    "HasBeenEmailed": false,
    "SlipHasBeenPrinted": false
  },
  {
    "WeekOfAssignment": "2016-04-11T00:00:00",
    "TalkType": 6,
    "StudentID_FK": 1,
    "AssistantID_FK": 41,
    "CounselPoint": 42,
    "HasBeenEmailed": false,
    "SlipHasBeenPrinted": false
  },
  {
    "WeekOfAssignment": "2016-04-25T00:00:00",
    "TalkType": 1,
    "StudentID_FK": 19,
    "AssistantID_FK": 0,
    "CounselPoint": 2,
    "HasBeenEmailed": false,
    "SlipHasBeenPrinted": false
  },
  {
    "WeekOfAssignment": "2016-04-25T00:00:00",
    "TalkType": 2,
    "StudentID_FK": 13,
    "AssistantID_FK": 12,
    "CounselPoint": 41,
    "HasBeenEmailed": false,
    "SlipHasBeenPrinted": false
  },
  {
    "WeekOfAssignment": "2016-04-25T00:00:00",
    "TalkType": 4,
    "StudentID_FK": 20,
    "AssistantID_FK": 42,
    "CounselPoint": 11,
    "HasBeenEmailed": false,
    "SlipHasBeenPrinted": false
  },
  {
    "WeekOfAssignment": "2016-04-25T00:00:00",
    "TalkType": 6,
    "StudentID_FK": 36,
    "AssistantID_FK": 39,
    "CounselPoint": 31,
    "HasBeenEmailed": false,
    "SlipHasBeenPrinted": false
  }
]

...I want to order these objects (or whatever they're called) based on how many times StudentID_FK has worked with AssistantID_FK, with the lowest number of times the two have been assigned together at the top of the list. IOW, two that have never been assigned together should be the first item in the ordered list, while those that have been assigned together the most times should be at the bottom.

Note: someone who appears as a "StudentID_FK" one time will likely be represented as an "AssistantID_FK" the next time, and vice versa.

To determine this, the entire json file will need to be queried. I believe I could get the count something like this (assuming studentlist is a list of students, as represented in the json file):

int countOfTimesWorkedTogether = GetCountOfTimesWorkedTogether(student1, student2);

private int GetCountOfTimesWorkedTogether(int student, int assistant)
{
    int StudentAndAssistant = studentlist.Where(s => s.StudentID_FK == student && a => a.AssistantID_FK == assistant).Count();
    int AssistantAndStudent = studentlist.Where(s => s.StudentID_FK == assistant && a => a.AssistantID_FK == student).Count();
    return StudentAndAssistant + AssistantAndStudent;
}

...but then how would I use that in a LINQ query of the list? The count is not a member of the student class, so what "trick" could be employed to order the results based on these counts?

What I need is two side-by-side orderings from the same list. The first one to be ordered by WeekOfAssignment, and the one "beside it" to contain a list of other people, ordered by the fewest times they have worked with the person currently highlighted in the checkedlistbox that is populated from that first query.

In pseudocode, what I need to end up with is:

List<Student> orderedStudents = studentlist.OrderBy(WeekOfAssignment);

That populates checklistboxStudents.

Then this:

List<Student> orderedAssistants = studentlist.OrderBy(CountOfTimesWorkedWithSelectedPersonInChecklistboxStudents);

...populates checklistboxAssistants; so this second query would have to be rerun, and checklistboxAssistants reordered, each time a different student was selected in checklistboxStudents.

UPDATE

I'm afraid I didn't explain clearly the true nature of this problem. It is:

There are, each week, a number (3) of assignments that requires two students each: "the" student, and an assistant.

Each student can play any role; they can give talk 2, 3, 4, 5, 6, or 7. The even-numbered are the Student assignments, and the odd-numbered are the Assistant assignments. Each list is unique - those that are slated to be assigned #2 next, those that are slated to be assigned #3 next, etc.

So each of the six lists are a unique subset of the superset list of students, with approximately 1/6th of the total number in each list on any given week (they move to the next list the following week).

And so I need to compare those in list 3 with those in list 2, those in list 5 with those in list 4, and those in list 7 with the ones in list 6.

There is a Students list which keeps track of student data, such as last assignment given (both the #/talk type and the date), etc. The Assignments list (an excerpt of which is shown above) contains, for each assignment/week, the student who was "the" student (StudentID_FK), and the student who was the Assistant on that occaison (AssistantID_FK).

So, rethinking it, I might be able to do order the assistants something like this (pseudocode):

0) Add an "OptionalOrderingVal" member to the Students class:

public int OptionalOrderingVal { get; set; }

First, get the intial Assistant list based on next assignment type, ordered by last assignment date; for example, for assignment #3, which supports #2:

var RVAssistantsList = studentlist.Where(t => t.talkType == 3).OrderBy(w => weekOfLastAssignment);

Next, create a new version of that sublist, populating the OptionalOrderingVal member:

int countOfAssignmentsWithStudent = 0;
int StudentId = comboboxRVStudent.SelectedValue;
foreach (Student assistant in RVAssistantsList)
{
    countOfAssignmentsWithStudent = assignmentsList.Where(StudentID_FK == StudentId && AssistantID_FK == AssistantId || StudentID_FK == AssistantId && AssistantID_FK == StudentId);
    assistant.OptionalOrderingVal = countOfAssignmentsWithStudent;
}

Then, order the assistant list by the new value:

RVAssistantsList = RVAssistantsList
    .OrderBy(o => o.OptionalOrderingVal)
    .ThenBy(w => w.WeekOfLastAssignment)
    .ToList();

Finally, assign that list to the appropriate checklistbox:

checklistboxRVAssistants.Items = RVAssistantsList;

I could call this either kludgelent or eligy; YMMV.

Upvotes: 1

Views: 241

Answers (2)

dbc
dbc

Reputation: 116805

What you want is to order by the count of times that a pair of student IDs appear as either the student or assistant or vice versa. Thus:

        var orderedAssistants = studentlist
            .OrderBy(a => GetCountOfTimesWorkedTogether(studentlist, a.StudentID_FK, a.AssistantID_FK))
            .ToList();

Using

    private static int GetCountOfTimesWorkedTogether(IEnumerable<Student> studentList, int id1, int id2)
    {
        var count = studentList.Where(s => s.StudentID_FK == id1 && s.AssistantID_FK == id2 || s.StudentID_FK == id2 && s.AssistantID_FK == id1).Count();
        return count;
    }

Note however this can be quadratic in the number of students. What you are looking for is a count of when two students worked together in any combination -- student + assistant or vice versa. A standard way to represent an order-independent combination is a HashSet<T> where T would be the type used to represent student IDs. Then you can use HashSet<T>.CreateSetComparer() to group sets by content equality:

Thus the following should not be quadratic:

        var orderedAssistants = studentlist
            .GroupBy(s => (new[] { s.AssistantID_FK, s.StudentID_FK }).ToSet(), HashSet<int>.CreateSetComparer())
            .OrderBy(g => g.Count())
            .SelectMany(g => g)
            .ToList();

Using

public static class EnumerableExtensions
{
    public static HashSet<T> ToSet<T>(this IEnumerable<T> source)
    {
        return new HashSet<T>(source);
    }
}

Upvotes: 1

Arturo Menchaca
Arturo Menchaca

Reputation: 15982

If you have a model like this:

public class Student
{
    public DateTime WeekOfAssignment { get; set; }
    public int TalkType { get; set; }
    public int StudentID_FK { get; set; }
    public int AssistantID_FK { get; set; }
    public int CounselPoint { get; set; }
    public bool HasBeenEmailed { get; set; }
    public bool SlipHasBeenPrinted { get; set; }
}

You can make a linq query grouping by properties StudentID_FK and AssistantID_FK using an anonymous type and order this groups according to its size, then joins the elements again.

This sort the elements according how many times the pair StudentID_FK and AssistantID_FK appears in the list, lowest times first:

var result = studentlist.GroupBy(m => new 
                                { 
                                    A = Math.Min(m.StudentID_FK, m.AssistantID_FK),  // This gets pairs like (1, 2) and (2, 1)
                                    B = Math.Max(m.StudentID_FK, m.AssistantID_FK)   // as the same (1, 2)
                                })
                    .OrderBy(g => g.Count())
                    .SelectMany(g => g)
                    .ToList();

Upvotes: 1

Related Questions