How do I split an array using nil as delimitor? - ruby

I'm searching a method for converting:
[1,2,3,nil,4,5,nil,6,7,8,9]
into:
[[1,2,3],[4,5],[6,7,8,9]]
Is there a built-in way to do that in Ruby?

I'd use:
[1,2,3,nil,4,5,nil,6,7,8,9].slice_before{ |e| e.nil? }.map(&:compact)
=> [[1, 2, 3], [4, 5], [6, 7, 8, 9]]
slice_before is really powerful when you want to break an array into chunks, either by searching for a repeating pattern by passing in a regex, or something you can compute via the block. It's much too powerful to summarize right here so take time to read the documentation and play with the examples.

This should work:
array = [1,2,3,nil,4,5,nil,6,7,8,9]
array.inject([[]]) do |result, number|
number ? result.last << number : result << []
result
end
#=> [[1, 2, 3], [4, 5], [6, 7, 8, 9]]
Explanation time :-)
inject starts with an array containing an empty array
for each element, it checks if it's nil
if it isn't, it appends the current number to the previous array
if it is, it creates a new empty array
all this while updating result, which is an array of arrays
-- EDIT --
Checking David's reply I checked Rails implementation of this:
def split(value = nil)
using_block = block_given?
inject([[]]) do |results, element|
if (using_block && yield(element)) || (value == element)
results << []
else
results.last << element
end
results
end
end
If you skip the block implementation, it has the exact same structure of my code. Yay! :)

[1,2,3,nil,4,5,nil,6,7,8,9].split(nil)
Whoops array#split is a Rails method

I found kind of an interesting one. There's probably a way to shorten it:
[1,2,3,nil,4,5,nil,6,7,8,9].chunk {|e| e.nil?}.select {|e| not e[0]}.flatten(1).delete_if {|e| not e}

I would join the array and then split the array.
a =[1,2,3,nil,4,5,nil,6,7,8,9]
a = a.join("-").split("--")
a.map! { |a| a.split("-") }
a.map! {|e| e.map! {|f| f.to_i}}
puts a.inspect
#[[1, 2, 3], [4, 5], [6, 7, 8, 9]]
Made edits (based on comments), to make it an integer once again. Still not a good answer though.

Related

Adding items to a Hash in ruby, why are items being overwritten?

I have the following code:
chartJsObject = [{label: "Label Stuff", datasets: []}]
i = 0
while i < 5 do
chartJsObject[i][:datasets] << [rand(10), rand(10)] if chartJsObject[i]
i+=1
end
While I expect datasets to include five sets of #s, like [9, 9], [1, 2] etc, I get:
chartJsObject # => [{"label":"Label Stuff","datasets":[[9,9]]}]
What am I doing wrong in the while loop?
chart_js_object has only one element, at index 0. So each call to chart_js_object[i] where i is anything other than zero is returning nil. This would cause an error, except that the conditional at the end of your operative provision is causing the code to ignore each iteration after the first.
Let's use Ruby standards for variable naming. I'll translate to snake_case. Also, while loops are pretty rare in Ruby. To perform an operation a specific number of times, you can just use the #times method.
So we end up with this:
>> 5.times { chart_js_object[0][:datasets] << [rand(10), rand(10)] }
>> chart_js_object
=> [{:label=>"Label Stuff", :datasets=>[[0, 4], [6, 0], [5, 4], [4, 6], [8, 6]]}]
The chartJsObject array has only one datum, so the array count is 1, and array index is chartJsObject[0]. The loop body runs only once, as the chartJsObject array has only one datum in it, and you have given the condition as
if chartJsObject[i]
That's the reason it gives you the result like that.

Passing block to select through send method

my_array = [[1, 'foo_parent', nil], [2,'bar_is_son_of_foo', 1], [3, 'zok_is_son_of_bar', 2]]
def children_block
Proc.new do |c|
if c.is_a? Array
c[2] == self[0]
end
end
end
my_array.send(:select) &children_block
gives me ArgumentError: wrong number of arguments (0 for 1..4)
You can do something like this to pass a block to select via send.
my_array.send(:select, &children_block)
Here is a working sample:
my_array = [[1, 'foo_parent', nil], [2,'bar_is_son_of_foo', 1], [3, 'zok_is_son_of_bar', 2]]
def children_block
Proc.new do |c|
p c
end
end
p my_array.send(:select, &children_block)
#=> [1, "foo_parent", nil]
[2, "bar_is_son_of_foo", 1]
[3, "zok_is_son_of_bar", 2]
The problem you are trying to solve can be also solved using:
my_array.select {|i| my_array.find {|j| j.first == i.last} }
or
my_array.select {|i| my_array.find {|j| i.first == j.last} }
depending on which elements you are interested in.
PS: There was another question today with similar issue with respect to passing block. First part of my answer is derivation of that question's answer
Blocks and lambdas/procs aren't the same, you can't just replace one with the other. The following should guide you in the right direction
my_array.send(:select) { |element| filter_somehow(element) }

Why does calling zip on an array with a block always return nil

So I was trying to take two arrays a and b return a third array such that the nth element of the third array is the sum of the nth elements of arrays a and b. I was looking at the #zip method which interleaves arrays such that if a = [1, 2, 3] and b = [4, 5, 6]
a.zip(b) = [[1, 4], [2, 5], [3, 6]]. ruby-doc.org says If a block is given, it is invoked for each output array... While messing around with it, I found something interesting though. If you call zip with a block, it always seems to return nil. Am I doing something wrong here?
c = a.zip(b) { |x| x.reduce(:+) }
returns nil
c = a.zip(b).map { |x| x.reduce(:+) }
returns the desired result
No
For better or for worse, that's just how it works. It either returns the result or yields it to the block; it doesn't do both.
By the way, in your example a.zip(b) is actually [[1, 4], [2, 5], [3, 6]].
The documentation on Array#zip states that if a block is given, nil is returned.

Ruby: Standard recursion patterns

One of the things I commonly get hooked up on in ruby is recursion patterns. For example, suppose I have an array, and that may contain arrays as elements to an unlimited depth. So, for example:
my_array = [1, [2, 3, [4, 5, [6, 7]]]]
I'd like to create a method which can flatten the array into [1, 2, 3, 4, 5, 6, 7].
I'm aware that .flatten would do the job, but this problem is meant as an example of recursion issues I regularly run into - and as such I'm trying to find a more reusable solution.
In short - I'm guessing there's a standard pattern for this sort of thing, but I can't come up with anything particularly elegant. Any ideas appreciated
Recursion is a method, it does not depend on the language. You write the algorithm with two kind of cases in mind: the ones that call the function again (recursion cases) and the ones that break it (base cases). For example, to do a recursive flatten in Ruby:
class Array
def deep_flatten
flat_map do |item|
if item.is_a?(Array)
item.deep_flatten
else
[item]
end
end
end
end
[[[1]], [2, 3], [4, 5, [[6]], 7]].deep_flatten
#=> [1, 2, 3, 4, 5, 6, 7]
Does this help? anyway, a useful pattern shown here is that when you are using recusion on arrays, you usually need flat_map (the functional alternative to each + concat/push).
Well, if you know a bit of C , you just have to visit the docs and click the ruby function to get the C source and it is all there..
http://www.ruby-doc.org/core-1.9.3/Array.html#method-i-flatten
And for this case, here is a Ruby implementation
def flatten values, level=-1
flat = []
values.each do |value|
if level != 0 && value.kind_of?(Array)
flat.concat(flatten(value, level-1))
else
flat << value
end
end
flat
end
p flatten [1, [2, 3, [4, 5, [6, 7]]]]
#=> [1, 2, 3, 4, 5, 6, 7]
Here's an example of a flatten that's written in a tail recursive style.
class Array
# Monkeypatching the flatten class
def flatten(new_arr = [])
self.each do |el|
if el.is_a?(Array)
el.flatten(new_arr)
else
new_arr << el
end
end
new_arr
end
end
p flatten [1, [2, 3, [4, 5, [6, 7]]]]
#=> [1, 2, 3, 4, 5, 6, 7]
ruby
Although it looks like ruby isn't always optimized for tail recursion: Does ruby perform tail call optimization?

How can I sort by multiple conditions with different orders?

I'd really like to handle this without monkey-patching but I haven't been able to find another option yet.
I have an array (in Ruby) that I need to sort by multiple conditions. I know how to use the sort method and I've used the trick on sorting using an array of options to sort by multiple conditions. However, in this case I need the first condition to sort ascending and the second to sort descending. For example:
ordered_list = [[1, 2], [1, 1], [2, 1]]
Any suggestions?
Edit: Just realized I should mention that I can't easily compare the first and second values (I'm actually working with object attributes here). So for a simple example it's more like:
ordered_list = [[1, "b"], [1, "a"], [2, "a"]]
How about:
ordered_list = [[1, "b"], [1, "a"], [2, "a"]]
ordered_list.sort! do |a,b|
[a[0],b[1]] <=> [b[0], a[1]]
end
I was having a nightmare of a time trying to figure out how to reverse sort a specific attribute but normally sort the other two. Just a note about the sorting for those that come along after this and are confused by the |a,b| block syntax. You cannot use the {|a,b| a.blah <=> b.blah} block style with sort_by! or sort_by. It must be used with sort! or sort. Also, as indicated previously by the other posters swap a and b across the comparison operator <=> to reverse the sort order. Like this:
To sort by blah and craw normally, but sort by bleu in reverse order do this:
something.sort!{|a,b| [a.blah, b.bleu, a.craw] <=> [b.blah, a.bleu, b.craw]}
It is also possible to use the - sign with sort_by or sort_by! to do a reverse sort on numerals (as far as I am aware it only works on numbers so don't try it with strings as it just errors and kills the page).
Assume a.craw is an integer. For example:
something.sort_by!{|a| [a.blah, -a.craw, a.bleu]}
I had this same basic problem, and solved it by adding this:
class Inverter
attr_reader :o
def initialize(o)
#o = o
end
def <=>(other)
if #o.is && other.o.is
-(#o <=> other.o)
else
#o <=> other.o
end
end
end
This is a wrapper that simply inverts the <=> function, which then allows you to do things like this:
your_objects.sort_by {|y| [y.prop1,Inverter.new(y.prop2)]}
Enumerable#multisort is a generic solution that can be applied to arrays of any size, not just those with 2 items. Arguments are booleans that indicate whether a specific field should be sorted ascending or descending (usage below):
items = [
[3, "Britney"],
[1, "Corin"],
[2, "Cody"],
[5, "Adam"],
[1, "Sally"],
[2, "Zack"],
[5, "Betty"]
]
module Enumerable
def multisort(*args)
sort do |a, b|
i, res = -1, 0
res = a[i] <=> b[i] until !res.zero? or (i+=1) == a.size
args[i] == false ? -res : res
end
end
end
items.multisort(true, false)
# => [[1, "Sally"], [1, "Corin"], [2, "Zack"], [2, "Cody"], [3, "Britney"], [5, "Betty"], [5, "Adam"]]
items.multisort(false, true)
# => [[5, "Adam"], [5, "Betty"], [3, "Britney"], [2, "Cody"], [2, "Zack"], [1, "Corin"], [1, "Sally"]]
I've been using Glenn's recipe for quite a while now. Tired of copying code from project to project over and over again, I've decided to make it a gem:
http://github.com/dadooda/invert

Resources