How to run enum functions with conditions of another enumerable? - enums

Finding it difficult to put my problem into words..
I have a function to sort an enumerable with specific conditions and i have another enumerable specified with Enum.take(x, -1)
Enum.map(list, fn x -> Enum.take(x, 1) end)
|> Enum.map(fn x -> Enum.join(x) end)
|> Enum.sort(&(byte_size(&1) > byte_size(&2)))
|> Enum.with_index
What i want to do is apply this function to the second enumerable but with the conditions of the first, such that the items match up with the original position of the first, sort of like this:
iex(1)> x = [1,2,3,4,5]
[1, 2, 3, 4, 5]
iex(2)> y = [:a,:b,:c,:d,:e]
[:a, :b, :c, :d, :e]
...
# arbitrary code to sort x such that x = [5,4,3,2,1]
# and y is sorted with the same conditions applied to x such that..
...
iex(4)> y = [:e,:d,:c,:b,:a]
[:e, :d, :c, :b, :a]
How might i achieve this? What sort of workaround can i use or are there any specific language features that can do what i need?
I hope my problem makes sense and thank you for reading :>

What I think you're trying to do is a list of one property sorted by another property. I think you could do something like this:
xs = [5, 4, 3, 2, 1]
ys = [:a, :b, :c, :d, :e]
Enum.zip(xs, ys) # Produces a list of pairs like {5, :a}, etc.
|> Enum.sort_by(fn {x, _y} -> x end)
|> Enum.map(&elem(&1, 1))
# => [:e, :d, :c, :b, :a]
So basically pack your data into pairs, sort the pairs and then unpack the second element.
You actually can just Enum.sort() instead of the sort_by, because the default order for tuples is to compare by first element, then the second if first is equal, etc., but this is maybe more explicit.

Related

Can I have a custom way to resolve conflicts in `Array#to_h`?

Suppose I have something like list = [[:a, 1], [:b, 1], [:a, 2]]. I want to turn this into {a: 3, b: 1}.
I feel like I should be able to do this just by doing list.to_h(:+) but that doesn't seem possible. I could of course use reduce, but that seems unnecessarily complex.
Is there a simpler way to do this? For example, if I was using Haskell I could do Map.fromListWith (+) for this functionality.
Doesn't seem possible since to_h doesn't take arguments. Why do you think it's unnecessarily complex to use each_with_object or reduce?
[[:a, 1], [:b, 1], [:a, 2]].each_with_object(Hash.new(0)) do |(k, v), memo|
memo[k] += v
end
That seems straight forward to me
For people who want to do this, here is a patch:
class Array
def to_h(&block)
return super unless block
each_with_object(Hash.new) do |(key, value), memo|
if memo.key?(key)
memo[key] = block.call(memo[key], value)
else
memo[key] = value
end
end
end
end
You can use it like puts [[:a, 1], [:b, 1], [:a, 2]].to_h(&:+)

Replace array elements with map

I have two arrays:
#a = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
#b = [a, b, c]
I need to replace n-th column in a with b like:
swap_column(0)
#=> [a, 2, 3]
[b, 5, 6]
[c, 8, 9]
(This is for using Cramer's rule for solving equations system, if anybody wonders.)
The code I've come up with:
def swap_column(n)
#a.map.with_index { |row, j| row[n] = #b[j] }
end
How do I get rid of assignment here so that map returns the modified matrix while leaving #a intact?
What you wanted is dup. Also, you had the return value of the map.with_index block wrong.
def swap_column(i)
#a.map.with_index{|row, j| row = row.dup; row[i] = #b[j]; row}
end
or
def swap_column(i)
#a.map.with_index{|row, j| row.dup.tap{|row| row[i] = #b[j]}}
end
The answer by sawa is good and the main point is you need to dup your inner arrays for this to work properly. The only reason for this additional post is to point out that often when you are using with_index so that you can directly 1:1 index into another array you can simplify the code by using zip.
def swap_column(n)
#a.zip(#b).map {|r,e| r.dup.tap{|r| r[n] = e}}
end
What zip does is combine your two arrays into a new array where each element is an array made of the two corresponding elements of the initial arrays. In this case it would be an array of an array and an element you want to later use for replacement. We then map over those results and automatically destructure each element into the two pieces. We then dup the array piece and tap it to replace the nth element.
You can use transpose to do the following:
class M
attr :a, :b
def initialize
#a = [[1,2,3],
[4,5,6],
[7,8,9]
]
#b = [:a, :b, :c]
end
def swap_column(n)
t = #a.transpose
t[0] = #b
t.transpose
end
end
m = M.new
=> #<M:0x007ffdc2952e38 #a=[[1, 2, 3], [4, 5, 6], [7, 8, 9]], #b=[:a, :b, :c]>
m.swap_column(0)
=> [[:a, 2, 3], [:b, 5, 6], [:c, 8, 9]]
m # m is unchanged
=> #<M:0x007ffdc2952e38 #a=[[1, 2, 3], [4, 5, 6], [7, 8, 9]], #b=[:a, :b, :c]>

Ruby, equally distribute elements and interleave/merge multiple arrays

I have multiple arrays with unknown element count like
a = []
a << [:a, :c, :e]
a << [:b, :f, :g, :h, :i, :j]
a << [:d]
result should be something like ~ (I don't really care details due rounding etc)
r = [:b, :a, :f, :g, :d, :c, :h, :i, :e, :j]
This is how I think it could be done
First we need to expand/distribute equally elements in each array to same length, so we get something like
a << [nil, :a, nil, :c, nil, :e]
a << [:b, :f, :g, :h, :i, :j]
a << [nil, nil, :d, nil, nil]
Next we interleave them as typically would do
r = a.shift
a.each { |e| r = r.zip(e) }
r = r.flatten.compact
My current problem is how to equally (as much as it's possible) distribute those elements across array? There could be one array with 4 elements and other with 5, but probably biggest should go first.
Of course would be nice to see if there's any other way to achieve this :)
I would use a sort to do this, based on element index postion, divided by size of array, plus some offset based on array id, to keep things consistent (if you don't need consistency, you could use a small random offset instead).
a = [:a,:b]
b = [:c]
c = [:d,:e,:f]
d = [:g:,:h,:i,:j]
def sort_pos array, id
(1..array.size).map { |i| (i - 0.5 + id/1000.0)/(array.size + 1e-6) }
end
# Combine all the arrays with their sort index, assigning ids to each array for consistency.
# Depending on how you receive these arrays, this structure can be built up programatically,
# as long as you add an array plus its sort index numbers at the same time
combined = (a + b + c + d).zip( sort_pos(a, 1) + sort_pos(b, 2) + sort_pos(c, 3) + sort_pos(d, 4) )
# Extract the values from the original arrays in their new order
combined.sort_by { |zipped| zipped[1] }.map { |zipped| zipped[0] }
=> [:g, :d, :a, :h, :e, :i, :b, :f, :j, :c]
There might be a cleaner way of doing this in Ruby . . . but I think the end result is what you are after - an "even" mix of multiple arrays.
If you only care about even-ness of mix from a statistical perspective (i.e. over time it is "fair"), you could just do this:
(a+b+c+d).shuffle
=> [:g, :b, :i, :c, :a, :h, :e, :j, :f, :d]

finding out which object has n instances in ruby

I have an array: x = [a, b, c, d, e, f, g, h] which can have objects from 1 to 9
Firstly, I have to count IF any of these objects is present 3 times. I don't want to write
if (x.count(1) == 3) or (x.count(2) == 3) ...etc...
is there a way to shorten this, like below?
x.count { |obj| obj } == 3
Secondly, if I know that an object has been found with 3 instances, how can I find out which one was it? (1 or 2 or 3.....)
x = [:a, :b, :b, :b, :c, :c, :c]
counted = Hash[
x.group_by do |e|
x.count(e)
end.map do |count, items|
[count, items.uniq]
end
]
p counted[3] #=> [:b, :c]
How does this work? Let's follow the steps. First, let's group the items by count:
grouped_by_count = x.group_by do |e|
x.count(e)
end
This produces a hash with the keys being the counts, and the values being the list of non-unique items having that count:
p grouped_by_count
#=> {1=>[:a], 3=>[:b, :b, :b, :c, :c, :c]}
We'd really rather have unique items, though, so let's do that transform:
grouped_by_count_unique = grouped_by_count.map do |count, items|
[count, items.uniq]
end
p grouped_by_count_unique
#=> [[1, [:a]], [3, [:b, :c]]]
That gives us an array of arrays, and not a hash. Fortunately, it's easy to turn an array of arrays into a hash:
counted = Hash[grouped_by_count_unique]
p counted
# => {1=>[:a], 3=>[:b, :c]}
Now just put the pieces together eliminating the temporaries and you get the answer at the top.

Parentheses in block variables

Given
a = [[:a, :b, :c]]
1) I understand this
a.each{|(x, y), z| p z} # => :b
that there are two variables (x, y) and z, so the third element :c is thrown away, and z matches :b. And I understand this
a.each{|(x, y), z| p y} # => nil
that (x, y) matches :a, and since it is not an array, there are no elements to it, and so y matches nil.
But how does
a.each{|(x, y), z| p x} # => :a
work? I expect nil to be returned.
2) Why are the return values like this?
a.each{|(x, y)| p x} #=> :a
a.each{|(x, y)| p y} #=> :b
I expect them to both return nil.
It's because of the syntax of parallel assignment.
a = [[:a, :b, :c]]
So a.each has only one element to iterate, which is [:a, :b, :c].
In the first case:
(x, y), z = [:a, :b, :c]
#=> x == :a, y == nil, z == :b
Here (x, y) is an array to match the first element :a, and x gets it, then z simply matches the second element :b.
And in the second case:
(x, y) = [:a, :b, :c]
#=> x == :a, y == :b
Here (x, y) as an entire array matches the array [:a, :b, :c], so x and y get :a and :b respectively.
This is just like requiring the "args + optional args (keyword args) + rest args" combination match provided arguments. It is just smart enough to take arguments by sequence.
Another smart example:
(a,b) = 1,2
=> [1, 2] # array match
#=> a == 1, b == 2
(a,b)=[1,2]
=> [1, 2] # array match
#=> a == 1, b == 2
In either case above, it will simply make the best guess on what it should take.

Resources