【问题标题】:How would you display an array of integers as a set of ranges? (algorithm)您如何将整数数组显示为一组范围? (算法)
【发布时间】:2010-09-12 04:10:32
【问题描述】:

给定一个整数数组,迭代它并找出它所涵盖的所有范围的最简单方法是什么?例如,对于一个数组,例如:

$numbers = array(1,3,4,5,6,8,11,12,14,15,16);

范围是:

 1,3-6,8,11-12,14-16

【问题讨论】:

  • 没关系,可以轻松排序

标签: algorithm arrays language-agnostic range


【解决方案1】:

如果数组按升序排序,那么问题就简单了。定义一个Range 结构或类,它有一个开始和一个结束。然后遍历数组。如果当前元素比前一个元素多一个,则更新Range.end,否则创建一个新范围,此元素为Range.begin。将范围存储到动态数组或链表中。或者直接打印出来。

如果数组可能没有排序,那么先排序。

【讨论】:

    【解决方案2】:

    这是 C# 3.0'y 的做法:

    兴趣点:

    • 自动属性(public int Property { get; set; })
    • 使用新的对象初始化器(新 Range { Begin = xxx; ... }
    • 使用 yield 进行惰性求值
    • 使用 linq 扩展方法(First() 和 Skip())

    -

    class Demo
    {
        private class Range
        {
            public int Begin { get; set; }
            public int End { get; set; }
    
            public override string ToString()
            {
                if (Begin == End)
                    return string.Format("{0}", Begin);
                else
                    return string.Format("{0}-{1}", Begin, End);
            }
        }
    
        static void Main(string[] args)
        {
            var list = new List<int> { 1, 3, 4, 5, 6, 8, 11, 12, 14, 15, 16 };
            // list.Sort();
            var ranges = GetRangesForSortedList(list);
    
            PrintRange(ranges);
    
            Console.Read();
        }
    
        private static void PrintRange(IEnumerable<Range> ranges)
        {
            if (ranges.Count() == 0)
                return;
    
            Console.Write("[{0}", ranges.First());
    
            foreach (Range range in ranges.Skip(1))
            {
                Console.Write(", {0}", range);
            }
    
            Console.WriteLine("]");
        }
    
        private static IEnumerable<Range> GetRangesForSortedList(IList<int> sortedList)
        {
            if (sortedList.Count < 1) 
                yield break;
    
            int firstItem = sortedList.First();
            Range currentRange = new Range { Begin = firstItem, End = firstItem };
    
            foreach (int item in sortedList.Skip(1))
            {
                if (item == currentRange.End + 1)
                {
                    currentRange.End = item;
                }
                else
                {
                    yield return currentRange;
                    currentRange = new Range { Begin = item, End = item };
                }
            }
    
            yield return currentRange;
        }
    }
    

    干杯,大卫

    【讨论】:

      【解决方案3】:

      这是一个python实现,应该很容易理解

      numbers = [1,3,4,5,6,8,11,12,14,15,16];
      
      def is_predecessor(i1, i2):
          if i1 == i2 - 1:
              return True;
          else:
              return False;
      
      def make_range(i1, i2):
          if i1 == i2:
              return str(i1);
          else:
              return str(i1) + "-" + str(i2);
      
      previous_element = None;
      current_start_element = None;
      
      for number in numbers:
          if not is_predecessor(previous_element, number):
              if current_start_element is not None:
                  print make_range(current_start_element, previous_element);
              current_start_element = number;
          previous_element = number;
      
      # handle last pair
      if current_start_element is not None:
          print make_range(current_start_element, previous_element);
      

      这个输出:

      1
      3-6
      8
      11-12
      14-16
      

      我知道,我知道,这不是一个算法,但我发现在没有缩进问题的情况下实际解释它比仅仅为它实现一个解决方案更难。

      【讨论】:

        【解决方案4】:

        首先:排序 第二:代币化 然后:打印第一个值并循环后续的值。如果“当前”值等于最后一个值 +1,则跳过它。否则,如果您跳过了值打印破折号和值,否则打印逗号并重复。

        应该可以。除非你想让我帮你写作业? :)

        【讨论】:

          【解决方案5】:

          如果数组已排序,如您的示例所示,我将定义包含最小值和最大值的存储桶。

          初始化:创建一个最小值和最大值等于第一个值的存储桶。

          循环:将每个值与当前存储桶的最大值进行比较。如果它等于或大于当前最大值 1,则更新最大值。如果比最大值大 1 以上,则将该存储桶保存到存储桶列表并启动一个新存储桶。

          最后,您将拥有一组桶,每个桶都有一个最小值和一个最大值。如果最小值与最大值相同,则打印一个数字(即:在您的示例中,第一个存储桶的最小值和最大值为 1)。如果它们不同,则打印为范围。

          lisp 中的示例实现:

          CL-USER> (defun print-ranges (integer-list)
                     (let ((sorted-list (sort integer-list #'<)))
                       (loop with buckets = ()
                             with current-bucket
                             for int in sorted-list
                               initially (setf current-bucket (cons (first sorted-list) (first sorted-list)))
                             do (cond ((= int (cdr current-bucket))) ; do nothing, this number is already in range
                                      ((= (1- int) (cdr current-bucket)) ; number is 1 higher--update bucket's max
                                       (setf (cdr current-bucket) int))
                                      (t
                                       (push current-bucket buckets)
                                       (setf current-bucket (cons int int)))) ; set up a new bucket
                             finally (push current-bucket buckets)
                                     (loop for firstp = t then nil
                                           for bucket in (nreverse buckets) do
                                             (cond ((= (car bucket) (cdr bucket))
                                                    (format t "~:[,~;~]~D" firstp (car bucket)))
                                                   (t
                                                    (format t "~:[,~;~]~D-~D" firstp (car bucket) (cdr bucket))))))))
          PRINT-RANGES
          CL-USER> (print-ranges (list 1 3 4 5 6 8 11 12 14 15 16))
          1,3-6,8,11-12,14-16
          NIL
          CL-USER> 
          

          基本上,您最终会得到一个事物列表,其中每个事物都有(最低桶,最高桶)。这些是你的范围。

          如果列表尚未排序,请先排序。

          【讨论】:

            【解决方案6】:

            C (gcc)

            类似于the Python's version

            void ranges(int n; int a[n], int n)
            {
              qsort(a, n, sizeof(*a), intcmp);
              for (int i = 0; i < n; ++i) {
                const int start = i;
                while(i < n-1 and a[i] >= a[i+1]-1)
                  ++i;
                printf("%d", a[start]);
                if (a[start] != a[i])
                  printf("-%d", a[i]);
                if (i < n-1)
                  printf(",");
              }
              printf("\n");
            }
            

            例子:

            /**
             * $ gcc -std=c99 -Wall ranges.c -o ranges && ./ranges
             */
            #include <iso646.h> // and
            #include <stdio.h>
            #include <stdlib.h>
            
            #define T(args...)                                              \
              {                                                             \
                int array[] = {args};                                       \
                ranges(array, sizeof(array) / sizeof(*array));              \
              }
            
            int intcmp(const void* a, const void* b)
            {
              const int *ai = a;
              const int *bi = b;
            
              if (*ai < *bi)
                return -1;
              else if (*ai > *bi)
                return 1;
              else
                return 0;
            }
            
            int main(void)
            {
              T(1,3,4,5,6,8,11,12,14,15,16);
              T();
              T(1);
              T(1, 2);
              T(3, 1);
              T(1, 3, 4);
              T(1, 2, 4);
              T(1, 1, 2, 4);
              T(1, 2, 2, 4);
              T(1, 2, 2, 3, 5, 5);
            }
            

            输出:

            1,3-6,8,11-12,14-16
            
            1
            1-2
            1,3
            1,3-4
            1-2,4
            1-2,4
            1-2,4
            1-3,5
            

            【讨论】:

              【解决方案7】:

              假设列表是有序的,您可以从末尾开始并继续减去下一个。虽然差值为 1,但您在一个范围内。如果不是,您将开始一个新的范围。

              16-15 = 1

              15-14 = 1

              14-12 = 2,范围是 16-14 - 开始一个新的范围。

              【讨论】:

              • 无需反转。您可以继续检查差异。缓存访问甚至更好。并注意如何处理最后一个范围。
              【解决方案8】:
              startRange = array[0];    
              for(i=0;i<array.length;i++)
              {
                 if (array[i + 1] - array[i] > 1)
                 {
                   endRange = array[i];
                   pushRangeOntoArray(startRange,endRange);
                   i++;
                   startRange = array[i]
                   // need to check for end of array here
                 }
              }
              

              【讨论】:

              • 小心,我相信您在最后一步出现了超出范围的情况。实际上,您没有正确处理最后一个范围。
              【解决方案9】:

              这是我的 Perl 解决方案。可能更清洁、更快,但它显示了它是如何工作的:

              # Just in case it's not sorted...
              my @list = sort { $a <=> $b } ( 1, 3, 4, 5, 6, 8, 11, 12, 14, 15, 16 );
              
              my $range = [ $list[0] ];
              
              for(@list[1 .. $#list]) {
                  if($_ == $range->[-1] + 1) {
                      push @$range, $_;
                  }
                  else {
                      print $#$range ? $range->[0] . '-' . $range->[-1] : $range->[0], "\n";
                      $range = [ $_ ];
                  }
              }
              

              【讨论】:

                【解决方案10】:

                我在 Java 1.5 中的解决方案是:

                public static List<String> getRanges(int... in) {
                    List<String> result = new ArrayList<String>();
                    int last = -1;
                    for (int i : in) {
                        if (i != (last + 1)) {
                            if (!result.isEmpty()) {
                                addRange(result, last);
                            }
                            result.add(String.valueOf(i));
                        } 
                        last = i;
                    }
                    addRange(result, last);
                    return result;
                }
                
                private static void addRange(List<String> result, int last) {
                    int lastPosition = result.size() - 1;
                    String lastResult = result.get(lastPosition);
                    if (!lastResult.equals(String.valueOf(last))) {
                        result.set(lastPosition, lastResult + "-" + last);
                    }
                }
                
                public static void main(String[] args) {
                    List<String> ranges = getRanges(1, 3, 4, 5, 6, 8, 11, 12, 14, 15, 16);
                    System.out.println(ranges);
                }
                

                哪个输出:

                [1, 3-6, 8, 11-12, 14-16]
                

                Greetz,GHad

                【讨论】:

                  【解决方案11】:

                  我相信在 1.5 版本中引入 Subversion 的 mergeinfo 属性具有与您要求的格式相同的格式,因此您可以查看 Subversion 的源代码以了解它们是如何做到的.如果它与已在此处发布的其他建议有任何不同,我会感到惊讶。

                  【讨论】:

                    【解决方案12】:

                    我假设数组 X() 是预先排序的(如果不是,则预先对数组进行排序)。

                    对于 X() 的每个元素作为 $element (以 $i 作为当前数组位置) 将 $element 添加到数组 Y() 的末尾 如果 (X($i) + 1 小于 X($i + 1)) AND ($i + 1 不大于 sizeof(X())) 则 将 Y(1)."-".Y(sizeof(Y())) 附加到 Z() 的末尾 未设置 Y() 万一 下一个 如果 Y() 中有任何内容,则附加到 Z() 的末尾

                    好吧,我就是这样做的。

                    【讨论】:

                      【解决方案13】:

                      创建一个包含范围值开始/结束的简单范围类型。添加一个只接受一个值并设置 start = end = value 的构造函数。维护一堆范围对象,按照自己的方式处理数组的排序副本,扩展顶部范围或根据需要添加新范围。更具体地说,当数组中的值为 1 + 堆栈中范围对象的结束值时,增加该范围的结束值,如果不是,则推入一个新范围(其中 start = end = value at数组中的索引)到堆栈上。

                      【讨论】:

                        【解决方案14】:
                        module Main where
                        
                        ranges :: [Int] -> [[Int]]
                        ranges [] = []
                        ranges list@(x:xs) = let adj = adjacent list in
                                     let len = length adj in
                                         if length adj == 1
                                        then [[x]] ++ ranges xs
                                        else [[x,(last adj)]] ++ ranges (drop ((length adj) - 1) xs)
                            where adjacent [] = []
                                  adjacent (x:xs) = if (xs /= []) && (x + 1) == head xs
                                     then [x] ++ adjacent (xs)
                                     else [x]
                        
                        main = do putStrLn $ show $ ranges [1,2,3,4,5,6,8,11,12,14,15,16]
                        
                        -- Output: [[1,6],[8],[11,12],[14,16]]
                        

                        这是我在 Haskell 中的最佳表现。

                        【讨论】:

                          【解决方案15】:

                          Perl 6

                          sub to_ranges( Int *@data ){
                            my @ranges;
                            
                            OUTER: for @data -> $item {
                              for @ranges -> $range {
                                # short circuit if the $item is in a range
                                next OUTER if $range[0] <= $item <= $range[1];
                                
                                given( $item ){
                                  when( $range[0]-1 ){ $range[0] = $item }
                                  when( $range[1]+1 ){ $range[1] = $item }
                                }
                              }
                              
                              push @ranges, [$item,$item];
                            }
                            
                            return @ranges;
                          }
                          

                          【讨论】:

                          • 我曾经在 D 中提出了一个解决方案,它的功能比我在这里看到的要多得多。我可以从范围对象的集合中删除一个范围,它会从正确的范围中删除元素,并在必要时创建一个新范围。
                          【解决方案16】:

                          Python (>= 2.6)

                          此版本还处理重复和未排序的序列。

                          from __future__ import print_function
                          
                          def ranges(a):
                              a.sort()
                              i = 0
                              while i < len(a):
                                  start = i
                                  while i < len(a)-1 and a[i] >= a[i+1]-1:
                                      i += 1
                                  print(a[start] if a[start] == a[i] else "%d-%d" % (a[start], a[i]),
                                        end="," if i < len(a)-1 else "\n")
                                  i += 1
                          

                          例子:

                          import random
                          r = range(10)
                          random.shuffle(r)
                          ranges(r)
                          ranges([1,3,4,5,6,8,11,12,14,15,16]);
                          ranges([])
                          ranges([1])
                          ranges([1, 2])
                          ranges([1, 3])
                          ranges([1, 3, 4])
                          ranges([1, 2, 4])
                          ranges([1, 1, 2, 4])
                          ranges([1, 2, 2, 4])
                          ranges([1, 2, 2, 3, 5, 5])
                          

                          输出:

                          0-9
                          1,3-6,8,11-12,14-16
                          1
                          1-2
                          1,3
                          1,3-4
                          1-2,4
                          1-2,4
                          1-2,4
                          1-3,5
                          

                          【讨论】:

                            猜你喜欢
                            • 2012-01-27
                            • 1970-01-01
                            • 1970-01-01
                            • 2019-10-17
                            • 2017-11-07
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            相关资源
                            最近更新 更多