研讨会登记算法 - 多个研讨会&发

时间:2015-11-13 13:46:19

标签: c# asp.net algorithm

我目前正在为学生开设研讨会注册系统,学生可以在这里注册几个研讨会。学生可以选择5个工作坊,这些工作坊共有4轮。每个研讨会都有19名学生的空间。

现在我想制作一个自动注册学生的算法,并尽可能高效地为每一轮选择一个研讨会。

学生不能在多轮中选择同一个工作坊。

现状:

Round 1 - space available
Workshop1 - 1 
Workshop2 - 6
Workshop3 - 1
Workshop4 - 0
Workshop5 - 4

total 12 spaces available

Round 2 - space available
Workshop1 - 1
Workshop2 - 8
Workshop3 - -1
Workshop4 - 3
Workshop5 - 1

total 12 spaces available

Round 3 - space available
Workshop1 - 1
Workshop2 - 7
Workshop3 - 1
Workshop4 - 2
Workshop5 - 1

total 12 spaces available

Round 4 - space available
Workshop1 - 0
Workshop2 - 4
Workshop3 - 0
Workshop4 - 5
Workshop5 - 3

total 12 spaces available

最好的方法是什么?

这就是我尝试为一名学生添加研讨会的目的。

int[] chosenWorkshops;
bool workshopFound;
foreach(var round in rounds)
{
    workshopFound = false;
    foreach(var workshop in round.workshops.orderByDescending(q => q.space))
    {
        if(!chosenWorkshops.contains(workshop.ID) && workshop.space > 0)
        {
            chosenWorkshops.push(workshop.ID);
            workshopFound = true;
            break;
        }
    }
    if(!workshopFound) break;
}

if(chosenWorkshops.length == rounds.length)
{
    var student = new Student();
    foreach(var workshopID in chosenWorkshops)
    {
        student.RegisterWorkshop(workshopID);
    }
} else {
    throw new InsufficientSpaceException();
}

此代码的问题在于它可能在最后一轮中没有选项,因为上一轮中的可用选项已经在前几轮中被采用。

有人可以把我推向正确的方向吗?

2 个答案:

答案 0 :(得分:0)

我不知道我是否完全理解你的问题,但是,如果你想让你的工作坊尽可能地满足并且每个学生都应该参加所有课程,那么我想你可以用以下方式完成

您可以创建一个额外的类,其中包含Round的订阅,并实现该回合包含对相关研讨会的引用。

然后以“最佳”的方式组织这些数据(大多数课程的人数尽可能多,所有轮次+工作坊都是这样)

class Organizer {
  public IList<Workshop> Workshops { get; } = new List<Workshop>();
  public IList<Student> Students { get; } = new List<Student>();
  public IList<Round> Rounds { get; } = new List<Round>();

  public void Add(Workshop workshop) {
    if (workshop == null || Workshops.Contains(workshop)) {
      return;
    }
    Workshops.Add( workshop );
  }

  public void Add(Student student) {
    if (student == null || Students.Contains(student)) {
      return;
    }
    Students.Add( student );
  }

  public void Add(Round round) {
    if (round == null) {
      throw new ArgumentException( "Round should be set!" );
    }
    if (round.Workshop == null) {
      throw new ArgumentException( "Round.Workshop must be set!" );
    }
    Rounds.Add( round );
    Add( round.Workshop );
  }

  public IList<Subscription> CreateSubscriptionsList(bool enableAverageAttendees = true) {
    IList<Subscription> results = new List<Subscription>();
    int totalRoomAvailable = Rounds.Sum( round => round.AvailableSpace ) / Workshops.Count;
    int totalUniqueRounds = Rounds.GroupBy( round => round.StartsAt.ToShortDateString() + round.EndsAt.ToShortDateString() ).Count();
    int averageStudentsPerRoundCount = (int)Math.Floor((totalRoomAvailable - (Students.Count * totalUniqueRounds)) / (double)Workshops.Count);

    // per round
    foreach ( var round in Rounds ) {
      // get all the students, with the nr of courses they are enlisted with already
      // and sort by ascending
      var additions = (from student in Students
                      select new { Student = student, CourseCount = results.Count( i => i.Student.Equals( student ) ) })
                      .OrderBy(i => i.CourseCount);

      // check how many should be added in this round
      int count = !enableAverageAttendees ? round.AvailableSpace : (averageStudentsPerRoundCount > 0 ? averageStudentsPerRoundCount : round.AvailableSpace + Math.Abs(averageStudentsPerRoundCount));

      // add all that don't attend a course at the same time, or did this course already
      foreach (var addition in additions ) {
        if (results.Any(subscription => subscription.Student.Equals(addition.Student) 
            && (round.Workshop.Equals(subscription.Round.Workshop) 
              || (round.StartsAt >= subscription.Round.StartsAt && round.EndsAt <= subscription.Round.EndsAt))
            )) {
          continue;
        }
        results.Add( new Subscription() { Round = round, Student = addition.Student } );
        count--;
        if (count <= 0) {
          break;
        }
      }
    }

    return results;
  }
}

如果使用以下参数运行:

  • 6门课程
  • 3轮(相隔一周)
  • 15名学生

输出类似于:

Workshop C# course
Round #1: 11/17/2015 - 11/18/2015
Student 1
Student 2
Round #7: 11/24/2015 - 11/25/2015
Student 13
Student 14
Round #13: 12/1/2015 - 12/2/2015
Student 10
Student 11
Workshop HTML 5
Round #4: 11/17/2015 - 11/18/2015
Student 7
Student 8
Round #10: 11/24/2015 - 11/25/2015
Student 4
Student 5
Round #16: 12/1/2015 - 12/2/2015
Student 1
Student 2
Workshop JS course
Round #3: 11/17/2015 - 11/18/2015
Student 5
Student 6
Round #9: 11/24/2015 - 11/25/2015
Student 2
Student 3
Round #15: 12/1/2015 - 12/2/2015
Student 14
Student 15
Workshop ReactJS course
Round #6: 11/17/2015 - 11/18/2015
Student 11
Student 12
Round #12: 11/24/2015 - 11/25/2015
Student 8
Student 9
Round #18: 12/1/2015 - 12/2/2015
Student 5
Student 6
Workshop Vb.net course
Round #2: 11/17/2015 - 11/18/2015
Student 3
Student 4
Round #8: 11/24/2015 - 11/25/2015
Student 15
Student 1
Round #14: 12/1/2015 - 12/2/2015
Student 12
Student 13
Workshop Webdesign
Round #5: 11/17/2015 - 11/18/2015
Student 9
Student 10
Round #11: 11/24/2015 - 11/25/2015
Student 6
Student 7
Round #17: 12/1/2015 - 12/2/2015
Student 3
Student 4

作为数据结构,我有以下类:

class Subscription {
  public Round Round { get; set; }
  public Student Student { get; set; }
}

abstract class IDIdentity {
  public int ID { get; set; }
}

class Workshop : IDIdentity {
  public string Name { get; set; }
}

class Round : IDIdentity {
  public Workshop Workshop { get; set; }

  public DateTime StartsAt { get; set; }
  public DateTime EndsAt { get; set; }
  public int AvailableSpace { get; set; }
}

class Student : IDIdentity {
  public string Name { get; set; }
}

答案 1 :(得分:0)

您可以使用约束满意度问题解决方案来解决此问题。例如,使用CSPNET

如果您创建了一个代表学生及其4个时段的课程Workshop和一个课程StudentSlot,您可以使用Variable<StudentSlot, Workshop>来跟踪指定学生时间的工作室分配槽。

// The variables are the StudentSlots and the domain is the workshops they want to attend
IReadOnlyCollection<Variable<StudentSlot, Workshop>> studentAssignmentVariables = 
    studentSlots.Select(ss => new Variable<StudentSlot, Workshop>(ss, 
    studentChoices[ss.Student])).ToList();

然后,您可以创建一些约束:一个用于检查每个学生只参加一次研讨会:

var workshopsCanBeAttendedAtMostOnce = studentAssignmentVariables.GroupBy(x => x.UserObject.Student)
    .Select(gp => new AllDifferentConstraint<StudentSlot, Workshop>(gp));

另一个限制是检查没有研讨会的学生超过19名(你需要一个自定义约束)。

现在你可以要求求解器解决它。

var problem = new Problem<StudentSlot, Workshop>(studentAssignmentVariables, constraints);
var solver = new RecursiveBacktrackSolver<StudentSlot, Workshop>();
var solution = solver.Solve(problem, CancellationToken.None);

CSPNET的源代码在Github上,你可以看到递归回溯如何解决给定约束的问题。

此处提供完整示例:https://gist.github.com/IanMercer/d7670577d5e88ab2bdf6