1

I have a sorted array of elements:

array = ["A", "B", "C", "D", "E"]

I defined a range of elements where the "key" is the starting element, and the "value" is the ending element:

range_1 = {"C" => "D"}
range_2 = {"B" => "E"}

How do I write code that returns the sub-arrays based on ranges parsing above array?

result_1 = ["C", "D"]
result_2 = ["B", "C", "D", "E"]
4
  • 3
    If you want to express ranges in Ruby, you should use ("C".."D") instead of a hash with a single key/value. Even a hash with start: 'C', end: 'D' would be a better way to represent the data. Commented Nov 1, 2013 at 15:44
  • I just started with Ruby and I love the Hash => notation. It seems I will love the range notation too. Commented Nov 1, 2013 at 16:35
  • @flyer: If the only tool you have is a hammer, every problem looks like a smashed thumb. :) Commented Nov 1, 2013 at 17:24
  • Please check another variant of sub-array: stackoverflow.com/q/19732759/743027 Commented Nov 1, 2013 at 18:15

3 Answers 3

2

Assuming that elements of your array are unique:

array = ["A", "B", "C", "D", "E"]

range_1 = {"C" => "D"}
range_2 = {"B" => "E"}

def subarray(array, range)
  from, to = range.first
  idx_from = array.index(from)
  idx_to = array.index(to)
  array[idx_from..idx_to]
end

subarray(array, range_1) # => ["C", "D"]
subarray(array, range_2) # => ["B", "C", "D", "E"]
Sign up to request clarification or add additional context in comments.

Comments

2
result_1 = array[array.index(range_1.keys.first)..array.index(range_1.values.first)]
result_2 = array[array.index(range_2.keys.first)..array.index(range_2.values.first)]

Comments

1

There's a smell to how you're doing this:

  • Instead of using something like:

    range_1 = {"C" => "D"}
    range_2 = {"B" => "E"}
    

    I'd opt for using real ranges:

    range_1 = 'C' .. 'D'
    range_2 = 'B' .. 'E'
    

    Then I could dispense with using array because it's so easy to convert a range to an array of consecutive values.

    range_1.to_a # => ["C", "D"]
    range_2.to_a # => ["B", "C", "D", "E"]
    
    [*range_1] # => ["C", "D"]
    [*range_2] # => ["B", "C", "D", "E"]
    
  • If the values in array aren't really consecutive, as in the example, then I'd use index values:

    array = ["A", "B", "C", "D", "E"]
    range_1 = 2 .. 3
    range_2 = 1 .. 4
    

    Which makes it easy to retrieve the values:

    array = ["A", "B", "C", "D", "E"]
    range_1 = 2 .. 3
    range_2 = 1 .. 4
    
    array[range_1] # => ["C", "D"]
    array[range_2] # => ["B", "C", "D", "E"]
    

If you need to use actual values from array as symbolic names for the indexes it becomes harder, similar to what you're doing now, but if you're working with a web front-end it's not too bad. A user's brain and eyes is a nice asset.

Because the use-case isn't well defined we can't really recommend better solutions. I suspect this is a case where you've decided a certain algorithm is the best and now you're trying to make it work, but entropy is setting in as you feel the corner of the room closing in. That's an analysis based on a lot of years of looking at code, not a snap-judgement.


but something like this desn't ["A","B","C","D"]["B".."D"]. I get in '[]': can't convert String into Integer

Something else to consider is that hashes are like arrays, only they allow random access more easily using values_at.

Consider this:

array = %w[a b c d x y z]
hash = Hash[array.map.with_index{ |e, i| [e, i] }]
hash # => {"a"=>0, "b"=>1, "c"=>2, "d"=>3, "x"=>4, "y"=>5, "z"=>6}

The hash doesn't need integers for the values, they could be nils or booleans, and I've used either depending on my needs. I'm using them here to make it more obvious what's happening. The big win is that a hash lets us pull content out in any order, just by specifying what that order is. If it's a range or set of ranges we can still extract values in the order we want.

This is a simple example using a single range:

hash.values_at(*('a' .. 'd')) # => [0, 1, 2, 3]
hash.values_at(*('x' .. 'z')) # => [4, 5, 6]

These are compound range examples:

hash.values_at(*('a' .. 'c'), *('x' .. 'z')) # => [0, 1, 2, 4, 5, 6]
hash.values_at(*('x' .. 'z'), *('a' .. 'c')) # => [4, 5, 6, 0, 1, 2]

Note that in the second on the ranges are reversed, and the values reflect that.

Also notice that the ranges are being broken down into arrays. The arrays could come from anywhere, and as long as the elements match keys in the hash you'll see the values returned in the same order as the array.

2 Comments

Thanks for helpful hints. Unfortunately this can't be used because I don't have a consecutive array elements and I need to define the 'range' by the strings. Suggested ["A","B","C"][1..2] works perfect, but something like this desn't ["A","B","C","D"]["B".."D"]. I get in '[]': can't convert String into Integer
Correct, you can't use an alpha range because [] only supports integers. Your example array needs to reflect your reality though; If you don't have consecutive values in the array then make sure that's noted. There are very interesting tricks that can be played with hash and array slicing, and a hash slice doesn't care whether things are in order in an array. Values would be retrieved in the order that the keys are specified.

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.