How do I extract specific elements from an array? - ruby

If I have an array a = [1,2,3,4,5,6,7,8,9,10] and I want a subset of this array - the 1st, 5th and 7th elements. Is it possible to extract these from this array in a simple way. I was thinking something like:
a[0,4,6] = [1,5,7]
but that doesn't work.
Also is there a way to return all indices except those specified? For example, something like
a[-0,-4,-6] = [2,3,4,6,8,9,10]

You can simply do:
[1] pry(main)> [1,2,3,4,5,6,7,8,9,10].values_at(0, 4, 6)
=> [1, 5, 7]

Here's one way:
[0,4,6].map{|i| a[i]}

a = [1,2,3,4,5,6,7,8,9,10]
For a[0,4,6] = [1,5,7] :
a.values_at(0, 4, 6)
=> [1, 5, 7]
For a[-0,-4,-6] = [2,3,4,6,8,9,10] :
a - a.values_at(0, 4, 6)
=> [2, 3, 4, 6, 8, 9, 10]

In Ruby, Array objects have a method/operator [] which allows you to get/reference an object at a specific index, or a contiguous subset of objects specified by a range.
ary[index] → obj or nil
ary[start, length] → new_ary or nil
ary[range] → new_ary or nil
Personally, I like your syntax. It would be useful but it is not provided by the standard Array object. Until Ruby adopts your syntax, as others have suggested, you have the values_at method to do exactly what you need.
values_at(selector,... ) → new_ary
If a = [1,2,3,4,5,6,7,8,9,10], then:
a.values_at(0, 4, 6)
=> [1, 5, 7]

Related

Ruby : Can you please explain why reverse range not giving expected result in ruby

why the below given [] for a reverse order range in ruby
(0..5).to_a
# => [0, 1, 2, 3, 4, 5]
(5..0).to_a
# => []
I am new on ruby PL
what is the best way to use a reverse range in ruby
The best way to use a reverse range for iteration
I know .reverse of array
But can I inherit the range and make a custom method of it and use it as a range
I also try
class Range
def r
to_a.reverse
end
end
# => :r
(5..0).r
# => []
You asked why (5..0).to_a doesn't give the "expected result", by which I assume you mean [5,4,3,2,1].
If a range is a..b that means it includes all values x such that a <= x <= b.1 If a > b, no values are included, so nil is returned. There is no such thing as an "empty" range, as there is an empty array ([]), hash ({}), string ("") and so on. (1..1 is a range, but it is not empty.) That's why 5..3 cannot return a Range object, and therefore returns nil.
Ruby does not support the concept of a "reversed range". If you just want to step down from 3 to 1 there are many ways to do that without involving a range.
Note also that ranges, unlike arrays, for example, may contain an infinite number of values. 1.0..3.0 is one such example.
1 The range a...b (three dots) includes all values x such that a <= x < b.
what should I do if I need a reverse range
(0..5).to_a.reverse
#=> [5, 4, 3, 2, 1, 0]
or
(0..5).reverse_each.to_a
#=> [5, 4, 3, 2, 1, 0]
Here's a way to do it that might more closely express they way you're thinking about it:
(5.downto 0).to_a
=> [5, 4, 3, 2, 1, 0]
Try:
[*0..5].reverse
#=> [5, 4, 3, 2, 1, 0]
OR
> r = Proc.new {|x| x.reverse}
> r.call((0..5).to_a)
#=> [5, 4, 3, 2, 1, 0]

Ruby split array into X groups

I need to split an array into X smaller array. I don't care about the number of elements in the smaller arrays I just need to create X arrays from a larger one. I've been doing some reading and it seems like I need a method similar to the in_groups method from rails. I am not using rails right now, just ruby.
Requiring Rails just to get that function is overkill. Just use each_slice:
team = ['alice', 'andy', 'bob', 'barry', 'chloe', 'charlie']
=> ["alice", "andy", "bob", "barry", "chloe", "charlie"]
team.each_slice(2).to_a
=> [["alice", "andy"], ["bob", "barry"], ["chloe", "charlie"]]
each_slice's parameter is the number of elements in each slice (except possibly the last slice). Since you're looking for X slices, you can do something like this:
team.each_slice(team.length/X).to_a
That's not perfect, as you'll get one extra slice if the array length is not a multiple of X, but gets you in the ballpark and you can tweak it from there depending on what exactly your needs are.
Since you say you don't care how many are in each, you could just use the length/x approach above, then check to see if you have one too many slices. If so, just join the last two slices into one jumbo-size slice. This might avoid some fussy math or floating point operations.
You can make your own method, here's a basic idea:
class Array
def my_group(x)
start = 0
size = (self.size() / Float(x)).ceil
while x > 0
yield self[start, size]
size = ((self.size() - 1 - start) / Float(x)).ceil
start += size
x -= 1
end
end
end
%w(1 2 3 4 5 6 7 8 9 10).my_group(3) {|group| p group}
# =>["1", "2", "3", "4"]
# =>["4", "5", "6"]
# =>["7", "8", "9"]
I decided to put:
require 'active_support/core_ext/array/grouping'
if x is a count of groups:
x = 2
a = [1,2,3,4,5,6,7,8,9,10,11,12]
a.in_groups(x)
=> [[1, 2, 3, 4, 5, 6], [7, 8, 9, 10, 11, 12]]
if group by x pieces:
x = 2
a = [1,2,3,4,5,6,7,8,9,10,11,12]
a.each_slice(x).to_a
=> [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11, 12]]
If you need to have N groups, you can use the in_groups monkey-patch provided by ActiveSupport, as mentioned in another answer:
require 'active_support/core_ext/array/grouping'
my_array = [1,2,3,4,5]
my_array.in_groups(2)
# => [[1, 2, 3], [4, 5, nil]]
my_array.in_groups(2, false)
# => [[1, 2, 3], [4, 5]]
If you care about the number of elements in the group as opposed to the number of groups, you can use each_slice provided by Ruby core:
my_array = [1,2,3,4,5]
my_array.each_slice(2).to_a
# => [[1, 2], [3, 4], [5]]

Three ways to create a range, hash, array in ruby

I am doing a tutorial course on ruby and it asks for 3 ways to create range, hash, array.
I can only think of 2: (1..3) and Range.new(1,3) (and similarly for hash and array).
What is the third way?
The tutorial in question is The Odin Project
Ranges may be constructed using the s..e and s...e literals, or with ::new.
Ranges constructed using .. run from the beginning to the end inclusively.
Those created using ... exclude the end value. When used as an iterator, ranges return each value in the sequence.
(0..2) == (0..2) #=> true
(0..2) == Range.new(0,2) #=> true
(0..2) == (0...2) #=> false
Read More Here
For Arrays there's Array::[] (example taken directly from the docs):
Array.[]( 1, 'a', /^A/ ) # => [1, "a", /^A/]
Array[ 1, 'a', /^A/ ] # => [1, "a", /^A/]
[ 1, 'a', /^A/ ] # => [1, "a", /^A/]
Similarly there's Hash::[]. Not sure about Ranges; in fact, the docs (as far as I can tell) only mention literals and Range::new.
I can't see why you'd use these over a literal, but there you go.
You can also make a exclusive range, using (1...4), which if turned into an array would become [1, 2, 3]
(1..3) is an inclusive range, so it contains all numbers, from 1 to 3, but if you used (1...3), having 3 dots instead of 2 makes it exclusive, so it contains all numbers from 1, up to but not including 3.
As for arrays and hashes, #to_a, Array#[], #to_h, and Hash#[] will work.
(1..3).to_a
=> [1, 2, 3]
Array[1, 2, 3]
=> [1, 2, 3]
[[1, 2], [3, 4], [5, 6]].to_h
=> {1=>2, 3=>4, 5=>6}
Hash[ [[1, 2], [3, 4], [5, 6]] ]
=> {1=>2, 3=>4, 5=>6}
But they are probably looking for Array#[] and Hash#[] on the array and hash part.

How to drop the end of an array in Ruby

Array#drop removes the first n elements of an array. What is a good way to remove the last m elements of an array? Alternately, what is a good way to keep the middle elements of an array (greater than n, less than m)?
This is exactly what Array#pop is for:
x = [1,2,3]
x.pop(2) # => [2,3]
x # => [1]
You can also use Array#slice method, e.g.:
[1,2,3,4,5,6].slice(1..4) # => [2, 3, 4, 5]
or
a = [1,2,3,4,5,6]
a.take 3 # => [1, 2, 3]
a.first 3 # => [1, 2, 3]
a.first a.size - 1 # to get rid of the last one
The most direct opposite of drop (drop the first n elements) would be take, which keeps the first n elements (there's also take_while which is analogous to drop_while).
Slice allows you to return a subset of the array either by specifying a range or an offset and a length. Array#[] behaves the same when passed a range as an argument or when passed 2 numbers
this will get rid of last n elements:
a = [1,2,3,4,5,6]
n = 4
p a[0, (a.size-n)]
#=> [1, 2]
n = 2
p a[0, (a.size-n)]
#=> [1, 2, 3, 4]
regard "middle" elements:
min, max = 2, 5
p a.select {|v| (min..max).include? v }
#=> [2, 3, 4, 5]
I wanted the return value to be the array without the dropped elements. I found a couple solutions here to be okay:
count = 2
[1, 2, 3, 4, 5].slice 0..-(count + 1) # => [1, 2, 3]
[1, 2, 3, 4, 5].tap { |a| a.pop count } # => [1, 2, 3]
But I found another solution to be more readable if the order of the array isn't important (in my case I was deleting files):
count = 2
[1, 2, 3, 4, 5].reverse.drop count # => [3, 2, 1]
You could tack another .reverse on there if you need to preserve order but I think I prefer the tap solution at that point.
You can achieve the same as Array#pop in a non destructive way, and without needing to know the lenght of the array:
a = [1, 2, 3, 4, 5, 6]
b = a[0..-2]
# => [1, 2, 3, 4, 5]
n = 3 # if we want drop the last n elements
c = a[0..-(n+1)]
# => [1, 2, 3]
Array#delete_at() is the simplest way to delete the last element of an array, as so
arr = [1,2,3,4,5,6]
arr.delete_at(-1)
p arr # => [1,2,3,4,5]
For deleting a segment, or segments, of an array use methods in the other answers.
You can also add some methods
class Array
# Using slice
def cut(n)
slice(0..-n-1)
end
# Using pop
def cut2(n)
dup.tap{|x| x.pop(n)}
end
# Using take
def cut3(n)
length - n >=0 ? take(length - n) : []
end
end
[1,2,3,4,5].cut(2)
=> [1, 2, 3]

How do I check an array for duplicates? [duplicate]

This question already has answers here:
How to find and return a duplicate value in array
(23 answers)
Closed 8 years ago.
I've got an array A. I'd like to check if it contains duplicate values. How would I do so?
Just call uniq on it (which returns a new array without duplicates) and see whether the uniqed array has less elements than the original:
if a.uniq.length == a.length
puts "a does not contain duplicates"
else
puts "a does contain duplicates"
end
Note that the objects in the array need to respond to hash and eql? in a meaningful for uniq to work properly.
In order to find the duplicated elements, I use this approach (with Ruby 1.9.3):
array = [1, 2, 1, 3, 5, 4, 5, 5]
=> [1, 2, 1, 3, 5, 4, 5, 5]
dup = array.select{|element| array.count(element) > 1 }
=> [1, 1, 5, 5, 5]
dup.uniq
=> [1, 5]
If you want to return the duplicates, you can do this:
dups = [1,1,1,2,2,3].group_by{|e| e}.keep_if{|_, e| e.length > 1}
# => {1=>[1, 1, 1], 2=>[2, 2]}
If you want just the values:
dups.keys
# => [1, 2]
If you want the number of duplicates:
dups.map{|k, v| {k => v.length}}
# => [{1=>3}, {2=>2}]
Might want to monkeypatch Array if using this more than once:
class Array
def uniq?
self.length == self.uniq.length
end
end
Then:
irb(main):018:0> [1,2].uniq?
=> true
irb(main):019:0> [2,2].uniq?
=> false

Resources