13
\$\begingroup\$

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


therefore:

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:

Input

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

Output

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"
\$\endgroup\$
  • 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\$ – Kevin Cruijssen Sep 30 '16 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 '16 at 9:30
  • \$\begingroup\$ a char array is basically a string, therefor empty chars just wont be printed or returned \$\endgroup\$ – downrep_nation Sep 30 '16 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 '16 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 '16 at 15:27

11 Answers 11

5
\$\begingroup\$

Python 2 - 239 211 210 bytes

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

def r(s):
 t=[]
 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)
  else:t+=int(x),
 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
        else:
            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")

Output:

['H', 'e', 'l']
['o']
['', '', '', '', '', '', '', '', '', '', '', 'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l']
['', 'e', 'l']
['o', 'W', ' ', 'o', 'l', 'l']
['', '']
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Great first answer! Welcome to PPCG! \$\endgroup\$ – mbomb007 Sep 30 '16 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 '16 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\$ – 1Darco1 Sep 30 '16 at 15:49
  • \$\begingroup\$ And I think you can use an unnamed lambda, since it's not recursive. \$\endgroup\$ – mbomb007 Sep 30 '16 at 15:50
  • 1
    \$\begingroup\$ t+=[int(x)] canbecome t+=int(x), \$\endgroup\$ – Cyoce Sep 30 '16 at 18:50
3
\$\begingroup\$

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

Explanation:

  • .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.

| improve this answer | |
\$\endgroup\$
  • \$\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\$ – Magic Octopus Urn Sep 30 '16 at 20:04
  • 1
    \$\begingroup\$ I love this clever coincidence \$\endgroup\$ – downrep_nation Sep 30 '16 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\$ – Magic Octopus Urn Sep 30 '16 at 20:10
2
\$\begingroup\$

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

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++)
                Console.Write(a[i]);
            Console.WriteLine();
        }

        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)("5"));
            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.

| improve this answer | |
\$\endgroup\$
2
\$\begingroup\$

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

Explanation:

(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 ""
) 
| improve this answer | |
\$\endgroup\$
2
\$\begingroup\$

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.

s,r=input()
o=""
for x in r.split("AND"):
    i=map(int,x.split("TO"));d=2*(i[0]<i[-1])-1
    for _ in-1,0:i[_]-=11**9*(i[_]<0)
    o+=s[i[0]-1:i[-1]+d-1:d]
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):

s,r=input()
o=""
for x in r.split("AND"):
    i=map(int,x.split("TO"));d=2*(i[0]<i[-1])-1
    for _ in-1,0:i[_]-=11**9*(i[_]<0)
    o+=s[i[0]:i[-1]+d:d]
print o
| improve this answer | |
\$\endgroup\$
  • \$\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\$ – 1Darco1 Oct 1 '16 at 8:53
2
\$\begingroup\$

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.

r=eval(parse(t=paste("c(",gsub("AND",",",gsub("TO",":",r)),")")))
o=strsplit(readline(),e<-"")[[1]][r[r>0]]
o[is.na(o)]=e
c(rep(e,sum(r<1)),o)

Some test cases:

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

r="5"
[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] "" ""

Ungolfed/explained

Line 1

r=eval(parse(t=paste("c(",gsub("AND",",",gsub("TO",":",r)),")")))

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

                                         gsub("TO",":",r)

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

o=strsplit(readline(),e<-"")[[1]][r[r>0]]

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

                      e<-""

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.

o[is.na(o)]=e

Line 4

c(rep(e,sum(r<1)),o)

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.

  rep(e,sum(r<1))

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

c(      ...      ,o)
| improve this answer | |
\$\endgroup\$
2
\$\begingroup\$

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

Test

f=
(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)
{
  R.value=x;
  O.textContent=f(L.value,x)
}

run("10 TO 5 AND 5 TO 10")
<table>
<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/>  
<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>
</select>
</td></tr>
</table>

| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

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]);
        }
    }
}
| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

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

| improve this answer | |
\$\endgroup\$
0
\$\begingroup\$

Jelly, 28 27 25 bytes

œṣ⁾TOj”rV
œṣ“Ñþ»Ç€Ff⁹J¤ị⁹

TryItOnline (will also work with a string instead of an array of char)

How?

œṣ⁾TOj”rV - Link 1, construct sub-range: subclause
  ⁾TO     - string "TO"
œṣ        - split on sublists
     j    - join with
      ”r  - character "r"
        V - evaluate as Jelly code
                (r is the Jelly dyad for inclusive range, which works just like TO
                 when no r is present the string evaluates to the number:
                 " -20 "       evaluates to -20;
                 " -20 r -19 " evaluates to [-20,-19];
                 " 3 r -3 "    evaluates to [3,2,1,0,-1,-2,-3]; etc.)

œṣ“Ñþ»Ç€Ff⁹J¤ị⁹ - Main link: range clause, array 
  “Ñþ»          - compression of the string "AND"
œṣ              - split on sublists
      ǀ        - call the last link (1) as a monad for each
        F       - flatten list
            ¤   - nilad and link(s) as a nilad
          ⁹     - right argument (the array)
           J    - range for length [1,2,...,len(array)]
         f      - filter keep
                      (Jelly indexing is modular so keep only in-bound indexes)
             ị⁹ - index into the array
| improve this answer | |
\$\endgroup\$
0
\$\begingroup\$

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

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ You can remove a heck of a lot of bytes by removing spaces in general, especially between the # chars. \$\endgroup\$ – clismique Dec 20 '16 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 '16 at 9:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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