Range expansion
You are encouraged to solve this task according to the task description, using any language you may know.
- 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
[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
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))
)
- 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#
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.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));
}
}
[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] 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.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;
}
void main() {
"-6,-3--1,3-5,7-11,14,15,17-20".rangeExpand.writeln;
}
- Output:
[-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:
- translate the task's range syntax into Groovy range syntax
- wrap with list delimiters
- evaluate the script expression
- flatten the nested lists
- express as a string
- 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'
to=: [ + i.@>:@-~
num=: _&".
normaliz=: rplc&(',-';',_';'--';'-_')@,~&','
subranges=:<@(to/)@(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:
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.
[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] 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] 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<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);
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] 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
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
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
A function to do the range expansion:
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
lst[[i]] <- eval(parse(text = x[i]))
}
unlist(lst, use.names = FALSE) # return the expanded integers
}
Now test it:
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
}
[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] 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] UNIX Shell
#!/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
- Programming Tasks
- Solutions by Programming Task
- Ada
- ALGOL 68
- ALGOL 68 examples needing attention
- Examples needing attention
- AutoHotkey
- AWK
- BBC BASIC
- Bracmat
- C
- C sharp
- C++
- Clojure
- Common Lisp
- D
- Forth
- F Sharp
- Go
- Groovy
- Haskell
- Icon
- Unicon
- J
- Java
- JavaScript
- K
- Liberty BASIC
- Maple
- Mathematica
- MATLAB
- Octave
- MUMPS
- NetRexx
- Oberon-2
- OCaml
- OoRexx
- Oz
- Perl
- Perl 6
- PHP
- PicoLisp
- PL/I
- Prolog
- Clpfd
- PureBasic
- Python
- R
- Racket
- Raven
- REXX
- Ruby
- Run BASIC
- Scala
- Scheme
- Seed7
- SNOBOL4
- Tcl
- TUSCRIPT
- UNIX Shell
- Ursala
- VBA
- XPL0