【问题标题】:List of integer ranges in C#C# 中的整数范围列表
【发布时间】:2017-05-09 20:27:10
【问题描述】:

我有一些具有整数序列的类,这些序列在另一个类中注册,该类检查序列中的数字是否尚未使用。

序列是最连续的,从一个数字到另一个。

现在我一直在使用一个简单的列表,这意味着如果一个序列表示从 5000 到 15000,那么列表中将有 10000 个元素。 我想用更合适的东西来代替它,可以在一个简单的元素中表示范围。

在我的特定情况下,我还希望这些范围代表一个对象(序列起源的类),这样当我查找一个数字时,我可以访问它的起源,而不是查看每个类看看它们是否包含我要查找的号码。

这是我的伪代码以及我期望的结果:

/* int is the integer type, while string is the "tag" object */
var animals = new IntRangeArray<int, string>();

animals.Add(1, "dog");
// [0] begin: 1, end: 1, object: "dog"

animals.Add(2, "dog");
// [0] begin: 1, end: 2, object: "dog"

/* AddRange with C#7.0 ValueTuple */
animals.AddRange((4,14), "dog");
// [0] begin: 1, end: 2, object: "dog"
// [1] begin: 4, end: 14, object: "dog"

animals.Add(3, "dog");
// [0] begin: 1, end: 14, object: "dog" 
/* All sequences have been merged because they are contiguous and have the same tag */

animals.AddRange( new int[]{ 15, 17, 18, 19 }, "dog");
// [0] begin: 1, end: 15, object: "dog"
// [1] begin: 17, end: 19, object: "dog"

animals.Add(16, "cat"); 
// [0] begin: 1, end: 15, object: "dog"
// [1] begin: 16, end: 16, object: "cat"
// [2] begin: 17, end: 19, object: "dog"

animals.Remove(8);
// [0] begin: 1, end: 7, object: "dog"
// [1] begin: 9, end: 15, object: "dog"
// [2] begin: 16, end: 16, object: "cat"
// [3] begin: 17, end: 18, object: "dog"

animals.At(11);
// struct { Begin = 9, End = 15, Tag = "dog" }

animals.RemoveWithTag("dog");
// [0] begin: 16, end: 16, object: "cat"

animals.TagOf(16);
// "cat"

我在 .NET Framework 中找不到任何实现此行为的类,因此我想知道如何实现此行为,或者是否已经存在任何实现。

【问题讨论】:

  • 你想要字典吗?因此,键将是一个类似 ("dog") 的字符串,而值将是该键的整数集合。字典已经在 C# 中了
  • 不,我的“狗”可能是多个整数范围的一部分(如我的示例所示),因为标签或范围都不适合作为键。我的图书馆只得到一个数字,需要知道它来自哪个范围并使用与该范围关联的标签。
  • 为什么投反对票?我认为这是一个非常有趣的问题。
  • 那么我可能会考虑一个以'dog'为键的字典,值是集合的集合。在“狗”下的每个范围中搜索包含您的号码的范围。除非一个数字可能在多个范围内。实际上,您不需要存储所有数字,只需要存储“dog”下每个范围的开始和结束的元组列表。您可以计算该数字是否在该范围内。如果一个范围的开始/结束与另一个范围的结束/开始匹配,这也将使组合范围变得容易。
  • 要真正有效地做到这一点,您可能希望使用区间树中的技术。 en.wikipedia.org/wiki/Interval_tree

标签: c# list integer partitioning sequencing


【解决方案1】:

对于这种事情,我通常最终会编写自己的课程。这是我会为此做的:

首先是Range 类,它具有BeginEndTag。它还有一些帮助方法来简化对重叠和相邻范围的查询,或者进行不区分大小写的标记比较,以及输出字符串值:

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();
}

并且输出与您预期的一样(除了有一项不同,但这是您这边的一个错误):

【讨论】:

    【解决方案2】:

    这是我的实现。我创建了一个帮助类来使用SortedList 管理整数范围,然后创建了主类来使用字典来管理标记的整数范围来合并标签。

    class IntegerRangeCollection {
        SortedList<int, int> ranges = new SortedList<int, int>();
    
        public class Range {
            public int begin, end;
        }
    
        public IntegerRangeCollection() {
        }
    
        public IntegerRangeCollection(int b, int e) {
            this.Add(b, e);
        }
    
        public void Add(int b, int e) {
            if (ranges.Any()) {
                if (ranges.ContainsKey(b)) {
                    if (e > ranges[b])
                        ranges[b] = e;
                }
                else
                    ranges.Add(b, e);
                FixUp();
            }
            else
                ranges.Add(b, e); // new ranges list
        }
    
        public void Add(int p) => this.Add(p, p);
    
        public void Remove(int p) {
            var r = ranges.Where(pr => pr.Key <= p && p <= pr.Value).First();
            if (r.Key == p) { // Remove Range begin
                ranges.Remove(r.Key);
                if (p+1 <= r.Value)
                    ranges.Add(p+1, r.Value);
            }
            else if (p == r.Value) // Remove Range end
                ranges[r.Key] = p - 1;
            else { // Split Range
                ranges[r.Key] = p-1;
                ranges.Add(p+1, r.Value);
            }
        }
    
        public Range At(int n) {
            var kvr = ranges.Where(kv => kv.Key <= n && n <= kv.Value);
            if (kvr.Any()) {
                var kvrf = kvr.First();
                return new Range { begin = kvrf.Key, end = kvrf.Value };
            }
            else
                return null;
        }
        public bool Contains(int n) => ranges.Where(kv => kv.Key <= n && n <= kv.Value).Any();
        public bool IsEmpty() => !ranges.Any();
    
        private bool DoFixUp() { // remove any overlapping ranges
            foreach (var r in ranges) {
                foreach (var pr in ranges.Where(pr => r.Key != pr.Key && r.Value == pr.Key - 1)) { // consolidate adjacent ranges
                    ranges.Remove(pr.Key);
                    ranges[r.Key] = pr.Value;
                    return true;
                }
                foreach (var pr in ranges.Where(pr => r.Key != pr.Key && pr.Key <= r.Value && r.Value <= pr.Value)) { // overlap end
                    if (pr.Key > r.Key) { // partial overlap, extend beginning
                        ranges.Remove(pr.Key);
                        ranges[r.Key] = pr.Value;
                        return true;
                    }
                    else { // complete overlap, remove
                        ranges.Remove(r.Key);
                        return true;
                    }
                }
            }
    
            return false;
        }
    
        private void FixUp() {
            while (DoFixUp())
                ;
        }
    }
    
    class ObjectRangeCollection<objType> where objType : class {
        Dictionary<objType, IntegerRangeCollection> d = new Dictionary<objType, IntegerRangeCollection>();
    
        public void Add(int begin, int end, objType obj) {
            if (d.TryGetValue(obj, out var ranges))
                ranges.Add(begin, end);
            else
                d.Add(obj, new IntegerRangeCollection(begin, end));
        }
    
        public void Add(int p, objType obj) => Add(p, p, obj);
        public void AddRange(ValueTuple<int, int> r, objType obj) => Add(r.Item1, r.Item2, obj);
        public void AddRange(int[] rs, objType obj) {
            foreach (var r in rs)
                this.Add(r, r, obj);
        }
    
        public class AtAnswer {
            public int begin, end;
            public object tag;
        }
    
        public AtAnswer At(int p) {
            var possibles = d.Where(kv => kv.Value.Contains(p));
            if (possibles.Any()) {
                var kv = possibles.First();
                var r = kv.Value.At(p);
                return new AtAnswer { tag = kv.Key, begin = r.begin, end = r.end };
            }
            else
                return null;
        }
    
        public objType TagOf(int p) {
            var possibles = d.Where(kv => kv.Value.Contains(p));
            if (possibles.Any())
                return possibles.First().Key;
            else
                return null;
        }
    
        public void Remove(int p) {
            var possibles = d.Where(kv => kv.Value.Contains(p));
            if (possibles.Any()) {
                foreach (var kv in possibles) {
                    kv.Value.Remove(p);
                    if (kv.Value.IsEmpty())
                        d.Remove(kv.Key);
                }
            }
        }
    
        public void RemoveWithTag(objType aTag) {
            d.Remove(aTag);
        }
    }
    

    【讨论】:

      【解决方案3】:

      试试这样的

      List<KeyValuePair<int, string>> animals = new List<KeyValuePair<int,string>>();
                 List<KeyValuePair<int, string>> newValues = Enumerable.Repeat(1,11).Select((x,i) => new KeyValuePair<int,string>(i + 4, "dog")).ToList();
                  animals.AddRange(newValues);
      

      【讨论】:

      • 正如我解释的那样,我正试图逃避这种设计。我的整数范围可能很大,我正在寻找将它们压缩在代表范围的小对象中,因此我可以拥有一个对象而不是 10k 个对象(假设它们都是连续的)。查找速度更快。
      • 您可以将代码添加到循环中。代码在一条指令中添加了11项,参数可以做成变量。
      【解决方案4】:
      Dictionary<string, List<Tuple<int, int>>> Test = new Dictionary<string, 
      List<Tuple<int, int>>>();
      
         int rangebegin=1;
         int rangeend=4;
      Test.Add("dog", new List<Tuple<int, int>> 
      { new Tuple<int, int>(rangebegin, rangend) });
      
         rangebegin=16;
         rangend=22;
      Test[dog].Add(new Tuple<int, int>(rangebegin, rangeend));
      
      
        // multiple ranges stored under same key
        // you can easily calculate which range your number is in and get 
         //  that range
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-11-04
        • 1970-01-01
        • 1970-01-01
        • 2010-09-20
        • 2013-05-18
        • 2017-11-07
        • 2011-06-05
        • 1970-01-01
        相关资源
        最近更新 更多