Using uniq! on collection with two parameters - ruby

At this point I am using uniq! to get the unique elements in a collection. Is is possible to get the unique elements based on two parameters? In other words, I would like to use uniq! to get "unique" elements based on both t.info and t.name.
collection.uniq! {|t| t.info }

Compare an array of those parameters:
T = Struct.new :info, :name
collection = [
T.new('a', 'b'),
T.new('a', 'b'),
T.new('a', 'a'),
]
collection.uniq! { |t| [t.info, t.name] }
#=> [#<struct T info="a", name="b">, #<struct T info="a", name="a">]

require 'pp'
require 'ostruct'
a = OpenStruct.new(a: 1, b: 2, c: 3)
b = OpenStruct.new(a: 2, b: 2, c: 3)
c = OpenStruct.new(a: 1, b: 2, c: 4)
pp [a, b, c].uniq # all different
pp [a, b, c].uniq { |t| [t.a, t.b] } # a and c are same

Related

Ruby block taking array or multiple parameters

Today I was surprised to find ruby automatically find the values of an array given as a block parameter.
For example:
foo = "foo"
bar = "bar"
p foo.chars.zip(bar.chars).map { |pair| pair }.first #=> ["f", "b"]
p foo.chars.zip(bar.chars).map { |a, b| "#{a},#{b}" }.first #=> "f,b"
p foo.chars.zip(bar.chars).map { |a, b,c| "#{a},#{b},#{c}" }.first #=> "f,b,"
I would have expected the last two examples to give some sort of error.
Is this an example of a more general concept in ruby?
I don't think my wording at the start of my question is correct, what do I call what is happening here?
Ruby block are quirky like that.
The rule is like this, if a block takes more than one argument and it is yielded a single object that responds to to_ary then that object is expanded. This makes yielding an array versus yielding a tuple seem to behave the same way for blocks that take two or more arguments.
yield [a,b] versus yield a,b do differ though when the block takes one argument only or when the block takes a variable number of arguments.
Let me demonstrate both of that
def yield_tuple
yield 1, 2, 3
end
yield_tuple { |*a| p a }
yield_tuple { |a| p [a] }
yield_tuple { |a, b| p [a, b] }
yield_tuple { |a, b, c| p [a, b, c] }
yield_tuple { |a, b, c, d| p [a, b, c, d] }
prints
[1, 2, 3]
[1]
[1, 2]
[1, 2, 3]
[1, 2, 3, nil]
Whereas
def yield_array
yield [1,2,3]
end
yield_array { |*a| p a }
yield_array { |a| p [a] }
yield_array { |a, b| p [a, b] }
yield_array { |a, b, c| p [a, b, c] }
yield_array { |a, b, c, d| p [a, b, c, d] }
prints
[[1, 2, 3]]
[[1, 2, 3]]
[1, 2] # array expansion makes it look like a tuple
[1, 2, 3] # array expansion makes it look like a tuple
[1, 2, 3, nil] # array expansion makes it look like a tuple
And finally to show that everything in Ruby uses duck-typing
class A
def to_ary
[1,2,3]
end
end
def yield_arrayish
yield A.new
end
yield_arrayish { |*a| p a }
yield_arrayish { |a| p [a] }
yield_arrayish { |a, b| p [a, b] }
yield_arrayish { |a, b, c| p [a, b, c] }
yield_arrayish { |a, b, c, d| p [a, b, c, d] }
prints
[#<A:0x007fc3c2969190>]
[#<A:0x007fc3c2969050>]
[1, 2] # array expansion makes it look like a tuple
[1, 2, 3] # array expansion makes it look like a tuple
[1, 2, 3, nil] # array expansion makes it look like a tuple
PS, the same array expansion behavior applies for proc closures which behave like blocks, whereas lambda closures behave like methods.
Ruby's block mechanics have a quirk to them, that is if you're iterating over something that contains arrays you can expand them out into different variables:
[ %w[ a b ], %w[ c d ] ].each do |a, b|
puts 'a=%s b=%s' % [ a, b ]
end
This pattern is very useful when using Hash#each and you want to break out the key and value parts of the pair: each { |k,v| ... } is very common in Ruby code.
If your block takes more than one argument and the element being iterated is an array then it switches how the arguments are interpreted. You can always force-expand:
[ %w[ a b ], %w[ c d ] ].each do |(a, b)|
puts 'a=%s b=%s' % [ a, b ]
end
That's useful for cases where things are more complex:
[ %w[ a b ], %w[ c d ] ].each_with_index do |(a, b), i|
puts 'a=%s b=%s # %d' % [ a, b, i ]
end
Since in this case it's iterating over an array and another element that's tacked on, so each item is actually a tuple of the form %w[ a b ], 0 internally, which will be converted to an array if your block only accepts one argument.
This is much the same principle you can use when defining variables:
a, b = %w[ a b ]
a
# => 'a'
b
# => 'b'
That actually assigns independent values to a and b. Contrast with:
a, b = [ %w[ a b ] ]
a
# => [ 'a', 'b' ]
b
# => nil
I would have expected the last two examples to give some sort of error.
It does in fact work that way if you pass a proc from a method. Yielding to such a proc is much stricter – it checks its arity and doesn't attempt to convert an array argument to an argument list:
def m(a, b)
"#{a}-#{b}"
end
['a', 'b', 'c'].zip([0, 1, 2]).map(&method(:m))
#=> wrong number of arguments (given 1, expected 2) (ArgumentError)
This is because zip creates an array (of arrays) and map just yields each element, i.e.
yield ['a', 0]
yield ['b', 1]
yield ['c', 2]
each_with_index on the other hand works:
['a', 'b', 'c'].each_with_index.map(&method(:m))
#=> ["a-0", "b-1", "c-2"]
because it yields two separate values, the element and its index, i.e.
yield 'a', 0
yield 'b', 1
yield 'c', 2

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"}

How to merge two hashes that have same keys in ruby

I have a two hashes that should have same keys like:
a = {a: 1, b: 2, c: 3}
b = {a: 2, b: 3, c: 4}
And I want to sum up each values like this:
if a.keys == b.keys
a.values.zip(b.values).map{|a, b| a+b}
end
But this code doesn't work if the order of keys are different like b = {a: 2, c: 4, b: 3}.
How can I write the code taking into account about order of keys?
Use Hash#merge or Hash#merge!:
a = {a: 1, b: 2, c: 3}
b = {a: 2, c: 4, b: 3}
a.merge!(b) { |k, o, n| o + n }
a # => {:a=>3, :b=>5, :c=>7}
The block is called with key, old value, new value. And the return value of the block is used as a new value.
If you're using Active Support (Rails), which adds Hash#transform_values, I really like this easy-to-read solution when you have n hashes:
hashes = [hash_1, hash_2, hash_3] # any number of hashes
hashes.flat_map(&:to_a).group_by(&:first).transform_values { |x| x.sum(&:last) }

Remove element from array by type

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)}

How can I map values in two different arrays as properties on an equivalent array of objects?

I have two arrays:
a = [a1, ..., an]
b = [b1, ..., bn]
I want to create an array of Objects from these arrays where an object has fields a and b. So it would look like:
o = [o1, ..., on]
where o1.a = a1 and o1.b = b1 and o2.a = a2 and o2.b = b2 and so on.
Right now, I have:
Obj = Struct.new(:a, :b)
a = [1, 2, 3, 4]
b = [5, 6, 7, 8]
objs = []
// Is there a better way of doing the following or is it okay?
a.zip(b).each do |ai|
objs << Obj.new(ai[0], ai[1])
a.zip(b).map { |args| Obj.new(*args) }
Per your edit:
a.zip(b).map { |(a, b)| Obj.new(a, b) }
In Ruby 1.9, the following works:
a = (1..10).to_a
b = (11..20).to_a
o = Struct.new(:a, :b)
a.zip(b).map {|x, y| o.new(x, y) }
# => [#<struct a=1, b=11>, #<struct a=2, b=12> ... ]
You can just pass multiple parameters to the block, and the array that would be passed to it gets expanded into those parameters, just like *args or a, b = [1, 11] would do.

Resources