How to convert telephone pad numbers to letters [closed] - ruby

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.

Related

Caesar Cipher - Ruby

I have to make a function that receives a phrase and encrypts it. The cipher is to each letter in alphabet the encrypted letter is 3 letter ahead.
Example
Alphabet: A B C D E F G ... X Y Z
Ciphered: D E F G H I J ... A B C
If this is my alphabet in Ruby:
a = ['a','b','c','d','e']
I need to map it to:
a = ['c','d','e','a','b']
I've tried iterate twice the array and remove some indexes but I know I'm missing something.
UPDATE--------------------------------------------------------------------
I've managed to solve the six tests where I receive a phrase and have to encrypts as the test require.
Received phrase: prefiro perder a guerra e ganhar a paz
Phrase expected: suhilur#shughu#d#jxhuud#h#jdqkdu#d#sd}
I realize that to cypher the phrase I should change the letters positions 3 positions ahead in the ascii table.
Example: The letter 'a' should be encrypted as 'd', The letter 'z' should be encrypted as '}' and also the 'space' 3 positions ahead in the ascii table is '#'.
Here follows the code I used to solve this:
def cipher(text)
key = 3
cipher_text = text.chars.map {|x| x.ord}
.map {|x| x+key}
cipher_text.map { |x| x.chr }.join
end
def decipher(text)
key = 3
decipher_text = text.chars.map {|x| x.ord}
.map {|x| x-key}
decipher_text.map { |x| x.chr }.join
end
For encryption mentioned in the comments use String.tr method
I have to make a function that receives a phrase and encrypts it. The
cipher is to each letter in alphabet the encrypted letter is 3 letter
ahead.
phrase = "abcd st xyz"
encrypted = phrase.tr("A-Za-z ", "D-ZA-Cd-za-c#")
# => "defg#vw#abc"
Update
Please notice that the letter 'z' (at the end of the phrase) means
'}'
You can map xyz character to {|} explicitly
phrase = "prefiro perder a guerra e ganhar a paz"
encrypted = phrase.tr("A-Wa-wXYZxyz ", "D-WA-Cd-wa-c{|}{|}#")
# => "suhilur#shughu#d#jxhuud#h#jdqkdu#d#sd}"
Not sure I understand your question, but the data looks like you rotate the elements in the array. In Ruby you have a special method for that.
a = %w[a b c d] #=> ["a", "b", "c", "d"]
a.rotate #=> ["b", "c", "d", "a"]
a #=> ["a", "b", "c", "d"]
a.rotate(2) #=> ["c", "d", "a", "b"]
a.rotate(-3) #=> ["b", "c", "d", "a"]
Given an alphabet:
alphabet = ('A'..'Z').to_a
#=> ["A", "B", "C", "D", "E", ..., "V", "W", "X", "Y", "Z"]
You can create the ciphered one by calling rotate:
ciphered = alphabet.rotate(3)
#=> ["D", "E", "F", "G", "H", ..., "Y", "Z", "A", "B", "C"]
And create a mapping from one to the other:
to_cipher = alphabet.zip(ciphered).to_h
#=> {"A"=>"D", "B"=>"E", "C"=>"F", ..., "X"=>"A", "Y"=>"B", "Z"=>"C"}
Now, to encrypt a given string, we have to run each character through that hash:
'HELLO WORLD!'.each_char.map { |char| to_cipher[char] }.join
#=> "KHOORZRUOG"
Well, almost. That also removed the space and exclamation mark. We can fix this by providing a fallback for characters that don't occur in the hash:
'HELLO WORLD!'.each_char.map { |char| to_cipher.fetch(char, char) }.join
#=> "KHOOR ZRUOG!"
Or, with regular expressions using gsub:
'HELLO WORLD!'.gsub(Regexp.union(to_cipher.keys), to_cipher)
#=> "KHOOR ZRUOG!"

Ruby: How to create a letter counting function

I wouldn't have asked for help without first spending a few hours trying to figure out my error but I'm at a wall. So there is my attempt but I'm getting false when I try to pass the argument though and I'm not sure why. I know there are other ways to solve this problem that are a little shorter but I'm more interested in trying to get my code to work. Any help is much appreciated.
Write a method that takes in a string. Your method should return the most common letter in the array, and a count of how many times it appears.
def most_common_letter(string)
idx1 = 0
idx2 = 0
counter1 = 0
counter2 = 0
while idx1 < string.length
idx2 = 0
while idx2 < string.length
if string[idx1] == string[idx2]
counter1 += 1
end
idx2 += 1
end
if counter1 > counter2
counter2 = counter1
counter1 = 0
var = [string[idx1], counter2]
end
idx1 += 1
end
return var
end
puts("most_common_letter(\"abca\") == [\"a\", 2]: #{most_common_letter("abca") == ["a", 2]}")
puts("most_common_letter(\"abbab\") == [\"b\", 3]: #{most_common_letter("abbab") == ["b", 3]}")
I didn't rewrite your code because I think it's important to point out what is wrong with the existing code that you wrote (especially since you're familiar with it). That said, there are much more 'ruby-like' ways to go about this.
The issue
counter1 is only being reset if you've found a 'new highest'. You need to reset it regardless of whether or not a new highest number has been found:
def most_common_letter(string)
idx1 = 0
idx2 = 0
counter1 = 0
counter2 = 0
while idx1 < string.length
idx2 = 0
while idx2 < string.length
if string[idx1] == string[idx2]
counter1 += 1
end
idx2 += 1
end
if counter1 > counter2
counter2 = counter1
# counter1 = 0 THIS IS THE ISSUE
var = [string[idx1], counter2]
end
counter1 = 0 # this is what needs to be reset each time
idx1 += 1
end
return var
end
Here's what the output is:
stackoverflow master % ruby letter-count.rb
most_common_letter("abca") == ["a", 2]: true
most_common_letter("abbab") == ["b", 3]: true
I think you're aware there are way better ways to do this but frankly the best way to debug this is with a piece of paper. "Ok counter1 is now 1, indx2 is back to zero", etc. That will help you keep track.
Another bit of advice, counter1 and counter2 are not very good variable names. I didn't realize what you were using them for initially and that should never be the case, it should be named something like current_count highest_known_count or something like that.
Your question has been answered and #theTinMan has suggested a more Ruby-like way of doing what you want to do. There are many other ways of doing this and you might find it useful to consider a couple more.
Let's use the string:
string = "Three blind mice. Oh! See how they run."
First, you need to answer a couple of questions:
do you want the frequency of letters or characters?
do you want the frequency of lowercase and uppercase letters combined?
I assume you want the frequency of letters only, independent of case.
#1 Count each unique letter
We can deal with the case issue by converting all the letters to lower or upper case, using the method String#upcase or String#downcase:
s1 = string.downcase
#=> "three blind mice. oh! see how they run."
Next we need to get rid of all the characters that are not letters. For that, we can use String#delete1:
s2 = s1.delete('^a-z')
#=> "threeblindmiceohseehowtheyrun"
Now we are ready to convert the string s2 to an an array of individual characters2:
arr = s2.chars
#=> ["t", "h", "r", "e", "e", "b", "l", "i", "n", "d",
# "m", "i", "c", "e", "o", "h", "s", "e", "e", "h",
# "o", "w", "t", "h", "e", "y", "r", "u", "n"]
We can combine these first three steps as follows:
arr = string.downcase.gsub(/[^a-z]/, '').chars
First obtain all the distinct letters present, using Array.uniq.
arr1 = arr.uniq
#=> ["t", "h", "r", "e", "b", "l", "i", "n",
# "d", "m", "c", "o", "s", "w", "y", "u"]
Now convert each of these characters to a two-character array consisting of the letter and its count in arr. Whenever you need convert elements of a collection to something else, think Enumerable#map (a.k.a. collect). The counting is done with Array#count. We have:
arr2 = arr1.map { |c| [c, arr.count(c)] }
#=> [["t", 2], ["h", 4], ["r", 2], ["e", 6], ["b", 1], ["l", 1],
# ["i", 2], ["n", 2], ["d", 1], ["m", 1], ["c", 1], ["o", 2],
# ["s", 1], ["w", 1], ["y", 1], ["u", 1]]
Lastly, we use Enumerable#max_by to extract the element of arr2 with the largest count3:
arr2.max_by(&:last)
#=> ["e", 6]
We can combine the calculation of arr1 and arr2:
arr.uniq.map { |c| [c, arr.count(c)] }.max_by(&:last)
and further replace arr with that obtained earlier:
string.downcase.gsub(/[^a-z]/, '').chars.uniq.map { |c|
[c, arr.count(c)] }.max_by(&:last)
#=> ["e", 6]
String#chars returns a temporary array, upon which the method Array#uniq is invoked. As alternative, which avoids the creation of the temporary array, is to use String#each_char in place of String#chars, which returns an enumerator, upon which Enumerable#uniq is invoked.
The use of Array#count is quite an inefficient way to do the counting because a full pass through arr is made for each unique letter. The methods below are much more efficient.
#2 Use a hash
With this approach we wish to create a hash whose keys are the distinct elements of arr and each value is the count of the associated key. Begin by using the class method Hash::new to create hash whose values have a default value of zero:
h = Hash.new(0)
#=> {}
We now do the following:
string.each_char { |c| h[c.downcase] += 1 if c =~ /[a-z]/i }
h #=> {"t"=>2, "h"=>4, "r"=>2, "e"=>6, "b"=>1, "l"=>1, "i"=>2, "n"=>2,
# "d"=>1, "m"=>1, "c"=>1, "o"=>2, "s"=>1, "w"=>1, "y"=>1, "u"=>1}
Recall h[c] += 1 is shorthand for:
h[c] = h[c] + 1
If the hash does not already have a key c when the above expression is evaluated, h[c] on the right side is replaced by the default value of zero.
Since the Enumerable module is included in the class Hash we can invoke max_by on h just as we did on the array:
h.max_by(&:last)
#=> ["e", 6]
There is just one more step. Using Enumerable#each_with_object, we can shorten this as follows:
string.each_char.with_object(Hash.new(0)) do |c,h|
h[c.downcase] += 1 if c =~ /[a-z]/i
end.max_by(&:last)
#=> ["e", 6]
The argument of each_with_object is an object we provide (the empty hash with default zero). This is represented by the additional block variable h. The expression
string.each_char.with_object(Hash.new(0)) do |c,h|
h[c.downcase] += 1 if c =~ /[a-z]/i
end
returns h, to which max_by(&:last) is sent.
#3 Use group_by
I will give a slightly modified version of the Tin Man's answer and show how it works with the value of string I have used. It uses the method Enumerable#group_by:
letters = string.downcase.delete('^a-z').each_char.group_by { |c| c }
#=> {"t"=>["t", "t"], "h"=>["h", "h", "h", "h"], "r"=>["r", "r"],
# "e"=>["e", "e", "e", "e", "e", "e"], "b"=>["b"], "l"=>["l"],
# "i"=>["i", "i"], "n"=>["n", "n"], "d"=>["d"], "m"=>["m"],
# "c"=>["c"], "o"=>["o", "o"], "s"=>["s"], "w"=>["w"],
# "y"=>["y"], "u"=>["u"]}
used_most = letters.max_by { |k,v| v.size }
#=> ["e", ["e", "e", "e", "e", "e", "e"]]
used_most[1] = used_most[1].size
used_most
#=> ["e", 6]
In later versions of Ruby you could simplify as follows:
string.downcase.delete('^a-z').each_char.group_by(&:itself).
transform_values(&:size).max_by(&:last)
#=> ["e", 6]
See Enumerable#max_by, Object#itself and Hash#transform_values.
1. Alternatively, use String#gsub: s1.gsub(/[^a-z]/, '').
2. s2.split('') could also be used.
3. More or less equivalent to arr2.max_by { |c, count| count }.
It's a problem you'll find asked all over Stack Overflow, a quick search should have returned a number of hits.
Here's how I'd do it:
foo = 'abacab'
letters = foo.chars.group_by{ |c| c }
used_most = letters.sort_by{ |k, v| [v.size, k] }.last
used_most # => ["a", ["a", "a", "a"]]
puts '"%s" was used %d times' % [used_most.first, used_most.last.size]
# >> "a" was used 3 times
Of course, now that this is here, and it's easily found, you can't use it because any teacher worth listening to will also know how to search Stack Overflow and will find this answer.

Convert array into a hash

I try to learn map and group_by but it's difficult...
My array of arrays :
a = [ [1, 0, "a", "b"], [1, 1, "c", "d"], [2, 0, "e", "f"], [3, 1, "g", "h"] ]
Expected result :
b= {
1=> {0=>["a", "b"], 1=>["c", "d"]} ,
2=> {0=>["e", "f"]} ,
3=> {1=>["g", "h"]}
}
Group by the first value, the second value can just be 0 or 1.
A starting :
a.group_by{ |e| e.shift}.map { |k, v| {k=>v.group_by{ |e| e.shift}} }
=> [{1=>{0=>[["a", "b"]], 1=>[["c", "d"]]}},
{2=>{0=>[["e", "f"]]}}, {3=>{1=>[["g", "h"]]}}]
I want to get "a" and "b" with the 2 first values, it's the only solution that I've found... (using a hash of hash)
Not sure if group_by is the simplest solution here:
a = [ [1, 0, "a", "b"], [1, 1, "c", "d"], [2, 0, "e", "f"], [3, 1, "g", "h"] ]
result = a.inject({}) do |acc,(a,b,c,d)|
acc[a] ||= {}
acc[a][b] = [c,d]
acc
end
puts result.inspect
Will print:
{1=>{0=>["a", "b"], 1=>["c", "d"]}, 2=>{0=>["e", "f"]}, 3=>{1=>["g", "h"]}}
Also, avoid changing the items you're operating on directly (the shift calls), the collections you could be receiving in your code might not be yours to change.
If you want a somewhat custom group_by I tend do just do it manually. group_by creates an Array of grouped values, so it creates [["a", "b"]] instead of ["a", "b"]. In addition your code is destructive, i.e. it manipulates the value of a. That is only a bad thing if you plan on re using a later on in its original form, but important to note.
As I mentioned though, you might as well just loop through a once and build the desired structure instead of doing multiple group_bys.
b = {}
a.each do |aa|
(b[aa[0]] ||= {})[aa[1]] = aa[2..3]
end
b # => {1=>{0=>["a", "b"], 1=>["c", "d"]}, 2=>{0=>["e", "f"]}, 3=>{1=>["g", "h"]}}
With (b[aa[0]] ||= {}) we check for the existence of the key aa[0] in the Hash b. If it does not exist, we assign an empty Hash ({}) to that key. Following that, we insert the last two elements of aa (= aa[2..3]) into that Hash, with aa[1] as key.
Note that this does not account for duplicate primary + secondary keys. That is, if you have another entry [1, 1, "x", "y"] it will overwrite the entry of [1, 1, "c", "d"] because they both have keys 1 and 1. You can fix that by storing the values in an Array, but then you might as well just do a double group_by. For example, with destructive behavior on a, handling "duplicates":
# Added [1, 1, "x", "y"], removed some others
a = [ [1, 0, "a", "b"], [1, 1, "c", "d"], [1, 1, "x", "y"] ]
b = Hash[a.group_by(&:shift).map { |k, v| [k, v.group_by(&:shift) ] }]
#=> {1=>{0=>[["a", "b"]], 1=>[["c", "d"], ["x", "y"]]}}
[[1, 0, "a", "b"], [1, 1, "c", "d"], [2, 0, "e", "f"], [3, 1, "g", "h"]].
group_by{ |e| e.shift }.
map{ |k, v| [k, v.inject({}) { |h, v| h[v.shift] = v; h }] }.
to_h
#=> {1=>{0=>["a", "b"], 1=>["c", "d"]}, 2=>{0=>["e", "f"]}, 3=>{1=>["g", "h"]}}
Here's how you can do it (nondestructively) with two Enumerable#group_by's and an Object#tap. The elements of a (arrays) could could vary in size and the size of each could be two or greater.
Code
def convert(arr)
h = arr.group_by(&:first)
h.keys.each { |k| h[k] = h[k].group_by { |a| a[1] }
.tap { |g| g.keys.each { |j|
g[j] = g[j].first[2..-1] } } }
h
end
Example
a = [ [1, 0, "a", "b"], [1, 1, "c", "d"], [2, 0, "e", "f"], [3, 1, "g", "h"] ]
convert(a)
#=> {1=>{0=>["a", "b"], 1=>["c", "d"]}, 2=>{0=>["e", "f"]}, 3=>{1=>["g", "h"]}}
Explanation
h = a.group_by(&:first)
#=> {1=>[[1, 0, "a", "b"], [1, 1, "c", "d"]],
# 2=>[[2, 0, "e", "f"]],
# 3=>[[3, 1, "g", "h"]]}
keys = h.keys
#=> [1, 2, 3]
The first value of keys passed into the block assigns the value 1 to the block variable k. We will set h[1] to a hash f, computed as follows.
f = h[k].group_by { |a| a[1] }
#=> [[1, 0, "a", "b"], [1, 1, "c", "d"]].group_by { |a| a[1] }
#=> {0=>[[1, 0, "a", "b"]], 1=>[[1, 1, "c", "d"]]}
We need to do further processing of this hash, so we capture it with tap and assign it to tap's block variable g (i.e., g will initially equal f above). g will be returned by the block after modification.
We have
g.keys #=> [0, 1]
so 0 is the first value passed into each's block and assigned to the block variable j. We then compute:
g[j] = g[j].first[2..-1]
#=> g[0] = [[1, 0, "a", "b"]].first[2..-1]
#=> ["a", "b"]
Similarly, when g's second key (1) is passed into the block,
g[j] = g[j].first[2..-1]
#=> g[1] = [[1, 1, "c", "d"]].first[2..-1]
#=> ["c", "d"]
Ergo,
h[1] = g
#=> {0=>["a", "b"], 1=>["c", "d"]}
h[2] and h[3] are computed similarly, giving us the desired result.

how to get the indexes of duplicating elements in a ruby array [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 9 years ago.
Improve this question
arr = ["A", "X", "X", "D", "C", "B", "A"}
arr.detect{|e| arr.count(e) > 1}
duplicating_value_index_int_array = arr.index(<all duplicating values>)
Hi I want to get all the duplicating element's indexes from a ruby array. How may I achieve this?
duplicates = arr.each_with_index.group_by(&:first).inject({}) do |result, (val, group)|
next result if group.length == 1
result.merge val => group.map {|pair| pair[1]}
end
This will return a hash where the keys will be the duplicate elements and the values will be an array containing the index of each occurrence.
For your test input, the result is:
{"A"=>[0, 6], "X"=>[1, 2]}
If all your care about is the indices you can do duplicates.values.flatten to get an array with just the indices.
In this case: [0, 6, 1, 2]
This is quite straightforward implementation. It may be improved greatly, I think
arr = ["A", "X", "X", "D", "C", "B", "A"]
groups = arr.each.with_index.group_by{|s, idx| s}.to_a # => [["A", [["A", 0], ["A", 6]]], ["X", [["X", 1], ["X", 2]]], ["D", [["D", 3]]], ["C", [["C", 4]]], ["B", [["B", 5]]]]
repeating_groups = groups.select{|key, group| group.length > 1} # => [["A", [["A", 0], ["A", 6]]], ["X", [["X", 1], ["X", 2]]]]
locations = repeating_groups.each_with_object({}) {|(key, group), memo| memo[key] = group.map{|g| g[1]}} # => {"A"=>[0, 6], "X"=>[1, 2]}
It's not clear exactly what you want, but this code will find the indices of all elements of an array that aren't unique. It's far from efficient, but probably it doesn't need to be.
arr = %W/ A X X D C B A /
dup_indices = arr.each_index.find_all { |i| arr.count(arr[i]) > 1 }
p dup_indices
output
[0, 1, 2, 6]
I will assume a valid Ruby array arr as follows:
arr = ["A", "X", "X", "D", "C", "B", "A"]
Under this arr, and further assumption that it does not include nil:
arr.map.with_index{|e, i| i if arr.count(e) > 1}.compact
# => [0, 1, 2, 6]

Reordering an array in the same order as another array was reordered

I have two arrays a, b of the same length:
a = [a_1, a_2, ..., a_n]
b = [b_1, b_2, ..., b_n]
When I sort a using sort_by!, the elements of a will be arranged in different order:
a.sort_by!{|a_i| some_condition(a_i)}
How can I reorder b in the same order/rearrangement as the reordering of a? For example, if a after sort_by! is
[a_3, a_6, a_1, ..., a_i_n]
then I want
[b_3, b_6, b_1, ..., b_i_n]
Edit
I need to do it in place (i.e., retain the object_id of a, b). The two answers given so far is useful in that, given the sorted arrays:
a_sorted
b_sorted
I can do
a.replace(a_sorted)
b.replace(b_sorted)
but if possible, I want to do it directly. If not, I will accept one of the answers already given.
One approach would be to zip the two arrays together and sort them at the same time. Something like this, perhaps?
a = [1, 2, 3, 4, 5]
b = %w(a b c d e)
a,b = a.zip(b).sort_by { rand }.transpose
p a #=> [3, 5, 2, 4, 1]
p b #=> ["c", "e", "b", "d", "a"]
How about:
ary_a = [ 3, 1, 2] # => [3, 1, 2]
ary_b = [ 'a', 'b', 'c'] # => ["a", "b", "c"]
ary_a.zip(ary_b).sort{ |a,b| a.first <=> b.first }.map{ |a,b| b } # => ["b", "c", "a"]
or
ary_a.zip(ary_b).sort_by(&:first).map{ |a,b| b } # => ["b", "c", "a"]
If the entries are unique, the following may work. I haven't tested it. This is partially copied from https://stackoverflow.com/a/4283318/38765
temporary_copy = a.sort_by{|a_i| some_condition(a_i)}
new_indexes = a.map {|a_i| temporary_copy.index(a_i)}
a.each_with_index.sort_by! do |element, i|
new_indexes[i]
end
b.each_with_index.sort_by! do |element, i|
new_indexes[i]
end

Resources