我需要将list
整数转换为范围列表。
我有一个List<int>
,其中包含8, 22, 41
。这些值是从1
到47
的完整列表中的分节符。
我正在尝试获取包含开始行和结束行的范围列表。输出应为
{(1,7),(8,21),(22,40),(41,47)}
我试图从此question 适应解决方案,但无法使其正常工作。
似乎应该很简单,但也许不是。
答案 0 :(得分:5)
即使在程序运行时,指出在查询期间应更改本地变量的答案也是危险的;不要养成这个坏习惯。
解决问题的最简单方法是编写迭代器块。假设您有明显的Pair<T>
类型;在C#7中,您可能会使用一个元组;修改它以使用元组是一个容易的练习:
static IEnumerable<Pair<int>> MakeIntervals(
/* this */ IEnumerable<int> dividers, /* You might want an extension method.*/
int start,
int end)
{
// Precondition: dividers is not null, but may be empty.
// Precondition: dividers is sorted.
// If that's not true in your world, order it here.
// Precondition: dividers contains no value equal to or less than start.
// Precondition: dividers contains no value equal to or greater than end.
// If it is possible for these preconditions to be violated then
// the problem is underspecified; say what you want to happen in those cases.
int currentStart = start;
for (int divider in dividers)
{
yield return new Pair<int>(currentStart, divider - 1);
currentStart = divider;
}
yield return new Pair<int>(currentStart, end);
}
这是解决此问题的正确方法。如果您想变得有点傻,可以使用Zip
。从两种有用的扩展方法开始:
static IEnumerable<T> Prepend<T>(this IEnumerable<T> items, T first)
{
yield return first;
foreach(T item in items) yield return item;
}
static IEnumerable<T> Append<T>(this IEnumerable<T> items, T last)
{
foreach(T item in items) yield return item;
yield return last;
}
现在我们有:
static IEnumerable<Pair<int>> MakeIntervals(
IEnumerable<int> dividers,
int start,
int end)
{
var starts = dividers.Prepend(start);
// This is the sequence 1, 8, 22, 41
var ends = dividers.Select(d => d - 1).Append(end);
// This is the sequence 7, 21, 40, 47
var results = starts.Zip(ends, (s, e) => new Pair<int>(s, e));
// Zip them up: (1, 7), (8, 21), (22, 40), (41, 47)
return results;
}
但是,与直接直接编写迭代器块相比,这似乎不必要。此外,这还会对集合进行两次迭代,这被很多人认为是不好的风格。
解决问题的一种可爱方法是概括第一个解决方案:
static IEnumerable<R> SelectPairs<T, R>(
this IEnumerable<T> items,
IEnumerable<T, T, R> selector
)
{
bool first = true;
T previous = default(T);
foreach(T item in items) {
if (first) {
previous = item;
first = false;
}
else
{
yield return selector(previous, item);
previous = item;
}
}
}
现在您的方法是:
static IEnumerable<Pair<int>> MakeIntervals(
IEnumerable<int> dividers,
int start,
int end)
{
return dividers
.Prepend(start)
.Append(end + 1)
.SelectPairs((s, e) => new Pair<int>(s, e - 1);
}
我很喜欢最后一个。也就是说,给我们8, 22, 41
,构造1, 8, 22, 41, 48
,然后从中挑选对并构造(1, 7), (8, 21),
,依此类推。
答案 1 :(得分:1)
它不是很干净,但是您可以构建一个新的数组并循环填充空白。假设您输入的数字已排序,并且我使用了Tuple<int, int>
返回值,因为我找不到任何有意义的简单范围类型。使用此代码,您不必担心将状态存储在循环变量之外的变量中,也不需要排除任何结果。
public Tuple<int, int>[] GetRanges(int start, int end, params int[] input)
{
// Create new array that includes a slot for the start and end number
var combined = new int[input.Length + 2];
// Add input at the first index to allow start number
input.CopyTo(combined, 1);
combined[0] = start;
// Increment end to account for subtraction later
combined[combined.Length - 1] = end + 1;
// Create new array of length - 1 (think fence-post, |-|-|-|-|)
Tuple<int, int>[] ranges = new Tuple<int, int>[combined.Length - 1];
for (var i = 0; i < combined.Length - 1; i += 1) {
// Create a range of the number and the next number minus one
ranges[i] = new Tuple<int, int>(combined[i], combined[i+1] - 1);
}
return ranges;
}
用法
GetRanges(1, 47, 8, 22, 41);
或
GetRanges(1, 47, new [] { 8, 22, 41 });
如果您想要替代的pure-linq解决方案,则可以使用它,
public Tuple<int, int>[] GetRanges(int start, int end, params int[] input)
{
return input
.Concat(new [] { start, end + 1 }) // Add first and last numbers, adding one to end to include it in the range
.SelectMany(i => new [] { i, i - 1 }) // Generate "end" numbers for each start number
.OrderBy(i => i)
.Except(new [] {start - 1, end + 1}) // Exclude pre-first and post-last numbers
.Select((Value, Index) => new { Value, Index }) // Gather information to bucket values
.GroupBy(p => p.Index / 2) // Create value buckets
.Select(g => new Tuple<int, int>(g.First().Value, g.Last().Value)) // Convert each bucket into a Tuple
.ToArray();
}
答案 2 :(得分:1)
使用C#
ValueTuple和C# 8.0
Ranges and indices以及新的switch expressions,可以通过创建方便的{{3 }}:
/// <summary>
/// Integer List array extensions.
/// </summary>
public static class RangesExtension
{
/// <summary>
/// Creates list of ranges from single dimension ranges list.
/// </summary>
/// <param name="ranges">Single dimension ranges list.</param>
/// <param name="from">Range first item.</param>
/// <param name="to">Range last item.</param>
/// <returns>List of ranges.</returns>
public static List<(int, int)> ToListOfRanges(
this List<int> ranges,
int from,
int to)
{
var list = ranges.ToArray();
return list.OrderBy(item => item)
.Select((item, index) => GetNext(ranges, from, index))
.Append((list[^1], to))
.ToList();
}
/// <summary>
/// Returns next couple of ranges from initial array.
/// </summary>
/// <param name="ranges">Single dimension ranges list.</param>
/// <param name="from">Range first item.</param>
/// <param name="index">Range current item index.</param>
/// <returns>Value Tuple of ranges.</returns>
private static (int, int) GetNext(
List<int> ranges,
int from,
int index)
{
return index switch
{
0 => (from, ranges[index] - 1),
_ => (ranges[index - 1], ranges[index] - 1),
};
}
}
答案 3 :(得分:0)
尝试使用 Linq ;假设范围为Tuple<int, int>
类型:
List<int> list = new List<int>() { 8, 22, 41};
int from = 1;
int upTo = 47;
var result = list
.OrderBy(item => item) // to be on the safe side in case of {22, 8, 41}
.Concat(new int[] { upTo + 1 }) // add upTo breaking point
.Select(item => new Tuple<int, int>(from, (from = item) - 1));
// .ToArray(); // in case you want to get materialized result (array)
Console.Write(String.Join(Environment.NewLine, result));
结果:
(1, 7)
(8, 21)
(22, 40)
(41, 47)
答案 4 :(得分:-1)
您没有指定目标语言。因此,在Scala中,一种方法是:
val breaks = List(8, 22, 41)
val range = (1, 47)
val ranges = (breaks :+ (range._2 + 1)).foldLeft((range._1, List.empty[(Int, Int)])){
case ((start, rangesAcc), break) => (break, rangesAcc :+ (start, break - 1))
}
println(ranges._2)
哪个打印:List((1,7), (8,21), (22,40), (41,47))
或者,我们可以使用递归:
def ranges(rangeStart: Int, rangeEnd: Int, breaks: List[Int]) = {
@tailrec
def ranges(start: Int, breaks: List[Int], rangesAcc: List[(Int, Int)]): List[(Int, Int)] = breaks match {
case break :: moreBreaks => ranges(break, moreBreaks, rangesAcc :+ (start, break - 1))
case nil => rangesAcc :+ (start, rangeEnd)
}
ranges(rangeStart, breaks, List.empty[(Int, Int)])
}