Interpret loose ranges

13

2

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"

downrep_nation

Posted 2016-09-30T08:33:20.200

Reputation: 1 152

3Are we allowed to use 0-index instead of 1-index as input string? So the range clause becomes "0 TO 2" => {'H', 'e', 'l'}? – Kevin Cruijssen – 2016-09-30T09:29:40.740

The ASCII table does not have an empty character (excluding non-printable ones). What's wrong with using space? – adrianmp – 2016-09-30T09:30:26.210

a char array is basically a string, therefor empty chars just wont be printed or returned – downrep_nation – 2016-09-30T10:26:59.750

Are you saying that e.g. el, eWoll, and (nothing) are valid outputs for the last three test cases? – Jordan – 2016-09-30T13:35:58.807

1Also, will e.g. 3 TO 3 ever be an input and what is the expected output? – Jordan – 2016-09-30T13:44:03.137

Actually, the fifth test case looks wrong. Shouldn't the '' be ' '? – Jordan – 2016-09-30T14:18:35.990

you are correct, nice catch yes 3 TO 3 is like 3 and is perfectly valid el oWoll (nothing)

is valid – downrep_nation – 2016-09-30T14:21:03.820

1You 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. – mbomb007 – 2016-09-30T15:27:25.707

were keeping 1 based indexing to stay original to the inspiration of the challenge, and ill add more test cases when im home – downrep_nation – 2016-09-30T16:17:10.710

Answers

5

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']
['', '']

1Darco1

Posted 2016-09-30T08:33:20.200

Reputation: 51

Great first answer! Welcome to PPCG! – mbomb007 – 2016-09-30T15:43:01.850

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.

– mbomb007 – 2016-09-30T15:44:01.283

Thanks! I already looked at and (tried to) use.d some of them. Will have another look through all the answers again later. :) – 1Darco1 – 2016-09-30T15:49:23.657

And I think you can use an unnamed lambda, since it's not recursive. – mbomb007 – 2016-09-30T15:50:31.360

1t+=[int(x)] canbecome t+=int(x), – Cyoce – 2016-09-30T18:50:56.700

3

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.

Magic Octopus Urn

Posted 2016-09-30T08:33:20.200

Reputation: 19 422

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. – Magic Octopus Urn – 2016-09-30T20:04:24.197

1I love this clever coincidence – downrep_nation – 2016-09-30T20:08:10.933

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. – Magic Octopus Urn – 2016-09-30T20:10:17.140

2

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.

adrianmp

Posted 2016-09-30T08:33:20.200

Reputation: 1 592

2

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

corvus_192

Posted 2016-09-30T08:33:20.200

Reputation: 1 889

2

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>

edc65

Posted 2016-09-30T08:33:20.200

Reputation: 31 086

2

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)

rturnbull

Posted 2016-09-30T08:33:20.200

Reputation: 3 689

2

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

mbomb007

Posted 2016-09-30T08:33:20.200

Reputation: 21 944

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 – 1Darco1 – 2016-10-01T08:53:48.143

1

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

Maxim Bernard

Posted 2016-09-30T08:33:20.200

Reputation: 21

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

Jonathan Allan

Posted 2016-09-30T08:33:20.200

Reputation: 67 804

0

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

NikoNyrh

Posted 2016-09-30T08:33:20.200

Reputation: 2 361

You can remove a heck of a lot of bytes by removing spaces in general, especially between the # chars. – clismique – 2016-12-20T04:14:36.520

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. – NikoNyrh – 2016-12-20T09:02:58.140

0

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

Jonathan Allan

Posted 2016-09-30T08:33:20.200

Reputation: 67 804