Array select to get true and false arrays? - ruby

I know I can get this easily:
array = [45, 89, 23, 11, 102, 95]
lower_than_50 = array.select{ |n| n<50}
greater_than_50 = array.select{ |n| !n<50}
But is there a method (or an elegant manner) to get this by only running select once?
[lower_than_50, greater_than_50] = array.split_boolean{ |n| n<50}

over, under_or_equal = [45, 89, 23, 11, 102, 95].partition{|x| x>50 }
Or simply:
result = array.partition{|x| x>50 }
p result #=> [[89, 102, 95], [45, 23, 11]]
if you rather want the result as one array with two sub-arrays.
Edit: As a bonus, here is how you would to it if you have more than two alternatives and want to split the numbers:
my_custom_grouping = -> x do
case x
when 1..50 then :small
when 51..100 then :large
else :unclassified
end
end
p [-1,2,40,70,120].group_by(&my_custom_grouping) #=> {:unclassified=>[-1, 120], :small=>[2, 40], :large=>[70]}

The answer above is spot on!
Here is a general solution for more than two partitions (for example: <20, <50, >=50):
arr = [45, 89, 23, 11, 102, 95]
arr.group_by { |i| i < 20 ? 'a' : i < 50 ? 'b' : 'c' }.sort.map(&:last)
=> [[11], [45, 23], [89, 102, 95]]
This can be very useful if you're grouping by chunks (or any mathematically computable index such as modulo):
arr.group_by { |i| i / 50 }.sort.map(&:last)
=> [[45, 23, 11], [89, 95], [102]]

Related

Subtract Range of Numbers Algorithm

How would I accomplish the following: Take one array of ranges and subtract another array of ranges from it.
For example:
arr0 = [[0,50],[60,80],[100,150]] # 0-50, 60-80, etc.
arr1 = [[4,8],[15,20]] # 4-8, 15-20, etc.
# arr0 - arr1 magic
result = [[0,3],[9,14],[21,50],[60,80],[100,150]] # 0-3, 9-14, etc.
What's the cleanest and most efficient way to do this in Ruby?
This is a deliberately naïve solution. It's not efficient, but easy to comprehend and quite short.
Deconstruct arr0 into a list of numbers:
n1 = arr0.flat_map { |a, b| (a..b).to_a }
#=> [0, 1, ..., 49, 50, 60, 61, ..., 79, 80, 100, 101, ..., 149, 150]
Same for arr1:
n2 = arr1.flat_map { |a, b| (a..b).to_a }
#=> [4, 5, 6, 7, 8, 15, 16, 17, 18, 19, 20]
Then, subtract n2 from n1 and recombine consecutive numbers:
(n1 - n2).chunk_while { |a, b| a.succ == b }.map(&:minmax)
#=> [[0, 3], [9, 14], [21, 50], [60, 80], [100, 150]]

Ruby: replacing a matching element in a multidimensional array?

Could someone tell me how I can achieve replacing an element in this 2D array? I tried each, include and replace and wasn't able to figure out where I am going wrong. Thank you in advance for any help.
class Lotto
def initialize
#lotto_slip = Array.new(5) {Array(6.times.map{rand(1..60)})}
end
def current_pick
#number = rand(1..60).to_s
puts "The number is #{#number}."
end
def has_number
#prints out initial slip
#lotto_slip.each {|x| p x}
#Prints slip with an "X" replacing number if is on slip
#Ex: #number equals 4th number on slip --> 1, 2, 3, X, 5, 6
#lotto_slip.each do |z|
if z.include?(#number)
z = "X"
p #lotto_slip
else
z = z
p #lotto_slip
end
end
end
end
test = Lotto.new
test.current_pick
test.has_number
Let me know if this works out (tried to reduce the variations from 1 to 10 in order to be able to test easier):
class Lotto
def initialize
#lotto_slip = Array.new(5) {Array(6.times.map{rand(1..10)})}
end
def current_pick
#number = rand(1..10)
puts "The number is #{#number}."
end
def has_number
#prints out initial slip
#lotto_slip.each {|x| p x}
#Prints slip with an "X" replacing number if is on slip
#Ex: #number equals 4th number on slip --> 1, 2, 3, X, 5, 6
#lotto_slip.each do |z|
if z.include?(#number)
p "#{#number} included in #{z}"
z.map! { |x| x == #number ? 'X' : x}
end
end
#lotto_slip
end
end
test = Lotto.new
test.current_pick
p test.has_number
The problems I saw with your code are:
You don't need the to_s for this line #number = rand(1..60).to_s, else how are you going to compare the numbers produced by the array with an actual string?
You need to re-generate the array instead of re-assigning, that's why I've replaced all of that code with z.map! { |x| x == #number ? 'X' : x} which basically re-generates the entire array.
Not necessary iterate with each, use map:
#lotto_slip = Array.new(5) {Array(6.times.map{rand(1..60)})}
#=> [[25, 22, 10, 10, 57, 17], [37, 4, 8, 52, 55, 7], [44, 30, 58, 58, 50, 19], [49, 49, 24, 31, 26, 28], [24, 18, 39, 27, 8, 54]]
#number = 24
#lotto_slip.map{|x| x.map{|x| x == #number ? 'X' : x}}
#=> [[25, 22, 10, 10, 57, 17], [37, 4, 8, 52, 55, 7], [44, 30, 58, 58, 50, 19], [49, 49, "X", 31, 26, 28], ["X", 18, 39, 27, 8, 54]]

How to pick top 5 values from a hash?

I have a hash of ids and their scores, it's something like this:
#objects = {1=>57, 4=>12, 3=>9, 5=>3, 55=>47, 32=>39, 17=>27, 29=>97, 39=>58}
How can I pick the top five and drop the rest ?
I'm doing this:
#orderedObject = #objects.sort_by {|k,v| v}.reverse
=>[[29, 97], [39, 58], [1, 57], [55, 47], [32, 39], [17, 27], [4, 12], [3, 9], [5, 3]]
Then I do this:
only Keys of the #orderedObjects:
#keys = #orderedObject.map { |key, value| key }
which gives me:
=>[29, 39, 1, 55, 32, 17, 4, 3, 5]
ALL I need is [29, 39, 1, 55, 32] the first 5 indexes. But I'm stuck I don't know how to do this.
You can do
#objects = {1=>57, 4=>12, 3=>9, 5=>3, 55=>47, 32=>39, 17=>27, 29=>97, 39=>58}
#objects.sort_by { |_, v| -v }[0..4].map(&:first)
# => [29, 39, 1, 55, 32]
#objects.sort_by { |_, v| -v }.first(5).map(&:first)
# => [29, 39, 1, 55, 32]
May i suggest this more verbose requires ruby > 1.9
Hash[#objects.sort_by{|k,v| -v}.first(5)].keys
A variant of Prof. Arup's answer:
objects = {1=>57, 4=>12, 3=>9, 5=>3, 55=>47, 32=>39, 17=>27, 29=>97, 39=>58}
objects.sort_by { |k,v| -v }.first(5).to_h.keys #=> [29, 39, 1, 55, 32]
Now suppose 3=>9 were instead 3=>39 and you wanted the keys corresponding to the top 5 values (which, in this case, would be 6 keys, as 39 is the fifth largest value, 3=>39 and 32=>39), you could first compute:
threshold = objects.values.sort.last(5).min #=> 39
If you wanted the keys to be ordered by the order of values threshold or larger,
objects.select { |_,v| v >= threshold }.sort_by { |_,v| -v }.map(&:first)
#=> [29, 39, 1, 55, 3, 32]
If you don't care about the order,
objects.select { |_,v| v >= threshold }.keys #=> [1, 3, 55, 32, 29, 39]

How do I split arrays using a while loop?

I want to write a program that splits an array into two arrays, where any element in one array is smaller than any element in the other array.
The input that I have is:
a = [6, 45, 23, 65, 17, 48, 97, 32, 18, 9, 88]
And I'd like output like this:
[6, 23, 17, 18 , 9] < [45, 65, 48, 97, 32, 88]
I've tried:
i = 0
max = 0
while i < a.size
if a[i] > max
max = a[i]
end
i+=1
end
puts "this is the larger array: " + max.to_s
Which is completely off. As I am new to this, any help is appreciated.
small, large = a.sort!.shift(a.size/2) ,a
p small, large
#=> [6, 9, 17, 18, 23]
#=> [32, 45, 48, 65, 88, 97]
Try this:
newarray = a.sort.each_slice((a.size/2.0).round).to_a
It will give you an array containing your split array:
newarray = [[6,9,17,18,23,32],[45,48,65,88,97]]
In this case, if you have an odd number of elements in your array, the first array returned will always have the extra element. You can also save the arrays separately if you would like, but this way you can call each of the halves with newarray[0] and newarray[1]. If you want to split them simply add:
b = newarray[0]
c = newarray[1]
Don't use a while loop - sort the array and then split it in two
a.sort
a.in_groups_of( a.size/2)
a.sort.each_slice( a.size/2) probably does the trick without rails.
a = [6, 45, 23, 65, 17, 48, 97, 32, 18, 9, 88]
a = a.sort
print a.shift(a.count/2), " < " , a
#=> [6, 9, 17, 18, 23] < [32, 45, 48, 65, 88, 97]
Another variation
a = [6, 45, 23, 65, 17, 48, 97, 32, 18, 9, 88]
a = a.sort
print a.values_at(0..a.count/2), " < ", a.values_at((a.count/2)+1 .. -1)
#=> [6, 9, 17, 18, 23] < [32, 45, 48, 65, 88, 97]
Assuming you want to preserve order, as in your example:
def split_it(a,n)
f = a.select {|e| e <= n}
[f, a-f]
end
a = [6, 45, 23, 65, 17, 48, 97, 32, 18, 9, 88]
f, l = split_it(a,23)
puts "#{f} < #{l}" # => [6, 23, 17, 18, 9] < [45, 65, 48, 97, 32, 88]
If you want to preserve order and have the first subarray contain nbr elements, add this:
def split_nbr(a, nbr)
n = 1
loop do
return [] if n > a.max
b = split_it(a,n)
return b if b.first.size == nbr
n += 1
end
end
f, l = split_nbr(a,3)
puts "#{f} < #{l}" # => [6, 17, 9] < [45, 23, 65, 48, 97, 32, 18, 88]

How to interleave arrays of different length in Ruby

If I want to interleave a set of arrays in Ruby, and each array was the same length, we could do so as:
a.zip(b).zip(c).flatten
However, how do we solve this problem if the arrays can be different sizes?
We could do something like:
def interleave(*args)
raise 'No arrays to interleave' if args.empty?
max_length = args.inject(0) { |length, elem| length = [length, elem.length].max }
output = Array.new
for i in 0...max_length
args.each { |elem|
output << elem[i] if i < elem.length
}
end
return output
end
But is there a better 'Ruby' way, perhaps using zip or transpose or some such?
Here is a simpler approach. It takes advantage of the order that you pass the arrays to zip:
def interleave(a, b)
if a.length >= b.length
a.zip(b)
else
b.zip(a).map(&:reverse)
end.flatten.compact
end
interleave([21, 22], [31, 32, 33])
# => [21, 31, 22, 32, 33]
interleave([31, 32, 33], [21, 22])
# => [31, 21, 32, 22, 33]
interleave([], [21, 22])
# => [21, 22]
interleave([], [])
# => []
Be warned: this removes all nil's:
interleave([11], [41, 42, 43, 44, nil])
# => [11, 41, 42, 43, 44]
If the source arrays don't have nil in them, you only need to extend the first array with nils, zip will automatically pad the others with nil. This also means you get to use compact to clean the extra entries out which is hopefully more efficient than explicit loops
def interleave(a,*args)
max_length = args.map(&:size).max
padding = [nil]*[max_length-a.size, 0].max
(a+padding).zip(*args).flatten.compact
end
Here is a slightly more complicated version that works if the arrays do contain nil
def interleave(*args)
max_length = args.map(&:size).max
pad = Object.new()
args = args.map{|a| a.dup.fill(pad,(a.size...max_length))}
([pad]*max_length).zip(*args).flatten-[pad]
end
Your implementation looks good to me. You could achieve this using #zip by filling the arrays with some garbage value, zip them, then flatten and remove the garbage. But that's too convoluted IMO. What you have here is clean and self explanatory, it just needs to be rubyfied.
Edit: Fixed the booboo.
def interleave(*args)
raise 'No arrays to interleave' if args.empty?
max_length = args.map(&:size).max
output = []
max_length.times do |i|
args.each do |elem|
output << elem[i] if i < elem.length
end
end
output
end
a = [*1..5]
# => [1, 2, 3, 4, 5]
b = [*6..15]
# => [6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
c = [*16..18]
# => [16, 17, 18]
interleave(a,b,c)
# => [1, 6, 16, 2, 7, 17, 3, 8, 18, 4, 9, 5, 10, 11, 12, 13, 14, 15]
Edit: For fun
def interleave(*args)
raise 'No arrays to interleave' if args.empty?
max_length = args.map(&:size).max
# assumes no values coming in will contain nil. using dup because fill mutates
args.map{|e| e.dup.fill(nil, e.size...max_length)}.inject(:zip).flatten.compact
end
interleave(a,b,c)
# => [1, 6, 16, 2, 7, 17, 3, 8, 18, 4, 9, 5, 10, 11, 12, 13, 14, 15]

Resources