对于这种事情,我通常最终会编写自己的课程。这是我会为此做的:
首先是Range
类,它有Begin
、End
和Tag
。它还有一些辅助方法来简化对重叠和相邻范围的查询,或者进行不区分大小写的标记比较,以及输出字符串值:
class Range
{
public int Begin { get; set; }
public int End { get; set; }
public string Tag { get; set; }
public bool CombineWith(Range other)
{
Range combinedRange;
if (TryCombine(this, other, out combinedRange))
{
this.Begin = combinedRange.Begin;
this.End = combinedRange.End;
return true;
}
return false;
}
public bool IsAdjacentTo(Range other)
{
return AreAdjacent(this, other);
}
public bool OverlapsWith(Range other)
{
return AreOverlapping(this, other);
}
public bool ContainsIndex(int index)
{
return this.Begin <= index && this.End >= index;
}
public bool TagEquals(string tag)
{
if (this.Tag == null) return tag == null;
return this.Tag.Equals(tag, StringComparison.OrdinalIgnoreCase);
}
public static bool TryCombine(Range first, Range second, out Range combined)
{
combined = new Range();
if (first == null || second == null) return false;
if (!TagsEqual(first, second)) return false;
if (!AreAdjacent(first, second) && !AreOverlapping(first, second)) return false;
combined.Begin = Math.Min(first.Begin, second.Begin);
combined.End = Math.Max(first.End, second.End);
combined.Tag = first.Tag;
return true;
}
public static bool AreAdjacent(Range first, Range second)
{
if (first == null || second == null) return false;
if (!Range.TagsEqual(first, second)) return false;
return (first.Begin == second.End + 1) ||
(first.End == second.Begin - 1);
}
public static bool AreOverlapping(Range first, Range second)
{
if (first == null || second == null) return false;
return (first.Begin >= second.Begin && first.Begin <= second.End) ||
(first.End >= second.Begin && first.End <= second.End);
}
public static bool TagsEqual(Range first, Range second)
{
if (first == null || second == null) return false;
return first.TagEquals(second.Tag);
}
public override string ToString()
{
return $"begin: {Begin}, end: {End}, tag: {Tag}";
}
}
接下来是您的IntRangeArray
类,它管理Range
对象列表中项目的添加和删除:
class IntRangeArray
{
private readonly List<Range> ranges = new List<Range>();
public bool Add(int index, string tag)
{
return AddRange(index, index, tag);
}
public bool AddRange(IEnumerable<int> indexes, string tag)
{
if (indexes == null || string.IsNullOrWhiteSpace(tag)) return false;
bool result = true;
foreach (var index in indexes)
{
if (!Add(index, tag)) result = false;
}
return result;
}
public bool AddRange(Tuple<int, int> range, string tag)
{
return AddRange(range.Item1, range.Item2, tag);
}
public bool AddRange(int begin, int end, string tag)
{
if (begin < 0 || end < 0 || string.IsNullOrWhiteSpace(tag)) return false;
var newRange = new Range {Begin = begin, End = end, Tag = tag};
var overlappingRanges = ranges.Where(r => r.OverlapsWith(newRange)).ToList();
var adjacentRanges = ranges.Where(r => r.IsAdjacentTo(newRange)).ToList();
if (overlappingRanges.Any())
{
if (!overlappingRanges.All(r => r.TagEquals(newRange.Tag)))
{
return false;
}
foreach (var overlappingRange in overlappingRanges)
{
newRange.CombineWith(overlappingRange);
ranges.Remove(overlappingRange);
}
}
foreach (var adjacentRange in adjacentRanges)
{
newRange.CombineWith(adjacentRange);
ranges.Remove(adjacentRange);
}
ranges.Add(newRange);
return true;
}
public string At(int index)
{
var matchingRange = ranges.SingleOrDefault(r => r.ContainsIndex(index));
return matchingRange?.ToString() ?? $"No item exists at {index}";
}
public void Remove(int index)
{
var matchingRange = ranges.SingleOrDefault(r => r.ContainsIndex(index));
if (matchingRange == null) return;
if (matchingRange.Begin == matchingRange.End)
{
ranges.Remove(matchingRange);
}
else if (index == matchingRange.Begin)
{
matchingRange.Begin += 1;
}
else if (index == matchingRange.End)
{
matchingRange.End -= 1;
}
else
{
// Split the range by creating a new one for the beginning
var newRange = new Range
{
Begin = matchingRange.Begin,
End = index - 1,
Tag = matchingRange.Tag
};
matchingRange.Begin = index + 1;
ranges.Add(newRange);
}
}
public void RemoveWithTag(string tag)
{
ranges.RemoveAll(r => r.TagEquals(tag));
}
public string TagOf(int index)
{
var matchingRange = ranges.SingleOrDefault(r => r.ContainsIndex(index));
return matchingRange == null ? $"No item exists at {index}" : matchingRange.Tag;
}
public override string ToString()
{
if (ranges == null || !ranges.Any()) return "No items exist.";
ranges.Sort((x, y) => x.Begin.CompareTo(y.Begin));
var output = new StringBuilder();
for(int i = 0; i < ranges.Count; i++)
{
output.AppendLine($"[{i}] {ranges[i]}");
}
return output.ToString();
}
}
为了测试它,我只是在上面复制并粘贴了您的代码示例:
private static void Main()
{
/* int is the integer type, while string is the "tag" object */
var animals = new IntRangeArray();
animals.Add(1, "dog");
Console.WriteLine(animals);
animals.Add(2, "dog");
Console.WriteLine(animals);
/* AddRange with C#7.0 ValueTuple */
animals.AddRange(Tuple.Create(4, 14), "dog");
Console.WriteLine(animals);
animals.Add(3, "dog");
Console.WriteLine(animals);
animals.AddRange(new int[] { 15, 17, 18, 19 }, "dog");
Console.WriteLine(animals);
animals.Add(16, "cat");
Console.WriteLine(animals);
animals.Remove(8);
Console.WriteLine(animals);
Console.WriteLine(animals.At(11));
animals.RemoveWithTag("dog");
Console.WriteLine(animals);
Console.WriteLine(animals.TagOf(16));
Console.WriteLine("\nDone!\nPress any key to exit...");
Console.ReadKey();
}
并且输出与您预期的一样(除了有一项不同,但这是您这边的一个错误):
