I am trying to create a method that will take a hash:
{"H"=> 1, "e"=> 1, "l"=> 3, "o"=> 2, "W"=> 1, "r"=> 1, "d"=> 1}
as a parameter and return an array of its key-value pairs like such:
arr = [["H", 1], ["e", 1], ..., ["d", 1]]
I have the following, but it is flawed:
def toCountsArray(counts)
arr = []
i = 0
counts.each do |key, value|
arr[i].push [key, value]
i += 1
end
return arr
end
I am not supposed to use the to_a method or any kind of helper like that. Any help or guidance is appreciated.
You're basically there. The arbitrary restriction on to_a is odd, since there's many ways to get effectively the same thing. Still, to fix your original example:
array = [ ]
counts.each do |pair|
array << pair
end
That's a messy way of doing to_a, but it should work. Your mistake was trying to append to a specific element of array, not append to the array itself.
A pattern to use when doing this sort of operation is this:
counts = Hash.new(0)
That creates a Hash with a default value of 0 for each element. This avoids the dance you have to do to assign to an undefined key.
There's a few other things you can do to reduce this and make it more Ruby-like:
def count_chars(string)
string.chars.each_with_object(Hash.new(0)) do |char, counts|
case (char)
when ' '
# Ignored
else
counts[char] += 1
end
end
end
The each_with_object method is handy in that it iterates over an array while passing through an object that each iteration can make use of. Combining the trick of having a Hash with a default value makes this pretty tidy.
If you have a longer list of "to ignore" characters, express that as an array. string.chars - exclusions can then delete the unwanted ones. I've used a case statement here to make adding special behaviour easier.
hash = { "H"=> 1, "e"=> 1, "l"=> 3, "o"=> 2, "W"=> 1, "r"=> 1, "d"=> 1 }
p [*hash]
# => [["H", 1], ["e", 1], ["l", 3], ["o", 2], ["W", 1], ["r", 1], ["d", 1]]
instead of
arr[i].push [key, value]
use
arr.push [key, value]
because arr[i] refers to the i-th element
I would do something like this:
hash = { "H"=> 1, "e"=> 1, "l"=> 3, "o"=> 2, "W"=> 1, "r"=> 1, "d"=> 1 }
hash.each_with_object([]) { |kv, a| a << kv }
#=> [["H",1],["e",1],["l",3],["o",2],["W",1],["r",1],["d",1]]
You can do this:
def to_counts_array(counts)
counts.map { |k, v| [k, v] }
end
h = { "H"=> 1, "e"=> 1, "l"=> 3, "o"=> 2, "W"=> 1, "r"=> 1, "d"=> 1 }
to_counts_array(h)
Although I like the #steenslag's answer as well.
Another way, just map to self:
x.map &:itself #=> [["H", 1], ["e", 1], ["l", 3], ["o", 2], ["W", 1], ["r", 1], ["d", 1]]
Related
This is an array which i want to convert into a hash
a = [[1, 3], [3, 2], [1, 2]]
but the hash i am getting is
2.2.0 :004 > a.to_h
=> {1=>2, 3=>2}
why is it so?
Hashes have unique keys. Array#to_h is effectively doing the following:
h = {}.merge(1=>3).merge(3=>2).merge(1=>2)
#=> { 1=>3 }.merge(3=>2).merge(1=>2)
#=> { 1=>3, 3=>2 }.merge(1=>2)
#=> { 1=>2, 3=>2 }
In the last merge the value of the key 1 (3) is replaced with 2.
Note that
h.merge(k=>v)
is (permitted) shorthand for
h.merge({ k=>v })
The keys of a Hash are basically a Set, so no duplicate keys are allowed.
If two pairs are present in your Array with the same first element, only the last pair will be kept in the Hash.
If you want to keep the whole information, you could define arrays as values :
a = [[1, 3], [3, 2], [1, 2]]
hash = Hash.new{|h,k| h[k] = []}
p a.each_with_object(hash) { |(k, v), h| h[k] << v }
#=> {1=>[3, 2], 3=>[2]}
Here's a shorter but less common way to define it :
hash = a.each_with_object(Hash.new{[]}) { |(k, v), h| h[k] <<= v }
Calling hash[1] returns [3,2], which are all the second elements from the pairs of your array having 1 as first element.
I just wanted to apply a binary operation to consecutive elements in an array, e.g.:
[1, 2, 3, 4].each_cons(2).map { |a, b| a.quo(b) }
#=> [(1/2), (2/3), (3/4)]
This is a contrived example, the operation doesn't really matter.
I was surprised, that I couldn't just write:
[1, 2, 3, 4].each_cons(2).map(&:quo)
#=> NoMethodError: undefined method `quo' for [1, 2]:Array
This is because each_cons doesn't yield multiple values, but an array containing the values.
It works like this:
def each_cons_arrays
return enum_for(__method__) unless block_given?
yield [1, 2]
yield [2, 3]
yield [3, 4]
end
each_cons_arrays.map(&:quo)
#=> NoMethodError: undefined method `quo' for [1, 2]:Array
And I was hoping for:
def each_cons_values
return enum_for(__method__) unless block_given?
yield 1, 2
yield 2, 3
yield 3, 4
end
each_cons_values.map(&:quo)
#=> [(1/2), (2/3), (3/4)]
What's the rationale behind this? Why could it be preferable to always have an array?
And by the way, with_index on the other hand does yield multiple values:
[1, 1, 1].each.with_index(2).map(&:quo)
#=> [(1/2), (1/3), (1/4)]
From my experience, it helps to think of multiple values in ruby as an array.
It think of it at as
[1,2,3].each_cons(2) do |iter|
a,b = iter
do_stuff(a,b)
end
If you want to do it like that, I'd add the quo method to a custom class, and do
class Foobar
def initialize(a,b)
#a = a
#b = b
end
def quo
do_stuff
end
end
[1,2,3]
.each_cons(2)
.map { |a,b| Foobar.new(a,b) }
.map(:quo)
Would that work for your usecase?
I believe arrays are mostly used for returning multiple values from methods:
def some_method
return [1, 2]
end
[a, b] = some_method # should yield a = 1 and b = 2
I presume this is a kind of syntactic sugar that Ruby provides. Can we get a similar result with hashes, for instance
def some_method
return { "a" => 1, "b" => 2 }
end
{"c", "d"} = some_method() # "c" => 1, "d" => 2
I'm looking for the result { "c" => 1, "d" => 2 }, which obviously does not happen. Is there any other way this can be done? I know that we can return a hash from the method and store it and use it like so
def some_method
return {"a" => 1, "b" => 2}
end
hash = some_method()
Just curious if there is another way similar to the one with arrays but using hashes....
I think a simpler way to put the question would be...
# If we have a hash
hash = {"a" => 1, "b" => 2}
# Is the following possible
hash = {2, 3} # directly assigning values to the hash.
OR
# another example
{"c", "d"} = {2, 3} # c and d would be treated as keys and {2, 3} as respective values.
First of all, you have a syntax error. Instead of this:
[a, b] = [1, 2]
you should use:
a, b = [1, 2]
And if you want to use similar syntax with hashes, you can do:
a, b = { "c" => 1, "d" => 2 }.values # a => 1, b => 2
This is actually the same thing as the array version, beacause Hash#values returns an array of the hash values in the order they were inserted to the hash (because ruby hashes have a nice feature of preserving their order)
What you are asking is syntactically not possible.
What you want to accomplish is possible, but you will have to code it.
One possible way to do that is shown below
hash = {"a" => 1, "b" => 2}
# Assign new keys
new_keys = ["c", "d"]
p [new_keys, hash.values].transpose.to_h
#=> {"c"=>1, "d"=>2}
# Assign new values
new_values = [3, 4]
p [hash.keys, new_values].transpose.to_h
#=> {"a"=>3, "b"=>4}
If you really want some more easier looking way of doing, you could monkey-patch Hash class and define new methods to manipulate the values of keys and values array. Please be cautioned that it may not be really worthwhile to mess with core classes. Anyways, a possible implementation is shown below. Use at your own RISK.
class Hash
def values= new_values
new_values.each_with_index do |v, i|
self[keys[i]] = v if i < self.size
end
end
def keys= new_keys
orig_keys = keys.dup
new_keys.each_with_index do |k, i|
if i < orig_keys.size
v = delete(orig_keys[i])
self[k] = v
rehash
end
end
end
end
hash = {"a" => 1, "b" => 2}
hash.values = [2,3]
p hash
#=> {"a"=>2, "b"=>3}
hash.keys = ["c", "d"]
p hash
#=> {"c"=>2, "d"=>3}
hash.keys, hash.values = ["x","y"], [9, 10]
p hash
#=> {"x"=>9, "y"=>10}
hash.keys, hash.values = ["x","y"], [9, 10]
p hash
#=> {"x"=>9, "y"=>10}
# *** results can be unpredictable at times ***
hash.keys, hash.values = ["a"], [20, 10]
p hash
#=> {"y"=>20, "a"=>10}
In Ruby, is there a short and sweet way to sort this hash of arrays by score descending:
scored = {:id=>[1, 2, 3], :score=>[8.3, 5, 10]}
so it looks like this?:
scored = {:id=>[3, 1, 2], :score=>[10, 8.3, 5]}
I couldnt find an example where I can sort arrays within a hash like this? I could do this with some nasty code but I feel like there should be a 1 or 2 liner that does it?
You could use sort_by
scored = {:id=>[1, 2, 3], :score=>[8.3, 5, 10]}
scored.tap do |s|
s[:id] = s[:id].sort_by.with_index{ |a, i| -s[:score][i] }
s[:score] = s[:score].sort_by{ |a| -a }
end
#=> {:id=>[3, 1, 2], :score=>[10, 8.3, 5]}
order = scored[:score].each_with_index.sort_by(&:first).map(&:last).reverse
#=> [2,0,1]
scored.update(scored) { |_,a| a.values_at *order }
#=> {:id=>[3, 1, 2], :score=>[10, 8.3, 5]}
If scored is to not to be mutated, replace update with merge.
Some points:
Computing order makes it easy for the reader to understand what's going on.
The second line uses the form of Hash#merge that employs a block to determine the values of keys that are present in both hashes being merged (which here is all keys). This is a convenient way to modify hash values (generally), in part because the new hash is returned.
I sorted then reversed, rather than sorted by negated values, to make the method more rubust. (That is, the elements of the arrays that are the values can be from any class that implements <=>).
With Ruby 2.2+, another way to sort an array arr in descending order is to use Enumerable#max_by: arr.max_by(arr.size).to_a.
The first line could be replaced with:
arr = scored[:score]
order = arr.each_index.sort_by { |i| arr[i] }.reverse
#=> [2,0,1]
Here is one possible solution. It has an intermediate step, where it utilizes a zipped version of the scores object, but produces the correct output:
s = scored.values.inject(&:zip).sort_by(&:last).reverse
#=> [[3, 10], [1, 8.3], [2, 5]]
result = { id: s.map(&:first), score: s.map(&:last) }
#=> { :id => [3, 1, 2], :score => [10, 8.3, 5] }
How would this get done? Assume I have the following
arr = [[test, 0, 0, 0], [apples, 0, 9, 8]]
I know I would do something like:
def delete_me(item)
arr.each do |a|
if a[0] == item
#delete the array containing test
end
end
end
delete_me('test')
As far as I can see you can only do: a.remove() but that leaves me with a empty [],m I don't want that, I want it completely gone.
You can use delete_if and match the first term to your argument:
arr = [['test', 0, 0, 0], ['apples', 0, 9, 8]]
def delete_me(array, term)
array.delete_if {|x, *_| x == term }
end
(I've included the array as an argument as well, as the execution context is not clear from your post).
Following up on #iamnotmaynard's suggestion:
arr.delete_if { |a| a[0] == 'test' }
assoc.
arr.delete(arr.assoc("test"))
I had a similar need to remove one or more columns that matched a text pattern.
col_to_delete = 'test'
arr = [['test','apples','pears'],[2,3,5],[3,6,8],[1,3,1]]
arr.transpose.collect{|a| a if (a[0] != col_to_delete)}.reject(&:nil?).transpose
=> [["apples", "pears"], [3, 5], [6, 8], [3, 1]]