Interpret loose ranges

ListSharp is an interpreted programming language that has many features, one of those features is a 1 index based range creator that works like this:

You define a range as (INT) TO (INT) or just (INT) where both or the single int can go from min to max int32 value

Then you can use those ranges to extract elements of an array without fearing to overstep it's boundaries


1 TO 5 generates: {1,2,3,4,5}

3 generates: {3}

Ranges can be added up using the AND operator

1 TO 5 AND 3 TO 6 generates: {1,2,3,4,5,3,4,5,6}

remember this works with negative numbers as well

3 TO -3 generates: {3,2,1,0,-1,-2,-3}

The challenge is the following:


A character array and the previously defined range clause as a string


The elements at the 1 index based locations of the range (non existing/negative indexes translate to an empty character)

How to win

As a challenge you are supposed to create the program with the shortest byte count to win

It has been pointed out empty characters do not exist, therefore you should be ignoring them (I only showed them here to make it easier to understand yet it confused people)

Test cases:

input array is:
{'H','e','l','l','o',' ','W','o','r','l','d'}

range clause:
"1 TO 3" => "Hel"
"5" => "o"
"-10 TO 10" => "Hello Worl"
"0 AND 2 AND 4" => "el"
"8 TO 3" => "oW oll"
"-300 AND 300" => ""
"1 TO 3 AND 3 TO 1" => "HelleH"
"-20 TO 0 AND 1 AND 4" => "Hl"
  • 3
    \$\begingroup\$ Are we allowed to use 0-index instead of 1-index as input string? So the range clause becomes "0 TO 2" => {'H', 'e', 'l'}? \$\endgroup\$ Sep 30, 2016 at 9:29
  • \$\begingroup\$ The ASCII table does not have an empty character (excluding non-printable ones). What's wrong with using space? \$\endgroup\$
    – adrianmp
    Sep 30, 2016 at 9:30
  • \$\begingroup\$ a char array is basically a string, therefor empty chars just wont be printed or returned \$\endgroup\$ Sep 30, 2016 at 10:26
  • 1
    \$\begingroup\$ Also, will e.g. 3 TO 3 ever be an input and what is the expected output? \$\endgroup\$
    – Jordan
    Sep 30, 2016 at 13:44
  • 1
    \$\begingroup\$ You need some test cases for ANDing multiples ranges. Also, You didn't answer if we can use zero-based indexing, which is standard in most languages. \$\endgroup\$
    – mbomb007
    Sep 30, 2016 at 15:27

12 Answers 12


Python 2 - 239 211 210 bytes

Thanks to @mbomb007 and @Cyoce for further golfing this solution!

def r(s):
 for x in s.split("AND"):
  if"T"in x:a=map(int,x.split("TO"));b=2*(a[0]<a[1])-1;t+=range(a[0],a[1]+b,b)
 return t
lambda p,v:[(['']+p+['']*max(map(abs,r(v))))[x]for x in r(v)]

Straight-forward approach. Tried generators and a recursive version, but they couldn't beat the simple for each loop. I'm a golfing noob, so this can most likely be improved quite a bit. Also, the major flaw of this snippet is that the range as a list object is computed again each time an element is retrieved from the character array (see last line, list comprehension). This means r(s) is executed len(r(s)) + 1 times.

Ungolfed code:

def find_range(string):
    result = []

    # Split at each AND and look at each element separately
    for element in string.split("AND"):
        # Element is just a number, so add that number to the result list
        if "TO" not in string:
            result += [int(string)]

        # Generate a list with all the values in between the boundaries 
        # (and the boundaries themselves, of course) and append it to the result list
            boundaries = map(int, string.split("TO"))
            ascending = boundaries[0] < boundaries[1]

            # range(start, stop, step) returns [start, ..., stop - 1], so extend the stop value accordingly
            # range(8, 3, 1) returns just [], so choose respective step (negative for a descending sequence)
            result += range(boundaries[0], boundaries[1] + (1 if ascending else -1), 1 if ascending else -1)

# Make the char array 1-indexed by appending an empty char in 0th position
# Add enough empty chars at the end so too large and negative values won't wrap around
interpret = lambda chars, range_expr: [(['']+chars+['']*max(map(abs, find_range(range_expr))))[x] for x in find_range(range_expr)]

Test cases:

c = list("Hello World")
print interpret(c, "1 TO 3")
print interpret(c, "5")
print interpret(c, "-10 TO 10")
print interpret(c, "0 AND 2 AND 4")
print interpret(c, "8 TO 3")
print interpret(c, "-300 AND 300")


['H', 'e', 'l']
['', '', '', '', '', '', '', '', '', '', '', 'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l']
['', 'e', 'l']
['o', 'W', ' ', 'o', 'l', 'l']
['', '']
  • \$\begingroup\$ Great first answer! Welcome to PPCG! \$\endgroup\$
    – mbomb007
    Sep 30, 2016 at 15:43
  • \$\begingroup\$ You may benefit from viewing Tips for Golfing in Python. You can replace 2 spaces for double-indentation with a single tab. You can put the code following the if on the same line and separate them with semicolons. And remove the space in [x] for. Also, 1if b else-1 can be replaced with either b and 1or-1 or 2*bool(b)-1 to save a byte. \$\endgroup\$
    – mbomb007
    Sep 30, 2016 at 15:44
  • \$\begingroup\$ Thanks! I already looked at and (tried to) use.d some of them. Will have another look through all the answers again later. :) \$\endgroup\$
    – Marco
    Sep 30, 2016 at 15:49
  • \$\begingroup\$ And I think you can use an unnamed lambda, since it's not recursive. \$\endgroup\$
    – mbomb007
    Sep 30, 2016 at 15:50
  • 1
    \$\begingroup\$ t+=[int(x)] canbecome t+=int(x), \$\endgroup\$
    – Cyoce
    Sep 30, 2016 at 18:50

Scala, 165 bytes

(s:String,r:String)=>r split "AND"map(_ split "TO"map(_.trim.toInt))flatMap{case Array(a,b)=>if(a<b)a to b else a to(b,-1)
case x=>x}map(i=>s lift(i-1)getOrElse "")


(s:String,r:String)=> //define a function
r split "AND"         //split the range expression at every occurance of "AND"
map(                  //map each part...
  _ split "TO"          //split at to
  map(                  //for each of these splitted parts, map them to...
    _.trim.toInt          //trim all whitespace and parse as an int
)                     //now we have an Array[Array[Int]]
flatMap{              //map each inner Array...
  case Array(a,b)=>if(a<b)a to b else a to(b,-1) //if it has two elements, create a Range
  case x=>x             //otherwise just return it
}                     //and flatten, so we get an Array[Int]
map(i=>               //for each int
  s lift(i-1)         //try to get the char with index i-1, since Scala is zero-based
  getOrElse ""        //otherwise return ""

Groovy (99 97 Bytes)

{n,v->Eval.me("[${n.replaceAll(" TO ","..").replaceAll(" AND ",",")}]").flatten().collect{v[it]}}

Try it here: https://groovyconsole.appspot.com/edit/5155820207603712


  • .replaceAll(" TO ","..") - Replace the to with a traditional range.
  • .replaceAll(" AND ", ",") - Replace all ands with a comma.
  • "[${...}]" - Surround it with the "list" notation in Groovy.
  • Eval.me(...) - Evaluate string as Groovy code.
  • .flatten() - Flatten the mixture of 2D array and 1D array into a 1D array.
  • .collect{v[it]} - Collect the indices from the array into a single structure.

Here's a 115 113 byte solution removing nulls from the output: https://groovyconsole.appspot.com/edit/5185924841340928

Here's a 117 byte solution if you say it MUST be indexed at 1 instead of 0: https://groovyconsole.appspot.com/edit/5205468955803648

If you want me to swap out the original for the 113/117 byte one, let me know.

  • \$\begingroup\$ If you don't like me using "nulls" for the characters that aren't there, +5 bytes at the end for "-null", which removes all nulls from the set. \$\endgroup\$ Sep 30, 2016 at 20:04
  • 1
    \$\begingroup\$ I love this clever coincidence \$\endgroup\$ Sep 30, 2016 at 20:08
  • \$\begingroup\$ I learned something from this though, never knew Groovy had Eval.me(...) until now; given using it in practice would be ridiculously insecure, still a cool thing to know. \$\endgroup\$ Sep 30, 2016 at 20:10

C#, 342 bytes

a=>r=>{var s=r.Replace(" AND","").Replace(" TO ","|").Split();int b=a.Length,i=0,n,m,j,o;var c=new List<char>();for(;i<s.Length;){var d=s[i++].Split('|');if(d.Length<2)c.Add(b<(n=int.Parse(d[0]))||n<1?' ':a[n-1]);else{o=(m=int.Parse(d[0]))<(n=int.Parse(d[1]))?1:-1;for(j=m;o>0?j<=n:j>=n;j+=o)c.Add(b<j||j<1?' ':a[j-1]);}}return c.ToArray();};

Ungolfed method:

static char[] f(char[] a, string r)
    var s=r.Replace(" AND","").Replace(" TO ","|").Split();
    int b=a.Length,i=0,n,m,j,o;
    var c=new List<char>();
        var d=s[i++].Split('|');
            c.Add(b<(n=int.Parse(d[0]))||n<1?' ':a[n-1]);
                c.Add(b<j||j<1?' ':a[j-1]);
    return c.ToArray();

Full program with test cases:

using System;
using System.Collections.Generic;

namespace InterpretLooseRanges
    class Program
        static void PrintCharArray(char[] a)
            for (int i=0; i<a.Length; i++)

        static void Main(string[] args)
            Func<char[],Func<string,char[]>>f= a=>r=>{var s=r.Replace(" AND","").Replace(" TO ","|").Split();int b=a.Length,i=0,n,m,j,o;var c=new List<char>();for(;i<s.Length;){var d=s[i++].Split('|');if(d.Length<2)c.Add(b<(n=int.Parse(d[0]))||n<1?' ':a[n-1]);else{o=(m=int.Parse(d[0]))<(n=int.Parse(d[1]))?1:-1;for(j=m;o>0?j<=n:j>=n;j+=o)c.Add(b<j||j<1?' ':a[j-1]);}}return c.ToArray();};
            char[] ar = {'H','e','l','l','o',' ','W','o','r','l','d'};
            PrintCharArray(f(ar)("1 TO 3"));
            PrintCharArray(f(ar)("-10 TO 10"));
            PrintCharArray(f(ar)("0 AND 2 AND 4"));
            PrintCharArray(f(ar)("8 TO 3"));
            PrintCharArray(f(ar)("-300 AND 300"));

A naive solution, using a char list, which uses ' ' as an empty character and gets the job done. Hoping to improve soon.


Python 2, 156 155 bytes

My answer has some similar ideas as 1Darco1's answer, but by using a different approach from the start (string slicing rather than lists), it ended up quite a bit shorter. It would be four bytes shorter if 0-indexing was allowed.

for x in r.split("AND"):
    for _ in-1,0:i[_]-=11**9*(i[_]<0)
print o

Try it online

Fortunately, I can parse strings containing spaces into integers. Negative indexing in Python indexes from the end of the string, so I use i[-1] to either be the same as i[0] or the second value, if there is one. Then I have to adjust any negative range values to more negative, so they won't mess with the slicing. Multiplying negative values by 11**9 (2357947691) will account for ranges using integer min value. Then, simply slice the string, using reverse slice if the range is reversed.

With zero-indexing (151 bytes):

for x in r.split("AND"):
    for _ in-1,0:i[_]-=11**9*(i[_]<0)
print o
  • \$\begingroup\$ Good job! Slicing definitely is the way to go here. My range approach is basically just a super verbose form of exactly that. And you even got rid of the whole if"T"in x: else: part. +1 \$\endgroup\$
    – Marco
    Oct 1, 2016 at 8:53

R, 142 bytes

Assuming I understood the challenge correctly, here I am assuming that r is the pre-defined range clause in string format, and that the input array ("Hello world", in the examples) is read from stdin.


Some test cases:

r="1 TO 3"
[1] "H" "e" "l"

[1] "o"

r="-10 TO 10"
[1] ""  ""  ""  ""  ""  ""  ""  ""  ""  ""  ""  "H" "e" "l" "l" "o" " " "w" "o" "r" "l"

r="0 AND 2 AND 4"
[1] ""  "e" "l"

r="8 TO 3"
[1] "o" "w" " " "o" "l" "l"

r="-300 AND 300"
[1] "" ""


Line 1


R has a nice infix operator : which generates sequences. 1:5 gives [1, 2, 3, 4, 5], and 0:-2 gives [0, -1, -2]. So, we replace the TO in the loose range clause with :.


Interpreting AND is just concatenation. We can use the function c for that, which handily is able to take an arbitrary number of arguments, comma-separated. So we replace AND with ,

                          gsub("AND",",",      ...       )

and then wrap the whole thing in c(, ).

               paste("c(",              ...               ,")")

This yields a character string that could look like c( 1 : 5 , 7 ). We call parse to convert to type "expression" and then eval to evaluate the expression. The resulting sequence of numbers is then re-assigned to the variable r.

r=eval(parse(t=                     ...                        ))

Line 2


Now for the ugly part - dealing with strings in R, which gets messy quickly. First we define e to be an empty string (we will need this later).


We read from stdin and convert the character string to an array of individual characters by splitting at the empty string. (E.g. we go from "Hi" to ["H", "i"].) This returns a list of length 1, so we have to ask for the first element [[1]] to get an array that we can work with. Ugh, I warned you this was messy.

  strsplit(readline(), ... )[[1]]

R indexes beginning at 1, and has a nice feature with negative numbers. Suppose x is ['a', 'b', 'c']. Calling x[1] unsurprisingly returns 'a'. Calling x[-1] returns all of x except index 1, i.e. ['b', 'c']. This is a cool feature, but means that we have to be careful with our negative indices for this problem. So for now, we just return the elements of the input array with index >0, and assign the result to o.

o=               ...             [r[r>0]]

Line 3

However, there's a problem! For indices that are greater than the length of the array, R just returns NA values. We need it to return empty strings. So we redefine the elements of o for which is.na(o) is TRUE to be the empty string.


Line 4


Finally, how do we deal with the negative (and zero) indices? They all need to return the empty string, so we repeat the empty string N times, where N is the number of indices that are <1.


Finally, we concatenate the previously-defined o to this (potentially empty) list.

c(      ...      ,o)

JavaScript (ES6), 141

Unnamed function with 2 parameters, the first being the character array (can be a string either), the second the string containing the range definition.

The return value is an array where each element can either be a single character or the js value undefined. When stringified, this result in a sequence of comma separated chars having undefined shown as the "empty" char - as the test cases in the first version of the question.
Using .join you can get a string result similar to the test case output in the current version of the question.

(l,r,u,z=[])=>(r+' E').replace(/\S+/g,x=>x>'T'?u=t:x>'A'?[...Array((t-(w=(u=u||t)-(d=+u<t?1:-1)-1)-1)*d)].map(_=>z.push(l[w+=d]),u=0):t=x)&&z

Less golfed

 l, r, // input paramaters, array/string and string
 u,    // local variable start at 'undefined'
 z=[]  // local variable, will contain the ouput
) => 
  (r+' E') // add an end marker
  .replace( /\S+/g, x=> // execute for each nonspace substring
    x > 'T' // check if 'TO'
    ? u = t // if 'TO' save first value in u (it's a string so even 0 is a truthy value)
    : x > 'A' // check if 'AND' or 'E'
      ? (
          u = u||t, // if u is falsy, it's a single value range t -> t
          d = +u < t ? 1 :-1, // direction of range up or down,comparison has to be numeric, so the leading +
          w = u - d - 1, // starting value (decrement by 1 as js array are 0 based)
          u = 0, // set u to falsy again for next round
          [...Array((t - w - 1) * d)] // build the array of required number of elements
          .map(_ => z.push(l[w+=d])) // iterate adding elements of l to z
      : t = x // if not a keyword, save value in t
  ) && z // return output in z


(l,r,u,z=[])=>(r+' E').replace(/\S+/g,x=>x>'T'?u=t:x>'A'?[...Array((t-(w=(u=u||t)-(d=+u<t?1:-1)-1)-1)*d)].map(_=>z.push(l[w+=d]),u=0):t=x)&&z

function run(x)

run("10 TO 5 AND 5 TO 10")
<tr><td>Base string</td><td><input id=L value="Hello World"></td></tr>
<tr><td>Custom range</td><td><input id=R ><button onclick='run(R.value)'>-></button></td></tr>
<tr><td>Output</td><td><pre id=O></pre></td></tr>
<tr><td>Test case ranges</td><td>
<select id=T onchange='run(this.value)'>
<option value="1 TO 3">1 TO 3 =&gt; {'H','e','l'}</option>
<option value="5">5 =&gt; {'o'}</option>
<option value="-10 TO 10">-10 TO 10 =&gt; {'','','','','','','','','','','','H','e','l','l','o',' ','W','o','r','l'}</option>
<option value="0 AND 2 AND 4">0 AND 2 AND 4 =&gt; {'','e','l'}
"8 TO 3" => {'o','W',' ','o','l','l'}</option>
<option value="-300 AND 300">-300 AND 300 =&gt; {'',''}</option>
<option value="1 TO 3 AND 3 TO 1">1 TO 3 AND 3 TO 1 =&gt; "HelleH"</option>
<option value="-20 TO 0 AND 1 AND 4">-20 TO 0 AND 1 AND 4 =&gt; "Hl"</option>


Perl - 110 bytes

Calling the script in the command line with the string as the first argument, and the range as the second.

for(split AND,pop@ARGV){$_>0?print+(split//,"@ARGV")[$_-1]:0for(/(.+)TO(.+)/?($1>$2?reverse$2..$1:$1..$2):$_)}

De-obfuscated :

for $subrange (split 'AND', $ARGV[1]) {
    for $index ($subrange =~ /(.+)TO(.+)/
        ? ($1 > $2 ? reverse $2..$1 : $1..$2) # All indices of that range
        : $subrange) # Otherwise, an index only
        if ($index > 0) {
            # Here, 'split' returns an array of all characters
            print((split //, $ARGV[0])[$index - 1]);

Python 2, 146 bytes

lambda s,a:[a[i]for x in[map(int,c.split('TO'))for c in s.split('AND')]for i in range(x[0]-1,x[-1]-2*(x[-1]<x[0]),1-2*(x[-1]<x[0]))if 0<=i<len(a)]

All tests are at ideone

Splits the clause, s, on "AND", splits each of the resulting sub-clauses on "TO", converts the resulting strings to int using map. The results will each have either 1 or 2 items (1 if no "TO" was present in the sub-clause).
Constructs 0-based ranges for each of these using the step parameter of range as 1 or -1 by inspection of the values at indexes 0 and -1 (a list with one entry has that entry at both indexes).
Runs through these ranges and constructs a list of the output, if the indexes provided are in-range (if 0<=i<len(a)).


Clojure 232 230 229 bytes

Oh what a monster I have created... But actually this was 260 when I was about to submit it.

Edit: removed a space from #(get r %_""), (if_(< f t) and (take-nth 2_%) (indicated as _).

(let[S clojure.string/split](fn[r s](apply str(map #(get r %"")(mapcat #(apply(fn([f][(dec f)])([f t](if(< f t)(range(dec f)t)(reverse(range(dec t)f)))))%)(map #(mapv read-string(take-nth 2%))(map #(S % #" ")(S s #" AND "))))))))

Less golfed:

(def f (let[S clojure.string/split]
         (fn[r s] (->> (map #(S % #" ") (S s #" AND "))
                       (map #(mapv read-string (take-nth 2 %)))
                       (mapcat #(apply(fn
                                        ([f][(dec f)])
                                        ([f t](if (< f t)
                                                (range (dec f) t)
                                                (reverse (range (dec t) f)))))  %))
                       (map #(get r % ""))
                       (apply str)))))

Uses clojure.string/split to split by " AND " and " ", take-nth drops "TO" between integers, function argument matching handles the case of 1 or 2 arguments and that is about it.

Calling convention: (f "Hello World" "1 TO 3 AND 2 AND 8 TO 2")

  • \$\begingroup\$ You can remove a heck of a lot of bytes by removing spaces in general, especially between the # chars. \$\endgroup\$
    – clismique
    Dec 20, 2016 at 4:14
  • \$\begingroup\$ Are you sure I could remove a space between any #? I tried it without success, it gets "merged" with the previous token. Oh, one more space to remove before % there. \$\endgroup\$
    – NikoNyrh
    Dec 20, 2016 at 9:02

Jelly, 28 27 25 19 bytes

-3 Thanks to caird coinheringaahing


A dyadic Link accepting the range clause on the left and the array on the right (both as lists of characters) that yields the resulting list of characters.



ḟØẎṣ”Aṣ€”OVr/€FfJ}ị - Link: range-clause, C; array, W
 ØẎ                 - upper-case consonants ['B','C','D','F',...,'Z']
ḟ                   - filter remove (from C) (i.e. TO->O, AND->A)
   ṣ”A              - split at 'A's
      ṣ€”O          - split each at 'O's      e.g. [["-1","2"],["7"]]
          V         - evaluate as Jelly code       [[-1,2],[7]]
            /€      - rescue each by:
           r        -   inclusive range            [[-1,0,1,2],[7]]
              F     - flatten                      [-1,0,1,2,7]
                J}  - range of length (of W)
               f    - filter keep (those of F that are in bounds of W)
                  ị - index into (W)
  • \$\begingroup\$ Your TIO link still has the 27 byte version. -3 bytes \$\endgroup\$ Mar 4, 2021 at 11:03
  • \$\begingroup\$ Thanks @cairdcoinheringaahing, golfed another three off :) \$\endgroup\$ Mar 4, 2021 at 13:45
  • \$\begingroup\$ Good to see that you’re rescueing the inputs ;) \$\endgroup\$ Mar 4, 2021 at 13:47

Scala, 151 bytes

_.split(" AND ").flatMap{case s"$a TO $b"=>a.toInt-1 to(b.toInt-1,(b.toInt-a.toInt).sign);case x=>Seq(x.toInt-1)}flatMap _.map("".+).orElse{case _=>""}

Try it in Scastie!


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.