Finding `uniq` values in array with their indexes - ruby

Let say I have an array
[1, 2, 3, 4, 3, 2]
I wonder how I can get the hash { 1 => 0, 4 => 3 } (value => index). I need the index of the earlier occurrence.

Could also try this
unique = array.select { |a| array.count(a) === 1 }
indexes = unique.map { |u| array.index(u) }
Hash[unique.zip(indexes)]
My algorithm is to first generate a new array of the single elements. I do that by filtering the array for the one's where the count is equal to 1. We can use the array#count method.
I then create a second array, where I map the values of the first array, to their indexes in the original array.
I then combine the values using the array#zip function and convert to a hash with Hash.
Output
{ 1 => 0, 4 => 3 }

a.each_with_index.with_object({}) { |(e, i), h| h.key?(e) ? h[e] = nil : h[e] = i }
.reject! { |k, v| v.nil? }

Related

How to sort hash in Ruby

I have the following hash:
scores = {
"Charlie" => 0
"Delta" => 5
"Beta" => 2
"Alpha" => 0
}
The numbers listed are integers, and the teams are represented as strings.
How can I sort the list by score, and if there is a tie, list it alphabetically, and then output it?
I imagine the intended output to look like:
1. Delta, 5 pts
2. Beta, 2 pts
3. Alpha, 0 pts
4. Charlie, 0 pts
I sorted the hash, but am not sure how to sort by alphabetical order if there is a tie. The code I used is below:
scores = Hash[ scores.sort_by { |team_name, scores_array| scores_array.sum.to_s } ]
In Ruby, Arrays are lexicographically ordered. You can make use of that fact for sorting by multiple keys: just create an array of the keys you want to sort by:
scores.sort_by {|team, score| [-score, team] }.to_h
#=> {'Delta' => 5, 'Beta' => 2, 'Alpha' => 0, 'Charlie' => 0}
The general pattern for sorting a Hash into a sorted Array of key/value pairs looks like this.
sorted_array_of_tuples = hash.sort { |a,b| ... }
A Hash has the Enumerable mixin which means it can use sort. Used on a Hash sort compares tuples (two element Arrays) of the key and value, so we can craft block that compares both the key and value easily.
Usually you then use the sorted Array.
sorted = hash.sort { ... }
sorted.each { |t|
puts "#{t[0]}: #{t[1]}"
}
But you can turn it back into a Hash with to_h. Since Hashes in Ruby remember the order their keys were inserted, the newly minted Hash created from the sorted Array will retain its sorting.
sorted_hash = hash.sort { |a,b| ... }.to_h
...but if more keys are added to sorted_hash they will not be sorted, they'll just go at the end. So you'll probably want to freeze the sorted hash to prevent modifications ruining the sorting.
sorted_hash.freeze
As for the sort block, in other languages an idiom of "compare by this, and if they're equal, compare by that" would look like this:
sorted_scores = scores.sort { |a,b|
# Compare values or compare keys
a[1] <=> b[1] || a[0] <=> b[0]
}
This takes advantage that <=> returns 0 when they're equal. In other languages 0 is false so you can use logical operators to create a whole chain of primary, secondary, and tertiary comparisons.
But in Ruby 0 isn't false. Only false is false. Usually this is a good thing, but here it means we need to be more specific.
sorted_scores = scores.sort { |a,b|
# Compare values
cmp = a[1] <=> b[1]
# Compare keys if the values were equal
cmp = a[0] <=> b[0] if cmp == 0
# Return the comparison
cmp
}
You can use <=> to compare and do it in a block where you first compare by value (score) and if those match, then compare by key (name).
scores = {
"Alpha" => 0,
"Beta" => 2,
"Charlie" => 0,
"Delta" => 5,
}
# convert to an array of arrays
# => [[Alpha, 0], [Beta, 2], [Charlie, 0], [Delta, 5]]
# then sort by value and, if needed, by key
scores = scores.to_a.sort! { |a,b|
cmp = a[1] <=> b[1] # sort by value (index = 1)
cmp = a[0] <=> b[0] if cmp == 0 # sort by key (index = 0) if values matched
cmp
}.to_h # convert back to a hash
puts scores
Or if you want to extract the comparison code into a method for reuse/clarity, you can have it call the method.
# Compare entries from the scores hash.
# Each entry has a name (key) and score (value) (e.g. ["Alpha", 0].
# First compare by scores, then by names (if scores match).
def compare_score_entries(a, b)
cmp = a[1] <=> b[1]
cmp = a[0] <=> b[0] if cmp == 0
return cmp
end
scores = scores.sort(&method(:compare_score_entries)).to_h
You can sort it like this
scores = {
"Charlie" => 0,
"Delta" => 5,
"Beta" => 2,
"Alpha" => 0
}
puts scores
.map{|name, score| [-score, name]}
.sort
.zip((0...scores.size).to_a)
.map{|(score, name), i| "#{i + 1}. #{name}, #{-score} pts"}
notice the minus score. It a trick to sort integer reversely.
You can try this
scores = {
'Charlie' => 0,
'Delta' => 5,
'Beta' => 2,
'Alpha' => 0
}
scores_sorted = scores.sort_by { |_key, value| -value }
scores_sorted.each.with_index(1) do |value, index|
puts "#{index}. #{value[0]}, #{value[1]} pts"
end

How do I find the keys and the value in a hash with the minimum value, only among certain keys?

Using Ruby 2.4. I have a hash with keys and values that are both numbers (integers). Given a set of keys, how do I find the entry (both key and value) that has the lowest value? If I wanted to find the minimum value I could do
my_hash.select{|k, v| my_selected_keys.include?(k) }.values.min_by(&:last)
But this only gets me the value, not both the key and the value. Also note that values in my hash are not necessarily unique.
Is this what you're looking for?
It gets all keys that have the minimum value. I've split out and named many of the intermediate objects for clarity:
hash = { 4 => 5, 2 => 9, 3 => 1, 8 => 5 }
selected = Set[2, 4, 8]
hash_subset = hash.slice(*selected)
keys_by_value = hash_subset.group_by(&:last).each_value { |group| group.map!(&:first) }
min_value, keys_with_min_val = keys_by_value.min_by(&:first)
# => [5, [4, 8]]
and the obligatory ruby oneliner:
hash.slice(*selected).group_by(&:pop).each_value(&:flatten!).min_by(&:first)
You can map the selected keys sel_keys:
hash = {1 => 2, 3 => 1, 4 => 5, 5 => 1}
sel_keys = [3, 4]
sel_keys.map { |k| [k,hash[k]] }.min_by(&:last) #=> [3, 1]
Caveat: This returns only the first key found with the min value.
You could sort the hash by the value and then take the first element
my_hash.select{|k, _| my_selected_keys.include?(k) }.sort_by{|_, v| v}.first
Here is an alternative solution
hash.reduce([nil, Float::INFINITY]) { |acc, pair| acc[1] < pair[1] ? acc : pair }

Is there a one liner or more efficient way to get this done?

Given a hash, for example:
hash = { "fish" => 2, "cat" => 3, "dog" => 1 }
I need to get:
A comma separated string for all the values, E.g. "2,3,1"
An integer that hold the total sum of the values, E.g. 6
My current code:
value_string = hash.map { |k,v| "#{v}"}.join(',')
sum = 0
hash.map { |k,v| sum += v}
You can do it like this:
hash.values.join(",") # => "2,3,1"
hash.values.inject(:+) # => 6
Here is a solution to compute both the values in single iteration (some sort of one liner)
r1,r2 = hash.reduce([nil, 0]){|m,(_,v)| [[m[0], v.to_s].compact.join(","),m[-1]+v]}
#=> ["2,3,1", 6]

Replace a single element in an array

I have an array with unique elements. Is there a way to replace a certain value in it with another value without using its index value?
Examples:
array = [1,2,3,4]
if array.include? 4
# "replace 4 with 'Z'"
end
array #=> [1,2,3,'Z']
hash = {"One" => [1,2,3,4]}
if hash["One"].include? 4
# "replace 4 with 'Z'"
end
hash #=> {"One" => [1,2,3,'Z']}
p array.map { |x| x == 4 ? 'Z' : x }
# => [1, 2, 3, 'Z']
You can do it as:
array[array.index(4)] = "Z"
If the element is not necessarily in the array, then
if i = array.index(4)
array[i] = "Z"
end
You can use Array#map
array = array.map do |e|
if e == 4
'Z'
else
e
end
end
to edit the array in place, rather than creating a new array, use Array#map!
If you have more than one thing you want to replace, you can use a hash to map old to new:
replacements = {
4 => 'Z',
5 => 'five',
}
array = array.map do |e|
replacements.fetch(e, e)
end
This make uses of a feature of Hash#fetch, where if the key is not found, the second argument is used as a default.
A very simple solution that assumes there will be no duplicates and that the order doesn't matter:
hash = { 'One' => [1, 2, 3, 4] }
hash['One'].instance_eval { push 'Z' if delete 4 }
instance_eval sets the value of self to the receiver (in this case, the array [1,2,3,4]) for the duration of the block passed to it.

Ruby Array to Histogram: How to group numbers by range?

I'm trying to group an array of integers into an hash based on where the individual values fall in a range. Basically I want to convert an array to a fixed-width histogram.
Example:
values = [1,3,4,4,4,4,4,10,12,15,18]
bin_width = 3
I need to group the array values into a range-based historgram by where they fall into a 3-unit wide bucket like so:
{'0..2'=>[1,3],'3..5'=>[4,4,4,4,4],'6..8'=>[],'9..11'=>[10]....
Is there a simple one line solution ( maybe something like values.group_by{|x| #range calc}) that would work here?
values = [1, 7, 2, 8, 2]
values.group_by { |x| x / 3 }.map { |k, vs| [(3*k..3*k+2), vs] }.to_h
#=> {0..2=>[1, 2, 2], 6..8=>[7, 8]}
If you really need the empty ranges, I don't think a clean one-liner is possible. But this should do:
grouped = values.group_by { |x| x / 3 }
min, max = grouped.keys.minmax
(min..max).map { |n| [(3*n..3*n+2), grouped.fetch(n, [])] }.to_h
#=> {0..2=>[1, 2, 2], 3..5=>[], 6..8=>[7, 8]}
I came up with a rather inefficient but quite clear solution:
ranges = 0.step(values.max, bin_width).each_cons(2).map { |s, e| Range.new(s, e, true) }
values.group_by { |v| ranges.find { |r| r.cover? v } }

Resources