Range expansion

Revision as of 19:18, 21 February 2011 by rosettacode>Wmeyer (Added F#)

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)
Task
Range expansion
You are encouraged to solve this task according to the task description, using any language you may know.
  • 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> Sample 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

C

<lang c>#include <stdio.h>

  1. include <stdlib.h>
  2. include <string.h>
  3. include <stdarg.h>
  4. include <stdbool.h>

enum range_action { RANGE_NEW, RANGE_NEXT, RANGE_RESET, RANGE_FREE };

typedef struct range_s {

 long int val;
 char *range;   // private members
 size_t pos;
 size_t slen;
 long int clower;
 long int cupper;
 bool inrange;

} *range_t;

range_t range(enum range_action action, ...) {

 range_t r;
 va_list al;
 char *ep, *tp;
 va_start(al, action);
 switch(action) {
 case RANGE_NEW:
   r = malloc(sizeof(struct range_s));
   
   if (r != NULL) {
     r->range = va_arg(al, char *);
     r->pos = 0;
     r->slen = strlen(r->range);
     r->inrange = false;
   }
   break;
 case RANGE_NEXT:
   r = va_arg(al, range_t);
   if ( r->inrange ) {
     if ( r->val < r->cupper ) {

r->val++; va_end(al); return r;

     } else {

r->inrange = false;

     }
   }
   if ( r->pos >= r->slen ) return NULL;
   r->val = strtol(r->range + r->pos, &ep, 10);
   if ( ep == (r->range + r->pos) ) return NULL;
   if ( *ep == '-' ) {
     r->clower = r->val;
     r->cupper = strtol(ep + 1, &tp, 10);
     if ( (ep+1) == tp ) return NULL;
     r->inrange = true;
     ep = tp;
   } // any other symbol works like ,
   r->pos = ep+1 - r->range;
   break;
 case RANGE_RESET:
   r = va_arg(al, range_t);
   r->pos = 0;
   break;
 case RANGE_FREE:
   r = va_arg(al, range_t);
   free(r);
   return NULL;
 }
 va_end(al);
 return r;

}

// usage int main() {

 range_t r = range(RANGE_NEW, "-6,-3--1,3-5,7-11,14,15,17-20");
 while( range(RANGE_NEXT, r) != NULL )
 {
   printf("%d ", r->val);
 }
 printf("\n");
 range(RANGE_FREE, r);
 return 0;

}</lang>

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>

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

D

Compiled with D V.2.048. From the second Python version. <lang d>import std.stdio, std.regex, std.string, std.conv, std.range;

int[] rangeExpand(string txt) {

 int[] result;
 foreach (r; txt.split(",")) {
   auto m = array(match(r, regex(r"^(-?\d+)(-?(-?\d+))?$")).captures);
   if (m.length > 2)
     result ~= array(iota(to!int(m[1]), to!int(m[3])+1));
   else
     result ~= to!int(m[1]);
 }
 return result;

}

void main() {

 writeln(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]


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]

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>

Sample 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.)/@:(0 1 + -~/\) num=: _&". normaliz=: rplc&(',-';',_';'--';'-_')@,~&',' lumps=:<@(num`([:to num;._1@,~&'-')@.('-'&e.));._1 rngexp=: ;@lumps@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>

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

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

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>

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 (in Oz list syntax): <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

Based on Python version.

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

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

</lang>

Prolog

Works with SWI-Prolog and 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> Sample 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>

Sample output

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

REXX

<lang rexx> /*REXX program to expand a range of integers into a list. */

old='-6,-3--1,3-5,7-11,14,15,17-20' /*original list of nums/ranges. */

say 'old list='old /*show old list of nums/ranges. */ a=translate(old,,',') /*translate commas to blanks */ new= /*new list of numbers (so far). */

 do j=1                               /*process each number or range.  */
 y=                                 /*nullify the output number(s).  */
 parse var a x a; if x== then leave /*get next num/range. Null? Done.*/
 minus=pos('-',x,2)                   /*find location of a dash (maybe)*/
 if minus\==0 then do                 /*if found then process range.   */
                     do k=left(x,minus-1) to substr(x,minus+1)
                     y=y k            /*build one integer at a time.   */
                     end
                   x=               /*nullify X, it's still a range. */
                   end
 new=new x y                          /*append them to the new list.   */
 end

new=space(new) /*remove extraneous blanks. */ say 'new list='new /*show the new list of numbers. */ </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

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]

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>

(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

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