Range expansion

From Rosetta Code
Jump to: navigation, search
Task
Range expansion
You are encouraged to solve this task according to the task description, using any language you may know.
A format for expressing an ordered list of integers is to use a comma separated list of either
  • individual integers
  • Or a range of integers denoted by the starting integer separated from the end integer in the range by a dash, '-'. (The range includes all integers in the interval including both endpoints)
  • The range syntax is to be used only for, and for every range that expands to more than two values.

Example
The list of integers:

-6, -3, -2, -1, 0, 1, 3, 4, 5, 7, 8, 9, 10, 11, 14, 15, 17, 18, 19, 20

Is accurately expressed by the range expression:

-6,-3-1,3-5,7-11,14,15,17-20

(And vice-versa).

The task

Expand the range description:

-6,-3--1,3-5,7-11,14,15,17-20

Note that the second element above, is the range from minus 3 to minus 1.

C.f. Range extraction

Contents

[edit] Ada

The function Expand takes a string and returns a corresponding array of integers. Upon syntax errors Constraint_Error is propagated:

with Ada.Text_IO; use Ada.Text_IO;
procedure Test_Range_Expansion is
type Sequence is array (Positive range <>) of Integer;
function Expand (Text : String) return Sequence is
To  : Integer := Text'First;
Count : Natural := 0;
Low  : Integer;
function Get return Integer is
From : Integer := To;
begin
if Text (To) = '-' then
To := To + 1;
end if;
while To <= Text'Last loop
case Text (To) is
when ',' | '-' => exit;
when others => To := To + 1;
end case;
end loop;
return Integer'Value (Text (From..To - 1));
end Get;
begin
while To <= Text'Last loop -- Counting items of the list
Low := Get;
if To > Text'Last or else Text (To) = ',' then
Count := Count + 1;
else
To := To + 1;
Count := Count + Get - Low + 1;
end if;
To := To + 1;
end loop;
return Result : Sequence (1..Count) do
Count := 0;
To := Text'First;
while To <= Text'Last loop -- Filling the list
Low := Get;
if To > Text'Last or else Text (To) = ',' then
Count := Count + 1;
Result (Count) := Low;
else
To := To + 1;
for Item in Low..Get loop
Count := Count + 1;
Result (Count) := Item;
end loop;
end if;
To := To + 1;
end loop;
end return;
end Expand;
procedure Put (S : Sequence) is
First : Boolean := True;
begin
for I in S'Range loop
if First then
First := False;
else
Put (',');
end if;
Put (Integer'Image (S (I)));
end loop;
end Put;
begin
Put (Expand ("-6,-3--1,3-5,7-11,14,15,17-20"));
end Test_Range_Expansion;
Output:
-6,-3,-2,-1, 3, 4, 5, 7, 8, 9, 10, 11, 14, 15, 17, 18, 19, 20

[edit] ALGOL 68

Works with: ALGOL 68 version Revision 1 - no extensions to language used
Works with: ALGOL 68G version Any - tested with release 1.18.0-9h.tiny - string parsing and formatting code tested with 2.6.win32
Works with: ELLA ALGOL 68 version Any (with appropriate job cards) - tested with release 1.8-8d
MODE YIELDINT = PROC(INT)VOID;
 
MODE RANGE = STRUCT(INT lwb, upb);
MODE RANGEINT = UNION(RANGE, INT);
 
OP SIZEOF = ([]RANGEINT list)INT: (
# determine the length of the output array #
INT upb := LWB list - 1;
FOR key FROM LWB list TO UPB list DO
CASE list[key] IN
(RANGE value): upb +:= upb OF value - lwb OF value + 1,
(INT): upb +:= 1
ESAC
OD;
upb
);
 
PROC gen range expand = ([]RANGEINT list, YIELDINT yield)VOID:
FOR key FROM LWB list TO UPB list DO
CASE list[key] IN
(RANGE range): FOR value FROM lwb OF range TO upb OF range DO yield(value) OD,
(INT int): yield(int)
ESAC
OD;
 
PROC range expand = ([]RANGEINT list)[]INT: (
[LWB list: LWB list + SIZEOF list - 1]INT out;
INT upb := LWB out - 1;
# FOR INT value IN # gen range expand(list, # ) DO #
## (INT value)VOID:
out[upb +:= 1] := value
# OD #);
out
);
 
#
test:(
[]RANGEINT list = (-6, RANGE(-3, -1), RANGE(3, 5), RANGE(7, 11), 14, 15, RANGE(17, 20));
print((range expand(list), new line))
)
#

 
 
# converts string containing a comma-separated list of ranges and values to a []RANGEINT #
OP TORANGE = ( STRING s )[]RANGEINT:
BEGIN
 
# counts the number of elements - one more than the number of commas #
# and so assumes there is always at least one element #
PROC count elements = INT:
BEGIN
 
INT elements := 1;
 
FOR pos FROM LWB s TO UPB s
DO
IF s[ pos ] = ","
THEN
elements +:= 1
FI
OD;
 
# RESULT #
elements
END; # count elements #
 
REF[]RANGEINT result = HEAP [ 1 : count elements ]RANGEINT;
 
# does the actual parsing - assumes the string is syntatically valid and doesn't check for errors #
# - in particular, a string with no elements will cause problems, as will space characters in the string #
PROC parse range string = []RANGEINT:
BEGIN
 
INT element := 0;
INT str pos := 1;
 
PROC next = VOID: str pos +:= 1;
PROC curr char = CHAR: IF str pos > UPB s THEN "?" ELSE s[ str pos ] FI;
PROC have minus = BOOL: curr char = "-";
PROC have digit = BOOL: curr char >= "0" AND curr char <= "9";
 
 
# parses a number out of the string #
# the number must be a sequence of digits with an optional leading minus sign #
PROC get number = INT:
BEGIN
 
INT number := 0;
 
INT sign multiplier = IF have minus
THEN
# negaive number #
# skip the sign #
next;
-1
ELSE
# positive number #
1
FI;
 
WHILE curr char >= "0" AND curr char <= "9"
DO
number *:= 10;
number +:= ( ABS curr char - ABS "0" );
next
OD;
 
# RESULT #
number * sign multiplier
END; # get number #
 
 
# main parsing #
WHILE str pos <= UPB s
DO
CHAR c = curr char;
 
IF have minus OR have digit
THEN
# have the start of a number #
INT from value = get number;
element +:= 1;
IF NOT have minus
THEN
# not a range #
result[ element ] := from value
ELSE
# have a range #
next;
INT to value = get number;
result[ element ] := RANGE( from value, to value )
FI
ELSE
# should be a comma #
next
FI
OD;
 
# RESULT #
result
END; # parse range string #
 
 
# RESULT #
parse range string
END; # TORANGE #
 
 
# converts a []INT to a comma separated string of the elements #
OP TOSTRING = ( []INT values )STRING:
BEGIN
 
# converts an integer to a string #
OP TOSTRING = ( INT value )STRING:
BEGIN
STRING result := "";
INT n := ABS value;
 
WHILE
REPR ( ( n MOD 10 ) + ABS "0" ) PLUSTO result;
n OVERAB 10;
n > 0
DO
SKIP
OD;
 
# RESULT #
IF value < 0 THEN "-" ELSE "" FI + result
END; # TOSTRING #
 
 
STRING result := "";
STRING separator := "";
 
FOR pos FROM LWB values TO UPB values
DO
result +:= ( separator + TOSTRING values[ pos ] );
separator := ","
OD;
 
# RESULT #
result
END; # TOSTRING #
 
 
 
test:(
print( ( TOSTRING range expand( TORANGE "-6,-3--1,3-5,7-11,14,15,17-20" ), newline ) )
)
 
Output:
-6,-3,-2,-1,3,4,5,7,8,9,10,11,14,15,17,18,19,20

[edit] AutoHotkey

msgbox % expand("-6,-3--1,3-5,7-11,14,15,17-20")
 
expand( range ) {
p := 0
while p := RegExMatch(range, "\s*(-?\d++)(?:\s*-\s*(-?\d++))?", f, p+1+StrLen(f))
loop % (f2 ? f2-f1 : 0) + 1
ret .= "," (A_Index-1) + f1
return SubStr(ret, 2)
}

[edit] AWK

#!/usr/bin/awk -f
BEGIN { FS=","; }
 
{ s="";
for (i=1; i<=NF; i++) { expand($i); }
print substr(s,2);
}
 
function expand(a) {
idx = match(a,/[0-9]-/);
if (idx==0) {
s = s","a;
return;
}
 
start= substr(a,1, idx)+0;
stop = substr(a,idx+2)+0;
for (m = start; m <= stop; m++) {
s = s","m;
}
return;
}
Usage: 
  echo -6,-3--1,3-5,7-11,14,15,17-20  | gawk -f ./range_expansion.awk 
  -6,-3,-2,-1,3,4,5,7,8,9,10,11,14,15,17,18,19,20

[edit] BBC BASIC

      PRINT FNrangeexpand("-6,-3--1,3-5,7-11,14,15,17-20")
END
 
DEF FNrangeexpand(r$)
LOCAL i%, j%, k%, t$
REPEAT
i% = INSTR(r$, "-", i%+1)
IF i% THEN
j% = i%
WHILE MID$(r$,j%-1,1)<>"," AND j%<>1
j% -= 1
ENDWHILE
IF i%>j% IF MID$(r$,j%,i%-j%)<>STRING$(i%-j%," ") THEN
t$ = ""
FOR k% = VALMID$(r$,j%) TO VALMID$(r$,i%+1)-1
t$ += STR$(k%) + ","
NEXT
r$ = LEFT$(r$,j%-1) + t$ + MID$(r$,i%+1)
i% = j% + LEN(t$) + 2
ENDIF
ENDIF
UNTIL i% = 0
= r$

Output:

-6,-3,-2,-1,3,4,5,7,8,9,10,11,14,15,17,18,19,20

[edit] Bracmat

  ( expandRanges
= a b L
. @( !arg
 : (#(?a:?b)|#?a "-" #?b)
(:?L|"," [%(expandRanges$!sjt:?L))
)
& whl
' ( (!L:&!b|(!b,!L))
 : ?L
& -1+!b:~<!a:?b
)
& !L
|
)
& out$(str$(expandRanges$"-6,-3--1,3-5,7-11,14,15,17-20"))
 

Output:

-6,-3,-2,-1,3,4,5,7,8,9,10,11,14,15,17,18,19,20

[edit] C

Recursive descent parser.

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
 
/* BNFesque
rangelist := (range | number) [',' rangelist]
range := number '-' number */

 
int get_list(const char *, char **);
int get_rnge(const char *, char **);
 
/* parser only parses; what to do with parsed items is up to
* the add_number and and_range functions */

void add_number(int x);
int add_range(int x, int y);
 
#define skip_space while(isspace(*s)) s++
#define get_number(x, s, e) (x = strtol(s, e, 10), *e != s)
int get_list(const char *s, char **e)
{
int x;
while (1) {
skip_space;
if (!get_rnge(s, e) && !get_number(x, s, e)) break;
s = *e;
 
skip_space;
if ((*s) == '\0') { putchar('\n'); return 1; }
if ((*s) == ',') { s++; continue; }
break;
}
*(const char **)e = s;
printf("\nSyntax error at %s\n", s);
return 0;
}
 
int get_rnge(const char *s, char **e)
{
int x, y;
char *ee;
if (!get_number(x, s, &ee)) return 0;
s = ee;
 
skip_space;
if (*s != '-') {
*(const char **)e = s;
return 0;
}
s++;
if(!get_number(y, s, e)) return 0;
return add_range(x, y);
}
 
void add_number(int x)
{
printf("%d ", x);
}
 
int add_range(int x, int y)
{
if (y <= x) return 0;
while (x <= y) printf("%d ", x++);
return 1;
}
 
int main()
{
char *end;
 
/* this is correct */
if (get_list("-6,-3--1,3-5,7-11,14,15,17-20", &end)) puts("Ok");
 
/* this is not. note the subtle error: "-6 -3" is parsed
* as range(-6, 3), so synax error comes after that */

get_list("-6 -3--1,3-5,7-11,14,15,17-20", &end);
 
return 0;
}
Output:
-6 -3 -2 -1 3 4 5 7 8 9 10 11 14 15 17 18 19 20 
Ok
-6 -5 -4 -3 -2 -1 0 1 2 3 
Syntax error at --1,3-5,7-11,14,15,17-20

[edit] C#

Works with: C sharp version 3.0
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
 
class Program
{
static void Main(string[] args)
{
var rangeString = "-6,-3--1,3-5,7-11,14,15,17-20";
var matches = Regex.Matches(rangeString, @"(?<f>-?\d+)-(?<s>-?\d+)|(-?\d+)");
var values = new List<string>();
 
foreach (var m in matches.OfType<Match>())
{
if (m.Groups[1].Success)
{
values.Add(m.Value);
continue;
}
 
var start = Convert.ToInt32(m.Groups["f"].Value);
var end = Convert.ToInt32(m.Groups["s"].Value) + 1;
 
values.AddRange(Enumerable.Range(start, end - start).Select(v => v.ToString()));
}
 
Console.WriteLine(string.Join(", ", values));
}
}

[edit] C++

#include <iostream>
#include <sstream>
#include <iterator>
#include <climits>
#include <deque>
 
// parse a list of numbers with ranges
//
// arguments:
// is: the stream to parse
// out: the output iterator the parsed list is written to.
//
// returns true if the parse was successful. false otherwise
template<typename OutIter>
bool parse_number_list_with_ranges(std::istream& is, OutIter out)
{
int number;
// the list always has to start with a number
while (is >> number)
{
*out++ = number;
 
char c;
if (is >> c)
switch(c)
{
case ',':
continue;
case '-':
{
int number2;
if (is >> number2)
{
if (number2 < number)
return false;
while (number < number2)
*out++ = ++number;
char c2;
if (is >> c2)
if (c2 == ',')
continue;
else
return false;
else
return is.eof();
}
else
return false;
}
default:
return is.eof();
}
else
return is.eof();
}
// if we get here, something went wrong (otherwise we would have
// returned from inside the loop)
return false;
}
 
int main()
{
std::istringstream example("-6,-3--1,3-5,7-11,14,15,17-20");
std::deque<int> v;
bool success = parse_number_list_with_ranges(example, std::back_inserter(v));
if (success)
{
std::copy(v.begin(), v.end()-1,
std::ostream_iterator<int>(std::cout, ","));
std::cout << v.back() << "\n";
}
else
std::cout << "an error occured.";
}
Output:
-6,-3,-2,-1,3,4,5,7,8,9,10,11,14,15,17,18,19,20

[edit] Clojure

There is a split method in clojure.contrib, but I don't know if it is able to skip first character to so that (split "-8--8") => (-8 -8).

(defn split [s sep]
(defn skipFirst [[x & xs :as s]]
(cond (empty? s) [nil nil]
(= x sep) [x xs]
true [nil s]))
(loop [lst '(), s s]
(if (empty? s) (reverse lst)
(let [[hd trunc] (skipFirst s)
[word news] (split-with #(not= % sep) trunc)
cWord (cons hd word)]
(recur (cons (apply str cWord) lst)
(apply str (rest news)))))))
 
(defn parseRange [[x & xs :as s]]
(if (some #(= % \-) xs)
(let [[r0 r1] (split s \-)]
(range (read-string r0) (inc (read-string r1))))
(list (read-string (str s))))))
 
(defn rangeexpand [s]
(flatten (map parseRange (split s \,))))
 
> (rangeexpand "-6,-3--1,3-5,7-11,14,15,17-20")
(-6 -3 -2 -1 3 4 5 7 8 9 10 11 14 15 17 18 19 20)

[edit] COBOL

This example does not show the output mentioned in the task description on this page (or a page linked to from here). Please ensure that it meets all task requirements and remove this message.
Note that phrases in task descriptions such as "print and display" and "print and show" for example, indicate that (reasonable length) output be a part of a language's solution.


Works with: GNU Cobol version 2.0
       >>SOURCE FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. expand-range.
 
DATA DIVISION.
WORKING-STORAGE SECTION.
01 comma-pos PIC 99 COMP VALUE 1.
01 dash-pos PIC 99 COMP.
01 end-num PIC S9(3).
01 Max-Part-Len CONSTANT 10.
01 num PIC S9(3).
01 edited-num PIC -(3)9.
01 part PIC X(10).
 
01 part-flag PIC X.
88 last-part VALUE "Y".
 
01 range-str PIC X(80).
01 Range-Str-Len CONSTANT 80.
01 start-pos PIC 99 COMP.
01 start-num PIC S9(3).
 
PROCEDURE DIVISION.
ACCEPT range-str
 
PERFORM WITH TEST AFTER UNTIL last-part
UNSTRING range-str DELIMITED BY "," INTO part WITH POINTER comma-pos
PERFORM check-if-last
 
PERFORM find-range-dash
 
IF dash-pos > Max-Part-Len
PERFORM display-num
ELSE
PERFORM display-range
END-IF
END-PERFORM
 
DISPLAY SPACES
 
GOBACK
.
check-if-last SECTION.
IF comma-pos > Range-Str-Len
SET last-part TO TRUE
END-IF
.
find-range-dash SECTION.
IF part (1:1) <> "-"
MOVE 1 TO start-pos
ELSE
MOVE 2 TO start-pos
END-IF
 
MOVE 1 TO dash-pos
INSPECT part (start-pos:) TALLYING dash-pos FOR CHARACTERS BEFORE "-"
COMPUTE dash-pos = dash-pos + start-pos - 1
.
display-num SECTION.
MOVE part TO edited-num
CALL "display-edited-num" USING CONTENT part-flag, edited-num
.
display-range SECTION.
MOVE part (1:dash-pos - 1) TO start-num
MOVE part (dash-pos + 1:) TO end-num
 
PERFORM VARYING num FROM start-num BY 1 UNTIL num = end-num
MOVE num TO edited-num
CALL "display-edited-num" USING CONTENT "N", edited-num
END-PERFORM
 
MOVE end-num TO edited-num
CALL "display-edited-num" USING CONTENT part-flag, edited-num
.
END PROGRAM expand-range.
 
 
IDENTIFICATION DIVISION.
PROGRAM-ID. display-edited-num.
 
DATA DIVISION.
LINKAGE SECTION.
01 hide-comma-flag PIC X.
88 hide-comma VALUE "Y".
01 edited-num PIC -(3)9.
 
PROCEDURE DIVISION USING hide-comma-flag, edited-num.
DISPLAY FUNCTION TRIM(edited-num) NO ADVANCING
IF NOT hide-comma
DISPLAY ", " NO ADVANCING
END-IF
.
END PROGRAM display-edited-num.

[edit] Common Lisp

(defun expand-ranges (string)
(loop
with prevnum = nil
for idx = 0 then (1+ nextidx)
for (number nextidx) = (multiple-value-list
(parse-integer string
:start idx :junk-allowed t))
append (cond
(prevnum
(prog1
(loop for i from prevnum to number
collect i)
(setf prevnum nil)))
((and (< nextidx (length string))
(char= (aref string nextidx) #\-))
(setf prevnum number)
nil)
(t
(list number)))
while (< nextidx (length string))))
 
CL-USER> (expand-ranges "-6,-3--1,3-5,7-11,14,15,17-20")
(-6 -3 -2 -1 3 4 5 7 8 9 10 11 14 15 17 18 19 20)

[edit] D

import std.stdio, std.regex, std.conv, std.range, std.algorithm;
 
enum rangeEx = (string s) /*pure*/ => s.matchAll(`(-?\d+)-?(-?\d+)?,?`)
.map!q{ a[1].to!int.iota(a[1 + !a[2].empty].to!int + 1) }.join;
 
void main() {
"-6,-3--1,3-5,7-11,14,15,17-20".rangeEx.writeln;
}
Output:
[-6, -3, -2, -1, 3, 4, 5, 7, 8, 9, 10, 11, 14, 15, 17, 18, 19, 20]

[edit] Erlang

 
-module( range ).
 
-export( [expansion/1, task/0] ).
 
expansion( String ) ->
lists:flatten( [expansion_individual(io_lib:fread("~d", X)) || X <- string:tokens(String, ",")] ).
 
task() ->
io:fwrite( "~p~n", [expansion("-6,-3--1,3-5,7-11,14,15,17-20")] ).
 
 
 
expansion_individual( {ok, [N], []} ) -> N;
expansion_individual( {ok, [Start], "-" ++ Stop_string} ) -> lists:seq( Start, erlang:list_to_integer(Stop_string) ).
 
Output:
34> range:task().
[-6,-3,-2,-1,3,4,5,7,8,9,10,11,14,15,17,18,19,20]

[edit] Forth

: >snumber ( str len -- 'str 'len n )
0. 2swap
over c@ [char] - = if
1 /string
>number 2swap drop
negate
else
>number 2swap drop
then ;
 
: expand ( str len -- )
begin dup while
>snumber >r
dup if over c@ [char] - = if
1 /string
>snumber r> over >r
do i . loop
then then
dup if over c@ [char] , = if
1 /string
then then
r> .
repeat 2drop ;
 
s" -6,-3--1,3-5,7-11,14,15,17-20" expand

[edit] F#

open System.Text.RegularExpressions
 
// simplify regex matching with an active pattern
let (|Regexp|_|) pattern txt =
match Regex.Match(txt, pattern) with
| m when m.Success -> [for g in m.Groups -> g.Value] |> List.tail |> Some
| _ -> None
 
// Parse and expand a single range description.
// string -> int list
let parseRange r =
match r with
| Regexp @"^(-?\d+)-(-?\d+)$" [first; last] -> [int first..int last]
| Regexp @"^(-?\d+)$" [single] -> [int single]
| _ -> failwithf "illegal range format: %s" r
 
 
let expand (desc:string) =
desc.Split(',')
|> List.ofArray
|> List.collect parseRange
 
printfn "%A" (expand "-6,-3--1,3-5,7-11,14,15,17-20")
Output:
[-6; -3; -2; -1; 3; 4; 5; 7; 8; 9; 10; 11; 14; 15; 17; 18; 19; 20]

[edit] Go

A version rather strict with input

package main
 
import (
"fmt"
"strconv"
"strings"
)
 
const input = "-6,-3--1,3-5,7-11,14,15,17-20"
 
func main() {
fmt.Println("range:", input)
var r []int
var last int
for _, part := range strings.Split(input, ",") {
if i := strings.Index(part[1:], "-"); i == -1 {
n, err := strconv.Atoi(part)
if err != nil {
fmt.Println(err)
return
}
if len(r) > 0 {
if last == n {
fmt.Println("duplicate value:", n)
return
} else if last > n {
fmt.Println("values not ordered:", last, ">", n)
return
}
}
r = append(r, n)
last = n
} else {
n1, err := strconv.Atoi(part[:i+1])
if err != nil {
fmt.Println(err)
return
}
n2, err := strconv.Atoi(part[i+2:])
if err != nil {
fmt.Println(err)
return
}
if n2 < n1+2 {
fmt.Println("invalid range:", part)
return
}
if len(r) > 0 {
if last == n1 {
fmt.Println("duplicate value:", n1)
return
} else if last > n1 {
fmt.Println("values not ordered:", last, ">", n1)
return
}
}
for i = n1; i <= n2; i++ {
r = append(r, i)
}
last = n2
}
}
fmt.Println("expanded:", r)
}

[edit] Groovy

Ad Hoc Solution:

  1. translate the task's range syntax into Groovy range syntax
  2. wrap with list delimiters
  3. evaluate the script expression
  4. flatten the nested lists
  5. express as a string
  6. unwrap the list delimiters
def expandRanges = { compressed ->
Eval.me('['+compressed.replaceAll(~/(\d)-/, '$1..')+']').flatten().toString()[1..-2]
}

Test:

def s = '-6,-3--1,3-5,7-11,14,15,17-20'
println (expandRanges(s))
Output:
-6, -3, -2, -1, 3, 4, 5, 7, 8, 9, 10, 11, 14, 15, 17, 18, 19, 20

[edit] Haskell

Given either of the below implementations of expandRange:

> expandRange "-6,-3--1,3-5,7-11,14,15,17-20"
[-6,-3,-2,-1,3,4,5,7,8,9,10,11,14,15,17,18,19,20]

[edit] With conventional list processing

expandRange :: String -> [Int]
expandRange = concatMap f . split ','
where f str@(c : cs) | '-' `elem` cs = [read (c : a) .. read b]
| otherwise = [read str]
where (a, _ : b) = break (== '-') cs
 
split :: Eq a => a -> [a] -> [[a]]
split delim [] = []
split delim l = a : split delim (dropWhile (== delim) b)
where (a, b) = break (== delim) l

[edit] With a parser

import Control.Monad
import Text.ParserCombinators.Parsec
 
expandRange :: String -> [Int]
expandRange s = case parse rangeParser "" s of Right l -> l
 
rangeParser :: Parser [Int]
rangeParser = liftM concat $ item `sepBy` char ','
where item = do
n1 <- num
n2 <- option n1 $ char '-' >> num
return [n1 .. n2]
num :: Parser Int
num = liftM read $ liftM2 (++)
(option "" $ string "-")
(many1 digit)

[edit] Icon and Unicon

procedure main()
s := "-6,-3--1,3-5,7-11,14,15,17-20"
write("Input string  := ",s)
write("Expanded list  := ", list2string(range_expand(s)) | "FAILED")
end
 
procedure range_expand(s) #: return list of integers extracted from an ordered string representation
local R,low,high
R := []
 
s ? until pos(0) do {
put(R,low := integer(tab(upto(',-')|0))| fail) # get lower bound
if ="-" || (high := integer(tab(find(",")|0))|fail) then
until low = high do put(R,low +:= 1) # find range
=","
}
return R
end
 
procedure list2string(L) #: helper function to convert a list to a string
local s
 
every (s := "[ ") ||:= !L || " "
return s || "]"
end
Output:
Input string      := -6,-3--1,3-5,7-11,14,15,17-20
Expanded list   := [ -6 -3 -2 -1 3 4 5 7 8 9 10 11 14 15 17 18 19 20 ]

[edit] J

require'strings'
thru=: <./ + i.@(+*)@-~
num=: _&".
normaliz=: rplc&(',-';',_';'--';'-_')@,~&','
subranges=:<@(thru/)@(num;._2)@,&'-';._1
rngexp=: ;@subranges@normaliz
Example:
   rngexp '-6,-3--1,3-5,7-11,14,15,17-20'
_6 _3 _2 _1 3 4 5 7 8 9 10 11 14 15 17 18 19 20

Notes:

thru: given two numbers (left: start of range, right: end of range) return the corresponding range

num: given the string representation of a number, returns the number

normaliz: given the task required string representing a sequence of ranges, create a fresh copy with fewer micro ambiguities: All subranges are preceded by a comma. Negative numbers use a different character ('_') than the continuous range character ('-').

subranges: given the result of normaliz, return a sequence of boxes (one box for each comma). Each box contains the subrange which is described after its comma.

Note that thru/ is an identity function when applied to a single number. This is because (verb/) inserts the verb between each number (or each item in a list), and this is an identity function on a single number, regardless of any definition of the verb.

[edit] Java

import java.util.*;
import java.util.regex.*;
 
class Range implements Enumeration {
private int clower, cupper;
private int value;
private boolean inrange;
private Scanner ps = null;
private String ss;
 
private static String del = "\\s*,\\s*";
 
public Range(String s) {
ss = s;
reset();
}
 
public boolean hasMoreElements() {
return (inrange && (value >= clower && value <= cupper)) || ps.hasNext();
}
 
public Object nextElement() throws NoSuchElementException {
if (!hasMoreElements())
throw new NoSuchElementException();
if (inrange && (value >= clower && value <= cupper)) {
value++;
return value-1;
}
inrange = false;
String n = ps.next();
if (n.matches("[+-]?\\d+-[+-]?\\d+")) {
Scanner ls = new Scanner(n);
ls.findInLine("([+-]?\\d+)-([+-]?\\d+)");
MatchResult r = ls.match();
clower = Integer.parseInt(r.group(1));
cupper = Integer.parseInt(r.group(2));
value = clower+1;
inrange = true;
ls.close();
return clower;
}
return Integer.parseInt(n);
}
 
public void reset() {
if (ps != null)
ps.close();
ps = new Scanner(ss).useDelimiter(del);
inrange = false;
}
 
protected void finalize() throws Throwable {
ps.close();
super.finalize();
}
}
 
class rangexp {
public static void main(String[] args) {
Range r = new Range("-6,-3--1,3-5,7-11,14,15,17-20");
while (r.hasMoreElements()) {
System.out.print(r.nextElement() + " ");
}
System.out.println();
}
}

[edit] JavaScript

[edit] Spidermonkey version

#!/usr/bin/env js
 
function main() {
print(rangeExpand('-6,-3--1,3-5,7-11,14,15,17-20'));
}
 
function rangeExpand(rangeExpr) {
 
function getFactors(term) {
var matches = term.match(/(-?[0-9]+)-(-?[0-9]+)/);
if (!matches) return {first:Number(term)};
return {first:Number(matches[1]), last:Number(matches[2])};
}
 
function expandTerm(term) {
var factors = getFactors(term);
if (factors.length < 2) return [factors.first];
var range = [];
for (var n = factors.first; n <= factors.last; n++) {
range.push(n);
}
return range;
}
 
var result = [];
var terms = rangeExpr.split(/,/);
for (var t in terms) {
result = result.concat(expandTerm(terms[t]));
}
 
return result;
}
 
main();
 

Example output:

-6,-3,-2,-1,3,4,5,7,8,9,10,11,14,15,17,18,19,20

[edit] Julia

slurp(s)  = readcsv(IOBuffer(s*",")) 
 
conv(s) = colon(map(int,(match(r"^(-?\d+)-(-?\d+)$", s).captures))...)
 
expand(s) = mapreduce(x -> isa(x,Number)? int(x): conv(x), vcat, slurp(s))
Output:
julia> show(expand("-6,-3--1,3-5,7-11,14,15,17-20"))
[-6,-3,-2,-1,3,4,5,7,8,9,10,11,14,15,17,18,19,20]

[edit] K

grp : {1_'(&x=*x)_ x:",",x}
pos : {:[3=l:#p:&"-"=x;0,p@1;2=l;p;0=*p;,0;0,p]}
conv: 0${(x;1_ y)}/'{(pos x)_ x}'
expd: {,/@[x;&2=#:'x;{(*x)+!1+,/-':x}]}
rnge: {expd@conv grp x}
Example:
  rnge "-6,-3--1,3-5,7-11,14,15,17-20"
-6 -3 -2 -1 3 4 5 7 8 9 10 11 14 15 17 18 19 20


[edit] Lasso

define range_expand(expression::string) => {
local(parts) = regexp(`^(-?\d+)-(-?\d+)$`)
 
return (
with elm in #expression->split(`,`)
let isRange = #parts->setInput(#elm)&matches
select #isRange
 ? (integer(#parts->matchString(1)) to integer(#parts->matchString(2)))->asString
| integer(#elm)->asString
)->join(', ')
}
 
range_expand(`-6,-3--1,3-5,7-11,14,15,17-20`)
Output:
-6, -3, -2, -1, 3, 4, 5, 7, 8, 9, 10, 11, 14, 15, 17, 18, 19, 20


[edit] Liberty BASIC

print ExpandRange$( "-6,-3--1,3-5,7-11,14,15,17-20")
end
 
function ExpandRange$( compressed$)
for i = 1 to ItemCount( compressed$, ",")
item$ = word$( compressed$, i, ",")
dash = instr( item$, "-", 2) 'dash that is not the first character, is a separator
if dash then
for k = val( left$( item$, dash - 1)) to val( mid$( item$, dash + 1))
ExpandRange$ = ExpandRange$ + str$( k) + ","
next k
else
ExpandRange$ = ExpandRange$ + item$ + ","
end if
next i
ExpandRange$ = left$( ExpandRange$, len( ExpandRange$) - 1)
end function
 
function ItemCount( list$, separator$)
while word$(list$, ItemCount + 1, separator$) <> ""
ItemCount = ItemCount + 1
wend
end function
Output:
-6,-3,-2,-1,3,4,5,7,8,9,10,11,14,15,17,18,19,20

[edit] Maple

 
ExpandRanges := proc( s :: string )
uses StringTools;
local DoOne := proc( input )
uses StringTools;
local lo, hi, pos;
if IsDigit( input ) or input[ 1 ] = "-"
and IsDigit( input[ 2 .. -1 ] ) then
parse( input )
else
pos := Search( "--", input );
if pos > 0 then
lo := input[ 1 .. pos - 1 ];
hi := input[ 1 + pos .. -1 ];
elif input[ 1 ] = "-" then
pos := FirstFromLeft( "-", input[ 2 .. -1 ] );
if pos = 0 then
lo := input;
hi := lo
else
lo := input[ 1 .. pos ];
hi := input[ 2 + pos .. -1 ];
end if;
else
pos := FirstFromLeft( "-", input );
if pos = 0 then
error "incorrect syntax"
end if;
lo := input[ 1 .. pos - 1 ];
hi := input[ 1 + pos .. -1 ];
end if;
lo := parse( lo );
hi := parse( hi );
seq( lo .. hi )
end if
end proc:
map( DoOne, map( Trim, Split( s, "," ) ) )
end proc:
 

Running this on the example input we get the following.

 
> rng := "-6,-3--1,3-5,7-11,14,15,17-20":
> ExpandRanges( rng );
[-6, -3, -2, -1, 3, 4, 5, 7, 8, 9, 10, 11, 14, 15, 17, 18, 19, 20]
 

Here is an additional example which my first attempt got wrong.

 
> rng := "-6,-3-1,3-5,7-11,14,15,17-20":
> ExpandRanges( rng );
[-6, -3, -2, -1, 0, 1, 3, 4, 5, 7, 8, 9, 10, 11, 14, 15, 17, 18, 19, 20]
 

[edit] Mathematica

rangeexpand[ rng_ ] := Module[ { step1 },
step1 = StringSplit[StringReplacePart[rng,"S",StringPosition[ rng,DigitCharacter~~"-"] /. {x_,y_} -> {y,y}],","];
Flatten@ToExpression/@Quiet@StringReplace[step1,x__~~"S"~~y__->"Range["<>x<>","<>y<>"]"] ]
Example:
rangeexpand["-6,-3--1,3-5,7-11,14,15,17-20"]
{-6,-3,-2,-1,3,4,5,7,8,9,10,11,14,15,17,18,19,20}

[edit] MATLAB / Octave

function L=range_expansion(S)
% Range expansion
if nargin < 1;
S='[]';
end
 
if ~all(isdigit(S) | (S=='-') | (S==',') | isspace(S))
error 'invalid input';
end
ixr = find(isdigit(S(1:end-1)) & S(2:end) == '-')+1;
S(ixr)=':';
S=['[',S,']'];
L=eval(S);

Usage:

   range_expansion('-6,-3--1,3-5,7-11,14,15,17-20 ')
ans =
   -6   -3   -2   -1    3    4    5    7    8    9   10   11   14   15   17   18   19   20

[edit] MUMPS

RANGEXP(X) ;Integer range expansion
NEW Y,I,J,X1,H SET Y=""
FOR I=1:1:$LENGTH(X,",") DO
.S X1=$PIECE(X,",",I) FOR Q:$EXTRACT(X1)'=" " S X1=$EXTRACT(X1,2,$LENGTH(X1)) ;clean up leading spaces
.SET H=$FIND(X1,"-")-1
.IF H=1 SET H=$FIND(X1,"-",(H+1))-1 ;If the first value is negative ignore that "-"
.IF H<0 SET Y=$SELECT($LENGTH(Y)=0:Y_X1,1:Y_","_X1)
.IF '(H<0) FOR J=+$EXTRACT(X1,1,(H-1)):1:+$EXTRACT(X1,(H+1),$LENGTH(X1)) SET Y=$SELECT($LENGTH(Y)=0:J,1:Y_","_J)
KILL I,J,X1,H
QUIT Y
Example:
USER>SET U="-6,-3--1,3-5,7-11,14,15,17-20"

USER>WRITE $$RANGEXP^ROSETTA(U)
-6,-3,-2,-1,3,4,5,7,8,9,10,11,14,15,17,18,19,20

[edit] NetRexx

Translation of: Rexx Version 2

/*NetRexx program to expand a range of integers into a list. *************
* 09.08.2012 Walter Pachl derived from my Rexx version
* Changes: translate(old,' ',',') -> old.translate(' ',',')
* dashpos=pos('-',x,2) -> dashpos=x.pos('-',2)
* Do -> Loop
* Parse Var a x a -> Parse a x a
* Parse Var x ... -> Parse x ...
**********************************************************************/

 
parse arg old
if old = '' then
old='-6,-3--1,3-5,7-11,14,15,17-20' /*original list of nums/ranges */
 
Say 'old='old /*show old list of nums/ranges. */
a=old.translate(' ',',') /*translate commas to blanks */
new='' /*new list of numbers (so far). */
 
comma=''
Loop While a<>'' /* as long as there is input */
Parse a x a /* get one element */
dashpos=x.pos('-',2) /* find position of dash, if any */
If dashpos>0 Then Do /* element is low-high */
Parse x low =(dashpos) +1 high /* split the element */
Loop j=low To high /* output all numbers in range */
new=new||comma||j /* with separating commas */
comma=',' /* from now on use comma */
End
End
Else Do /* element is a number */
new=new||comma||x /* append (with comma) */
comma=',' /* from now on use comma */
End
End
Say 'new='new /*show the expanded list */
 

Output:

old=-6,-3--1,3-5,7-11,14,15,17-20
new=-6,-3,-2,-1,3,4,5,7,8,9,10,11,14,15,17,18,19,20   

[edit] Nimrod

import parseutils, re, strutils
 
proc expandRange(input: string): string =
var output: seq[string] = @[]
for range in input.split(','):
var sep = range.find('-', 1)
if sep > 0: # parse range
var first = -1
if range.substr(0, sep-1).parseInt(first) == 0:
break
var last = -1
if range.substr(sep+1).parseInt(last) == 0:
break
for i in first..last:
output.add($i)
else: # parse single number
var n = -1
if range.parseInt(n) > 0:
output.add($n)
else:
break
return output.join(",")
 
echo("-6,-3--1,3-5,7-11,14,15,17-20".expandRange)

Output:

-6,-3,-2,-1,3,4,5,7,8,9,10,11,14,15,17,18,19,20

[edit] Oberon-2

Oxford Oberon-2

 
MODULE LIVector;
IMPORT SYSTEM;
TYPE
LIPool = POINTER TO ARRAY OF LONGINT;
LIVector*= POINTER TO LIVectorDesc;
LIVectorDesc = RECORD
cap-: INTEGER;
len-: INTEGER;
LIPool: LIPool;
END;
 
PROCEDURE (v: LIVector) Init*(cap: INTEGER);
BEGIN
v.cap := cap;
v.len := 0;
NEW(v.LIPool,cap);
END Init;
 
PROCEDURE (v: LIVector) Add*(x: LONGINT);
VAR
newLIPool: LIPool;
BEGIN
IF v.len = LEN(v.LIPool^) THEN
(* run out of space *)
v.cap := v.cap + (v.cap DIV 2);
NEW(newLIPool,v.cap);
SYSTEM.MOVE(SYSTEM.ADR(v.LIPool^),SYSTEM.ADR(newLIPool^),v.cap * SIZE(LONGINT));
v.LIPool := newLIPool
END;
v.LIPool[v.len] := x;
INC(v.len)
END Add;
 
PROCEDURE (v: LIVector) At*(idx: INTEGER): LONGINT;
BEGIN
RETURN v.LIPool[idx];
END At;
END LIVector.
 
MODULE LIRange;
IMPORT Out, LIV := LIVector;
 
TYPE
Range* = POINTER TO RangeDesc;
RangeDesc = RECORD
l,r: POINTER TO ARRAY 1 OF LONGINT;
END;
 
PROCEDURE (r: Range) Init*();
BEGIN
r.l := NIL;
r.r := NIL;
END Init;
 
PROCEDURE (r: Range) IsEmpty*(): BOOLEAN;
BEGIN
RETURN (r.l = NIL) & (r.l = NIL);
END IsEmpty;
 
PROCEDURE (r: Range) SetLeft*(v: LONGINT);
BEGIN
IF r.l = NIL THEN NEW(r.l) END;
r.l[0] := v;
END SetLeft;
 
PROCEDURE (r: Range) SetRight*(v : LONGINT);
BEGIN
IF r.r = NIL THEN NEW(r.r) END;
r.r[0] := v;
END SetRight;
 
PROCEDURE (r: Range) LeftPart*(): BOOLEAN;
BEGIN
RETURN r.l # NIL;
END LeftPart;
 
PROCEDURE (r: Range) GetLeft(): LONGINT;
BEGIN
RETURN r.l[0];
END GetLeft;
 
PROCEDURE (r: Range) RightPart*(): BOOLEAN;
BEGIN
RETURN r.l # NIL;
END RightPart;
 
PROCEDURE (r: Range) GetRight*(): LONGINT;
BEGIN
RETURN r.r[0];
END GetRight;
 
PROCEDURE (r: Range) Show*();
BEGIN
Out.Char('(');
IF r.l # NIL THEN Out.LongInt(r.l[0],10) END;
Out.String(" - ");
IF r.r # NIL THEN Out.LongInt(r.r[0],10); END;
Out.Char(')');Out.Ln
END Show;
 
PROCEDURE (r: Range) Expand*(VAR liv: LIV.LIVector);
VAR
from, to : LONGINT;
BEGIN
IF r.l # NIL THEN from := r.l[0] ELSE from := 0 END;
IF r.r # NIL THEN to := r.r[0] ELSE to := from END;
WHILE (from <= to) DO
liv.Add(from);INC(from)
END
END Expand;
END LIRange.
 
MODULE Splitter;
TYPE
Splitter* = POINTER TO SplitterDesc;
SplitterDesc = RECORD
from: INTEGER;
c: CHAR;
s: POINTER TO ARRAY OF CHAR;
END;
 
PROCEDURE (s: Splitter) Init*;
BEGIN
s.c := ',';
s.from := 0;
s.s := NIL;
END Init;
 
PROCEDURE (s: Splitter) On*(str: ARRAY OF CHAR);
BEGIN
s.from := 0;
NEW(s.s,LEN(str));
COPY(str,s.s^)
END On;
 
PROCEDURE (s: Splitter) OnWithChar*(str: ARRAY OF CHAR;c: CHAR);
BEGIN
s.from := 0;
s.c := c;
NEW(s.s,LEN(str));
COPY(str,s.s^)
END OnWithChar;
 
PROCEDURE (s: Splitter) Next*(VAR str: ARRAY OF CHAR);
VAR
k : INTEGER;
BEGIN
k := 0;
IF (s.from < LEN(s.s^) - 1) & (s.s[s.from] = 0X) THEN str[0] := 0X END;
WHILE (k < LEN(str) - 1) & (s.from < LEN(s.s^) - 1) & (s.s[s.from] # s.c) DO
str[k] := s.s[s.from];
INC(k);INC(s.from)
END;
IF k < LEN(str) - 1 THEN str[k] := 0X ELSE str[LEN(str) - 1] := 0X END;
WHILE (s.from < LEN(s.s^) - 1) & (s.s[s.from] # s.c) DO INC(s.from) END;
INC(s.from)
END Next;
END Splitter.
 
MODULE ExpandRange;
IMPORT Out, LIV := LIVector, LIR := LIRange, S := Splitter;
 
PROCEDURE GetNumberFrom(s: ARRAY OF CHAR; VAR from: INTEGER; VAR done: BOOLEAN): LONGINT;
VAR
d,i: INTEGER;
num,sign: LONGINT;
BEGIN
i := from; num := 0;sign := 1;
CASE s[i] OF
'-': sign := -1;INC(i)
|'+': INC(i);
ELSE
END;
WHILE (i < LEN(s) - 1) & (s[i] >= '0') & (s[i] <= '9') DO
d := ORD(s[i]) - ORD('0');
num := d + num * 10;
INC(i);
END;
IF i = from THEN done := FALSE ELSE done := TRUE; from := i END;
RETURN sign * num
END GetNumberFrom;
 
PROCEDURE GetRange(s: ARRAY OF CHAR): LIR.Range;
VAR
r: LIR.Range;
i: INTEGER;
num: LONGINT;
done: BOOLEAN;
BEGIN
i := 0;NEW(r);r.Init();
WHILE (i < LEN(s) - 1) & (s[i] = 20X) DO INC(i) END;
(* Left value *)
done := FALSE;
num := GetNumberFrom(s,i,done);
IF ~done THEN RETURN r END;
r.SetLeft(num);
 
WHILE (i < LEN(s) - 1) & (s[i] = 20X) DO INC(i) END;
CASE s[i] OF
'-' : INC(i);
| 0X : RETURN r;
ELSE
END;
WHILE (i < LEN(s) - 1) & (s[i] = 20X) DO INC(i) END;
 
(* Right Value *)
done := FALSE;
num := GetNumberFrom(s,i,done);
IF ~done THEN RETURN r END;
r.SetRight(num);
RETURN r;
END GetRange;
 
VAR
i: INTEGER;
r: LIR.Range;
sp: S.Splitter;
p : ARRAY 128 OF CHAR;
liv: LIV.LIVector;
BEGIN
NEW(sp);sp.Init();
NEW(liv);liv.Init(128);
 
sp.On("-6,-3--1,3-5,7-11,14,15,17-20");
sp.Next(p);
WHILE (p[0] # 0X) DO
r := GetRange(p);
r.Expand(liv);
sp.Next(p);
END;
FOR i := 0 TO liv.len - 2 DO
Out.LongInt(liv.At(i),3);Out.Char(',');
END;
Out.LongInt(liv.At(liv.len - 1),3);Out.Ln;
END ExpandRange.
 
 

Output:

-6, -3, -2, -1,  3,  4,  5,  7,  8,  9, 10, 11, 14, 15, 17, 18, 19, 20

[edit] OCaml

#load "str.cma"
 
let range a b =
if b < a then invalid_arg "range";
let rec aux i acc =
if i = b then List.rev(i::acc)
else aux (succ i) (i::acc)
in
aux a []
 
let parse_piece s =
try Scanf.sscanf s "%d-%d" (fun a b -> range a b)
with _ -> [int_of_string s]
 
let range_expand rng =
let ps = Str.split (Str.regexp_string ",") rng in
List.flatten (List.map parse_piece ps)
 
let () =
let rng = "-6,-3--1,3-5,7-11,14,15,17-20" in
let exp = range_expand rng in
List.iter (Printf.printf " %d") exp;
print_newline()

[edit] ooRexx

 
list = '-6,-3--1,3-5,7-11,14,15,17-20'
expanded = expandRanges(list)
 
say "Original list: ["list"]"
say "Expanded list: ["expanded~tostring("l", ",")"]"
 
-- expand a string expression a range of numbers into a list
-- of values for the range. This returns an array
::routine expandRanges
use strict arg list
values = list~makearray(',')
-- build this up using an array first. Make this at least the
-- size of the original value set.
expanded = .array~new(values~items)
 
-- now process each element in the range
loop element over values
-- if this is a valid number, it's not a range, so add it directly
if element~datatype('whole') then expanded~append(element)
else do
-- search for the divider, starting from the second position
-- to allow for the starting value to be a minus sign.
split = element~pos('-', 2)
parse var element start =(split) +1 finish
loop i = start to finish
expanded~append(i)
end
end
end
return expanded
 

Output:

Original list: [-6,-3--1,3-5,7-11,14,15,17-20]
Expanded list: [-6,-3,-2,-1,3,4,5,7,8,9,10,11,14,15,17,18,19,20]

[edit] Oz

declare
fun {Expand RangeDesc}
{Flatten
{Map {ParseDesc RangeDesc}
ExpandRange}}
end
 
fun {ParseDesc Txt}
{Map {String.tokens Txt &,} ParseRange}
end
 
fun {ParseRange R}
if {Member &- R.2} then
First Second
in
{String.token R.2 &- ?First ?Second}
{String.toInt R.1|First}#{String.toInt Second}
else
Singleton = {String.toInt R}
in
Singleton#Singleton
end
end
 
fun {ExpandRange From#To}
{List.number From To 1}
end
in
{System.showInfo
{Value.toVirtualString {Expand "-6,-3--1,3-5,7-11,14,15,17-20"} 100 100}}
Sample output:
[~6 ~3 ~2 ~1 3 4 5 7 8 9 10 11 14 15 17 18 19 20]

[edit] Perl

One-liner:

sub rangex {
map { /^(.*\d)-(.+)$/ ? $1..$2 : $_ } split /,/, shift
}
 
# Test and display
print join(',', rangex('-6,-3--1,3-5,7-11,14,15,17-20')), "\n";
Output:
-6,-3,-2,-1,3,4,5,7,8,9,10,11,14,15,17,18,19,20

Alternative:

sub rangex {
(my $range = shift) =~ s/(?<=\d)-/../g;
eval $range;
}

[edit] Perl 6

sub range-expansion (Str $range-description) {
my $range-pattern = rx/ ( '-'? \d+ ) '-' ( '-'? \d+) /;
my &expand = -> $term { $term ~~ $range-pattern ?? +$0..+$1 !! $term };
return $range-description.split(',').map(&expand)
}
 
say range-expansion('-6,-3--1,3-5,7-11,14,15,17-20').join(', ');
Output:
-6, -3, -2, -1, 3, 4, 5, 7, 8, 9, 10, 11, 14, 15, 17, 18, 19, 20

[edit] PHP

Translation of: Python
function rangex($str) {
$lst = array();
foreach (explode(',', $str) as $e) {
if (strpos($e, '-', 1) !== FALSE) {
list($a, $b) = explode('-', substr($e, 1), 2);
$lst = array_merge($lst, range($e[0] . $a, $b));
} else {
$lst[] = (int) $e;
}
}
return $lst;
}

[edit] PicoLisp

(de rangeexpand (Str)
(make
(for S (split (chop Str) ",")
(if (index "-" (cdr S))
(chain
(range
(format (head @ S))
(format (tail (- -1 @) S)) ) )
(link (format S)) ) ) ) )
Output:
: (rangeexpand "-6,-3--1,3-5,7-11,14,15,17-20")
-> (-6 -3 -2 -1 3 4 5 7 8 9 10 11 14 15 17 18 19 20)

[edit] PL/I

range_expansion:
procedure options (main);
 
get_number:
procedure (Number, c, eof);
declare number fixed binary (31), c character (1), eof bit (1) aligned;
declare neg fixed binary (1);
 
number = 0; eof = false;
do until (c ^= ' ');
get edit (c) (a(1));
end;
if c = '-' then do; get edit (c) (a(1)); neg = -1; end; else neg = 1;
do forever;
select (c);
when ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9')
number = number*10 + c;
when (',', '-') do; number = neg*number; return; end;
otherwise signal error;
end;
on endfile (sysin) go to exit;
get edit (c) (a(1));
end;
exit:
number = neg*number;
eof = true;
end get_Number;
 
declare c character, (i, range_start, range_end) fixed binary (31);
declare eof bit (1) aligned;
declare true bit (1) value ('1'b), false bit (1) value ('0'b);
declare delimiter character (1) initial (' ');
declare out file output;
 
open file (out) output title ('/out, type(text),recsize(80)');
do while (^eof);
call get_number(range_start, c, eof);
if c = '-' then /* we have a range */
do;
call get_number (range_end, c, eof);
do i = range_start to range_end;
put file (out) edit (delimiter, i) (a, f(3));
end;
end;
else
do;
put file (out) edit (delimiter, range_start) (a, f(3));
end;
delimiter = ',';
end;
end range_expansion;
Output:
  -6, -3, -2, -1,  3,  4,  5,  7,  8,  9, 10, 11, 14, 15, 17, 18, 19, 20

[edit] Prolog

Works with: SWI Prolog
Library: clpfd

The code uses three predicates extract_Range/2, study_Range/2 and pack_Range/2.
Every predicate works in both directions arg1 towards arg2 and arg2 towards arg1, so that Range expansion and Range extraction work with the same predicates but in reverse order.

range_expand :-
L = '-6,-3--1,3-5,7-11,14,15,17-20',
writeln(L),
atom_chars(L, LA),
extract_Range(LA, R),
maplist(study_Range, R, LR),
pack_Range(LX, LR),
writeln(LX).
 
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% extract_Range(?In, ?Out)
% In  : '-6,-3--1,3-5,7-11,14,15,17-20'
% Out : [-6], [-3--1], [3-5],[7-11], [14],[15], [17-20]
%
extract_Range([], []).
 
extract_Range(X , [Range | Y1]) :-
get_Range(X, U-U, Range, X1),
extract_Range(X1, Y1).
 
get_Range([], Range-[], Range, []).
get_Range([','|B], Range-[], Range, B) :- !.
 
get_Range([A | B], EC, Range, R) :-
append_dl(EC, [A | U]-U, NEC),
get_Range(B, NEC, Range, R).
 
 
append_dl(X-Y, Y-Z, X-Z).
 
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% study Range(?In, ?Out)
% In  : [-6]
% Out : [-6,-6]
%
% In  : [-3--1]
% Out : [-3, -1]
%
study_Range(Range1, [Deb, Deb]) :-
catch(number_chars(Deb, Range1), Deb, false).
 
study_Range(Range1, [Deb, Fin]) :-
append(A, ['-'|B], Range1),
A \= [],
number_chars(Deb, A),
number_chars(Fin, B).
 
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
:- use_module(library(clpfd)).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% Pack Range(?In, ?Out)
% In  : -6,
% Out : [-6]
%
% In  : -3, -2,-1
% Out : [-3,-1]
%
pack_Range([],[]).
 
pack_Range([X|Rest],[[X | V]|Packed]):-
run(X,Rest, [X|V], RRest),
pack_Range(RRest,Packed).
 
 
run(Fin,[Other|RRest], [Deb, Fin],[Other|RRest]):-
Fin #\= Deb,
Fin #\= Deb + 1,
Other #\= Fin+1.
 
run(Fin,[],[_Var, Fin],[]).
 
run(Var,[Var1|LRest],[Deb, Fin], RRest):-
Fin #\= Deb,
Fin #\= Deb + 1,
Var1 #= Var + 1,
run(Var1,LRest,[Deb, Fin], RRest).
 
run(Val,[Other|RRest], [Val, Val],[Other|RRest]).
Output:
 ?- range_expand.
-6,-3--1,3-5,7-11,14,15,17-20
[-6,-3,-2,-1,3,4,5,7,8,9,10,11,14,15,17,18,19,20]
true

[edit] PureBasic

Procedure rangeexpand(txt.s, List outputList())
Protected rangesCount = CountString(txt, ",") + 1
Protected subTxt.s, r, rangeMarker, rangeStart, rangeFinish, rangeIncrement, i
 
LastElement(outputList())
For r = 1 To rangesCount
subTxt = StringField(txt, r, ",")
rangeMarker = FindString(subTxt, "-", 2)
If rangeMarker
rangeStart = Val(Mid(subTxt, 1, rangeMarker - 1))
rangeFinish = Val(Mid(subTxt, rangeMarker + 1))
 
If rangeStart > rangeFinish
rangeIncrement = -1
Else
rangeIncrement = 1
EndIf
 
i = rangeStart - rangeIncrement
Repeat
i + rangeIncrement
AddElement(outputList()): outputList() = i
Until i = rangeFinish
Else
AddElement(outputList()): outputList() = Val(subTxt)
EndIf
Next
EndProcedure
 
Procedure outputListValues(List values())
Print("[ ")
ForEach values()
Print(Str(values()) + " ")
Next
PrintN("]")
EndProcedure
 
If OpenConsole()
NewList values()
rangeexpand("-6,-3--1,3-5,7-11,14,15,17-20", values())
outputListValues(values())
 
Print(#CRLF$ + #CRLF$ + "Press ENTER to exit")
Input()
CloseConsole()
EndIf
Output:
[ -6 -3 -2 -1 3 4 5 7 8 9 10 11 14 15 17 18 19 20 ]

[edit] Python

def rangeexpand(txt):
lst = []
for r in txt.split(','):
if '-' in r[1:]:
r0, r1 = r[1:].split('-', 1)
lst += range(int(r[0] + r0), int(r1) + 1)
else:
lst.append(int(r))
return lst
 
print(rangeexpand('-6,-3--1,3-5,7-11,14,15,17-20'))

Another variant, using regular expressions to parse the ranges:

import re
 
def rangeexpand(txt):
lst = []
for rng in txt.split(','):
start,end = re.match('^(-?\d+)(?:-(-?\d+))?$', rng).groups()
if end:
lst.extend(xrange(int(start),int(end)+1))
else:
lst.append(int(start))
return lst
Output:
[-6, -3, -2, -1, 3, 4, 5, 7, 8, 9, 10, 11, 14, 15, 17, 18, 19, 20]

[edit] R

 
rangeExpand <- function(text) {
lst <- gsub("(\\d)-", "\\1:", unlist(strsplit(text, ",")))
unlist(sapply(lst, function (x) eval(parse(text=x))), use.names=FALSE)
}
 
rangeExpand("-6,-3--1,3-5,7-11,14,15,17-20")
[1] -6 -3 -2 -1 3 4 5 7 8 9 10 11 14 15 17 18 19 20
 

[edit] Racket

 
#lang racket
 
(define (range-expand s)
(append*
(for/list ([r (regexp-split "," s)])
(match (regexp-match* "(-?[0-9]+)-(-?[0-9]+)" r
#:match-select cdr)
[(list (list f t))
(range (string->number f) (+ (string->number t) 1))]
[(list)
(list (string->number r))]))))
 
(range-expand "-6,-3--1,3-5,7-11,14,15,17-20")
 

Output:

'(-6 -3 -2 -1 3 4 5 7 8 9 10 11 14 15 17 18 19 20)


Here is an alternative version without regular expressions. It uses the builtin function read to read the numbers. Since 3--4 is normally parsed as a symbol rather than 3 followed by - followed by -4, a readtable is installed that makes - a delimiter.

 
#lang racket
 
(define on-minus
(case-lambda
[(ch ip) (on-minus ch ip #f #f #f #f)]
[(ch ip src line col pos)
(if (char-numeric? (peek-char ip))
(- (read ip))
(datum->syntax #f '-))]))
 
(define minus-delimits
(make-readtable (current-readtable) #\- 'terminating-macro on-minus))
 
(define (range-expand s)
(parameterize ([current-readtable minus-delimits])
(append*
(for/list ([f (in-port read s)])
(match (peek-char s)
[#\, (read-char s)
(list f)]
[#\- (read-char s)
(define t (read s))
(read-char s)
(range f (+ t 1))])))))
 
(range-expand (open-input-string "-6,-3--1,3-5,7-11,14,15,17-20"))
 

Note that one can use the full number syntax in this alternative version:

> (range-expand (open-input-string "1-6/3,3e1-32"))
'(1 2 30.0 31.0 32.0)

[edit] Raven

Based loosely on Ruby

define get_num use $lst
# "-22" split by "-" is [ "", "22" ] so check if
# first list item is "" -> a negative number
$lst 0 get "" = if
# negative number
#
# convert str to integer and multiply by -1
-1 $lst 1 get 0 prefer *
$lst shift $lst shift drop drop
else
# positive number
$lst 0 get 0 prefer
$lst shift drop
 
define range_expand use $rng
[ ] as $res
$rng "," split each as $r
$r m/^(-?\d+)-(-?\d+)$/ TRUE = if
$r s/-/g as $parts
$parts get_num as $from
$parts get_num as $to
# int list to str list, then joined by ","
group
$from $to 1 range each "" prefer
list "," join $res push
# range doesn't include the $to, so add to end of generated range
$to "%d" $res push
else
$r $res push
$res "," join print
"\n" print
 
'-6,-3--1,3-5,7-11,14,15,17-20' range_expand
Output:
-6,-3,-2,-1,3,4,5,7,8,9,10,11,14,15,17,18,19,20

[edit] REXX

[edit] version 1

Extra blanks were added to the original list (which are ignored) to make an over/under comparison easier.

/*REXX program expands an ordered list of integers into an expanded list*/
old='-6,-3--1, 3-5, 7-11,14,15, 17-20'; a=translate(old,,',')
new= /*change , ──► blanks [↑] */
do until a==''; parse var a X a /*obtain the next integer|range. */
p=pos('-',X,2) /*find location of a dash (maybe)*/
if p==0 then new=new X /*append X to the new list. */
else do j=left(X,p-1) to substr(X,p+1) /*Range? Build it*/
new=new j /*append single integer at a time*/
end /*j*/
end /*until*/
 
new=translate( strip(new), ',' ," ") /*remove first blank, add commas.*/
say 'old list =' old /*show old list of numbers/ranges*/
say 'new list =' new /*show the new list of numbers.*/
/*stick a fork in it, we're done.*/

output

old list = -6,-3--1,   3-5,  7-11,14,15,       17-20
new list = -6,-3,-2,-1,3,4,5,7,8,9,10,11,14,15,17,18,19,20

[edit] Version 2 somewhat simplified !?!

/*REXX program to expand a range of integers into a list. *************
* 09.08.2012 Walter Pachl
**********************************************************************/

 
parse arg old
if old = '' then -
old='-6,-3--1,3-5,7-11,14,15,17-20' /*original list of nums/ranges */
 
Say 'old='old /*show old list of nums/ranges. */
a=translate(old,,',') /*translate commas to blanks */
new='' /*new list of numbers (so far). */
 
comma=''
Do While a<>'' /* as long as there is input */
Parse var a x a /* get one element */
dashpos=pos('-',x,2) /* find position of dash, if any */
If dashpos>0 Then Do /* element is low-high */
Parse Var x low =(dashpos) +1 high /* split the element */
Do j=low To high /* output all numbers in range */
new=new||comma||j /* with separating commas */
comma=',' /* from now on use comma */
End
End
Else Do /* element is a number */
new=new||comma||x /* append (with comma) */
comma=',' /* from now on use comma */
End
End
Say 'new='new /*show the expanded list */

Output:

old=-6,-3--1,3-5,7-11,14,15,17-20
new=-6,-3,-2,-1,3,4,5,7,8,9,10,11,14,15,17,18,19,20   

[edit] Ruby

def range_expand(rng)
rng.split(',').collect do |part|
if part =~ /^(-?\d+)-(-?\d+)$/
($1.to_i .. $2.to_i).to_a
else
Integer(part)
end
end.flatten
end
 
p range_expand('-6,-3--1,3-5,7-11,14,15,17-20')
Output:
[-6, -3, -2, -1, 3, 4, 5, 7, 8, 9, 10, 11, 14, 15, 17, 18, 19, 20]

[edit] Run BASIC

PRINT rangeExpand$("-6,-3--1,3-5,7-11,14,15,17-20")
end
 
function rangeExpand$(range$)
[loop]
i = INSTR(range$, "-", i+1)
IF i THEN
j = i
WHILE MID$(range$,j-1,1) <> "," AND j <> 1
j = j - 1
wend
IF i > j then
IF MID$(range$,j,i-j) <> str$(i-j)+" " THEN
t$ = ""
FOR k = VAL(MID$(range$,j)) TO VAL(MID$(range$,i+1))-1
t$ = t$ + str$(k) + ","
NEXT k
range$ = LEFT$(range$,j-1) + t$ + MID$(range$,i+1)
i = j + LEN(t$) + 2
end if
end if
end if
if i <> 0 then goto [loop]
rangeExpand$ = range$
end function
-6,-3,-2,-1,3,4,5,7,8,9,10,11,14,15,17,18,19,20

[edit] Scala

def rangex(str: String): Seq[Int] =
str split "," flatMap { (s) =>
val r = """(-?\d+)(?:-(-?\d+))?""".r
val r(a,b) = s
if (b == null) Seq(a.toInt) else a.toInt to b.toInt
}
Output:
> println(rangex("-6,-3-1,3-5,7-11,14,15,17-20"))
ArraySeq(-6, -3, -2, -1, 0, 1, 3, 4, 5, 7, 8, 9, 10, 11, 14, 15, 17, 18, 19, 20)
> println(rangex("-6,-3--1,3-5,7-11,14,15,17-20"))
ArraySeq(-6, -3, -2, -1, 3, 4, 5, 7, 8, 9, 10, 11, 14, 15, 17, 18, 19, 20)

[edit] Scheme

(define split
(lambda (str char skip count)
(let ((len (string-length str)))
(let loop ((index skip)
(last-index 0)
(result '()))
(if (= index len)
(reverse (cons (substring str last-index) result))
(if (eq? char (string-ref str index))
(loop (if (= count (+ 2 (length result)))
len
(+ index 1))
(+ index 1)
(cons char (cons (substring str last-index index)
result)))
(loop (+ index 1)
last-index
result)))))))
 
(define range-expand
(lambda (str)
(for-each
(lambda (token)
(if (char? token)
(display token)
(let ((range (split token #\- 1 2)))
(if (null? (cdr range))
(display (car range))
(do ((count (string->number (list-ref range 0)) (+ 1 count))
(high (string->number (list-ref range 2))))
((= count high) (display high))
(display count)
(display ","))))))
(split str #\, 0 0))
(newline)))
Output:
(range-expand "-6,-3--1,3-5,7-11,14,15,17-20")
-6,-3,-2,-1,3,4,5,7,8,9,10,11,14,15,17,18,19,20

[edit] Seed7

The library scanstri.s7i defines the function getInteger to extract substrings with integer literals (optional sign followed by a sequence of digits) from a string. The integer literals are converted to the type integer with the parse operator.

$ include "seed7_05.s7i";
include "scanstri.s7i";
 
const func array integer: rangeExpansion (in var string: rangeStri) is func
result
var array integer: numbers is 0 times 0;
local
var integer: number is 0;
begin
while rangeStri <> "" do
number := integer parse getInteger(rangeStri);
numbers &:= number;
if startsWith(rangeStri, "-") then
rangeStri := rangeStri[2 ..];
for number range succ(number) to integer parse getInteger(rangeStri) do
numbers &:= number;
end for;
end if;
if startsWith(rangeStri, ",") then
rangeStri := rangeStri[2 ..];
elsif rangeStri <> "" then
raise RANGE_ERROR;
end if;
end while;
end func;
 
const proc: main is func
local
var integer: number is 0;
begin
for number range rangeExpansion("-6,-3--1,3-5,7-11,14,15,17-20") do
write(number <& " ");
end for;
writeln;
end func;
Output:
-6 -3 -2 -1 3 4 5 7 8 9 10 11 14 15 17 18 19 20 

[edit] Sidef

func rangex(str) {
str.split(',').map { |range|
var match = range.match(/^
(?(DEFINE)
(?<int>[+-]?[0-9]+)
)
(?<from>(?&int))-(?<to>(?&int))
$/x
);
 
match.is_successful ? (var ncap = match.named_captures;
ncap[:from].to_i .. ncap[:to].to_i)
 : [range.to_num];
}
}
 
# Test and display
say rangex('-6,-3--1,3-5,7-11,14,15,17-20').flatten.join(',');
Output:
-6,-3,-2,-1,3,4,5,7,8,9,10,11,14,15,17,18,19,20

[edit] SNOBOL4

*       # Return range n1 .. n2        
define('range(n1,n2)') :(range_end)
range range = range n1 ','; n1 = lt(n1,n2) n1 + 1 :s(range)
range rtab(1) . range :(return)
range_end
 
define('rangex(range)d1,d2')
num = ('-' | '') span('0123456789') :(rangex_end)
rangex range num . d1 '-' num . d2 = range(d1,d2) :s(rangex)
rangex = range :(return)
rangex_end
 
* # Test and display
output = rangex('-6,-3--1,3-5,7-11,14,15,17-20')
end
Output:
-6,-3,-2,-1,3,4,5,7,8,9,10,11,14,15,17,18,19,20

[edit] Tcl

proc rangeExpand desc {
set result {}
foreach term [split $desc ","] {
set count [scan $term %d-%d from to]
if {$count == 1} {
lappend result $from
} elseif {$count == 2} {
for {set i $from} {$i <= $to} {incr i} {lappend result $i}
}
}
return $result
}
 
puts [rangeExpand "-6,-3--1,3-5,7-11,14,15,17-20"]
Output:
-6 -3 -2 -1 3 4 5 7 8 9 10 11 14 15 17 18 19 20

[edit] TUSCRIPT

$$ MODE TUSCRIPT
rangednrs="-6,-3--1,3-5,7-11,14,15,17-20"
expandnrs=SPLIT (rangednrs,":,:")
 
LOOP/CLEAR r=expandnrs
test=STRINGS (r,":><-><<>>/:")
sz_test=SIZE (test)
IF (sz_test==1) THEN
expandnrs=APPEND (expandnrs,r)
ELSE
r=SPLIT (r,"::<|->/::-:",beg,end)
expandnrs=APPEND (expandnrs,beg)
LOOP/CLEAR next=beg,end
next=next+1
expandnrs=APPEND (expandnrs,next)
IF (next==end) EXIT
ENDLOOP
ENDIF
ENDLOOP
expandnrs= JOIN (expandnrs,",")
 
PRINT expandnrs
Output:
-6,-3,-2,-1,3,4,5,7,8,9,10,11,14,15,17,18,19,20 

[edit] TXR

A solution with three main parts:

  • a parse-expression-grammar driven parser to decimate the input to a Lisp data structure;
  • some Lisp code to expand the list, sort it, and remove duplicates (recursion, hashing, sorting).
  • driver code which matches the input with the grammar, and produces output with the help of the Lisp code.

The grammar is:

num := [ + | - ] { digit } +

entry := num [ ws ] - [ ws ] num
      |  num

rangelist := entry [ ws ] , [ ws ] rangelist
          |  entry
          |  /* empty */

Code:

@(define num (n))@(local tok)@{tok /[+\-]?\d+/}@(bind n @(int-str tok))@(end)
@(define entry (e))@\
@(local n1 n2)@\
@(cases)@\
@(num n1)@/\s*-\s*/@(num n2)@\
@(bind e (n1 n2))@\
@(or)@\
@(num n1)@\
@(bind e n1)@\
@(end)@\
@(end)
@(define rangelist (list))@\
@(local first rest)@\
@(cases)@\
@(entry first)@/\s*,\s*/@(rangelist rest)@\
@(bind list @(cons first rest))@\
@(or)@\
@(entry first)@\
@(bind list (first))@\
@(or)@\
@(bind list nil)@\
@(end)@\
@(end)
@(do
(defun expand-helper (list)
(cond
((null list) nil)
((consp (first list))
(append (range (first (first list))
(second (first list)))
(rangeexpand (rest list))))
(t (cons (first list) (rangeexpand (rest list))))))
 
(defun sortdup (li)
(let ((h [group-by identity li]))
[sort (hash-keys h) <]))
 
(defun rangeexpand (list)
(sortdup (expand-helper list))))
@(repeat)
@(rangelist x)@{trailing-junk}
@(output)
raw syntax: @x
expansion: @(rangeexpand x)
your junk: @{trailing-junk}
@(end)
@(end)

Run:

$ txr range-expansion.txr -
1,2,3-5,-3--1
raw syntax: 1 2 (3 5) (-3 -1)
expansion:  (-3 -2 -1 1 2 3 4 5)
your junk:
-6,-3--1,3-5,7-11,14,15,17-20
raw syntax: -6 (-3 -1) (3 5) (7 11) 14 15 (17 20)
expansion:  (-6 -3 -2 -1 3 4 5 7 8 9 10 11 14 15 17 18 19 20)
your junk:
-6,-3--1,3-5,7-11,14,15,17-20,cg@foo
raw syntax: -6 (-3 -1) (3 5) (7 11) 14 15 (17 20)
expansion:  (-6 -3 -2 -1 3 4 5 7 8 9 10 11 14 15 17 18 19 20)
your junk:  cg@foo

Note how the junk in the last example does not contain the trailing comma. This is because the rangelist grammar production allows for an empty range, so syntax like "5," is valid: it's an entry followed by a comma and a rangelist, where the rangelist is empty.

[edit] UNIX Shell

Works with: bash
#!/usr/bin/bash
 
range_expand () (
IFS=,
set -- $1
n=$#
for element; do
if [[ $element =~ ^(-?[0-9]+)-(-?[0-9]+)$ ]]; then
set -- "$@" $(eval echo "{${BASH_REMATCH[1]}..${BASH_REMATCH[2]}}")
else
set -- "$@" $element
fi
done
shift $n
echo "$@"
# to return a comma-separated value: echo "${*// /,}"
)
 
range_expand "-6,-3--1,3-5,7-11,14,15,17-20"
Output:
-6 -3 -2 -1 3 4 5 7 8 9 10 11 14 15 17 18 19 20

[edit] Ursala

#import std
#import int
 
rex = sep`,; zrange+*= %zp~~htttPzztPQhQXbiNC+ rlc ~&r~=`-
 
#cast %zL
 
t = rex '-6,-3--1,3-5,7-11,14,15,17-20'
Output:
<-6,-3,-2,-1,3,4,5,7,8,9,10,11,14,15,17,18,19,20>

[edit] VBA

Public Function RangeExpand(AString as string)
' return a list with the numbers expressed in AString
Dim Splits() As String
Dim List() As Integer
Dim count As Integer
 
count = -1 'to start a zero-based List() array
' first split it using comma as delimiter
Splits = Split(AString, ",")
' process all fragments
For Each fragment In Splits
'is there a "-" in it (do not consider first character)?
P = InStr(2, fragment, "-")
If P > 0 Then 'yes, so it's a range: find start and end numbers
nstart = Val(left$(fragment, P - 1))
nend = Val(Mid$(fragment, P + 1))
j = count
count = count + (nend - nstart + 1)
'add numbers in range to List
ReDim Preserve List(count)
For i = nstart To nend
j = j + 1
List(j) = i
Next
Else
'not a range, add a single number
count = count + 1
ReDim Preserve List(count)
List(count) = Val(fragment)
End If
Next
RangeExpand = List
End Function
 
Public Sub RangeExpandTest()
'test function RangeExpand
Dim X As Variant
 
X = RangeExpand("-6,-3--1,3-5,7-11,14,15,17-20")
'print X
Debug.Print "Result:"
For Each el In X
Debug.Print el;
Next
Debug.Print
End Sub
Output:
RangeExpandTest
Result:
-6 -3 -2 -1  3  4  5  7  8  9  10  11  14  15  17  18  19  20 

[edit] XPL0

include c:\cxpl\codes;          \intrinsic 'code' declarations
string 0; \use zero-terminated strings, instead of MSb
char Str;
int Char, Inx;
 
 
proc GetCh; \Get character from Str
[Char:= Str(Inx);
Inx:= Inx+1;
]; \GetCh
 
 
func GetNum; \Get number from Str and return its value
int Neg, Num;
[Neg:= false;
if Char = ^- then [Neg:= true; GetCh];
Num:= 0;
while Char>=^0 & Char<=^9 do
[Num:= Num*10 + Char-^0;
GetCh;
];
return if Neg then -Num else Num;
]; \GetNum
 
 
int I, N0, N1;
[Str:= "-6,-3--1,3-5,7-11,14,15,17-20";
Inx:= 0;
GetCh; \one character look ahead
loop [N0:= GetNum;
IntOut(0,N0);
case Char of
^,: [GetCh; ChOut(0,^,)];
^-: [GetCh;
N1:= GetNum;
for I:= N0+1 to N1 do \expand range
[ChOut(0,^,); IntOut(0,I)];
if Char=^, then [GetCh; ChOut(0,^,)] else quit]
other quit; \must be 0 string terminator
];
CrLf(0);
]

Output:

-6,-3,-2,-1,3,4,5,7,8,9,10,11,14,15,17,18,19,20

[edit] zkl

fcn rangex(s){
fcn(s,re){
if (re.search(s)){
a,b:=re.matched[1,*].apply("toInt");
[a..b].walk();
}
else s;
} :
s.split(",").pump(List, _.fp1(RegExp(0'|(.*\d+)-(.*\d+)|)))
.flatten().concat(",");
}
ns="-6,-3-1,3-5,7-11,14,15,17-20";
rangex(ns).println();
 
ns="-6,-3--1,3-5,7-11,14,15,17-20";
rangex(ns).println();
Output:
-6,-3,-2,-1,0,1,3,4,5,7,8,9,10,11,14,15,17,18,19,20
-6,-3,-2,-1,3,4,5,7,8,9,10,11,14,15,17,18,19,20
Personal tools
Namespaces

Variants
Actions
Community
Explore
Misc
Toolbox