Range expansion: Difference between revisions
(→{{header|TXR}}: Lose sortdup function; use uniq.) |
|||
Line 1,186: | Line 1,186: | ||
// s -> [n] |
// s -> [n] |
||
function expansion(strExpr) { |
function expansion(strExpr) { |
||
⚫ | |||
// concat map yields flattened output list |
|||
⚫ | |||
return x.split('-').reduce(function (a, s, i, l) { |
return x.split('-').reduce(function (a, s, i, l) { |
||
// negative (after item 0) if preceded by an empty string |
|||
// (i.e. a hyphen-split artefact, otherwise ignored) |
|||
return s.length ? i ? a.concat( |
|||
parseInt(l[i - 1].length ? s : '-' + s, 10) |
|||
) : [+s] : a; |
|||
}, []); |
}, []); |
||
// two-number lists are interpreted as ranges |
|||
}).map(function (r) { |
|||
return [].concat.apply([], lstParse.map(function (r) { |
|||
return r.length > 1 ? range.apply(null, r) : r; |
return r.length > 1 ? range.apply(null, r) : r; |
||
})) |
})); |
||
} |
} |
||
// [m..n] |
// [m..n] |
Revision as of 04:41, 24 November 2015
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
<lang algol68>MODE YIELDINT = PROC(INT)VOID;
MODE RANGE = STRUCT(INT lwb, upb); MODE RANGEINT = UNION(RANGE, INT);
OP SIZEOF = ([]RANGEINT list)INT: (
- determine the length of the output array #
INT upb := LWB list - 1; FOR key FROM LWB list TO UPB list DO CASE list[key] IN (RANGE value): upb +:= upb OF value - lwb OF value + 1, (INT): upb +:= 1 ESAC OD; upb
);
PROC gen range expand = ([]RANGEINT list, YIELDINT yield)VOID:
FOR key FROM LWB list TO UPB list DO CASE list[key] IN (RANGE range): FOR value FROM lwb OF range TO upb OF range DO yield(value) OD, (INT int): yield(int) ESAC OD;
PROC range expand = ([]RANGEINT list)[]INT: (
[LWB list: LWB list + SIZEOF list - 1]INT out; INT upb := LWB out - 1;
- FOR INT value IN # gen range expand(list, # ) DO #
- (INT value)VOID:
out[upb +:= 1] := value
- OD #);
out
);
test:(
[]RANGEINT list = (-6, RANGE(-3, -1), RANGE(3, 5), RANGE(7, 11), 14, 15, RANGE(17, 20)); print((range expand(list), new line))
)
- converts string containing a comma-separated list of ranges and values to a []RANGEINT #
OP TORANGE = ( STRING s )[]RANGEINT: BEGIN
# counts the number of elements - one more than the number of commas # # and so assumes there is always at least one element # PROC count elements = INT: BEGIN
INT elements := 1;
FOR pos FROM LWB s TO UPB s DO IF s[ pos ] = "," THEN elements +:= 1 FI OD;
# RESULT # elements END; # count elements #
REF[]RANGEINT result = HEAP [ 1 : count elements ]RANGEINT;
# does the actual parsing - assumes the string is syntatically valid and doesn't check for errors # # - in particular, a string with no elements will cause problems, as will space characters in the string # PROC parse range string = []RANGEINT: BEGIN
INT element := 0; INT str pos := 1;
PROC next = VOID: str pos +:= 1; PROC curr char = CHAR: IF str pos > UPB s THEN "?" ELSE s[ str pos ] FI; PROC have minus = BOOL: curr char = "-"; PROC have digit = BOOL: curr char >= "0" AND curr char <= "9";
# parses a number out of the string # # the number must be a sequence of digits with an optional leading minus sign # PROC get number = INT: BEGIN
INT number := 0;
INT sign multiplier = IF have minus THEN # negaive number # # skip the sign # next; -1 ELSE # positive number # 1 FI;
WHILE curr char >= "0" AND curr char <= "9" DO number *:= 10; number +:= ( ABS curr char - ABS "0" ); next OD;
# RESULT # number * sign multiplier END; # get number #
# main parsing # WHILE str pos <= UPB s DO CHAR c = curr char;
IF have minus OR have digit THEN # have the start of a number # INT from value = get number; element +:= 1; IF NOT have minus THEN # not a range # result[ element ] := from value ELSE # have a range # next; INT to value = get number; result[ element ] := RANGE( from value, to value ) FI ELSE # should be a comma # next FI OD;
# RESULT # result END; # parse range string #
- RESULT #
parse range string
END; # TORANGE #
- converts a []INT to a comma separated string of the elements #
OP TOSTRING = ( []INT values )STRING: BEGIN
# converts an integer to a string # OP TOSTRING = ( INT value )STRING: BEGIN STRING result := ""; INT n := ABS value;
WHILE REPR ( ( n MOD 10 ) + ABS "0" ) PLUSTO result; n OVERAB 10; n > 0 DO SKIP OD;
# RESULT # IF value < 0 THEN "-" ELSE "" FI + result END; # TOSTRING #
STRING result := ""; STRING separator := "";
FOR pos FROM LWB values TO UPB values DO result +:= ( separator + TOSTRING values[ pos ] ); separator := "," OD;
- RESULT #
result
END; # TOSTRING #
test:(
print( ( TOSTRING range expand( TORANGE "-6,-3--1,3-5,7-11,14,15,17-20" ), newline ) )
) </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>
AWK
<lang 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; } </lang>
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
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>
- 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; }</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#
<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.Groups[1].Success) { values.Add(m.Value); continue; }
var start = Convert.ToInt32(m.Groups["f"].Value); var end = Convert.ToInt32(m.Groups["s"].Value) + 1;
values.AddRange(Enumerable.Range(start, end - start).Select(v => v.ToString())); }
Console.WriteLine(string.Join(", ", values)); }
}</lang>
<lang csharp>using System; using System.Collections.Generic; using System.Linq;
namespace RangeExpansion {
internal static class StringExtensions { internal static IEnumerable<int> ExpandRange(this string s) { return s.Split(',') .Select(rstr => { int start; if (int.TryParse(rstr, out start)) return new {Start = start, End = start}; var istr = new string(("+-".Any(_ => rstr[0] == _) ? rstr.Take(1).Concat(rstr.Skip(1).TakeWhile(char.IsDigit)) : rstr.TakeWhile(char.IsDigit) ).ToArray()); rstr = rstr.Substring(istr.Length + 1, (rstr.Length - istr.Length) - 1); return new {Start = int.Parse(istr), End = int.Parse(rstr)}; }).SelectMany(_ => Enumerable.Range(_.Start, _.End - _.Start + 1)); } }
internal static class Program { private static void Main() { const string RANGE_STRING = "-6,-3--1,3-5,7-11,14,15,17-20"; var values = RANGE_STRING.ExpandRange().ToList(); var vstr = string.Join(", ", values.Select(_ => _.ToString())); Console.WriteLine(vstr); } }
}</lang>
C++
<lang cpp>#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.";
}</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>
COBOL
<lang cobol> >>SOURCE FREE IDENTIFICATION DIVISION. PROGRAM-ID. expand-range.
DATA DIVISION. WORKING-STORAGE SECTION. 01 comma-pos PIC 99 COMP VALUE 1. 01 dash-pos PIC 99 COMP. 01 end-num PIC S9(3). 01 Max-Part-Len CONSTANT 10. 01 num PIC S9(3). 01 edited-num PIC -(3)9. 01 part PIC X(10).
01 part-flag PIC X.
88 last-part VALUE "Y".
01 range-str PIC X(80). 01 Range-Str-Len CONSTANT 80. 01 start-pos PIC 99 COMP. 01 start-num PIC S9(3).
PROCEDURE DIVISION.
ACCEPT range-str
PERFORM WITH TEST AFTER UNTIL last-part UNSTRING range-str DELIMITED BY "," INTO part WITH POINTER comma-pos PERFORM check-if-last
PERFORM find-range-dash IF dash-pos > Max-Part-Len PERFORM display-num ELSE PERFORM display-range END-IF END-PERFORM
DISPLAY SPACES GOBACK .
check-if-last SECTION.
IF comma-pos > Range-Str-Len SET last-part TO TRUE END-IF .
find-range-dash SECTION.
IF part (1:1) <> "-" MOVE 1 TO start-pos ELSE MOVE 2 TO start-pos END-IF
MOVE 1 TO dash-pos INSPECT part (start-pos:) TALLYING dash-pos FOR CHARACTERS BEFORE "-" COMPUTE dash-pos = dash-pos + start-pos - 1 .
display-num SECTION.
MOVE part TO edited-num CALL "display-edited-num" USING CONTENT part-flag, edited-num .
display-range SECTION.
MOVE part (1:dash-pos - 1) TO start-num MOVE part (dash-pos + 1:) TO end-num
PERFORM VARYING num FROM start-num BY 1 UNTIL num = end-num MOVE num TO edited-num CALL "display-edited-num" USING CONTENT "N", edited-num END-PERFORM
MOVE end-num TO edited-num CALL "display-edited-num" USING CONTENT part-flag, edited-num .
END PROGRAM expand-range.
IDENTIFICATION DIVISION.
PROGRAM-ID. display-edited-num.
DATA DIVISION. LINKAGE SECTION. 01 hide-comma-flag PIC X.
88 hide-comma VALUE "Y".
01 edited-num PIC -(3)9.
PROCEDURE DIVISION USING hide-comma-flag, edited-num.
DISPLAY FUNCTION TRIM(edited-num) NO ADVANCING IF NOT hide-comma DISPLAY ", " NO ADVANCING END-IF .
END PROGRAM display-edited-num.</lang>
Setup:
$ ./expand-range -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
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
<lang d>import std.stdio, std.regex, std.conv, std.range, std.algorithm;
enum rangeEx = (string s) /*pure*/ => s.matchAll(`(-?\d+)-?(-?\d+)?,?`)
.map!q{ a[1].to!int.iota(a[1 + !a[2].empty].to!int + 1) }.join;
void main() {
"-6,-3--1,3-5,7-11,14,15,17-20".rangeEx.writeln;
}</lang>
- Output:
[-6, -3, -2, -1, 3, 4, 5, 7, 8, 9, 10, 11, 14, 15, 17, 18, 19, 20]
EchoLisp
<lang scheme>
- parsing [spaces][-]digit(s)-[-]digit(s)[spaces]
(define R (make-regexp "^ *(\-?\\d+)\-(\-?\\d+) *$" ))
- the native (range a b) is [a ... b[
- (range+ a b) is [a ... b]
(define (range+ a b) (if (< a b) (range a (1+ b)) (if (> a b) (range a (1- b) -1) (list a))))
- in
- string
- "number" or "number-number"
- out
- a range = list of integer(s)
(define (do-range str) (define from-to (regexp-exec R str)) ;; "1-3" --> ("1" "3") (if from-to
(range+ (string->number (first from-to)) (string->number (second from-to))) (list (string->number str))))
(define (ranges str)
(apply append (map do-range (string-split str ","))))
(define task "-6,-3--1,3-5,7-11,14,15,17-20")
(ranges task)
→ (-6 -3 -2 -1 3 4 5 7 8 9 10 11 14 15 17 18 19 20)
</lang>
Elixir
<lang elixir>defmodule RC do
def expansion(range) do Enum.flat_map(String.split(range, ","), fn part -> case Regex.scan(~r/^(-?\d+)-(-?\d+)$/, part) do _,a,b -> Enum.to_list(String.to_integer(a) .. String.to_integer(b)) [] -> [String.to_integer(part)] end end) end
end
IO.inspect RC.expansion("-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]
Erlang
<lang Erlang> -module( range ).
-export( [expansion/1, task/0] ).
expansion( String ) ->
lists:flatten( [expansion_individual(io_lib:fread("~d", X)) || X <- string:tokens(String, ",")] ).
task() ->
io:fwrite( "~p~n", [expansion("-6,-3--1,3-5,7-11,14,15,17-20")] ).
expansion_individual( {ok, [N], []} ) -> N; expansion_individual( {ok, [Start], "-" ++ Stop_string} ) -> lists:seq( Start, erlang:list_to_integer(Stop_string) ). </lang>
- Output:
34> range:task(). [-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:
- 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
<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.Applicative (Applicative((<*>),(*>)),(<$>)) import Text.Parsec
expandRange :: String -> Maybe [Int] expandRange = either (const Nothing) Just . parse rangeParser ""
rangeParser :: Parser [Int] rangeParser = concat <$> (item `sepBy` char ',')
where item = do n1 <- num n2 <- option n1 (char '-' *> num) return [n1 .. n2] num :: Parser Int num = read `dot` (++) <$> option "" (string "-") <*> many1 digit dot = ((.).(.))</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' thru=: <./ + i.@(+*)@-~ num=: _&". normaliz=: rplc&(',-';',_';'--';'-_')@,~&',' subranges=:<@(thru/)@(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:
thru: given two integers (left: start of range, right: end of range) return the corresponding sequence of adjacent integers
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.
As an aside, note also that thru/ is an identity function when applied to a single number. This is because (verb/) inserts the verb between each number (or each item in a list), and this is an identity function on a single number, regardless of any definition of the verb. Fortunately, this is consistent with the definition of thru (and is also consistent for any combining verb which has an identity element).
Java
<lang java>import java.util.*;
class RangeExpander implements Iterator<Integer>, Iterable<Integer> {
private static final Pattern TOKEN_PATTERN = Pattern.compile("([+-]?\\d+)-([+-]?\\d+)");
private final Iterator<String> tokensIterator;
private boolean inRange; private int upperRangeEndpoint; private int nextRangeValue;
public RangeExpander(String range) { String[] tokens = range.split("\\s*,\\s*"); this.tokensIterator = Arrays.asList(tokens).iterator(); }
@Override public boolean hasNext() { return hasNextRangeValue() || this.tokensIterator.hasNext(); }
private boolean hasNextRangeValue() { return this.inRange && this.nextRangeValue <= this.upperRangeEndpoint; }
@Override public Integer next() { if (!hasNext()) { throw new NoSuchElementException(); }
if (hasNextRangeValue()) { return this.nextRangeValue++; }
String token = this.tokensIterator.next();
Matcher matcher = TOKEN_PATTERN.matcher(token); if (matcher.find()) { this.inRange = true; this.upperRangeEndpoint = Integer.valueOf(matcher.group(2)); this.nextRangeValue = Integer.valueOf(matcher.group(1)); return this.nextRangeValue++; }
this.inRange = false; return Integer.valueOf(token); }
@Override public Iterator<Integer> iterator() { return this; }
}
class RangeExpanderTest {
public static void main(String[] args) { RangeExpander re = new RangeExpander("-6,-3--1,3-5,7-11,14,15,17-20"); for (int i : re) { System.out.print(i + " "); } }
}</lang>
JavaScript
Imperative (Spidermonkey)
<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>
- Output:
-6,-3,-2,-1,3,4,5,7,8,9,10,11,14,15,17,18,19,20
Functional (ES 5)
<lang JavaScript>(function (strTest) {
'use strict';
// s -> [n] function expansion(strExpr) { // concat map yields flattened output list return [].concat.apply([], strExpr.split(',').map(function (x) { return x.split('-').reduce(function (a, s, i, l) { // negative (after item 0) if preceded by an empty string // (i.e. a hyphen-split artefact, otherwise ignored) return s.length ? i ? a.concat( parseInt(l[i - 1].length ? s : '-' + s, 10) ) : [+s] : a; }, []); // two-number lists are interpreted as ranges }).map(function (r) { return r.length > 1 ? range.apply(null, r) : r; })); }
// [m..n] function range(m, n) { return Array.apply(null, Array(n - m + 1)).map(function (x, i) { return m + i; }); }
return expansion(strTest);
})('-6,-3--1,3-5,7-11,14,15,17-20');</lang>
- Output:
<lang JavaScript>[-6, -3, -2, -1, 3, 4, 5, 7, 8, 9, 10, 11, 14, 15, 17, 18, 19, 20]</lang>
jq
<lang jq>def expand_range:
def number: "-?[0-9]+"; def expand: [range(.[0]; .[1] + 1)]; split(",") | reduce .[] as $r ( []; . + ($r | if test("^\(number)$") then [tonumber] else sub( "(?<x>\(number))-(?<y>\(number))"; "\(.x):\(.y)") | split(":") | map(tonumber) | expand
end));</lang> Example: <lang jq>"-6,-3--1,3-5,7-11,14,15,17-20" | expand_range</lang>
- Output:
$ jq -c -n -f Range_expansion.jq
[-6,-3,-2,-1,3,4,5,7,8,9,10,11,14,15,17,18,19,20]
Julia
<lang Julia>slurp(s) = readcsv(IOBuffer(s))
conv(s)= colon(map(x->parse(Int,x),match(r"^(-?\d+)-(-?\d+)$", s).captures)...)
expand(s) = mapreduce(x -> isa(x,Number)? Int(x) : conv(x), vcat, slurp(s))</lang>
- Output:
julia> show(expand("-6,-3--1,3-5,7-11,14,15,17-20")) [-6,-3,-2,-1,3,4,5,7,8,9,10,11,14,15,17,18,19,20]
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>
Lasso
<lang lasso>define range_expand(expression::string) => {
local(parts) = regexp(`^(-?\d+)-(-?\d+)$`) return ( with elm in #expression->split(`,`) let isRange = #parts->setInput(#elm)&matches select #isRange ? (integer(#parts->matchString(1)) to integer(#parts->matchString(2)))->asString | integer(#elm)->asString )->join(', ')
}
range_expand(`-6,-3--1,3-5,7-11,14,15,17-20`)</lang>
- Output:
-6, -3, -2, -1, 3, 4, 5, 7, 8, 9, 10, 11, 14, 15, 17, 18, 19, 20
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
LiveCode
<lang LiveCode>function range beginning ending stepping
local tRange, tBegin, tEnd, tstep if stepping is empty or stepping is 0 then put 1 into tstep else put abs(stepping) into tstep end if if ending is empty or isNumber(ending) is not true then put 0 into tEnd else put ending into tEnd end if if beginning is empty or isNumber(beginning) is not true then put 0 into tBegin else put beginning into tBegin end if repeat with r = tBegin to tEnd step tstep put space & r after tRange end repeat return word 1 to -1 of tRange
end range
function expandRange rangeExpr
put rangeExpr into tRange split tRange by comma repeat with n = 1 to the number of elements of tRange if matchText(tRange[n],"^(\-*\d+)\-(\-*\d+)",beginning, ending) then put range(beginning, ending, 1) & space after z else put tRange[n] & space after z end if end repeat return z
end expandRange</lang>
Test <lang LiveCode>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>
Lua
<lang lua>function range(i, j)
local t = {} for n = i, j, i<j and 1 or -1 do t[#t+1] = n end return t
end
function expand_ranges(rspec)
local ptn = "([-+]?%d+)%s?-%s?([-+]?%d+)" local t = {}
for v in string.gmatch(rspec, '[^,]+') do local s, e = v:match(ptn)
if s == nil then t[#t+1] = tonumber(v) else for i, n in ipairs(range(tonumber(s), tonumber(e))) do t[#t+1] = n end end end return t
end
local ranges = "-6,-3--1,3-5,7-11,14,15,17-20" print(table.concat(expand_ranges(ranges), ', '))</lang>
Due to the way Lua's tonumber
function works and the way the string pattern to parse ranges is written, whitespace is allowed around commas and the dash separating the range start and end (but not between the plus/minus sign and the number).
- 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 < 1; S='[]'; end
if ~all(isdigit(S) | (S=='-') | (S==',') | isspace(S)) error 'invalid input'; end ixr = find(isdigit(S(1:end-1)) & S(2:end) == '-')+1; S(ixr)=':'; S=['[',S,']']; L=eval(S);</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
Nim
<lang nim>import parseutils, re, strutils
proc expandRange(input: string): string =
var output: seq[string] = @[] for range in input.split(','): var sep = range.find('-', 1) if sep > 0: # parse range var first = -1 if range.substr(0, sep-1).parseInt(first) == 0: break var last = -1 if range.substr(sep+1).parseInt(last) == 0: break for i in first..last: output.add($i) else: # parse single number var n = -1 if range.parseInt(n) > 0: output.add($n) else: break return output.join(",")
echo("-6,-3--1,3-5,7-11,14,15,17-20".expandRange)</lang>
- Output:
-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
}
- 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
<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
PowerShell
<lang PowerShell> function range-expansion($array) {
function expansion($arr) { if($arr) { $arr = $arr.Split(',') $arr | foreach{ $a = $_ $b, $c, $d, $e = $a.Split('-') switch($a) { $b {return $a} "-$c" {return $a} "$b-$c" {return "$(([Int]$b)..([Int]$c))"} "-$c-$d" {return "$(([Int]$("-$c"))..([Int]$d))"} "-$c--$e" {return "$(([Int]$("-$c"))..([Int]$("-$e")))"} } } } else {""} } $OFS = ", " "$(expansion $array)" $OFS = " "
} range-expansion "-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
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.
<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]
another variant, using a functional style to parse the ranges: <lang python>from functools import reduce from operator import add
def rangeexpand(s):
return reduce(add, map(lambda x: list(range(*map(int, x.split('-')))) if '-' in x else [int(x)], s.split(',')))
</lang>
- Output:
[-6, -3, -2, -1, 3, 4, 5, 7, 8, 9, 10, 11, 14, 15, 17, 18, 19, 20]
R
<lang R> rangeExpand <- function(text) {
lst <- gsub("(\\d)-", "\\1:", unlist(strsplit(text, ","))) unlist(sapply(lst, function (x) eval(parse(text=x))), use.names=FALSE)
}
rangeExpand("-6,-3--1,3-5,7-11,14,15,17-20")
[1] -6 -3 -2 -1 3 4 5 7 8 9 10 11 14 15 17 18 19 20
</lang>
Racket
<lang 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") </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>
- 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 (imbedded) 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= /*translate , ───► blanks [↑] */
do until a==; parse var a X a /*obtain next integer ─or─ 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); new=new j end /*j*/ /*append single int at a time [↑]*/ end /*until*/ /*stick a fork in it, we're done.*/
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.*/</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(',').flat_map do |part| if part =~ /^(-?\d+)-(-?\d+)$/ ($1.to_i .. $2.to_i).to_a else Integer(part) end end
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>
- Output:
-6,-3,-2,-1,3,4,5,7,8,9,10,11,14,15,17,18,19,20
Rust
Rust doesn't have regex in standard library yet.
<lang rust>use std::str::FromStr;
// Precondition: range doesn't contain multibyte UTF-8 characters fn range_expand(range : &str) -> Vec<i32> {
range.split(',').flat_map(|item| { match i32::from_str(item) { Ok(n) => n..n+1, _ => { let dashpos= match item.rfind("--") { Some(p) => p, None => item.rfind('-').unwrap(), }; let rstart=i32::from_str( unsafe{ item.slice_unchecked(0,dashpos)} ).unwrap(); let rend=i32::from_str( unsafe{ item.slice_unchecked(dashpos+1,item.len()) } ).unwrap(); rstart..rend+1 }, } }).collect()
}
fn main() {
println!("{:?}", 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 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 }</lang>
- Output:
> println(rangex("-6,-3-1,3-5,7-11,14,15,17-20")) ArraySeq(-6, -3, -2, -1, 0, 1, 3, 4, 5, 7, 8, 9, 10, 11, 14, 15, 17, 18, 19, 20) > println(rangex("-6,-3--1,3-5,7-11,14,15,17-20")) ArraySeq(-6, -3, -2, -1, 3, 4, 5, 7, 8, 9, 10, 11, 14, 15, 17, 18, 19, 20)
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
Sidef
<lang ruby>func rangex(str) {
str.split(',').map { |range| var m = range.match(/^ (?(DEFINE) (?<int>[+-]?[0-9]+) ) (?<from>(?&int))-(?<to>(?&int)) $/x); m ? ( var ncap = m.ncap; ncap{:from}.to_i .. ncap{:to}.to_i ) : range.to_i; }
}
say rangex('-6,-3--1,3-5,7-11,14,15,17-20').flatten.join(',');</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
TXR
A solution with three main parts:
- a parse-expression-grammar driven parser to decimate the input to a Lisp data structure;
- some Lisp code to expand the list, sort it, and remove duplicates (recursion, hashing, sorting).
- driver code which matches the input with the grammar, and produces output with the help of the Lisp code.
The grammar is:
num := [ + | - ] { digit } + entry := num [ ws ] - [ ws ] num | num rangelist := entry [ ws ] , [ ws ] rangelist | entry | /* empty */
Code:
<lang txr>@(define num (n))@(local tok)@{tok /[+\-]?\d+/}@(bind n @(int-str tok))@(end) @(define entry (e))@\
@(local n1 n2)@\ @(cases)@\ @(num n1)@/\s*-\s*/@(num n2)@\ @(bind e (n1 n2))@\ @(or)@\ @(num n1)@\ @(bind e n1)@\ @(end)@\
@(end) @(define rangelist (list))@\
@(local first rest)@\ @(cases)@\ @(entry first)@/\s*,\s*/@(rangelist rest)@\ @(bind list @(cons first rest))@\ @(or)@\ @(entry first)@\ @(bind list (first))@\ @(or)@\ @(bind list nil)@\ @(end)@\
@(end) @(do
(defun expand-helper (list) (cond ((null list) nil) ((consp (first list)) (append (range (first (first list)) (second (first list))) (rangeexpand (rest list)))) (t (cons (first list) (rangeexpand (rest list))))))
(defun rangeexpand (list) (uniq (expand-helper list))))
@(repeat) @(rangelist x)@{trailing-junk} @(output) raw syntax: @x expansion: @(rangeexpand x) your junk: @{trailing-junk} @(end) @(end)</lang>
- Run:
$ txr range-expansion.txr - 1,2,3-5,-3--1 raw syntax: 1 2 (3 5) (-3 -1) expansion: (-3 -2 -1 1 2 3 4 5) your junk: -6,-3--1,3-5,7-11,14,15,17-20 raw syntax: -6 (-3 -1) (3 5) (7 11) 14 15 (17 20) expansion: (-6 -3 -2 -1 3 4 5 7 8 9 10 11 14 15 17 18 19 20) your junk: -6,-3--1,3-5,7-11,14,15,17-20,cg@foo raw syntax: -6 (-3 -1) (3 5) (7 11) 14 15 (17 20) expansion: (-6 -3 -2 -1 3 4 5 7 8 9 10 11 14 15 17 18 19 20) your junk: cg@foo
Note how the junk in the last example does not contain the trailing comma. This is because the rangelist grammar production allows for an empty range, so syntax like "5," is valid: it's an entry followed by a comma and a rangelist, where the rangelist is empty.
UNIX Shell
<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
- import int
rex = sep`,; zrange+*= %zp~~htttPzztPQhQXbiNC+ rlc ~&r~=`-
- 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
zkl
<lang zkl>fcn rangex(s){
fcn(s,re){ if (re.search(s)){
a,b:=re.matched[1,*].apply("toInt"); [a..b].walk();
} else s; } : s.split(",").pump(List, _.fp1(RegExp(0'|(.*\d+)-(.*\d+)|))) .flatten().concat(",");
}</lang> <lang zkl>ns="-6,-3-1,3-5,7-11,14,15,17-20"; rangex(ns).println();
ns="-6,-3--1,3-5,7-11,14,15,17-20"; rangex(ns).println();</lang>
- Output:
-6,-3,-2,-1,0,1,3,4,5,7,8,9,10,11,14,15,17,18,19,20 -6,-3,-2,-1,3,4,5,7,8,9,10,11,14,15,17,18,19,20
- Programming Tasks
- Solutions by Programming Task
- Ada
- ALGOL 68
- AutoHotkey
- AWK
- BBC BASIC
- Bracmat
- C
- C sharp
- C++
- Clojure
- COBOL
- Common Lisp
- D
- EchoLisp
- Elixir
- Erlang
- Forth
- F Sharp
- Go
- Groovy
- Haskell
- Icon
- Unicon
- J
- Java
- JavaScript
- Jq
- Julia
- K
- Lasso
- Liberty BASIC
- LiveCode
- Lua
- Maple
- Mathematica
- MATLAB
- Octave
- MUMPS
- NetRexx
- Nim
- Oberon-2
- OCaml
- OoRexx
- Oz
- Perl
- Perl 6
- PHP
- PicoLisp
- PL/I
- PowerShell
- Prolog
- Clpfd
- PureBasic
- Python
- R
- Racket
- Raven
- REXX
- Ruby
- Run BASIC
- Rust
- Scala
- Scheme
- Seed7
- Sidef
- SNOBOL4
- Tcl
- TUSCRIPT
- TXR
- UNIX Shell
- Ursala
- VBA
- XPL0
- Zkl