简体   繁体   English

如何获得一系列时间块的所有非重叠排列?

[英]How do I get all non-overlapping permutations for a series of time blocks?

I have what seems to be a simple problem that I am having a hard time modeling in code (C#) - 我有一个似乎是一个简单的问题,我很难在代码中建模(C#) -

I am trying to find the highest potential credit hours available to a person attending a conference. 我正在努力为参加会议的人找到最高的潜在学分。 Courses have time blocks, such as Security 101 @ 9AM-10AM, Finance 202 @ 4PM-6PM, etc. 课程有时间块,如安保101 @ 9 AM-10AM,财务202 @ 4 PM-6PM等。

The main rule is, you can't attend two courses at once - so you would get credit for courses at 9-10 and 10-11, but you could not also get credit for a course that ran for 9-11. 主要规则是,你不能同时参加两门课程 - 所以你会在9-10和10-11的课程中获得学分,但你也不能获得9-11的课程学分。

What I would like to do is the following: 我想做的是以下内容:

I would like to get an array of valid (valid meaning non-overlapping) paths throughout a day. 我想在一天内获得一系列有效(有效的非重叠)路径。

So, for example, the full set of courses for a day may be the following: 因此,例如,一天的全套课程可能如下:

|---------------------------------------------------|
| COURSE            |   START       |   END         |
|-------------------|---------------|---------------|
| FINANCE 101       |   9:00 AM     |   10:00 AM    |
| FINANCE 102       |   10:00 AM    |   11:00 AM    |
| PYTHON 300        |   10:00 AM    |   11:00 AM    |
| SECURITY 101      |   11:00 AM    |   12:00 PM    |
| ECONOMICS 101     |   9:00 AM     |   12:00 PM    |
| DATABASE 200      |   11:00 AM    |   1:00 PM     |
|---------------------------------------------------|

There are a few paths someone might take throughout this day: 有一天,有人可能会采取一些途径:

  • FINANCE 101 (9-10) -> FINANCE 102 (10-11) -> SECURITY 101 (11-12) -> DONE
  • FINANCE 101 (9-10) -> PYTHON 300 (10-11) -> SECURITY 101 (11-12) -> DONE
  • FINANCE 101 (9-10) -> FINANCE 102 (10-11) -> DATABASE 200 (11-1) -> DONE
  • FINANCE 101 (9-10) -> PYTHON 300 (10-11) -> DATABASE 200 (11-1) -> DONE
  • ECONOMICS 101 (9-12)-> DONE

This is a somewhat simple scenario, in reality it would be possible to have multiple branching scenarios, such as having three 9-10 courses that would create more permutations on top of this. 这是一个有点简单的场景,实际上可以有多个分支场景,例如有三个9-10个课程可以在此基础上创建更多的排列。

The reason I would like an array of paths (instead of one single optimal path) is because there isn't necessarily a direct 1 Hour = 1 Credit Hour correlation, there would be a second level calculation based on the set of paths to sum the credit hour value of the path to determine what is 'best'. 我想要一个路径数组(而不是一个单一的最佳路径)的原因是因为不一定有直接的1小时= 1个小时信用相关性,所以会有一个基于路径集的第二级计算来求和确定什么是“最佳”的路径的信用小时值。

My question is this - is there a technique or software pattern that I can follow in order to generate these permutations so that I can measure the results to determine the path that would yield the most credits for a course-taker? 我的问题是 - 我是否可以遵循一种技术或软件模式来生成这些排列,以便我可以测量结果以确定为课程接受者提供最多学分的路径?

Edited for Solution: 编辑解决方案:

Thanks everyone for your input and help, both solutions from Bradley Uffner and Xiaoy312 nailed it! 感谢大家的投入和帮助, Bradley UffnerXiaoy312的解决方案将其钉住了!

在此输入图像描述

Answer adapted from Ordered Permutation of List<Int> : 答案改编自List <Int>的有序排列

public static class CourseExtensions
{    
    public static IEnumerable<IEnumerable<Course>> GetPermutations(this IEnumerable<Course> courses)
    {
        return GetCoursesHelper(courses, TimeSpan.Zero);
    }
    private static IEnumerable<IEnumerable<Course>> GetCoursesHelper(IEnumerable<Course> courses, TimeSpan from)
    {        
        foreach (var course in courses)
        {
            if (course.Start < from) continue;

            yield return new[] { course };

            var permutations = GetCoursesHelper(courses, course.End);
            foreach (var subPermutation in permutations)
            {
                yield return new[]{ course }.Concat(subPermutation);
            }
        }
    }
}

Full code: 完整代码:

void Main()
{
    foreach (var courses in GetCourses().GetPermutations())
    {
        Console.WriteLine(string.Join(" -> ", courses
            .Select(x => x.ToString())
            .Concat(new [] { "DONE" })));
    }
}

// Define other methods and classes here
public class Course
{
    public string Name { get; set; }
    public TimeSpan Start { get; set; }
    public TimeSpan End { get; set; }

    public override string ToString()
    {
        return string.Format("{0} ({1:hhmm}-{2:hhmm})",
           Name, Start, End);
    }
}

IEnumerable<Course> GetCourses() 
{
    var data = @"
| FINANCE 101       |   9:00 AM     |   10:00 AM    |
| FINANCE 102       |   10:00 AM    |   11:00 AM    |
| PYTHON 300        |   10:00 AM    |   11:00 AM    |
| SECURITY 101      |   11:00 AM    |   12:00 PM    |
| ECONOMICS 101     |   9:00 AM     |   12:00 PM    |
| DATABASE 200      |   11:00 AM    |   1:00 PM     |
".Trim();

    return data.Split('\n')
        .Select(r => r.Split('|').Select(c => c.Trim()).ToArray())
        .Select(x => new Course
        {
            Name = x[1],
            Start = DateTime.ParseExact(x[2], "h:mm tt", CultureInfo.InvariantCulture).TimeOfDay,
            End = DateTime.ParseExact(x[3], "h:mm tt", CultureInfo.InvariantCulture).TimeOfDay
        });
}

public static class CourseExtensions
{    
    public static IEnumerable<IEnumerable<Course>> GetPermutations(this IEnumerable<Course> courses)
    {
        return GetCoursesHelper(courses, TimeSpan.Zero);
    }
    private static IEnumerable<IEnumerable<Course>> GetCoursesHelper(IEnumerable<Course> courses, TimeSpan from)
    {        
        foreach (var course in courses)
        {
            if (course.Start < from) continue;

            yield return new[] { course };

            var permutations = GetCoursesHelper(courses, course.End);
            foreach (var subPermutation in permutations)
            {
                yield return new[]{ course }.Concat(subPermutation);
            }
        }
    }
}

Output: 输出:

FINANCE 101 (0900-1000) -> DONE
FINANCE 101 (0900-1000) -> FINANCE 102 (1000-1100) -> DONE
FINANCE 101 (0900-1000) -> FINANCE 102 (1000-1100) -> SECURITY 101 (1100-1200) -> DONE
FINANCE 101 (0900-1000) -> FINANCE 102 (1000-1100) -> DATABASE 200 (1100-1300) -> DONE
FINANCE 101 (0900-1000) -> PYTHON 300 (1000-1100) -> DONE
FINANCE 101 (0900-1000) -> PYTHON 300 (1000-1100) -> SECURITY 101 (1100-1200) -> DONE
FINANCE 101 (0900-1000) -> PYTHON 300 (1000-1100) -> DATABASE 200 (1100-1300) -> DONE
FINANCE 101 (0900-1000) -> SECURITY 101 (1100-1200) -> DONE
FINANCE 101 (0900-1000) -> DATABASE 200 (1100-1300) -> DONE
FINANCE 102 (1000-1100) -> DONE
FINANCE 102 (1000-1100) -> SECURITY 101 (1100-1200) -> DONE
FINANCE 102 (1000-1100) -> DATABASE 200 (1100-1300) -> DONE
PYTHON 300 (1000-1100) -> DONE
PYTHON 300 (1000-1100) -> SECURITY 101 (1100-1200) -> DONE
PYTHON 300 (1000-1100) -> DATABASE 200 (1100-1300) -> DONE
SECURITY 101 (1100-1200) -> DONE
ECONOMICS 101 (0900-1200) -> DONE
DATABASE 200 (1100-1300) -> DONE

This will just recursively walk through the list of courses, picking any courses that start on or after the end of the last course taken. 这将只是递归地浏览课程列表,选择在最后一门课程结束时或之后开始的任何课程。

It probably isn't as efficient as @Xiaoy312's answer, but it shows another method. 它可能没有@ Xiaoy312的答案那么高效,但它显示了另一种方法。 I've also added course credits, displaying the total credit for a particular path, as well as selecting the optimal path. 我还添加了课程学分,显示特定路径的总学分,以及选择最佳路径。

This could be cleanup up significantly by adding a proper CourseLoad class to store the class list instead of using List<> and List<List<>> . 这可以通过添加适当的CourseLoad类来存储类列表而不是使用List<>List<List<>>来显着清理。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

namespace CoursePath
{
    class Program
    {
        static void Main(string[] args)
        {
            var courses = new List<CourseInfo>()
                          {
                              new CourseInfo("Finance 101", 1, DateTime.Parse("9:00 AM"), DateTime.Parse("10:00 AM")),
                              new CourseInfo("Finance 102", 2, DateTime.Parse("10:00 AM"), DateTime.Parse("11:00 AM")),
                              new CourseInfo("Python 300", 3, DateTime.Parse("10:00 AM"), DateTime.Parse("11:00 AM")),
                              new CourseInfo("Security 101", 4, DateTime.Parse("11:00 AM"), DateTime.Parse("12:00 PM")),
                              new CourseInfo("Economics 201", 5, DateTime.Parse("9:00 AM"), DateTime.Parse("12:00 PM")),
                              new CourseInfo("Database 200", 6, DateTime.Parse("11:00 AM"), DateTime.Parse("1:00 PM"))
                          };

            var results = new List<List<CourseInfo>>();

            BuildCourseList(null, courses, results);

            results.ForEach(c => Console.WriteLine(string.Join(" -> ", c.Select(x => x.Name)) + $" -> Done ({c.Sum(x => x.Credits)} credits)"));
            Console.WriteLine();
            var optimal = results.Select(path => new {Path = path, TotalCredits = path.Sum(c => c.Credits)}).OrderByDescending(path => path.TotalCredits).First();
            Console.WriteLine("Optimal Path: " + string.Join(" -> ", optimal.Path.Select(x => x.Name)) + $" -> Done ({optimal.TotalCredits} credits)");
            Console.Read();
        }

        public static void BuildCourseList(List<CourseInfo> currentPath, List<CourseInfo> courses, List<List<CourseInfo>> results)
        {
            CourseInfo currentCourse = currentPath?.LastOrDefault();
            var candidates = (currentCourse == null ? courses : courses.Where(c => c.StarTime >= currentCourse.EndTime));
            if (currentPath != null)
            {
                results.Add(currentPath);
            }
            foreach (var course in candidates)
            {
                var nextPath = currentPath == null ? new List<CourseInfo>() : new List<CourseInfo>(currentPath);
                nextPath.Add(course);
                BuildCourseList(nextPath, courses, results);
            }
        }
    }

    public class CourseInfo
    {
        public CourseInfo(string name, int credits, DateTime starTime, DateTime endTime)
        {
            Name = name;
            Credits = credits;
            StarTime = starTime;
            EndTime = endTime;
        }

        public string Name { get; set; }
        public int Credits { get; set; }
        public DateTime StarTime { get; set; }
        public DateTime EndTime { get; set; }
    }
}

Output: 输出:

Finance 101 -> Done (1 credits)
Finance 101 -> Finance 102 -> Done (3 credits)
Finance 101 -> Finance 102 -> Security 101 -> Done (7 credits)
Finance 101 -> Finance 102 -> Database 200 -> Done (9 credits)
Finance 101 -> Python 300 -> Done (4 credits)
Finance 101 -> Python 300 -> Security 101 -> Done (8 credits)
Finance 101 -> Python 300 -> Database 200 -> Done (10 credits)
Finance 101 -> Security 101 -> Done (5 credits)
Finance 101 -> Database 200 -> Done (7 credits)
Finance 102 -> Done (2 credits)
Finance 102 -> Security 101 -> Done (6 credits)
Finance 102 -> Database 200 -> Done (8 credits)
Python 300 -> Done (3 credits)
Python 300 -> Security 101 -> Done (7 credits)
Python 300 -> Database 200 -> Done (9 credits)
Security 101 -> Done (4 credits)
Economics 201 -> Done (5 credits)
Database 200 -> Done (6 credits)

Optimal Path: Finance 101 -> Python 300 -> Database 200 -> Done (10 credits)

You can represent your data as a graph with vertices - courses (time periods) connected iff their time periods are overlapping. 您可以将数据表示为具有顶点的图形 - 如果它们的时间段重叠,则连接的课程(时间段)。 Then your problems turns into finding maximal independent sets of vertices: https://en.wikipedia.org/wiki/Independent_set_(graph_theory) 然后你的问题变成找到最大的独立顶点集: https//en.wikipedia.org/wiki/Independent_set_ (graph_theory)

If you want all day plans you get: 如果你想要全天计划,你会得到:

The maximal independent set listing problem: the input is an undirected graph, and the output is a list of all its maximal independent sets. 最大独立集列表问题:输入是无向图,输出是其所有最大独立集的列表。

If each of your courses (vertices) has a weight value then you get: 如果您的每个课程(顶点)都有一个权重值,那么您将得到:

The maximum-weight independent set problem: the input is an undirected graph with weights on its vertices and the output is an independent set with maximum total weight 最大权重独立集问题:输入是一个无向图,其顶点有权重,输出是一个独立的集合,最大总权重

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM