Related
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
I have an array of strings, and all contain at least one letter:
["abc", "FFF", "EEE"]
How do I find the index of the first string that is of a different case than any previous string in the array? The function should give 1 for the above since:
FFF".eql?("FFF".upcase)
and that condition isn't true for any previous string in the array, whereas:
["P", "P2F", "ccc", "DDD"]
should yield 2 since "ccc" is not capitalized and all its predecessors are.
I know how to find the first string that is capitalized using
string_tokens.find_index { |w| w == w.upcase }
but I can't figure out how to adjust the above to account for differing case.
You could take each consecutive pair of elements and compare their upcase-ness. When they differ, you return the index.
def detect_case_change(ary)
ary.each_cons(2).with_index(1) do |(a, b), idx|
return idx if (a == a.upcase) != (b == b.upcase)
end
nil
end
detect_case_change ["abc", "FFF", "EEE"] # => 1
detect_case_change ["P", "P2F", "ccc", "DDD"] # => 2
This makes some assumptions about your data being composed entirely of 'A'..'Z' and 'a'..'z':
def find_case_mismatch(list)
index = list.each_cons(2).to_a.index do |(a,b)|
a[0].ord & 32 != b[0].ord & 32
end
index && index + 1
end
This compares the character values. 'A' differs from 'a' by one bit, and that bit is always in the same place (0x20).
Enumerable#chunk helps a lot for this task.
Enumerates over the items, chunking them together based on the return
value of the block. Consecutive elements which return the same block
value are chunked together.
l1 = ["abc", "FFF", "EEE"]
l2 = ["P", "P2F", "ccc", "DDD"]
p l1.chunk{|s| s == s.upcase }.to_a
# [[false, ["abc"]], [true, ["FFF", "EEE"]]]
p l2.chunk{|s| s == s.upcase }.to_a
# [[true, ["P", "P2F"]], [false, ["ccc"]], [true, ["DDD"]]]
The fact that you need an index makes it a bit less readable, but here's an example. The desired index (if it exists) is the size of the first chunk:
p l1.chunk{|s| s == s.upcase }.first.last.size
# 1
p l2.chunk{|s| s == s.upcase }.first.last.size
# 2
If the case doesn't change at all, it returns the length of the whole array:
p %w(aaa bbb ccc ddd).chunk{|s| s == s.upcase }.first.last.size
# 4
I assume that each element (string) of the array contains at least one letter and only letters of the same case.
def first_case_change_index(arr)
s = arr.map { |s| s[/[[:alpha:]]/] }.join
(s[0] == s[0].upcase ? s.swapcase : s) =~ /[[:upper:]]/
end
first_case_change_index ["abc", "FFF", "EEE"] #=> 1
first_case_change_index ["P", "P2F", "ccc"] #=> 2
first_case_change_index ["P", "P2F", "DDD"] #=> nil
The steps are as follows.
arr = ["P", "2PF", "ccc"]
a = arr.map { |s| s[/[[:alpha:]]/] }
#=> ["P", "P", "c"]
s = a.join
#=> "PPc"
s[0] == s[0].upcase
#=> "P" == "P"
#=> true
t = s.swapcase
#=> "ppC"
t =~ /[[:upper:]]/
#=> 2
Here is another way.
def first_case_change_index(arr)
look_for_upcase = (arr[0] == arr[0].downcase)
arr.each_index.drop(1).find do |i|
(arr[i] == arr[i].upcase) == look_for_upcase
end
end
I'm trying to create a program that would take a string from user input and return the 'value' of the word where a=1, b=2, c=3 etc. i.e. "cab" = 6.
Unfortunately I can't figure out how to break down the user input variable and have it added together:
print "Give us a word to calculate: "
word = gets.chomp
alphabet = Hash[
"a" => 1, "b" => 2, "c" => 3,
"d" => 4, "e" => 5, "f" => 6,
"g" => 7, "h" => 8, "i" => 9,
"j" => 10, "k" => 11, "l" => 12,
"m" => 13, "n" => 14, "o" => 15,
"p" => 16, "q" => 17, "r" => 18,
"s" => 19, "t" => 20, "u" => 21,
"v" => 22, "w" => 23, "x" => 24,
"y" => 25, "z" => 26
]
value = word.split("")
puts "Your word, \'#{word}\' has a value of: #{value}"
You can use reduce method to add up the values of each char.
value = word.split("")
sum = value.reduce(0) {|sum, char| alphabet[char] + sum }
puts "Your word, \'#{word}\' has a value of: #{sum}"
#=> Your word, 'cab' has a value of: 6
Here we use reduce (which has an alias method inject) to reduce the array into a single value. We start with initial value of 0, and iterate through each element of the array - in the block, we add the numeric equivalent of given char to the sum so far - and eventually end up with sum of all numeric values.
Answer to question in comments:
My only relevant follow-up question to this, is it possible to define
the hash using ranges? I know that I can define them with ("a".."z")
and (1..26) but I didn't know if there is a way to set those two
ranges equal to one another based on their index values or somesuch
You can make use of Array#zip method that allows to merge two arrays by pairing elements at same index as sub-arrays. Subsequently, we can take advantage of method Array#to_h which converts any array of 2-element arrays into hash.
alphabet = ('a'..'z').zip(1..26).to_h
I'd suggest the following as a good Ruby-way:
base = 'a'.ord-1
"catsup".each_char.map { |c| c.ord - base }.reduce(:+)
#=> 80
Breaking it down:
d = 'a'.ord
#=> 97
base = d-1
#=> 96
e = "catsup".each_char.map { |c| c.ord - base }
#=> [3, 1, 20, 19, 21, 16]
e.reduce(:+)
#=> 80
Let's look more carefully at the calculation of e:
enum0 = "catsup".each_char
#=> #<Enumerator: "catsup":each_char>
Note:
enum0.map { |c| c.ord - base }
#=> [3, 1, 20, 19, 21, 16]
To see the elements of the enumerator enum0, which will be passed to map, convert it to an array:
enum0.to_a
#=> ["c", "a", "t", "s", "u", "p"]
Now lets write:
enum1 = enum0.map
#=> #<Enumerator: #<Enumerator: "catsup":each_char>:map>
Study the return value. You can think of enum1 as a "compound" enumerator.
enum1.to_a
#=> ["c", "a", "t", "s", "u", "p"]
enum1.each { |c| c.ord - base }
#=> [3, 1, 20, 19, 21, 16]
We can now use Enumerator#next to extract each element of enum, set the block variable c to that value and perform the block calculation:
c = enum1.next #=> "c"
c.ord - base #=> 99-96 = 3
c = enum1.next #=> "a"
c.ord - base #=> 1
c = enum1.next #=> "t"
c.ord - base #=> 20
c = enum1.next #=> "s"
c.ord - base #=> 19
c = enum1.next #=> "u"
c.ord - base #=> 21
c = enum1.next #=> "p"
c.ord - base #=> 16
c = enum1.next #=> StopIteration: iteration reached an end
DESCRIPTION:
The purpose of my code is to take in input of a sequence of R's and C's and to simply store each number that comes after the character in its proper array.
For Example: "The input format is as follows: R1C4R2C5
Column Array: [ 4, 5 ] Row Array: [1,2]
My problem is I am getting the output like this:
[" ", 1]
[" ", 4]
[" ", 2]
[" ", 5]
**How do i get all the Row integers following R in one array, and all the Column integers following C in another seperate array. I do not want to create multiple arrays, Rather just two.
Help!
CODE:
puts 'Please input: '
input = gets.chomp
word2 = input.scan(/.{1,2}/)
col = []
row = []
word2.each {|a| col.push(a.split(/C/)) if a.include? 'C' }
word2.each {|a| row.push(a.split(/R/)) if a.include? 'R' }
col.each do |num|
puts num.inspect
end
row.each do |num|
puts num.inspect
end
x = "R1C4R2C5"
col = []
row = []
x.chars.each_slice(2) { |u| u[0] == "R" ? row << u[1] : col << u[1] }
p col
p row
The main problem with your code is that you replicate operations for rows and columns. You want to write "DRY" code, which stands for "don't repeat yourself".
Starting with your code as the model, you can DRY it out by writing a method like this to extract the information you want from the input string, and invoke it once for rows and once for columns:
def doit(s, c)
...
end
Here s is the input string and c is the string "R" or "C". Within the method you want
to extract substrings that begin with the value of c and are followed by digits. Your decision to use String#scan was a good one, but you need a different regex:
def doit(s, c)
s.scan(/#{c}\d+/)
end
I'll explain the regex, but let's first try the method. Suppose the string is:
s = "R1C4R2C5"
Then
rows = doit(s, "R") #=> ["R1", "R2"]
cols = doit(s, "C") #=> ["C4", "C5"]
This is not quite what you want, but easily fixed. First, though, the regex. The regex first looks for a character #{c}. #{c} transforms the value of the variable c to a literal character, which in this case will be "R" or "C". \d+ means the character #{c} must be followed by one or more digits 0-9, as many as are present before the next non-digit (here a "R" or "C") or the end of the string.
Now let's fix the method:
def doit(s, c)
a = s.scan(/#{c}\d+/)
b = a.map {|str| str[1..-1]}
b.map(&:to_i)
end
rows = doit(s, "R") #=> [1, 2]
cols = doit(s, "C") #=> [4, 5]
Success! As before, a => ["R1", "R2"] if c => "R" and a =>["C4", "C5"] if c => "C". a.map {|str| str[1..-1]} maps each element of a into a string comprised of all characters but the first (e.g., "R12"[1..-1] => "12"), so we have b => ["1", "2"] or b =>["4", "5"]. We then apply map once again to convert those strings to their Fixnum equivalents. The expression b.map(&:to_i) is shorthand for
b.map {|str| str.to_i}
The last computed quantity is returned by the method, so if it is what you want, as it is here, there is no need for a return statement at the end.
This can be simplified, however, in a couple of ways. Firstly, we can combine the last two statements by dropping the last one and changing the one above to:
a.map {|str| str[1..-1].to_i}
which also gets rid of the local variable b. The second improvement is to "chain" the two remaining statements, which also rids us of the other temporary variable:
def doit(s, c)
s.scan(/#{c}\d+/).map { |str| str[1..-1].to_i }
end
This is typical Ruby code.
Notice that by doing it this way, there is no requirement for row and column references in the string to alternate, and the numeric values can have arbitrary numbers of digits.
Here's another way to do the same thing, that some may see as being more Ruby-like:
s.scan(/[RC]\d+/).each_with_object([[],[]]) {|n,(r,c)|
(n[0]=='R' ? r : c) << n[1..-1].to_i}
Here's what's happening. Suppose:
s = "R1C4R2C5R32R4C7R18C6C12"
Then
a = s.scan(/[RC]\d+/)
#=> ["R1", "C4", "R2", "C5", "R32", "R4", "C7", "R18", "C6", "C12"]
scan uses the regex /([RC]\d+)/ to extract substrings that begin with 'R' or 'C' followed by one or more digits up to the next letter or end of the string.
b = a.each_with_object([[],[]]) {|n,(r,c)|(n[0]=='R' ? r : c) << n[1..-1].to_i}
#=> [[1, 2, 32, 4, 18], [4, 5, 7, 6, 12]]
The row values are given by [1, 2, 32, 4, 18]; the column values by [4, 5, 7, 6, 12].
Enumerable#each_with_object (v1.9+) creates an array comprised of two empty arrays, [[],[]]. The first subarray will contain the row values, the second, the column values. These two subarrays are represented by the block variables r and c, respectively.
The first element of a is "R1". This is represented in the block by the variable n. Since
"R1"[0] #=> "R"
"R1"[1..-1] #=> "1"
we execute
r << "1".to_i #=> [1]
so now
[r,c] #=> [[1],[]]
The next element of a is "C4", so we will execute:
c << "4".to_i #=> [4]
so now
[r,c] #=> [[1],[4]]
and so on.
rows, cols = "R1C4R2C5".scan(/R(\d+)C(\d+)/).flatten.partition.with_index {|_, index| index.even? }
> rows
=> ["1", "2"]
> cols
=> ["4", "5"]
Or
rows = "R1C4R2C5".scan(/R(\d+)/).flatten
=> ["1", "2"]
cols = "R1C4R2C5".scan(/C(\d+)/).flatten
=> ["4", "5"]
And to fix your code use:
word2.each {|a| col.push(a.delete('C')) if a.include? 'C' }
word2.each {|a| row.push(a.delete('R')) if a.include? 'R' }
update: sorry, I fixed my program:
a = [ 'str1' , 'str2', 'str2', 'str3' ]
name = ''
a.each_with_index do |x, i |
if x == name
puts "#{x} found duplicate."
else
puts x
name = x if i!= 0
end
end
output:
str1
str2
str2 found duplicate.
str3
Is there another beautiful way in ruby language to do the same thing ?
btw, actually. a is a ActiveRecord::Relation in my real case.
Thanks.
The problem you might have with each_cons is that it iterates through n-1 pairs (if the length of the Enumerable is n). In some cases this means you have to separately handle edge cases for the first (or last) element.
In that case, it's really easy to implement a method similar to each_cons, but which would yield (nil, elem0) for the first element (as opposed to each_cons, which yields (elem0, elem1):
module Enumerable
def each_with_previous
self.inject(nil){|prev, curr| yield prev, curr; curr}
self
end
end
you can use each_cons:
irb(main):014:0> [1,2,3,4,5].each_cons(2) {|a,b| p "#{a} = #{b}"}
"1 = 2"
"2 = 3"
"3 = 4"
"4 = 5"
You can use each_cons
a.each_cons(2) do |first,last|
if last == name
puts 'got you!'
else
name = first
end
end
You may use Enumerable#each_cons:
a = [ 'str1' , 'str2', 'str3' , ..... ]
name = ''
a.each_cons(2) do |x, y|
if y == name
puts 'got you! '
else
name = x
end
end
As you probably want to do more than puts with the duplicates, I would rather keep the duplicates in a structure:
### question's example:
a = [ 'str1' , 'str2', 'str2', 'str3' ]
# => ["str1", "str2", "str2", "str3"]
a.each_cons(2).select{|a, b| a == b }.map{|m| m.first}
# => ["str2"]
### a more complex example:
d = [1, 2, 3, 3, 4, 5, 4, 6, 6]
# => [1, 2, 3, 3, 4, 5, 4, 6, 6]
d.each_cons(2).select{|a, b| a == b }.map{|m| m.first}
# => [3, 6]
More on at: https://www.ruby-forum.com/topic/192355 (cool answer of David A. Black)