Remove element from array by type - ruby

I have the following array:
["--",1,2,3,4]
How can I remove elements from the array by element type, ie. remove all non-integer values from the array?

I'd do :-
ary = ["--",1,2,3,4]
ary = ary.grep(Integer)
ary # => [1, 2, 3, 4]
Note :- If you don't want to mutate the original array use new_ary instead of ary. Like
new_ary = ary.grep(Integer)

You can use delete_if to remove items from the list, however this modifies the list.
a = ["--", 1, 2, 3, 4]
a.delete_if { |n| !n.kind_of?(Fixnum) }
p a
You can select items out of the list maintaining the original list by using select
a = ["--", 1, 2, 3, 4]
b = a.select { |n| n.kind_of?(Fixnum) }
p b
p a

This solution addresses the title, rather than the example, and permits the selection of elements by class, as well as the rejection of elements by class.
Code
good_classes and bad_classes are arrays of classes.
def filter_select(arr, *good_classes)
arr.select { |e| good_classes.include? e.class }
end
def filter_reject(arr, *bad_classes)
arr.reject { |e| bad_classes.include? e.class }
end
Examples
arr = [1, :a, {b: 3}, "cat", [4,5], true, 3..4, false]
filter_select(arr, Fixnum, Hash, TrueClass, Range)
#=> [1, {:b=>3}, true, 3..4]
filter_reject(arr, Fixnum, Hash, String, Array)
#=> [:a, true, 3..4, false]

I'd do
new_array = ary.reject {|x| x.is_a?(String)}

Related

Reduce hash with key, value and index as block parameters

h = { "a" => 1, "b" => 2 }
Is there a way to reduce a hash and have the key, value and index as block parameters?
As a starting point I can iterate over a hash getting key, value and index:
h.each_with_index { |(k,v), i| puts [k,v,i].inspect }
# => ["a", 1, 0]
# => ["b", 2, 1]
However when I add reduce I seem to loose the ability to have the key and value as separate values and instead they are provided as a two element array:
h.each_with_index.reduce([]) { |memo, (kv,i)| puts [kv,i].inspect }
# => [["a", 1], 0]
# => [["b", 2], 1]
This is okay, I can in the block do kv[0] and kv[1], but I'd like something like this:
h.each_with_index.reduce([]) { |memo, (k,v), i| puts [k,v,i].inspect }
I'd like to do this without monkey-patching.
Maybe something like this?:
h.each_with_index.reduce([]) { |memo, ((k,v), i)| puts [k,v,i].inspect }
#=> ["a", 1, 0]
#=> ["b", 2, 1]
#=> nil
All you need is scoping: ((k,v), i).
Keeping in mind with reduce, we always have to return the object at the end of block. Which is kind of an extra overhead unless last operation isn't on the memo object which returns the object itself.Otherwise it won't return the desired result.
Same thing can be achieved with each_with_index chained with with_object like so:
h.each_with_index.with_object([]) { |((k,v), i), memo| memo << [k,v,i].inspect }
#=> ["a", 1, 0]
#=> ["b", 2, 1]
#=> []
See the array at last line of output? That's our memo object, which isn't same as reduce that we used above.
When in doubt what the block arguments are, create an instance of an Enumerator and call #next on it:
▶ h = {a: 1, b: 2}
#⇒ {:a=>1, :b=>2}
▶ enum = h.each.with_index.with_object([])
#⇒ #<Enumerator: ...>
▶ enum.next
#⇒ [[[:a, 1], 0], []]
The returned value consists of:
array of key and value, joined into:
array with an index, joined into:
array with an accumulator (for reduce it’d go in front, if reduce returned an enumerator when called without a block—credits to #Stefan for nitpicking.)
Hence, the proper parentheses for decomposing it would be:
# ⇓ ⇓ ⇓ ⇓
# [ [ [:a, 1], 0 ], [] ]
{ | ( (k, v), idx ), memo| ...
Enumerable#each_with_index yields two values into the block: the item and its index. When it is invoked for a Hash, the item is an array that contains two elements: the key and the associated value.
When you declare the block arguments |(k,v), i| you, in fact, deconstruct the first block argument (the item) into its two components: the key and the value. Without a block h.each_with_index produces an Enumerator that yields both arguments of the previously used block wrapped into an array.
This array is the second argument of Enumerator#reduce.
You can tell this by running:
irb> h.each_with_index.reduce([]) { |memo, j| p j }
[["a", 1], 0]
[["b", 2], 1]
Now, the answer to your question is easy: just deconstruct j and you get:
irb> h.each_with_index.reduce([]) { |memo, ((k,v), i)| puts [k,v,i].inspect }
["a", 1, 0]
["b", 2, 1]
Of course, you should memo << [k,v,i] or put the values in memo using other other rules and return memo to get your final desired result.

Create a Hash from two arrays of different sizes and iterate until none of the keys are empty

Having two arrays of different sizes, I'd like to get the longer array as keys and the shorter one as values. However, I don't want any keys to remain empty, so that is why I need to keep iterating on the shorter array until all keys have a value.
EDIT: I want to keep array longer intact, but without empty values, that means keep iterating on shorter until all keys have a value.
longer = [1, 2, 3, 4, 5, 6, 7]
shorter = ['a', 'b', 'c']
Hash[longer.zip(shorter)]
#=> {1=>"a", 2=>"b", 3=>"c", 4=>nil, 5=>nil, 6=>nil, 7=>nil}
Expected Result
#=> {1=>"a", 2=>"b", 3=>"c", 4=>"a", 5=>"b", 6=>"c", 7=>"a"}
Here's an elegant one. You can "loop" the short array
longer = [1, 2, 3, 4, 5, 6, 7]
shorter = ['a', 'b', 'c']
longer.zip(shorter.cycle).to_h # => {1=>"a", 2=>"b", 3=>"c", 4=>"a", 5=>"b", 6=>"c", 7=>"a"}
A crude way until you find something more elegant:
Slice the longer array as per length of shorter one, and iterate over it to re-map the values.
mapped = longer.each_slice(shorter.length).to_a.map do |slice|
Hash[slice.zip(shorter)]
end
=> [{1=>"a", 2=>"b", 3=>"c"}, {4=>"a", 5=>"b", 6=>"c"}, {7=>"a"}]
Merge all hashes withing the mapped array into a single hash
final = mapped.reduce Hash.new, :merge
=> {1=>"a", 2=>"b", 3=>"c", 4=>"a", 5=>"b", 6=>"c", 7=>"a"}
Here's a fun answer.
longer = [1, 2, 3, 4, 5, 6, 7]
shorter = ['a', 'b', 'c']
h = Hash.new do |h,k|
idx = longer.index(k)
idx ? shorter[idx % shorter.size] : nil
end
#=> {}
h[1] #=> a
h[2] #=> b
h[3] #=> c
h[4] #=> a
h[5] #=> b
h[6] #=> c
h[7] #=> a
h[8] #=> nil
h #=> {}
h.values_at(3,5) #=> ["c", "b"]
If this is not good enough (e.g., if you wish to use Hash methods such as keys, key?, merge, to_a and so on), you could create the associated hash quite easily:
longer.each { |n| h[n] = h[n] }
h #=> {1=>"a", 2=>"b", 3=>"c", 4=>"a", 5=>"b", 6=>"c", 7=>"a"}

Grouping an array on the basis of its first element, without duplication in Ruby

I'm executing an active record command Product.pluck(:category_id, :price), which returns an array of 2 element arrays:
[
[1, 500],
[1, 100],
[2, 300]
]
I want to group on the basis of the first element, creating a hash that looks like:
{1 => [500, 100], 2 => [300]}
group_by seems logical, but replicates the entire array. I.e. a.group_by(&:first) produces:
{1=>[[1, 500], [1, 100]], 2=>[[2, 300]]}
You can do a secondary transform to it:
Hash[
array.group_by(&:first).collect do |key, values|
[ key, values.collect { |v| v[1] } ]
end
]
Alternatively just map out the logic directly:
array.each_with_object({ }) do |item, result|
(result[item[0]] ||= [ ]) << item[1]
end
This one-liner seemed to work for me.
array.group_by(&:first).map { |k, v| [k, v.each(&:shift)] }.to_h
Since you're grouping by the first element, just remove it with shift and turn the result into a hash:
array.group_by(&:first).map do |key, value|
value = value.flat_map { |x| x.shift; x }
[key, value]
end #=> {1=>[500, 100], 2=>[300]}
I do not like the destructive operation.
array.group_by(&:first).map { |id, a| [id, a.map(&:last)] }.to_h
Used this functionality several times in my app, added extension to an array:
# config/initializers/array_ext.rb
class Array
# given an array of two-element arrays groups second element by first element, eg:
# [[1, 2], [1, 3], [2, 4]].group_second_by_first #=> {1 => [2, 3], 2 => [4]}
def group_second_by_first
each_with_object({}) { |(first, second), h| (h[first] ||= []) << second }
end
end

get coordinates of value in 2D array

I want to get the coordinates of every occurrence of an object stored in an array of arrays. If I have an array:
array = [["foo", "bar", "lobster"], ["camel", "trombone", "foo"]]
and an object "foo", I want to get:
[[0,0], [1,2]]
The following will do this, but it's elaborate and ugly:
array.map
.with_index{
|row,row_index| row.map.with_index {
|v,col_index| v=="foo" ? [row_index,col_index] : v
}
}
.flatten(1).find_all {|x| x.class==Array}
Is there a more straightforward way to do this? This was asked before, and produced a similarly inelegant solution.
Here's a slightly more elegant solution. I have:
Used flat_map instead of flattening at the end
Used .each_index.select instead of .map.with_index and then having to strip non-arrays at the end, which is really ugly
Added indentation
array.flat_map.with_index {|row, row_idx|
row.each_index.select{|i| row[i] == 'foo' }.map{|col_idx| [row_idx, col_idx] }
}
Another way:
array = [["foo", "bar", "lobster"], ["camel", "trombone", "foo"],
["goat", "car", "hog"], ["foo", "harp", "foo"]]
array.each_with_index.with_object([]) { |(a,i),b|
a.each_with_index { |s,j| b << [i,j] if s == "foo" } }
#=> [[0,0], [1,2], [3,0], [3,2]
It's better to work with flat arrays.
cycle = array.first.length
#=> 3
array.flatten.to_enum.with_index
.select{|e, i| e == "foo"}
.map{|e, i| i.divmod(cycle)}
#=> [[0, 0], [1, 2]]
or
cycle = array.first.length
#=> 3
array = array.flatten
array.each_index.select{|i| array[i] == "foo"}.map{|e, i| i.divmod(cycle)}
#=> [[0, 0], [1, 2]]

Swap hash keys with values and convert keys to symbols in Ruby?

This is the input hash:
p Score.periods #{"q1"=>0, "q2"=>1, "q3"=>2, "q4"=>3, "h1"=>4, "h2"=>5}
This is my current code to exchange the keys with the values, while converting the keys to symbols:
periods = Score.periods.inject({}) do |hsh,(k,v)|
hsh[v] = k.to_sym
hsh
end
Here is the result:
p periods #{0=>:q1, 1=>:q2, 2=>:q3, 3=>:q4, 4=>:h1, 5=>:h2}
It just seems like my code is clunky and it shouldn't take 4 lines to do what I'm doing here. Is there a cleaner way to write this?
You can do this:
Hash[periods.values.zip(periods.keys.map(&:to_sym))]
Or if you're using a version of Ruby where to_h is available for arrays, you can do this:
periods.values.zip(periods.keys.map(&:to_sym)).to_h
What the two examples above do is make arrays of the keys and values of the original hash. Note that the string keys of the hash are mapped to symbols by passing to_sym to map as a Proc:
periods.keys.map(&:to_sym)
# => [:q1, :q2, :q3, :q4, :h1, :h2]
periods.values
# => [0, 1, 2, 3, 4, 5]
Then it zips them up into an array of [value, key] pairs, where each corresponding elements of values is matched with its corresponding key in keys:
periods.values.zip(periods.keys.map(&:to_sym))
# => [[0, :q1], [1, :q2], [2, :q3], [3, :q4], [4, :h1], [5, :h2]]
Then that array can be converted back into a hash using Hash[array] or array.to_h.
The simplest way is:
data = {"q1"=>0, "q2"=>1, "q3"=>2, "q4"=>3, "h1"=>4, "h2"=>5}
Hash[data.invert.collect { |k, v| [ k, v.to_sym ] }]
The Hash[] method converts an array of key/value pairs into an actual Hash. Quite handy for situations like this.
If you're using Ruby on Rails this could be even easier:
data.symbolize_keys.invert
h = {"q1"=>0, "q2"=>1, "q3"=>2, "q4"=>3, "h1"=>4, "h2"=>5}
h.each_with_object({}) { |(k,v),g| g[v] = k.to_sym }
#=> {0=>:q1, 1=>:q2, 2=>:q3, 3=>:q4, 4=>:h1, 5=>:h2}
The steps are as follows (for the benefit of Ruby newbies).
enum = h.each_with_object({})
#=> #<Enumerator: {0=>"q1", 1=>"q2", 2=>"q3", 3=>"q4",
# 4=>"h1", 5=>"h2"}:each_with_object({})>
The elements that will be generated by the enumerator and passed to the block can be seen by converting the enumerator to an array, using Enumerable#entries or Enumerable#to_a.
enum.entries
#=> [[["q1", 0], {}], [["q2", 1], {}], [["q3", 2], {}],
# [["q4", 3], {}], [["h1", 4], {}], [["h2", 5], {}]]
Continuing,
enum.each { |(k,v),g| g[v] = k.to_sym }
#=> {0=>:q1, 1=>:q2, 2=>:q3, 3=>:q4, 4=>:h1, 5=>:h2}
In the last step, Enumerator#each passes the first element generated by enum to the block and assigns the three block variables. Consider the first element of enum that is passed to the block and the associated calculation of values for the three block variables. (I must first execute enum.rewind to reinitialize enum, as each above took the enumerator to its end. See Enumerator#rewind).
(k, v), g = enum.next
#=> [["q1", 0], {}]
k #=> "q1"
v #=> 0
g #=> {}
See Enumerator#next. The block calculation is therefore
g[v] = k.to_sym
#=> :q1
Hence,
g #=> {0=>:q1}
The next element of enum is passed to the block and similar calculations are performed.
(k, v), g = enum.next
#=> [["q2", 1], {0=>:q1}]
k #=> "q2"
v #=> 1
g #=> {0=>:q1}
g[v] = k.to_sym
#=> :q2
g #=> {0=>:q1, 1=>:q2}
The remaining calculations are similar.

Resources