Range expansion

From Rosetta Code
Revision as of 14:29, 13 June 2013 by rosettacode>IanOsgood (add forth)
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

Ada

The function Expand takes a string and returns a corresponding array of integers. Upon syntax errors Constraint_Error is propagated: <lang Ada>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;</lang>

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

ALGOL 68

This example is incorrect. Please fix the code and remove this message.

Details: The task example was modified after discussion in the talk page.

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
Works with: ELLA ALGOL 68 version Any (with appropriate job cards) - tested with release 1.8-8d

<lang algol68>MODE YIELDINT = PROC(INT)VOID;

MODE RANGE = STRUCT(INT lwb, upb); MODE RANGEINT = UNION(RANGE, INT);

OP SIZEOF = ([]RANGEINT list)INT: (

  1. 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;
  1. FOR INT value IN # gen range expand(list, # ) DO #
    1. (INT value)VOID:
   out[upb +:= 1] := value
  1. 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))

)</lang>

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

AutoHotkey

<lang 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)

}</lang>

BBC BASIC

<lang bbcbasic> 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$</lang>

Output:

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

Bracmat

<lang 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")) </lang> Output:

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

C

Recursive descent parser. <lang c>#include <stdio.h>

  1. include <stdlib.h>
  2. 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);

  1. define skip_space while(isspace(*s)) s++
  2. 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; }</lang>

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

C#

Works with: C sharp version 3.0

<lang csharp>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+)-(?-?\d+))|(-?\d+)");
       var values = new List<string>();
       foreach (var m in matches.OfType<Match>())
       {
           if (m.Length == 2)
           {
               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));
   }

}</lang>

C++

<lang cpp>#include <iostream>

  1. include <sstream>
  2. include <iterator>
  3. include <climits>
  4. 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.";

}</lang>

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

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). <lang clojure>(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)</lang>

Common Lisp

<lang 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)</lang>

D

Translation of: Python

<lang d>import std.stdio, std.regex, std.string, std.conv, std.range,

      std.algorithm;

int[] rangeExpand(in string txt) /*pure nothrow*/ {

   return txt.split(",").map!((r) {
       const m = r.match(r"^(-?\d+)(-?(-?\d+))?$").captures.array;
       return m[2].empty ? [m[1].to!int] :
                           iota(m[1].to!int, m[3].to!int + 1).array;
   }).join.array;

}

void main() {

   "-6,-3--1,3-5,7-11,14,15,17-20".rangeExpand.writeln;

}</lang>

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

Forth

<lang 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</lang>

F#

<lang fsharp>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")</lang>

Output:
[-6; -3; -2; -1; 3; 4; 5; 7; 8; 9; 10; 11; 14; 15; 17; 18; 19; 20]

Go

A version rather strict with input <lang go>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)

}</lang>

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

<lang groovy>def expandRanges = { compressed ->

   Eval.me('['+compressed.replaceAll(~/(\d)-/, '$1..')+']').flatten().toString()[1..-2]

}</lang> Test: <lang groovy>def s = '-6,-3--1,3-5,7-11,14,15,17-20' println (expandRanges(s))</lang>

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

Haskell

Given either of the below implementations of expandRange: <lang haskell>> 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]</lang>

With conventional list processing

<lang haskell>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</lang>

With a parser

<lang haskell>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)</lang>

Icon and Unicon

<lang Icon>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</lang>

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 ]

J

<lang j>require'strings' to=: [ + i.@>:@-~ num=: _&". normaliz=: rplc&(',-';',_';'--';'-_')@,~&',' subranges=:<@(to/)@(num;._2)@,&'-';._1 rngexp=: ;@subranges@normaliz</lang>

Example:

<lang j> 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</lang>

Notes:

to: 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 to/ 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.

Java

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

}</lang>

JavaScript

Spidermonkey version

<lang javascript>#!/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(); </lang>

Example output:

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

K

<lang 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}</lang>

Example:

<lang k> 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</lang>

Liberty BASIC

<lang lb>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</lang>

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

Maple

<lang 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: </lang> Running this on the example input we get the following. <lang Maple> > 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]

</lang> Here is an additional example which my first attempt got wrong. <lang Maple> > 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] </lang>

Mathematica

<lang 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<>"]"] ]</lang>

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}

MATLAB / Octave

<lang MATLAB>function L=range_expansion(S) % Range expansion if nargin<0; 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);</lang> 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

MUMPS

<lang 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</lang>
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

NetRexx

Translation of: Rexx Version 2 <lang NetRexx>/*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         */

</lang> 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   

Oberon-2

Oxford Oberon-2 <lang oberon2> 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.

</lang> Output:

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

OCaml

<lang 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()</lang>

ooRexx

<lang 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

</lang> 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]

Oz

<lang 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}}</lang>
Sample output:

<lang oz>[~6 ~3 ~2 ~1 3 4 5 7 8 9 10 11 14 15 17 18 19 20]</lang>

Perl

One-liner: <lang Perl>sub rangex {

   map { /^(.*\d)-(.+)$/ ? $1..$2 : $_ } split /,/, shift

}

  1. Test and display

print join(',', rangex('-6,-3--1,3-5,7-11,14,15,17-20')), "\n";</lang>

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

Alternative: <lang Perl>sub rangex {

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

}</lang>

Perl 6

<lang Perl6>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(', ');</lang>

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

PHP

Translation of: Python

<lang PHP>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;

}</lang>

PicoLisp

<lang 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)) ) ) ) )</lang>
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)

PL/I

<lang 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;</lang>

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

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. <lang Prolog>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]).</lang>

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

PureBasic

<lang 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</lang>

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

Python

<lang 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'))</lang> Another variant, using regular expressions to parse the ranges: <lang python>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</lang>
Output:
[-6, -3, -2, -1, 3, 4, 5, 7, 8, 9, 10, 11, 14, 15, 17, 18, 19, 20]

R

A function to do the range expansion: <lang R> rangeExpand <- function(text) { x <- unlist(strsplit(text, ",")) # split the string on commas x <- gsub("(\\d)-", "\\1:", x) # substitute dashes following a digit with a colon lst <- list(mode = "list", length = length(x)) # an empty list to hold evaluated elements for (i in 1:length(x)) { # evaluate each element and add to the list lsti <- eval(parse(text = x[i])) } unlist(lst, use.names = FALSE) # return the expanded integers } </lang> Now test it: <lang R> 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

</lang>

Racket

<lang racket>

  1. 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") </lang> 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>

  1. 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")) </lang> 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)

Raven

Based loosely on Ruby <lang raven>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</lang>

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

REXX

version 1

Extra blanks were added to the original list (which are ignored) to make an over/under comparison easier. <lang rexx>/*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.*/</lang>

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

Version 2 somewhat simplified !?!

<lang rexx>/*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         */</lang>

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   

Ruby

<lang 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')</lang>

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

Run BASIC

<lang runbasic>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</lang>

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

Scala

<lang ruby>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
 }</lang>

Scheme

<lang 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)))</lang>
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

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. <lang seed7>$ 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;</lang>
Output:
-6 -3 -2 -1 3 4 5 7 8 9 10 11 14 15 17 18 19 20 

SNOBOL4

<lang 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</lang>

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

Tcl

<lang 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"]</lang>

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

TUSCRIPT

<lang 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</lang>

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

UNIX Shell

Works with: bash

<lang 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"</lang>

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

Ursala

<lang Ursala>#import std

  1. import int

rex = sep`,; zrange+*= %zp~~htttPzztPQhQXbiNC+ rlc ~&r~=`-

  1. cast %zL

t = rex '-6,-3--1,3-5,7-11,14,15,17-20'</lang>

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

VBA

<lang 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</lang>

Output:
RangeExpandTest
Result:
-6 -3 -2 -1  3  4  5  7  8  9  10  11  14  15  17  18  19  20 

XPL0

<lang 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); ]</lang>

Output:

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