如何将数组中的数字序列转换为数字范围

时间:2010-02-16 05:41:21

标签: javascript range sequence

在javascript中如何将数组中的数字序列转换为数字范围?

例如。 [2,3,4,5,10,18,19,20][2-5,10,18-20]

23 个答案:

答案 0 :(得分:27)

这是我创建的some time ago算法,最初是为C#编写的,现在我把它移植到JavaScript:

function getRanges(array) {
  var ranges = [], rstart, rend;
  for (var i = 0; i < array.length; i++) {
    rstart = array[i];
    rend = rstart;
    while (array[i + 1] - array[i] == 1) {
      rend = array[i + 1]; // increment the index if the numbers sequential
      i++;
    }
    ranges.push(rstart == rend ? rstart+'' : rstart + '-' + rend);
  }
  return ranges;
}

getRanges([2,3,4,5,10,18,19,20]);
// returns ["2-5", "10", "18-20"]
getRanges([1,2,3,5,7,9,10,11,12,14 ]);
// returns ["1-3", "5", "7", "9-12", "14"]
getRanges([1,2,3,4,5,6,7,8,9,10])
// returns ["1-10"]

答案 1 :(得分:5)

从CMS的解决方案中获得乐趣:

  function getRanges (array) {
    for (var ranges = [], rend, i = 0; i < array.length;) {
      ranges.push ((rend = array[i]) + ((function (rstart) {
        while (++rend === array[++i]);
        return --rend === rstart;
      })(rend) ? '' : '-' + rend)); 
    }
    return ranges;
  }

答案 2 :(得分:5)

我只是在寻找这个确切的事情。我需要一个PHP版本,因此移植了CMS的解决方案。对于那些因这个问题而停下来寻找同样事情的人来说,就是这样:

function getRanges( $nums )
{
    $ranges = array();

    for ( $i = 0, $len = count($nums); $i < $len; $i++ )
    {
        $rStart = $nums[$i];
        $rEnd = $rStart;
        while ( isset($nums[$i+1]) && $nums[$i+1]-$nums[$i] == 1 )
            $rEnd = $nums[++$i];

        $ranges[] = $rStart == $rEnd ? $rStart : $rStart.'-'.$rEnd;
    }

    return $ranges;
}

答案 3 :(得分:5)

我发现这个答案很有用,但需要一个Python版本:

def GroupRanges(items):
  """Yields 2-tuples of (start, end) ranges from a sequence of numbers.

  Args:
    items: an iterable of numbers, sorted ascendingly and without duplicates.

  Yields:
    2-tuples of (start, end) ranges.  start and end will be the same
    for ranges of 1 number
  """
  myiter = iter(items)
  start = myiter.next()
  end = start
  for num in myiter:
    if num == end + 1:
      end = num
    else:
      yield (start, end)
      start = num
      end = num
  yield (start, end)


numbers = [1, 2, 3, 5, 6, 7, 8, 9, 10, 20]
assert [(1, 3), (5, 10), (20, 20)] == list(GroupRanges(numbers))
assert [(1, 1)] == list(GroupRanges([1]))
assert [(1, 10)] == list(GroupRanges([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]))

答案 4 :(得分:3)

非常好的问题:这是我的尝试:

function ranges(numbers){
    var sorted = numbers.sort(function(a,b){return a-b;});
    var first = sorted.shift();
    return sorted.reduce(function(ranges, num){
        if(num - ranges[0][1] <= 1){
            ranges[0][1] = num;        
        } else {
            ranges.unshift([num,num]);
        }
        return ranges;
    },[[first,first]]).map(function(ranges){
        return ranges[0] === ranges[1] ? 
            ranges[0].toString() : ranges.join('-');
    }).reverse();
}

Demo on JSFiddler

答案 5 :(得分:1)

如果您只是想要一个表示范围的字符串,那么您将找到序列的中点,这将成为您的中间值(在您的示例中为10)。然后,您将获取序列中的第一个项目,以及紧接在您的中点之前的项目,并构建您的第一个序列表示。您将按照相同的步骤获取最后一个项目,以及紧跟您的中点的项目,并构建您的最后序列表示。

// Provide initial sequence
var sequence = [1,2,3,4,5,6,7,8,9,10];
// Find midpoint
var midpoint = Math.ceil(sequence.length/2);
// Build first sequence from midpoint
var firstSequence = sequence[0] + "-" + sequence[midpoint-2];
// Build second sequence from midpoint
var lastSequence  = sequence[midpoint] + "-" + sequence[sequence.length-1];
// Place all new in array
var newArray = [firstSequence,midpoint,lastSequence];

alert(newArray.join(",")); // 1-4,5,6-10

在线演示:http://jsbin.com/uvahi/edit

答案 6 :(得分:1)

这是Perl的一个版本:

use strict;
use warnings;

my @numbers = (0,1,3,3,3,4,4,7,8,9,12, 14, 15, 19, 35, 35, 37, 38, 38, 39);
@numbers =  sort {$a <=> $b} @numbers ; # Make sure array is sorted.

# Add "infinity" to the end of the array.
$numbers[1+$#numbers] = undef ;

my @ranges = () ; # An array where the range strings are stored.

my $start_number = undef ;
my $last_number  = undef ;
foreach my $current_number (@numbers)
{
  if (!defined($start_number))
  {
    $start_number = $current_number ;
    $last_number  = $current_number ;
  }
  else
  {
    if (defined($current_number) && (($last_number + 1) >= $current_number))
    {
      $last_number = $current_number ;
      next ;
    }
    else
    {
      if ($start_number == $last_number)
      {
        push(@ranges, $start_number) ;
      } 
      else
      {
        push(@ranges, "$start_number-$last_number") ;
      }
      $start_number = $current_number ;
      $last_number  = $current_number ;
    }
  }
}

# Print the results
print join(", ", @ranges) . "\n" ; 
# Returns "0-1, 3-4, 7-9, 12, 14-15, 19, 35, 37-39"

答案 7 :(得分:1)

这是我对此的看法......

function getRanges(input) {

  //setup the return value
  var ret = [], ary, first, last;

  //copy and sort
  var ary = input.concat([]);
  ary.sort(function(a,b){
    return Number(a) - Number(b);
  });

  //iterate through the array
  for (var i=0; i<ary.length; i++) {
    //set the first and last value, to the current iteration
    first = last = ary[i];

    //while within the range, increment
    while (ary[i+1] == last+1) {
      last++;
      i++;
    }

    //push the current set into the return value
    ret.push(first == last ? first : first + "-" + last);
  }

  //return the response array.
  return ret;
}

答案 8 :(得分:1)

在C#中

    public string compressNumberRange(string inputSeq)
    {
        //Convert String array to long List and removing the duplicates
        List<long> longList = inputSeq.Split(',').ToList().ConvertAll<long>(s => Convert.ToInt64(s)).Distinct().ToList();

        //Sort the array
        longList.Sort();

        StringBuilder builder = new StringBuilder();


        for (int itr = 0; itr < longList.Count(); itr++)
        {
            long first = longList[itr];
            long end = first;

            while (longList[itr + 1] - longList[itr] == 1) //Seq check 
            {
                end = longList[itr + 1];
                itr++;
                if (itr == longList.Count() - 1)
                    break;
            }
            if (first == end) //not seq
                builder.Append(first.ToString() + ",");
            else //seq
                builder.Append(first.ToString() + "-" + end.ToString() + ",");
        }

        return builder.ToString();
    }

答案 9 :(得分:1)

以下是CMS的BASH代码的端口:

#!/usr/bin/env bash
# vim: set ts=3 sts=48 sw=3 cc=76 et fdm=marker: # **** IGNORE ******
get_range() { RANGE= # <-- OUTPUT                  **** THIS   ******
   local rstart rend i arr=( "$@" )  # ported from **** JUNK   ******
   for (( i=0 ; i < $# ; i++ )); do  # http://stackoverflow.com
      (( rstart = arr[i] ))          # /a/2270987/912236
      rend=$rstart; while (( arr[i+1] - arr[i] == 1 )); do
      (( rend = arr[++i] )); done; (( rstart == rend )) &&
   RANGE+=" $rstart" || RANGE+=" $rstart-$rend"; done; } # }}}

答案 10 :(得分:0)

大致流程如下:

  • 创建一个名为 ranges 的空数组
  • 对于 sorted 输入数组中的每个 value
    • 如果 ranges 为空,则插入项目 {min: value, max: value}
    • 否则如果 max 中最后一项的 ranges 和当前 value 是连续的,则设置 max 中最后一项的 ranges = {{1} }
    • 否则插入项目value
  • 根据需要格式化 {min: value, max: value} 数组,例如通过组合 rangesmin(如果相同)

以下代码使用max,结合步骤2.1和2.3简化逻辑。

Array.reduce

答案 11 :(得分:0)

这是Coffeescript中的一个版本

getRanges = (array) -> 
    ranges = []
    rstart
    rend
    i = 0
    while  i < array.length
      rstart = array[i]
      rend = rstart
      while array[i + 1] - array[i] is 1
        rend = array[i + 1] # increment the index if the numbers sequential
        i = i  + 1
      if rstart == rend 
        ranges.push  rstart + ''
      else
        ranges.push rstart + '-' + rend
      i = i + 1
    return ranges

答案 12 :(得分:0)

我今天需要TypeScript代码来解决这个问题-在OP之后很多年-并决定尝试以一种比此处其他答案更实用的方式编写的版本。当然,只有参数和返回类型注释可将此代码与标准ES6 JavaScript区别开。

  function toRanges(values: number[],
                    separator = '\u2013'): string[] {
    return values
      .slice()
      .sort((p, q) => p - q)
      .reduce((acc, cur, idx, src) => {
          if ((idx > 0) && ((cur - src[idx - 1]) === 1))
            acc[acc.length - 1][1] = cur;
          else acc.push([cur]);
          return acc;
        }, [])
      .map(range => range.join(separator));
  }

请注意,slice是必要的,因为sort会在适当的位置排序,我们无法更改原始数组。

答案 13 :(得分:0)

我需要一个同时支持向下范围的PHP版本(例如[10,9,8]转换为[10-8])。因此,我修改了移植了CMS解决方案的DisgruntledGoat的版本。它还可以正确处理输入中的字符串。

function getRanges($nums)
{
    $ranges = array();

    for ($i = 0; $i < count($nums); $i++) {
        if (!is_numeric($nums[$i]) || !isset($nums[$i+1]) || !is_numeric($nums[$i+1])) {
            $ranges[] = $nums[$i];
            continue;
        }
        $rStart = $nums[$i];
        $rEnd = $rStart;
        $rDiff = $nums[$i+1] > $nums[$i] ? 1 : -1;
        while (isset($nums[$i+1]) && is_numeric($nums[$i+1]) && $nums[$i+1]-$nums[$i] == $rDiff)
            $rEnd = $nums[++$i];
        $ranges[] = $rStart == $rEnd ? $rStart : $rStart.'-'.$rEnd;
    }

    return $ranges;
}

示例:

getRanges([2,3,4,5,10,18,19,20,"downwards",10,9,8,7,6,5])
// Returns [2-5,10,18-20,"downwards",10-5]

答案 14 :(得分:0)

使用ES6,解决方案是:

function display ( vector ) { // assume vector sorted in increasing order
    // display e.g.vector [ 2,4,5,6,9,11,12,13,15 ] as "2;4-6;9;11-13;15"
    const l = vector.length - 1; // last valid index of vector array
    // map [ 2,4,5,6,9,11,12,13,15 ] into array of strings (quote ommitted)
    // --> [ "2;", "4-", "-", "6;", "9;", "11-", "-", "13;", "15;" ]
    vector = vector.map ( ( n, i, v ) => // n is current number at index i of vector v
        i < l && v [ i + 1 ] - n === 1 ? // next number is adjacent ? 
            `${ i > 0 && n - v [ i - 1 ] === 1 ? "" : n }-` :
            `${ n };`
        );
    return vector.join ( "" ).  // concatenate all strings in vector array
        replace ( /-+/g, "-" ). // replace multiple dashes by single dash
        slice ( 0, -1 );        // remove trailing ;
    }

如果您想为可读性添加额外的空格,只需添加额外的string.prototype.replace()

如果未对输入向量进行排序,则可以在display()函数的左大括号后面添加以下行:

vector.sort ( ( a, b ) => a - b ); // sort vector in place, in increasing order

请注意,这可以改进,以避免测试两次整数相邻性(邻近?我不是英语母语者; - )。

当然,如果您不想将单个字符串作为输出,请将其拆分为“;”。

答案 15 :(得分:0)

Tiny ES6模块适合你们。它接受一个函数来确定何时必须中断序列(breakDetectorFunc param - 默认是整数序列输入的简单事物)。 注意:由于输入是抽象的 - 在处理之前没有自动排序,因此如果您的序列没有排序 - 在调用此模块之前执行此操作

function defaultIntDetector(a, b){
    return Math.abs(b - a) > 1;
}

/**
 * @param {Array} valuesArray
 * @param {Boolean} [allArraysResult=false] if true - [1,2,3,7] will return [[1,3], [7,7]]. Otherwise [[1.3], 7]
 * @param {SequenceToIntervalsBreakDetector} [breakDetectorFunc] must return true if value1 and value2 can't be in one sequence (if we need a gap here)
 * @return {Array}
 */
const sequenceToIntervals = function (valuesArray, allArraysResult, breakDetectorFunc) {
    if (!breakDetectorFunc){
        breakDetectorFunc = defaultIntDetector;
    }
    if (typeof(allArraysResult) === 'undefined'){
        allArraysResult = false;
    }

    const intervals = [];
    let from = 0, to;
    if (valuesArray instanceof Array) {
        const cnt = valuesArray.length;
        for (let i = 0; i < cnt; i++) {
            to = i;
            if (i < cnt - 1) { // i is not last (to compare to next)
                if (breakDetectorFunc(valuesArray[i], valuesArray[i + 1])) {
                    // break
                    appendLastResult();
                }
            }
        }
        appendLastResult();
    } else {
        throw new Error("input is not an Array");
    }

    function appendLastResult(){
        if (isFinite(from) && isFinite(to)) {
            const vFrom = valuesArray[from];
            const vTo = valuesArray[to];

            if (from === to) {
                intervals.push(
                    allArraysResult
                        ? [vFrom, vTo] // same values array item
                        : vFrom // just a value, no array
                );
            } else if (Math.abs(from - to) === 1) { // sibling items
                if (allArraysResult) {
                    intervals.push([vFrom, vFrom]);
                    intervals.push([vTo, vTo]);
                } else {
                    intervals.push(vFrom, vTo);
                }
            } else {
                intervals.push([vFrom, vTo]); // true interval
            }
            from = to + 1;
        }
    }

    return intervals;
};

module.exports = sequenceToIntervals;

/** @callback SequenceToIntervalsBreakDetector
 @param value1
 @param value2
 @return bool
 */

第一个参数是输入序列排序数组,第二个是控制输出模式的布尔标志:如果为true - 单项(在区间外)将作为数组返回:[1,7],[9,9] ,[10,10],[12,20],否则返回单个项目,因为它们出现在输入数组中

您的样本输入

[2,3,4,5,10,18,19,20]

它将返回:

sequenceToIntervals([2,3,4,5,10,18,19,20], true) // [[2,5], [10,10], [18,20]]
sequenceToIntervals([2,3,4,5,10,18,19,20], false) // [[2,5], 10, [18,20]]
sequenceToIntervals([2,3,4,5,10,18,19,20]) // [[2,5], 10, [18,20]]

答案 16 :(得分:0)

这是我在Swift中整理的内容。它首先消除了重复数据并对数组进行了排序,并且不介意给出一个空数组或一个数组。

func intArrayToString(array: [Int]) -> String {
    var intArray = Array(Set(array))
    intArray.sortInPlace()
    if intArray.count == 0 {
        return ""
    }
    var intString = "\(intArray[0])"
    if intArray.count > 1 {
        for j in 1..<intArray.count-1 {
            if intArray[j] == intArray[j-1]+1 {
                if intArray[j] != intArray[j+1]-1 {
                    intString += "-\(intArray[j])"
                }
            } else {
                intString += ",\(intArray[j])"
            }
        }
        if intArray.last! == intArray[intArray.count-2]+1 {
            intString += "-\(intArray.last!)"
        } else {
            intString += ",\(intArray.last!)"
        }
    }
    return intString
}

答案 17 :(得分:0)

CMS's javascript solution对Cold Fusion的改编

它首先对列表进行排序,以便1,3,2,4,5,8,9,10(或类似)正确转换为1-5,8-10

<cfscript>
    function getRanges(nArr) {
        arguments.nArr = listToArray(listSort(arguments.nArr,"numeric"));
        var ranges = [];
        var rstart = "";
        var rend = "";
        for (local.i = 1; i <= ArrayLen(arguments.nArr); i++) {
            rstart = arguments.nArr[i];
            rend = rstart;
            while (i < ArrayLen(arguments.nArr) and (val(arguments.nArr[i + 1]) - val(arguments.nArr[i])) == 1) {
                rend = val(arguments.nArr[i + 1]); // increment the index if the numbers sequential
                i++;
            }
            ArrayAppend(ranges,rstart == rend ? rstart : rstart & '-' & rend);
        }
        return arraytolist(ranges);
    }
</cfscript>

答案 18 :(得分:0)

我编写了自己的方法,它依赖于Lo-Dash,但不只是返回一个范围数组,而是只返回一个范围组数组。

[1,2,3,4,6,8,10]成为:

[[1,2,3,4],[6,8,10]]

http://jsfiddle.net/mberkom/ufVey/

答案 19 :(得分:0)

import java.util.ArrayList;
import java.util.Arrays;



public class SequencetoRange {

    /**
     * @param args
     */
    public static void main(String[] args) {
    // TODO Auto-generated method stub

        int num[] = {1,2,3,63,65,66,67,68,69,70,80,90,91,94,95,4,101,102,75,76,71};

        int l = num.length;
        int i;
        System.out.print("Given number : ");
        for (i = 0;i < l;i++ ){
            System.out.print("  " + num[i]);
        }
        System.out.println("\n");
        Arrays.sort(num);

        ArrayList newArray = new ArrayList();
        newArray = getRanges(num);
        System.out.print("Range : ");
        for(int y=0;y<newArray.size();y++)
        {
            System.out.print(" " +newArray.get(y));
        }
    }

    public static ArrayList getRanges(int num[])
    {  
        ArrayList ranges = new ArrayList();
        int rstart, rend;   
        int lastnum = num[num.length-1];
        for (int i = 0; i < num.length-1; i++) 
        {     
            rstart = num[i];     
            rend = rstart;     
            while (num[i + 1] - num[i] == 1) 
            {       
                rend = num[i + 1]; 
                // increment the index if the numbers sequential       
                if(rend>=lastnum)
                {
                    break;
                }
                else
                {
                    i++;
                }  
            }  
            if(rstart==rend)
            {
                ranges.add(rend);
            }
            else
            {
                ranges.add(+rstart+"..."+rend);
            }
        } 
        return ranges; 
    } 
}

答案 20 :(得分:0)

PHP

function getRanges($nums) {
sort($nums);
$ranges = array();

for ( $i = 0, $len = count($nums); $i < $len; $i++ )
{
    $rStart = $nums[$i];
    $rEnd = $rStart;
    while ( isset($nums[$i+1]) && $nums[$i+1]-$nums[$i] == 1 )
        $rEnd = $nums[++$i];

    $ranges[] = $rStart == $rEnd ? $rStart : $rStart.'-'.$rEnd;
}

return $ranges;
}


echo print_r(getRanges(array(2,21,3,4,5,10,18,19,20)));
echo print_r(getRanges(array(1,2,3,4,5,6,7,8,9,10)));

答案 21 :(得分:0)

 ; For all cells of the array
    ;if current cell = prev cell + 1 -> range continues
    ;if current cell != prev cell + 1 -> range ended

int[] x  = [2,3,4,5,10,18,19,20]
string output = '['+x[0]
bool range = false; --current range
for (int i = 1; i > x[].length; i++) {
  if (x[i+1] = [x]+1) {
    range = true;
  } else { //not sequential
  if range = true
     output = output || '-' 
  else
     output = output || ','
  output.append(x[i]','||x[i+1])
  range = false;
  } 

}

类似的东西。

答案 22 :(得分:0)

您可以迭代数字,看看下一个数字是否比当前数字大1。所以有一个:

struct range {
    int start;
    int end;
} range;

其中if array[i+1] == array[i]+1;(其中i是当前观察到的数字) 然后range.end = array[i+1];。然后你进入下一个i;如果array[i+1] != array[i]+1;range.end = array[i];

您可以将范围存储在vector< range > ranges;

中 打印很简单:

for(int i = 0; i < ranges.size(); i++) {
    range rng = (range)ranges.at(i);
    printf("[%i-%i]", rng.start, rng.end);
}