How to detect duplicate keys in hash and add prefix to the duplicate? - ruby

I have two arrays and I am creating a key-value-pair using hash in Ruby. How can I detect a duplicate key when zipping two arrays into key-value-pair and adding a prefix like "A-" in front of the key name for the duplicates?
I am using .zip to merge two arrays and making one a key and other one a value
[0] = "David"
[1] = "John"
[2] = "Alex"
[3] = "Sam"
[4] = "Caleb"
[5] = "David"
[6] = "John"
[7] = "Alex"
[8] = "Sam"
[0] = "1"
[1] = "2"
[2] = "3"
[3] = "4"
[4] = "5"
[5] = "6"
[6] = "7"
[7] = "8"
[8] = "9"
name_number_key_value_pair_hash = first_names.zip(numbers).to_h
puts(name_number_key_value_pair_hash)
Expected:
{"David"=>"1", "John"=>"2", "Alex"=>"3", "Sam"=>"4", "Caleb"=>"5", "A-David"=>"6", "A-John"=>"7", "A-Alex"=>"8", "A-Sam"=>"9"}
Actual:
{"David"=>"6", "John"=>"7", "Alex"=>"8", "Sam"=>"9", "Caleb"=>"5"}

It seems straight forward Have attached code snippet
names = %w[David John Alex Sam Caleb David John Alex Sam]
numbers = %w[1 2 3 4 5 6 7 8 9]
key_pair = {}
names.each_with_index do |name, index|
name = "A-#{name}" if key_pair[name]
key_pair[name] = numbers[index]
end
It generates the expected output:
{"David"=>"1", "John"=>"2", "Alex"=>"3", "Sam"=>"4", "Caleb"=>"5", "A-David"=>"6", "A-John"=>"7", "A-Alex"=>"8", "A-Sam"=>"9"}

You basically just need to keep track of the state of the hash as you build it and, when you find a conflict, create a new key instead. This captures the general approach:
def hash_with_prefixes(a, b, prefixes)
kv_pairs = a.zip(b)
prefixes = prefixes.to_enum
result_hash = {}
kv_pairs.each do |initial_key, value|
final_key = initial_key
while result_hash.include? final_key
final_key = "#{pfx.next}-#{initial_key}"
end
prefixes.rewind
result_hash[final_key] = value
end
result_hash
rescue StopIteration
fail "Insufficient prefixes to provide unique keys for input lists."
end
At the slight expense of clarity, you can also write it in a rather shorter form:
def hash_with_prefixes(a, b, prefixes)
pi = Hash[a.map {|k| [k, prefixes.lazy.map {|p| "#{p}-#{k}"}]}]
a.zip(b).inject({}) {|h, kv| h[h.include?(kv[0]) ? pi[kv[0]].next : kv[0]] = kv[1]; h}
rescue StopIteration
fail "Insufficient prefixes to provide unique keys for input lists."
end
(Don't do this.)

This is really very simple.
names = ["John","John", "John", "David", "David", "Susan", "Sue"]
numbers = ["1", "2", "3", "4", "5", "6","7"]
def uniq_hash_keys(names, numbers)
hash = {}
names.each_with_index do |name,i|
if hash[name]
prefix = 'A1-'
key = prefix + name
while hash[key]
version = prefix.match(/A(\d+)-.*/i)[1].to_i
prefix = "A#{version + 1}-"
key = prefix + name
end
name = key
end
hash[name] = numbers[i]
end
hash
end
This function produces:
{
"John"=>"1",
"A1-John"=>"2",
"A2-John"=>"3",
"David"=>"4",
"A1-David"=>"5",
"Susan"=>"6",
"Sue"=>"7"
}
Notice that there are 3 Johns, this is why the while loop is inside the function.

This is one way to create the desired hash. Note that in arr1 "John" appears three times.
arr1 = ["David", "John", "Alex", "Sam", "Caleb",
"David", "John", "Alex", "John", "Sam"]
arr2 = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]
prefixes =
arr1.each_with_object({}) do |s,h|
if h.key?(s)
prefix = "A-"
(h[s].size-1).times { prefix = prefix.next }
h[s] << prefix
else
h[s] = ['']
end
end
#=> {"David"=>["", "A-"], "John"=>["", "A-", "B-"],
# "Alex"=>["", "A-"], "Sam"=>["", "A-"],
# "Caleb"=>[""]}
arr1.map { |s| "#{prefixes[s].shift}#{s}" }.zip(arr2).to_h
#=> {"David"=>"1", "John"=>"2", "Alex"=>"3", "Sam"=>"4",
# "Caleb"=>"5", "A-David"=>"6", "A-John"=>"7",
# "A-Alex"=>"8", "B-John"=>"9", "A-Sam"=>"10"}
Note that "A-".next #=> "B-" and "Z-".next #=> "AA-".
Alternative data structure
You may wish to consider a different data structure, one that returns
{"David"=>["1", "6"], "John"=>["2", "7", "9"],
"Alex" =>["3", "8"], "Sam" =>["4", "10"], "Caleb"=>["5"]}
You could do that as follows.
arr1.each_with_index.
group_by(&:first).
transform_values { |v| arr2.values_at(*v.map(&:last)) }
#=> {"David"=>["1", "6"], "John"=>["2", "7", "9"],
# "Alex" =>["3", "8"], "Sam" =>["4", "10"],
# "Caleb"=>["5"]}
See Enumerable#each_with_index, Enumerable#group_by, Hash#transform_values1 and Array#values_at. v.map(*:last) is here the same as v.map { |arr| arr.last }.
The steps are as follows.
a = arr1.each_with_index
#=> #<Enumerator: ["David", "John", "Alex", "Sam",
# "Caleb", "David", "John", "Alex", "John", "Sam"]:
# each_with_index>
We can see the values that will be generated by this enumerator by converting it to an array.
a.to_a
#=> [["David", 0], ["John", 1], ["Alex", 2], ["Sam", 3],
# ["Caleb", 4], ["David", 5], ["John", 6], ["Alex", 7],
# ["John", 8], ["Sam", 9]]
Continuing,
b = a.group_by(&:first)
#=> {"David"=>[["David", 0], ["David", 5]],
# "John"=> [["John", 1], ["John", 6], ["John", 8]],
# "Alex"=> [["Alex", 2], ["Alex", 7]],
# "Sam"=> [["Sam", 3], ["Sam", 9]],
# "Caleb"=>[["Caleb", 4]]}
b.transform_values { |v| arr2.values_at(*v.map(&:last)) }
#=> {"David"=>["1", "6"], "John"=>["2", "7", "9"],
# "Alex"=> ["3", "8"], "Sam"=> ["4", "10"], "Caleb"=>["5"]}
For the last step, the first value of the hash b is passed to the block and the block variable is assigned to that value.
v = b.values.first
#=> [["David", 0], ["David", 5]]
The block calculations are then as follows.
c = v.map(&:last)
#=> [0, 5]
arr2.values_at(*c)
#=> arr2.values_at(0, 5)
#=> ["1", "6"]
The calculations are similar for each of the remaining values of b that are passed to the block.
1. New in Ruby MRI v2.4.

This code is less readable but compact and functional-style.
It conceptually the same as rahul mishra code https://stackoverflow.com/a/54697573/2109121
names = %w[David John Alex Sam Caleb David John Alex Sam]
numbers = %w[1 2 3 4 5 6 7 8 9]
result = names.zip(numbers).reduce({}) { |a, (b, c)| a.merge(a.key?(b) ? "A-#{b}" : b => c) }

Using zip and each_with_object
names = %w[David John Alex Sam Caleb David John Alex Sam]
numbers = %w[1 2 3 4 5 6 7 8 9]
names.zip(numbers).each_with_object({}) do |(name, number), hash|
key = hash.key?(name) ? "A-#{name}" : name
hash[key] = number
end

Related

How to convert telephone pad numbers to letters [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 2 years ago.
Improve this question
I'm trying to write code that simulates writing a text message using a multi-tap telephone keypad in Ruby. This is the telephone keypad:
1 2 3
ABC DEF
4 5 6
GHI JKL MNO
7 8 9
PQRS TUV WXYZ
0
(space)
I tried to define it in Ruby as: (doesn't work)
"0" = [" "] # (adds a space)
"1" = [""] # (adds nothing)
"2" = ["a", "b", "c"]
"3" = ["d", "e", "f"]
"4" = ["g", "h", "i"]
"5" = ["j", "k", "l"]
"6" = ["m", "n", "o"]
"7" = ["p", "q", "r", "s"]
"8" = ["t", "u", "v"]
"9" = ["w", "x", "y", "z"]
I will explain how it works with two examples. First I will send the string goat. To send the g I press 4 once. Next, to send o I press 6 three times (as pressing 6 once would send m and pressing 6 twice would send n). For a press 2 once and for t press 8 once. We therefore would send
466628
g oat
Next, consider cake. By the same procedure we would send
22225533
ca k e
Here there is problem. When decoding this there are several possibilities for 2222. It could be aaaa, bb and so on. To overcome this ambiguity a "pause", represented as a space, is inserted after each string of digits that is followed by a string of the same digit. For cake, therefore, we would write
222 25533
c a k e
I already have a hash with the numbers and its corresponding letters, and I know that I have to sort the numbers by how many times they repeat themselves. But I do not know which method I use for it.
Also, do I have to use the same logic in case I need to encode (number to letter)?
(I had the encoding part first when Cary Swoveland pointed out that you might want decoding. The answer now contains both ways and became quite long, I hope you don't mind)
Your example code doesn't work. You can't just assign to a string literal. However, you could use a hash like this to define your keypad in Ruby:
keypad = {
'0' => [' '],
'1' => [], # <- you can leave this out
'2' => %w[a b c],
'3' => %w[d e f],
'4' => %w[g h i],
'5' => %w[j k l],
'6' => %w[m n o],
'7' => %w[p q r s],
'8' => %w[t u v],
'9' => %w[w x y z],
}
Decoding
To turn 222 25533 into cake, I'd start by splitting consecutive and space-delimited numbers. You could use a regex:
parts = '222 25533'.gsub(/(\d)\1*/).map(&:itself)
#=> ["222", "2", "55", "33"]
This can be converted to an array containing key/number-of-times pairs:
key_strokes = parts.map { |part| [part[0], part.length] }
#=> [["2", 3], ["2", 1], ["5", 2], ["3", 2]]
which can be converted to the letters using the keypad hash:
letters = key_strokes.map { |key, times| key_pad[key][times - 1] }
#=> ["c", "a", "k", "e"]
That - 1 is needed because array indices are zero-based. Finally, turn the letters into a word:
letters.join
#=> "cake"
Encoding
To convert characters to key strokes, I'd create a hash based on the keypad which maps each character to a key/number-of-times pair:
mapping = {}
key_pad.each do |key, values|
values.each.with_index(1) do |char, times|
mapping[char] = [key, times]
end
end
mapping
#=> {
# " "=>["0", 1], "a"=>["2", 1], "b"=>["2", 2], "c"=>["2", 3], "d"=>["3", 1],
# "e"=>["3", 2], "f"=>["3", 3], "g"=>["4", 1], "h"=>["4", 2], "i"=>["4", 3],
# "j"=>["5", 1], "k"=>["5", 2], "l"=>["5", 3], "m"=>["6", 1], "n"=>["6", 2],
# "o"=>["6", 3], "p"=>["7", 1], "q"=>["7", 2], "r"=>["7", 3], "s"=>["7", 4],
# "t"=>["8", 1], "u"=>["8", 2], "v"=>["8", 3], "w"=>["9", 1], "x"=>["9", 2],
# "y"=>["9", 3], "z"=>["9", 4]
# }
In the above hash, "c"=>["2", 3] means that in order to get c you have to press the 2 key 3 times. To render the sequence for a single key in Ruby, we can utilize String#* which repeats a string:
key, times = mapping['c']
key #=> '2'
times #=> 3
key * times
#=> '222'
Getting the key strokes for an entire word (or sentence) is a matter of mapping each character to its respective hash value:
parts = 'cake'.each_char.map { |char| mapping[char] }
#=> [["2", 3], ["2", 1], ["5", 2], ["3", 2]]
To render the actual sequence, we have to first group consecutive runs of the same key:
chunks = parts.chunk_while { |(a, _), (b, _)| a == b }.to_a
#=> [
# [["2", 3], ["2", 1]],
# [["5", 2]],
# [["3", 2]]
# ]
We can now join identical key strokes via space and the chunks without space:
chunks.map { |chunk| chunk.map { |k, t| k * t }.join(' ') }.join
#=> "222 25533"
You are given:
arr = [
["0", [" "]],
["1", [""]],
["2", ["a", "b", "c"]],
["3", ["d", "e", "f"]],
["4", ["g", "h", "i"]],
["5", ["j", "k", "l"]],
["6", ["m", "n", "o"]],
["7", ["p", "q", "r", "s"]],
["8", ["t", "u", "v"]],
["9", ["w", "x", "y", "z"]]
]
I have been puzzled by the inclusion of
["1", [""]],
which seems to serve no purpose. It would have a purpose, however, if instead of
222 25533
to represent the string, "cake", we used
222125533
That is, if two successive characters that are represented by strings of the same digit (such as "222" and "2") they are to be separated by a "1", rather than by a "pause", expressed as a space. If that were done we could encode and decode strings as follows.
Encoding
CHAR_TO_DIGITS = arr.each_with_object({}) do |(num, a),h|
a.each.with_index(1) { |ltr,i| h[ltr] = num * i }
end
#=> {" "=>"0", ""=>"1", "a"=>"2", "b"=>"22", "c"=>"222",
# "d"=>"3", "e"=>"33", "f"=>"333", "g"=>"4", "h"=>"44",
# "i"=>"444", "j"=>"5", "k"=>"55", "l"=>"555", "m"=>"6",
# "n"=>"66", "o"=>"666", "p"=>"7", "q"=>"77", "r"=>"777",
# "s"=>"7777", "t"=>"8", "u"=>"88", "v"=>"888", "w"=>"9",
# "x"=>"99", "y"=>"999", "z"=>"9999"}
def encode(plain_text)
plain_text.each_char.with_object('') do |c,s|
digits = CHAR_TO_DIGITS[c]
s << '1' if !s.empty? && digits[0] == s[-1]
s << digits
end
end
Then
encoded_1 = encode "cake"
#=> "222125533"
encoded_2 = encode "my dog has fleas"
#=> "69990366640442777703335553327777"
Decoding
Decoding is even easier.
DIGITS_TO_CHAR = CHAR_TO_DIGITS.invert
#=> {"0"=>" ", "1"=>"", "2"=>"a", "22"=>"b", "222"=>"c",
# "3"=>"d", "33"=>"e", "333"=>"f", "4"=>"g", "44"=>"h",
# "444"=>"i", "5"=>"j", "55"=>"k", "555"=>"l", "6"=>"m",
# "66"=>"n", "666"=>"o", "7"=>"p", "77"=>"q", "777"=>"r",
# "7777"=>"s", "8"=>"t", "88"=>"u", "888"=>"v", "9"=>"w",
# "99"=>"x", "999"=>"y", "9999"=>"z"}
def decode(encoded_text)
encoded_text.gsub(/(\d)\1*/, DIGITS_TO_CHAR)
end
Then
decode encoded_1
#=> "cake"
decode encoded_2
#=> "my dog has fleas"
This uses the form of String#gsub that employs a hash to make substitutions. See also Hash#invert.
I'd start with converting "222 25533" into an array [[2,3],[2,1],[5,2],[3,2]] where the first number represents a digit and the second is a number of its occurrences.
Having this you can easily find letters from the keypad.

Sort hash by key which is a string

Assuming I get back a string:
"27,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,12,17,17,41,17,17,17,17,17,17,17,17,17,17,17,17,17,26,26,26,26,26,26,26,26,26,29,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,40,48,28,28,28,28,28,28,28,28,28,28,28,28,28,28,29,29,29,29,29,29,29,29,29,29,29,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,34,34,34,34,34,34,36,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,40,40,40,40,40,40,40,40,41,41,41,41,41,41,41,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,43,43,43,43,43,43,43,43,43,43,43,43,43,44,44,44,44,48,49,29,41,6,30,11,29,29,36,29,29,36,29,43,1,29,29,29,1,41"
I turn that into an array by calling
str.split(',')
Then turning it into a hash by calling
arr.compact.inject(Hash.new(0)) { |h, e| h[e] += 1 ; h }
I would get back a hash that looks like
{"1"=>2, "6"=>1, "39"=>23, "36"=>23, "34"=>39, "32"=>31, "30"=>18, "3"=>8, "2"=>10, "28"=>36, "29"=>21, "26"=>41, "27"=>48, "49"=>1, "44"=>4, "43"=>14, "42"=>34, "48"=>2, "40"=>9, "41"=>10, "11"=>1, "17"=>15, "12"=>1}
However, I'd like to sort that hash by key.
I've tried the solutions listed here.
I believe my problem is related to the fact they keys are strings.
The closest I got was using
Hash[h.sort_by{|k,v| k.to_i}]
Hashes shouldn't be treated as a sorted data structure. They have other advantages and use case as to return their values sequentially. As Mladen Jablanović already pointed out a array of tuples might be the better data structure when you need a sorted key/value pair.
But in current versions of Ruby there actually exists a certain order in which key/value pairs are returned when you call for example each on a hash and that is the order of insertion. Using this behavior you can just build a new hash and insert all key/value pairs into that new hash in the order you want them to be. But keep in mind that the order will break when you add more entries later on.
string = "27,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,12,17,17,41,17,17,17,17,17,17,17,17,17,17,17,17,17,26,26,26,26,26,26,26,26,26,29,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,40,48,28,28,28,28,28,28,28,28,28,28,28,28,28,28,29,29,29,29,29,29,29,29,29,29,29,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,34,34,34,34,34,34,36,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,40,40,40,40,40,40,40,40,41,41,41,41,41,41,41,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,43,43,43,43,43,43,43,43,43,43,43,43,43,44,44,44,44,48,49,29,41,6,30,11,29,29,36,29,29,36,29,43,1,29,29,29,1,41"
sorted_number_count_tupels = string.split(',').
group_by(&:itself).
map { |k, v| [k, v.size] }.
sort_by { |(k, v)| k.to_i }
#=> [["1",2],["2",10],["3",8],["6",1],["11",1],["12",1],["17",15],["26",41],["27",48],["28",36],["29",21],["30",18],["32",31],["34",39],["36",23],["39",23],["40",9],["41",10],["42",34],["43",14],["44",4],["48",2],["49",1]]
sorted_number_count_hash = sorted_number_count_tupels.to_h
#=> { "1" => 2, "2" => 10, "3" => 8, "6" => 1, "11" => 1, "12" => 1, "17" => 15, "26" => 41, "27" => 48, "28" => 36, "29" => 21, "30" => 18, "32" => 31, "34" => 39, "36" => 23, "39" => 23, "40" => 9, "41" => 10, "42" => 34, "43" => 14, "44" => 4, "48" => 2, "49" => 1}
Suppose you started with
str = "27,2,2,2,41,26,26,26,48,48,41,6,11,1,41"
and created the following hash
h = str.split(',').inject(Hash.new(0)) { |h, e| h[e] += 1 ; h }
#=> {"27"=>1, "2"=>3, "41"=>3, "26"=>3, "48"=>2, "6"=>1, "11"=>1, "1"=>1}
I removed compact because the array str.split(',') contains only (possibly empty) strings, no nils.
Before continuing, you may want to change this last step to
h = str.split(/\s*,\s*/).each_with_object(Hash.new(0)) { |e,h| h[e] += 1 }
#=> {"27"=>1, "2"=>3, "41"=>3, "26"=>3, "48"=>2, "6"=>1, "11"=>1, "1"=>1}
Splitting on the regex allows for the possibility of one or more spaces before or after each comma, and Enumerable#each_with_object avoids the need for that pesky ; h. (Notice the block variables are reversed.)
Then
h.sort_by { |k,_| k.to_i }.to_h
#=> {"1"=>1, "2"=>3, "6"=>1, "11"=>1, "26"=>3, "27"=>1, "41"=>3, "48"=>2}
creates a new hash that contains h's key-value pairs sorted by the integer representations of the keys. See Hash#sort_by.
Notice we've created two hashes. Here's a way to do that by modifying h in place.
h.keys.sort_by(&:to_i).each { |k| h[k] = h.delete(k) }
#=> ["1", "2", "6", "11", "26", "27", "41", "48"] (each always returns the receiver)
h #=> {"1"=>1, "2"=>3, "6"=>1, "11"=>1, "26"=>3, "27"=>1, "41"=>3, "48"=>2}
Lastly, another alternative is to sort str.split(',') before creating the hash.
str.split(',').sort_by(&:to_i).each_with_object(Hash.new(0)) { |e,h| h[e] += 1 }
#=> {"1"=>1, "2"=>3, "6"=>1, "11"=>1, "26"=>3, "27"=>1, "41"=>3, "48"=>2}
Notes
compact
String#split cannot return a nil element. compact won't be useful, here. split might return an empty string, though :
p "1,,2,3".split(',')
# ["1", "", "2", "3"]
p "1,,2,3".split(',').compact
# ["1", "", "2", "3"]
p "1,,2,3".split(',').reject(&:empty?)
# ["1", "2", "3"]
inject
If you have to use two statements inside inject block, each_with_object might be a better idea :
arr.compact.inject(Hash.new(0)) { |h, e| h[e] += 1 ; h }
can be rewritten :
arr.compact.each_with_object(Hash.new(0)) { |e, h| h[e] += 1 }
Hash or Array?
If you need to sort results, an Array of pairs might be more suitable than a Hash.
String or Integer?
If you accept to have an integer as key, it might make your code easier to write.
Refactoring
Here's a possibility to rewrite your code :
str.split(',')
.reject(&:empty?)
.map(&:to_i)
.group_by(&:itself)
.map { |k, v| [k, v.size] }
.sort
It outputs :
[[1, 2], [2, 10], [3, 8], [6, 1], [11, 1], [12, 1], [17, 15], [26, 41], [27, 48], [28, 36], [29, 21], [30, 18], [32, 31], [34, 39], [36, 23], [39, 23], [40, 9], [41, 10], [42, 34], [43, 14], [44, 4], [48, 2], [49, 1]]
If you really want a Hash, you can add .to_h :
{1=>2, 2=>10, 3=>8, 6=>1, 11=>1, 12=>1, 17=>15, 26=>41, 27=>48, 28=>36, 29=>21, 30=>18, 32=>31, 34=>39, 36=>23, 39=>23, 40=>9, 41=>10, 42=>34, 43=>14, 44=>4, 48=>2, 49=>1}
You can assign the arr.compact.inject(Hash.new(0)) { |h, e| h[e] += 1 ; h } to a variable and sort it by key:
num = arr.compact.inject(Hash.new(0)) { |h, e| h[e] += 1 ; h }
num.keys.sort
That would sort the hash by key.
A Ruby hash will keep the order of keys added. If the array is small enough to sort I would just change
str.split(',').
to
str.split(',').sort_by(&:to_i)
in order to get the values, and therefore also you hash sorted...

Make the first row as the keys for hash for the next rows?

I am having a hard time figuring out how to make the next rows a hash with the key from the first row.
I have an array structured like this:
[["id", "name", "address"], [1, "James", "...."], [2, "John", "...."] ]
To be:
[{ id : 1, name: "James", address: "..."}, ...]
I used a gem "simple_xlsx_reader", I am extracting out only the first sheet.
wb.sheets.first.row
and got a similar array output from above.
thanks!
arr = [["id", "name"], [1, "Jack"], [2, "Jill"]]
[arr.first].product(arr.drop 1).map { |a| a.transpose.to_h }
#=> [{"id"=>1, "name"=>"Jack"}, {"id"=>2, "name"=>"Jill"}]
The steps:
b = [arr.first]
#=> [["id", "name"]]
c = arr.drop 1
#=> [[1, "Jack"], [2, "Jill"]]
d = b.product(c)
#=> [[["id", "name"], [1, "Jack"]], [["id", "name"], [2, "Jill"]]]
d.map { |a| a.transpose.to_h }
#=> [{"id"=>1, "name"=>"Jack"}, {"id"=>2, "name"=>"Jill"}]
The first element of d passed to map's block is:
a = d.first
[["id", "name"], [1, "Jack"]]
The block calculation is therefore:
e = a.transpose
#=> [["id", 1], ["name", "Jack"]]
e.to_h
#=> {"id"=>1, "name"=>"Jack"}
This is what you're looking for:
arr = [["id", "name", "address"], [1, "James", "...."], [2, "John", "...."] ]
keys, *values = arr
values.map {|vals| keys.zip(vals).to_h }
Enumerable#zip takes two arrays (the receiver and the argument) and "zips" them together, producing an array of tuples (two-element arrays) e.g.:
keys = [ "foo", "bar", "baz" ]
values = [ 1, 2, 3 ]
p keys.zip(values)
# => [ [ "foo", 1 ], [ "bar", 2 ], [ "baz", 3 ] ]
Array#to_h takes an array of tuples and turns it into a hash.
If you're using a version of Ruby earlier than 2.1 you'll need to use Hash[ *keys.zip(vals) ] instead.
P.S. If you want symbol keys instead of string keys you'll want to perform that conversion before the map, e.g.:
keys = keys.map(&:to_sym)
Or, if you don't mind modifying the original array:
keys.map!(&:to_sym)
You can try this very simple one line that make your work
arr =[["id", "name", "address"], [1, "James", "add 1"], [2, "John", "add2"] ]
arr.map {|a| arr.first.zip(a).to_h unless a == arr.first }.compact

To find the integer (Fixnum) values in ruby array

I have an array [1, 2, "3", "4", "1a", "abc", "a"] with
pure integers (1, 2),
string formatted integers ("1", "2"),
strings ("a", "b"), and
mixed string numbers ("1a", "2s").
From this, I need to pick up only the integers (including string formatted) 1, 2, "3", "4".
First I tried with to_i:
arr = [1, 2, "3", "4", "1a", "abc", "a"]
arr.map {|x| x.to_i}
# => [1, 2, 3, 4, 1, 0, 0]
but this one converts "1a" to 1, which I don't expect.
Then I tried Integer(item):
arr.map {|x| Integer(x) } # and it turned out to be
# => ArgumentError: invalid value for Integer(): "1a"
Now I am out of straight conversion options here. Finally, I decided to do this way, which converts the value to_i and to_s. So "1" == "1".to_i.to_s is an integer, but not "1a" == "1a".to_i.to_s and "a" == "a".to_i.to_s
arr = arr.map do |x|
if (x == x.to_i.to_s)
x.to_i
else
x
end
end
and
ids, names= arr.partition { |item| item.kind_of? Fixnum }
Now I got the arrays of integers and strings. Is there a simple way to do this?
Similar solution as provided by #maerics, but a bit slimmer:
arr.map {|x| Integer(x) rescue nil }.compact
class Array
def to_i
self.map {|x| begin; Integer(x); rescue; nil; end}.compact
end
end
arr = [1, 2, "3", "4", "1a", "abc", "a"]
arr.to_i # => [1, 2, 3, 4]
something like this:
a = [1,2,"3","4","1a","abc","a"]
irb(main):005:0> a.find_all { |e| e.to_s =~ /^\d+$/ }.map(&:to_i)
=> [1, 2, 3, 4]
Hey, thanks awakening my ruby. Here is my go at this problem:
arr=[1,2,"3","4","1a","abc","a"]
arr.map {|i| i.to_s}.select {|s| s =~ /^[0-9]+$/}.map {|i| i.to_i}
//=> [1, 2, 3, 4]
I noticed most of the answer so far changes the value of "3" and "4" to actual integers.
>> array=[1, 2, "3", "4", "1a", "abc", "a", "a13344a" , 10001, 3321]
=> [1, 2, "3", "4", "1a", "abc", "a", "a13344a", 10001, 3321]
>> array.reject{|x| x.to_s[/[^0-9]/] }
=> [1, 2, "3", "4", 10001, 3321]
#OP, I have not tested my solution exhaustively, but so far it seems to work (of course its done according to provided sample ), so please test thoroughly yourself.
How about this?
[1,2,"3","4","1a","abc","a"].select{|x| x.to_i.to_s == x.to_s}
# => [1, 2, "3", "4"]
Looks pretty simple
arr.select{ |b| b.to_s =~ /\d+$/ }
# or
arr.select{ |b| b.to_s[/\d+$/] }
#=> [1, 2, "3", "4"]

How to count duplicates in Ruby Arrays

How do you count duplicates in a ruby array?
For example, if my array had three a's, how could I count that
Another version of a hash with a key for each element in your array and value for the count of each element
a = [ 1, 2, 3, 3, 4, 3]
h = Hash.new(0)
a.each { | v | h.store(v, h[v]+1) }
# h = { 3=>3, 2=>1, 1=>1, 4=>1 }
Given:
arr = [ 1, 2, 3, 2, 4, 5, 3]
My favourite way of counting elements is:
counts = arr.group_by{|i| i}.map{|k,v| [k, v.count] }
# => [[1, 1], [2, 2], [3, 2], [4, 1], [5, 1]]
If you need a hash instead of an array:
Hash[*counts.flatten]
# => {1=>1, 2=>2, 3=>2, 4=>1, 5=>1}
This will yield the duplicate elements as a hash with the number of occurences for each duplicate item. Let the code speak:
#!/usr/bin/env ruby
class Array
# monkey-patched version
def dup_hash
inject(Hash.new(0)) { |h,e| h[e] += 1; h }.select {
|k,v| v > 1 }.inject({}) { |r, e| r[e.first] = e.last; r }
end
end
# unmonkeey'd
def dup_hash(ary)
ary.inject(Hash.new(0)) { |h,e| h[e] += 1; h }.select {
|_k,v| v > 1 }.inject({}) { |r, e| r[e.first] = e.last; r }
end
p dup_hash([1, 2, "a", "a", 4, "a", 2, 1])
# {"a"=>3, 1=>2, 2=>2}
p [1, 2, "Thanks", "You're welcome", "Thanks",
"You're welcome", "Thanks", "You're welcome"].dup_hash
# {"You're welcome"=>3, "Thanks"=>3}
Simple.
arr = [2,3,4,3,2,67,2]
repeats = arr.length - arr.uniq.length
puts repeats
arr = %w( a b c d c b a )
# => ["a", "b", "c", "d", "c", "b", "a"]
arr.count('a')
# => 2
Another way to count array duplicates is:
arr= [2,2,3,3,2,4,2]
arr.group_by{|x| x}.map{|k,v| [k,v.count] }
result is
[[2, 4], [3, 2], [4, 1]]
requires 1.8.7+ for group_by
ary = %w{a b c d a e f g a h i b}
ary.group_by{|elem| elem}.select{|key,val| val.length > 1}.map{|key,val| key}
# => ["a", "b"]
with 1.9+ this can be slightly simplified because Hash#select will return a hash.
ary.group_by{|elem| elem}.select{|key,val| val.length > 1}.keys
# => ["a", "b"]
To count instances of a single element use inject
array.inject(0){|count,elem| elem == value ? count+1 : count}
arr = [1, 2, "a", "a", 4, "a", 2, 1]
arr.group_by(&:itself).transform_values(&:size)
#=> {1=>2, 2=>2, "a"=>3, 4=>1}
Ruby >= 2.7 solution here:
A new method .tally has been added.
Tallies the collection, i.e., counts the occurrences of each element. Returns a hash with the elements of the collection as keys and the corresponding counts as values.
So now, you will be able to do:
["a", "b", "c", "b"].tally #=> {"a"=>1, "b"=>2, "c"=>1}
What about a grep?
arr = [1, 2, "Thanks", "You're welcome", "Thanks", "You're welcome", "Thanks", "You're welcome"]
arr.grep('Thanks').size # => 3
Its Easy:
words = ["aa","bb","cc","bb","bb","cc"]
One line simple solution is:
words.each_with_object(Hash.new(0)) { |word,counts| counts[word] += 1 }
It works for me.
Thanks!!
I don't think there's a built-in method. If all you need is the total count of duplicates, you could take a.length - a.uniq.length. If you're looking for the count of a single particular element, try
a.select {|e| e == my_element}.length.
Improving #Kim's answer:
arr = [1, 2, "a", "a", 4, "a", 2, 1]
Hash.new(0).tap { |h| arr.each { |v| h[v] += 1 } }
# => {1=>2, 2=>2, "a"=>3, 4=>1}
Ruby code to get the repeated elements in the array:
numbers = [1,2,3,1,2,0,8,9,0,1,2,3]
similar = numbers.each_with_object([]) do |n, dups|
dups << n if seen.include?(n)
seen << n
end
print "similar --> ", similar
Another way to do it is to use each_with_object:
a = [ 1, 2, 3, 3, 4, 3]
hash = a.each_with_object({}) {|v, h|
h[v] ||= 0
h[v] += 1
}
# hash = { 3=>3, 2=>1, 1=>1, 4=>1 }
This way, calling a non-existing key such as hash[5] will return nil instead of 0 with Kim's solution.
I've used reduce/inject for this in the past, like the following
array = [1,5,4,3,1,5,6,8,8,8,9]
array.reduce (Hash.new(0)) {|counts, el| counts[el]+=1; counts}
produces
=> {1=>2, 5=>2, 4=>1, 3=>1, 6=>1, 8=>3, 9=>1}

Resources