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]]
Related
I have a hash whose keys are a range of integers (lets say [1..5]) and its corresponding 5 values are all nil. I have also an array of integers (lets say [1,2,3,4,5]. What I want to do is very specific: I want to take every single key and add it to every single of the array elements, giving me a hash that has the original keys, but has now for values the entire shifted array.
After spending a few hours I have concluded that this is impossible through a really laconic expression, because it is leading to .each shadowing statements.
I think that the only way to go through with this is to create 5 almost identical methods and call them separately.
def a1
array.each do |x|
x+1
end
end
def a2
array.each do |x|
x+2
end
end
and so on..
The end product I want to achieve is this:
{1=>[2,3,4,5,6],2=>[3,4,5,6,7],3=>[4,5,6,7,8],4=>[5,6,7,8,9],5=>[6,7,8,9,10]}
It feels like there should be a more DRY way to achieve this. Any ideas?
Assuming these initial conditions:
h = {1=>nil, 2=>nil, 3=>nil, 4=>nil, 5=>nil}
arr = [1,2,3,4,5]
...it's pretty straightforward:
h.keys.each do |key|
h[key] = arr.map {|i| i+key}
end
# h is now: {1=>[2, 3, 4, 5, 6], 2=>[3, 4, 5, 6, 7], 3=>[4, 5, 6, 7, 8], 4=>[5, 6, 7, 8, 9], 5=>[6, 7, 8, 9, 10]}
(However, it may be that your question is about achieving the initial conditions. If so, I didn't grasp that, and I didn't worry about it; I just started with what I took to be your initial conditions and ended up with your desired result.)
Why don't you do this
h = {}
rng.each{|i| h[i] = ary.map{|j| j + i}}
That should work where rng is the range and ary is the array.
For example
h = {}
(1..5).each{|i| h[i] = [1,2,3,4,5].map{|j| j+i}}
results in
h = {1=>[2, 3, 4, 5, 6], 2=>[3, 4, 5, 6, 7], 3=>[4, 5, 6, 7, 8], 4=>[5, 6, 7, 8, 9], 5=>[6, 7, 8, 9, 10]}
I have a two simple arrays of numbers, representing the cartesian position of an object.
a = [3, 4]
b = [8, 5]
I want to check if "a" and "b" are beside each other. I would like to convert the two into a matrix and perform a subtractions of the two positions, and then check if the absolute value of either element is "1".
Is there a way to do this?
You're getting the uninitialized constant error because you first need:
require 'matrix'
Then you could just:
Matrix[a,b]
Sample interactive output:
irb(main):011:0> require 'matrix'
=> true
irb(main):012:0> Matrix[a,b]
=> Matrix[[3, 4], [8, 5]]
I don't think using Matrix class methods is justified here. The only method that would be marginally useful is Matrix#-, but to use that you need to convert your arrays to Matrix objects, apply Matrix#-, then convert the resultant matrix object back to an array to determine if the absolute value of any element equals one (whew!). I'd just do this:
def adjacent?(a,b)
a.zip(b).any? { |i,j| (i-j).abs == 1 }
end
adjacent?([3, 4], [8, 5]) #=> true
adjacent?([3, 7], [8, 5]) #=> false
adjacent?([3, 7], [2, 5]) #=> true
For the first example:
a = [3, 4]
b = [8, 5]
c = a.zip(b)
#=> [[3, 8], [4, 5]]
c.any? { |i,j| (i-j).abs == 1 }
#=> true
The last statements determines if either of the following is true.
(3-8).abs == 1
(4-5).abs == 1
Using Ruby 2.1, if I have an array like:
[[1,1], [2,3], [5,8], [6, 4]]
How can I convert that to an array that only has internal arrays with a count > 3?
For example, it should be:
[1, 2, 2, 2, [5,8], [6,4]]
So [5,8] and [6,4] would "pass" because their counts are > 3 but [1,1] and [2,3] would "fail" and explode out because their counts are < than 4.
EDIT
Sorry, I wasn't very clear. By "counts" I mean the second value in the internal arrays. For example, the [2,3] would have a value of 2 and a count of 3. [5,8] would have a value of 5 and a count of 8.
So if the count is > 3 then keep the original array. If the count is 3 or less, then explode the value out count number of times.
I'm pretty sure someone can come up with a better way of doing this, but:
input = [[1,1], [2,3], [5,8], [6, 4]]
input.flat_map {|val, ct| ct > 3 ? [[val, ct]] : Array.new(ct, val) }
# => [1, 2, 2, 2, [5, 8], [6, 4]]
The basic idea is that we just map the inputs (each entry) to an output (the original entry or an exploded list of values) by the count. I'm using flat_map here, but you could use the same technique with map {}.flatten(1) if you wanted. You could also use inject or each_with_object to collect the output values, which may be more straightforward but slightly less terse.
Try this:
data = [[1,1], [2,3], [5,8], [6, 4]]
results = []
data.each do |arr|
val, count = arr
if count > 3
results << arr
else
results.concat [val] * count
end
end
p results
--output:--
[1, 2, 2, 2, [5, 8], [6, 4]]
arr = [[1,1], [2,3], [5,8], [6, 4]]
arr.flat_map { |a| (a.last > 3) ? [a] : [a.first]*a.last }
#=> [1, 2, 2, 2, [5, 8], [6, 4]]
Thanks to #ChrisHeald for pointing out that flat_map is equivalent to map {}.flatten(1) (I previously had the latter) and to #7stud for telling me my original solution was incorrect, which gave me the opportunity to make my solution more interesting as well as (hopefully) correct.
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.
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]