我有一个合同 list ,这些合同都有完成的时间跨度.有些合同比其他合同更重要.如果两个合约之间有重叠,我们就会将不太重要的合约砍成两半,从头开始或从尾砍掉,或者完全取消.

My problem is: I've implemented the algorithm but I still have some minor errors which cause a false output.
e.g.

Input:
Start     Ende        Preis  Priorität
1.1.2020  1.3.2020    20,40  1
1.2.2020  31.12.2020  18,00  2
1.6.2020  1.9.2020    16,90  3

Output should be:
Start     Ende        Preis
1.1.2020  31.1.2020   20,40
1.2.2020  31.5.2020   18,00
1.6.2020  1.9.2020    16,90
2.9.2020  31.12.2020  18,00

我的代码:

List<Contract> contractList = //get value based on a file and get sorted based on the priority;
List<Contract> resultContracts = new List<Contract>()
{
    contractList[0]
};
int n = contractList.Count;
for (var i = 0; i < contractList.Count; i++)
{

    var lastProcessedContract = contractList[i]; // high priority
    var contractsToRemove = new List<Contract>();
    var contractsToAdd = new List<Contract>();
    for (var j = i + 1;  j < contractList.Count; j++){

        var lessPriorityContract = contractList[j];
        if (contractList[j].priority == contractList[i].priority)
        {
            continue;
        }
        if (lessPriorityContract.start >= lastProcessedContract.start && lessPriorityContract.end <= lastProcessedContract.end)
        /*
         *  ------------------------>
         *           ------>
         * oder
         *  ------------------------>
         *  -------->
         * oder
         *  ------------------------->
         *                    ------->
         * oder
         * --------------------------->
         * --------------------------->
         */
        {
            contractsToRemove.Add(lessPriorityContract);
        }
        else if ((lessPriorityContract.start < lastProcessedContract.start 
                 && (lessPriorityContract.end > lastProcessedContract.start && lessPriorityContract.end <= lastProcessedContract.end)) || 
                 ((lastProcessedContract.start < lessPriorityContract.end && lastProcessedContract.start > lessPriorityContract.start)
                     && lastProcessedContract.end > lessPriorityContract.end))
       /* 
          *     ------------------------>
          *  ------>
          * oder
          *     ------------------------------->
          * ----------------------------------->

        // oder

          *                   -------------->
          *  ------------------------>
        */

        {
            lessPriorityContract.end = lastProcessedContract.start.AddDays(-1);
            resultContracts.Add(lessPriorityContract);
        }
        else if (((lessPriorityContract.start < lastProcessedContract.end && lessPriorityContract.start >= lastProcessedContract.start)
                 && lessPriorityContract.end > lastProcessedContract.end) || (lastProcessedContract.start < lessPriorityContract.start &&
                                                                              (lastProcessedContract.end > lessPriorityContract.start && lastProcessedContract.end < lessPriorityContract.end)))
        /*
         * -------------------->
         *            ------------------------->
         * oder
         * -------------------->
         * ---------------------------------->
         *

        //oder

          *  ------------>
          *       ------------------->
          * 
         */ 
        {
            lessPriorityContract.start = lastProcessedContract.end.AddDays(1);
            resultContracts.Add(lessPriorityContract);
        }
        else if(lessPriorityContract.start < lastProcessedContract.start
                && lessPriorityContract.end > lastProcessedContract.end)
            /*
             *       ------------>
             * --------------------------->
             * oder
             * --------------------------->
             * --------------------------->
             * 
             */
        {
            contractsToRemove.Add(lessPriorityContract);
            var contract = new Contract
            {
                start = lessPriorityContract.start,
                end = lastProcessedContract.start.AddDays(-1),
                priority = lessPriorityContract.priority,
                price = lessPriorityContract.price
            };
            contractsToAdd.Add(contract);

            //contractList.Remove(contractList[i]);
            //contractList.Add(contract);
            resultContracts.Add(contract);
            contract = new Contract
            {
                start = lastProcessedContract.end.AddDays(1),
                end = lessPriorityContract.end,
                priority = lessPriorityContract.priority,
                price = lessPriorityContract.price
            };
            resultContracts.Add(contract);
            contractsToAdd.Add(contract);

            //contractList.Add(contract);
        }
        else
        {
            resultContracts.Add(lessPriorityContract);
        }
    }
    foreach (var contractToRemove in contractsToRemove)
    {
        contractList.Remove(contractToRemove);
        resultContracts.Remove(contractToRemove);
    }

    foreach (var contractToAdd in contractsToAdd)
    {
        contractList.Add(contractToAdd);
    }
    contractList.Sort();
}

resultContracts.Sort(new ContractComparer()); // Sorting based on start time of the contract
resultContracts = resultContracts.Distinct().ToList();
foreach (var flatElement in resultContracts)
{
    Console.WriteLine(flatElement.ToString());
    Console.WriteLine();
}

resultContracts.Sort();
Console.WriteLine("-------------------------------");
foreach (var flatElement in resultContracts)
{
    Console.WriteLine(flatElement.ToString());
    Console.WriteLine();
}

Class协定具有属性(开始、结束、价格、优先级)和一个从Icomparable开始的重写方法,以帮助根据优先级进行排序.

我对获得新的高效和更快的算法也持开放态度,否则修复这个也会很有帮助.

编辑:小通知是,我现在并不关心我输出了多少属性,只要我解决了算法,我就可以处理这些:)

推荐答案

你的代码看起来很做作.我建议退一步,先概括一下.

从根本上说,您的问题是在另一个日期范围的基础上"削减"一个日期范围.所以让我们先解决这个问题:

public record DateRange(DateTime From, DateTime To) {
    public bool Overlaps(DateRange other) => From <= other.To && other.From <= To;

    public IEnumerable<DateRange> Cut(DateRange other) {
        if (!Overlaps(other)) {
            yield return this;
        } else if (From >= other.From && To > other.To) {
            yield return this with { From = other.To.AddDays(1) };
        } else if (From < other.From && To <= other.To) {
            yield return this with { To = other.From.AddDays(-1) };
        } else if (From < other.From && To > other.To) {
            yield return this with { To = other.From.AddDays(-1) };
            yield return this with { From = other.To.AddDays(1) };
        }
    }
}

现在我们有了一个日期范围类型和一个称为"Cut"的操作,它可以根据一个日期范围与另一个日期范围的重叠来缩短、分割或完全"删除"该日期范围.否则,当没有重叠时,它将保持日期范围不变.我将把测试类添加到这个答案的末尾,作为工作的证明.

因为我们不仅要处理两个,而且可能要处理更多来自合约的日期范围,所以我们可以在DateRange中添加另一个方法来帮助我们:

public IEnumerable<DateRange> CutAll(IEnumerable<DateRange> others) => others
    .Aggregate(
        Enumerable.Repeat(this, 1),
        (cuts, other) => cuts.SelectMany(s => s.Cut(other)));

如果一个日期范围与多个其他日期范围重叠,这将把它拆分成许多"子日期范围".

Your business code now boils down to:

  • 按优先级(升序)对合同进行排序.
  • 遍历合同:
    • 将当前合同的日期范围与列表中其继任者的所有日期范围一起剪切.
    • 使用日期范围削减结果从当前合同创建"子合同".

下面是(未经测试的)代码中的一个示例:

var contracts = new List<Contract> {
    new(Period: new(From: new(2020, 01, 01), To: new(2020, 03, 01)), Price: 20.40m, Priority: 1),
    new(Period: new(From: new(2020, 02, 01), To: new(2020, 12, 31)), Price: 18.00m, Priority: 2),
    new(Period: new(From: new(2020, 06, 01), To: new(2020, 09, 01)), Price: 16.90m, Priority: 3)
};

for (var i = 0; i < contracts.Count; i++) {
    var current = contracts[i];
    var successors = contracts[(i + 1)..];
    var subDateRanges = current.Period.CutAll(successors.Select(c => c.Period));

    foreach (var dr in subDateRanges) {
        Console.WriteLine($"{dr.From} {dr.To} {current.Price} {current.Priority}");
    }
}

public record Contract(DateRange Period, decimal Price, int Priority);

namespace Tests;

using FluentAssertions;
using Xunit;

public static class DateRangeTests {
    public class Overlaps {
        [Fact]
        public void ReturnsFalseWhenRangesDontOverlap1() =>
            Range("2019-01-01", "2019-01-15")
            .Overlaps(Range("2019-01-20", "2019-01-31"))
            .Should().BeFalse();

        [Fact]
        public void ReturnsFalseWhenRangesDontOverlap2() =>
            Range("2019-01-20", "2019-01-31")
            .Overlaps(Range("2019-01-01", "2019-01-15"))
            .Should().BeFalse();

        [Fact]
        public void ReturnsTrueWhenRangesOverlap1() =>
            Range("2019-01-01", "2019-01-20")
            .Overlaps(Range("2019-01-15", "2019-01-31"))
            .Should().BeTrue();

        [Fact]
        public void ReturnsTrueWhenRangesOverlap2() =>
            Range("2019-01-15", "2019-01-31")
            .Overlaps(Range("2019-01-01", "2019-01-20"))
            .Should().BeTrue();

        [Fact]
        public void ReturnsTrueWhenRangesOverlap3() =>
            Range("2019-01-01", "2019-01-31")
            .Overlaps(Range("2019-01-10", "2019-01-20"))
            .Should().BeTrue();

        [Fact]
        public void ReturnsTrueWhenRangesOverlap4() =>
            Range("2019-01-10", "2019-01-20")
            .Overlaps(Range("2019-01-01", "2019-01-31"))
            .Should().BeTrue();
    }

    public class Cut {
        [Fact]
        public void ReturnsLeftSideWhenRightSideDoesNotOverlap() =>
            Range("2019-01-01", "2019-01-10")
            .Cut(Range("2019-01-20", "2019-01-31"))
            .Should().ContainSingle().Which.Should().Be(Range("2019-01-01", "2019-01-10"));

        [Fact]
        public void AbsorbsLeftSideWhenRightSideOverlapsCompletely() =>
            Range("2019-01-10", "2019-01-20")
            .Cut(Range("2019-01-01", "2019-01-31"))
            .Should().BeEmpty();

        [Fact]
        public void MovesStartOfLeftSideWhenRightSideOverlapsBeginning() =>
            Range("2019-01-10", "2019-01-20")
            .Cut(Range("2019-01-01", "2019-01-15"))
            .Should().ContainSingle().Which.Should().Be(Range("2019-01-16", "2019-01-20"));

        [Fact]
        public void MovesEndOfLeftSideWhenRightSideOverlapsEnd() =>
            Range("2019-01-10", "2019-01-20")
            .Cut(Range("2019-01-15", "2019-01-25"))
            .Should().ContainSingle().Which.Should().Be(Range("2019-01-10", "2019-01-14"));

        [Fact]
        public void SplitsLeftSideWhenRightSideIsInside() =>
            Range("2019-01-01", "2019-01-31")
            .Cut(Range("2019-01-15", "2019-01-25"))
            .Should().SatisfyRespectively(
                dr1 => dr1.Should().Be(Range("2019-01-01", "2019-01-14")),
                dr2 => dr2.Should().Be(Range("2019-01-26", "2019-01-31")));
    }

    public class CutAll {
        [Fact]
        public void CutsLeftSideByAllFromRightSide() =>
            Range("2019-02-01", "2019-02-28")
            .CutAll([Range("2019-01-01", "2019-02-03"), Range("2019-02-10", "2019-02-15"), Range("2019-02-25", "2019-03-31")])
            .Should().SatisfyRespectively(
                dr1 => dr1.Should().Be(Range("2019-02-04", "2019-02-09")),
                dr2 => dr2.Should().Be(Range("2019-02-16", "2019-02-24")));

        [Fact]
        public void ReturnsLeftSideWhenRightSideIsEmpty() =>
            Range("2019-02-01", "2019-02-28")
            .CutAll([])
            .Should().ContainSingle().Which.Should().Be(Range("2019-02-01", "2019-02-28"));
    }

    private static DateRange Range(string from, string to) => new(DateTime.Parse(from), DateTime.Parse(to));
}

Csharp相关问答推荐

Microsoft.AspNetCore.Mvc. Controller Base.用户:属性或索引器Controller Base.用户无法分配给--它是只读的

在ASP.NET中为数据注释 Select 合适的语言

Blazor:用参数创建根路径

在命令行中使用时安装,但在单击时不会安装

如何保持主摄像头视角保持一致?

无法解析数据库上下文的服务

==和Tuple对象的相等<>

在FilePath中搜索一个词,并根据First Match从左到右提取文件路径

使用带有WithAppOnly()请求选项的pageIterator

使用C#HttpClient以多部分形式数据发送带有非ASCII文件名的文件的问题

为什么在使用动态obj+类obj时会调用串联?

当索引和外键是不同的数据类型时,如何设置导航属性?

C#Null判断处理失败

CA1508:';NULL=>;TRUE;始终为';TRUE';.移除或重构条件(S)以避免死代码

如何比较C#中的L和ł(波兰字符)返回TRUE

在使用StringBuilder时,如何根据 colored颜色 设置为richTextBox中的特定行着色?

如何在.NET Maui中将事件与MVVM一起使用?

RavenDb:为什么在字符串==空的情况下过滤会过滤得太多?

SignalR跨域

XmlSerializer在遇到XML属性(命名空间)时崩溃