C#如何将时间列表分割成时间段。

5
我正在使用linq从整数列表中提取范围:
例如,我想要拆分以下列表:
List<int> numberList = new List<int>() { 30, 60, 90, 120, 150, 180, 270, 300, 330 };  

将其转化为整数范围列表,看起来应该像这样:
{ 30, 180 }
{ 270, 330 }

“ie:下一个seq大于30。”
“另一个例子:”
List<int> numberList = new List<int>() { 30, 60, 120, 150, 270, 300, 330 };  

将其转换为一个整数范围列表,看起来像:
{ 30, 60 }
{ 120, 150 }
{ 270, 330 }

我已经尝试使用for循环找到最佳方法,但我不知道如何开始尝试使用linq查询来完成这个任务。

2
你能更清楚地说明一下你是如何选择要提取的序列吗? - Wilson
3
他的意思是,每当一个元素比上一个元素大30以上时,就会开始一个新的范围。 - hatchet - done with SOverflow
3
你在意你是使用for循环还是linq吗?如果给定范围{30, 100, 200},你会怎么写代码来表示它呢?会是{30,30},{100,100}等吗? - hatchet - done with SOverflow
抱歉,如果我没有表达清楚,就像@hatchet所说的那样——无论何时一个元素大于30,那就标志着一个范围的结束和下一个范围的开始。 - Wattcey
7个回答

3
您可以编写一个方法来处理拆分:
您可以编写一个方法来处理拆分:
IEnumerable<IList<int>> SplitValues(IList<int> input, int difference = 30)
{
    List<int> results = new List<int>();
    int last = input.First();
    foreach(var value in input)
    {
        if (value - last > difference)
        {
            yield return new[] {results.First(), results.Last()};
            results = new List<int>();
        }

        results.Add(value);
        last = value;
    }

    yield return new[] {results.First(), results.Last()};
}

这符合您的要求,返回以下内容:
{ 30, 60 }
{ 120, 150 }
{ 270, 330 }

请注意,集合中没有范围的单个值将被重复。例如,{ 30, 120, 150 } 将返回:
{ 30, 30 }
{ 120, 150 }

哇 - 这对我很有效 - 我特别喜欢差异参数的添加! - Wattcey
抱歉 - 第一次使用stackoverflow - 是否有礼仪要等待24小时才能接受答案? - Wattcey
@Wattcey 不是的 - 但我(个人而言)喜欢给人们20-30分钟的时间来至少先得到一个答案。 - Reed Copsey
谢谢,我以后会给更多时间的。 - Wattcey

1
你可以用一个linq语句完成这个操作:
var numberList = new List<int>() { 30, 60, 120, 150, 270, 300, 330 };
var section = 0;
var result = numberList
            .Select( (x, i) => new {value = x, section = (i == 0 ? 0 : ((x - numberList[i - 1]) > 30 ? ++section : section))})
            .GroupBy(x => x.section)
            .Select(x => x.Select(v => v.value).ToList()).ToList();

1

好的。有许多方法可以实现,每种方法都有其优缺点。 所以这里提供另一种解决方案,希望对某些人有帮助。

public static IEnumerable<TSource[]> ToRanges<TSource>(
    this IEnumerable<TSource> source, Func<TSource, TSource, TSource, bool> isNear)
{            
    List<TSource[]> result = source./*OrderBy(value => value).*/Aggregate(
        new List<TSource[]> { new[] { source.First(), source.First() } },
        (ranges, currentValue) => {
            TSource[] currentRange = ranges.Last();
            TSource previousValue = currentRange[1];

            if (isNear(currentRange[0], previousValue, currentValue))
                currentRange[1] = currentValue;
            else
                ranges.Add(new[] { currentValue, currentValue});

            return ranges;
        }
    );

    return result;
}

使用示例:

List<int> numbers = new List<int>() { 30, 60, 90, 120, 150, 180, 270, 300, 330 };

// split by max difference
numberList.ToRanges(
    (first, previous, current) => current - previous <= 30).ToArray();
// { 30, 180 }
// { 270, 330 }

// split by max range
numberList.ToRanges(
    (first, previous, current) => current - first <= 90).ToArray();
// { 30, 120 }
// { 150, 180 }
// { 270, 330 }

此外,您不仅可以按整数拆分,还可以按照首字母拆分单词,例如DateTime/TimeSpan等任何内容。

Eloquent - 我可能会像你建议的那样,考虑在DateTime / Timespans中使用它! - Wattcey

0

你可以使用 TakeWhile 并将结果添加到另一个列表中。

void SplitByRange()
{
    List<int> numberList = new List<int>() { 30, 60, 120, 150, 270, 300, 330 }; 
    IEnumerable<int> aux = new List<int>();

    int n = numberList.First();
    int skip = 0;
    List<List<int>> output = new List<List<int>>();

    while ((aux = numberList.Skip(skip).TakeWhile(o => { bool r = (o - n) <= 30; n = o; return r; })).Count() > 0)
    {
        output.Add(aux.ToList());
        skip += aux.Count();
    }
}

最终,numberList将为空,output将是一个列表的列表。
output[0]  // { 30, 60 }
...

当前代码需要列表中至少有1个元素,如果您有

{ 30, 100 }

它将返回两个列表,每个列表中只有1个元素

{ 30 }
{ 100 }

这个很好用,但它会改变输入(所以你可能需要复制),而且不是非常高效(因为有多个RemoveRange调用)... - Reed Copsey
@ReedCopsey 我改成了使用 Skip 而不是删除 - BrunoLM

0

你必须使用LINQ吗?如果不是,那么怎么样:

List<int> numberList = new List<int>() { 30, 60, 120, 150, 270, 300, 330 };  

Dictionary<int, int> result = new Dictionary<int, int>();
int lastStart = numberList.First();
for(int i=1; i < numberList.Count; i++)
{
    if(numberList[i] >= lastStart + 30)
    {
        result.Add(lastStart, numberList[i]);
        if (i == numberList.Count - 1) break;
        lastStart = numberList[i + 1];
        i++;
    }
}

foreach (var item in result)
{
    Console.WriteLine("{{{0}, {1}}}", item.Key, item.Value);
}

0

试试这个:

private static List<int[]> GetGroups(List<int> numberList)
{
    List<List<int>> groups = new List<List<int>>();
    numberList.Zip(numberList.Skip(1), (a, b) =>
    {
        if ((b - a) == 30)
        {
            if (groups.Count == 0)
                groups.Add(new List<int>());
            groups[groups.Count - 1].Add(a);
        }
        else if (a == b)
        {
            groups[groups.Count - 1].Add(a);
        }
        else
        {
            groups[groups.Count - 1].Add(a);
            groups.Add(new List<int>());
        }
        return a;
    }).ToList();
    groups[groups.Count - 1].Add(numberList.Last());
    return groups.Select(g => new[] { g.First(), g.Last() }).ToList();
}

示例用法:

//List<int> numberList = new List<int>() { 30, 60, 90, 120, 150, 180, 270, 300, 330 };
List<int> numberList = new List<int>() { 30, 60, 120, 150, 270, 300, 330 };
var result = GetGroups(numberList);

-1
    int bin = 15;
    
    var start = DateTime.Parse("08:00:00");
    var end = DateTime.Parse("09:00:00");
    
var timeFrame = Enumerable
    .Range(0, Convert.ToInt32((end.TimeOfDay.TotalMinutes - start.TimeOfDay.TotalMinutes) / bin))
    .Select((s,i) => start.AddMinutes(i * bin)).ToList();
    
    foreach (var t in timeFrame)
    {
        Console.WriteLine($"T: {t}");
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接