Fill array with element N times - ruby

I want to fill an array with 1 element but 5 times.
What I got so far.
number = 1234
a = []
5.times { a << number }
puts a # => 1234, 1234, 1234, 1234, 1234
It works but this feels not the ruby way.
Can someone point me in the right direction to init an array with 5 times the same value?

For immutable objects like Fixnums etc
Array.new(5, 1234) # Assigns the given instance to each item
# => [1234, 1234, 1234, 1234, 1234]
For Mutable objects like String Arrays
Array.new(5) { "Lorem" } # Calls block for each item
# => ["Lorem", "Lorem", "Lorem", "Lorem", "Lorem"]

This should work:
[1234] * 5
# => [1234, 1234, 1234, 1234, 1234]

Although the accepted answer is fine in the case of strings and other immutable objects, I think it's worth expanding on Max's comment about mutable objects.
The following will fill an array of elements with 3 individually instantiated hashes:
different_hashes = Array.new(3) { {} } # => [{}, {}, {}]
The following will fill an array of elements with a reference to the same hash 3 times:
same_hash = Array.new(3, {}) # => [{}, {}, {}]
If you modify the first element of different_hashes:
different_hashes.first[:hello] = "world"
Only the first element will be modified.
different_hashes # => [{ hello: "world" }, {}, {}]
On the other hand, if you modify the first element of same_hash, all three elements will be modified:
same_hash.first[:hello] = "world"
same_hash # => [{ hello: "world" }, { hello: "world" }, { hello: "world" }]
which is probably not the intended result.

You can fill the array like this:
a = []
=> []
a.fill("_", 0..5) # Set given range to given instance
=> ["_", "_", "_", "_", "_"]
a.fill(0..5) { "-" } # Call block 5 times

these two methods will give an array of N values in ruby
Array.new(5, 2) => [2,2,2,2,2]
[2] * 5 => [2,2,2,2,2]

Related

Convert a matrix into a hash data strucutre

i have a matrix like this
[
["name", "company1", "company2", "company3"],
["hr_admin", "Tom", "Joane", "Kris"],
["manager", "Philip", "Daemon", "Kristy"]
]
How can I convert into this data structure?
{
"company1" => {
"hr_admin"=> "Tom",
"manager" => "Philip"
},
"Company2" => {
"hr_admin"=> "Joane",
"manager" => "Daemon"
},
"company3" => {
"hr_admin"=> "Kris",
"manager" => "Kristy"
}
}
I have tried approach like taking out the first row of matrix (header) and zipping the rest o
f the matrix to change their position. It worked to some extent but it doesnt looks very good. So I am turning up here for help.
matrix[0][1...matrix[0].length].each_with_index.map do |x,i|
values = matrix[1..matrix.length].map do |x|
[x[0], x[i+1]]
end.to_h
[x, values]
end.to_h
matrix[0].length and matrix.length could be omittable depending on ruby version.
First you take all elements of first row but first.
then you map them with index to e.g. [["hr_admin", "Tom"],["manager", "Phil"]] using the index
then you call to_h on every element and on whole array.
arr = [
["name", "company1", "company2", "company3"],
["hr_admin", "Tom", "Joane", "Kris"],
["manager", "Philip", "Daemon", "Kristy"]
]
Each key-value pair of the hash to be constructed is formed from the "columns" of arr. It therefore is convenient to compute the transpose of arr:
(_, *positions), *by_company = arr.transpose
#=> [["name", "hr_admin", "manager"],
# ["company1", "Tom", "Philip"],
# ["company2", "Joane", "Daemon"],
# ["company3", "Kris", "Kristy"]]
I made use of Ruby's array decomposition (a.k.a array destructuring) feature (see this blog for elabortion) to assign different parts of the inverse of arr to variables. Those values are as follows1.
_ #=> "name"
positions
#=> ["hr_admin", "manager"]
by_company
#=> [["company1", "Tom", "Philip"],
# ["company2", "Joane", "Daemon"],
# ["company3", "Kris", "Kristy"]]
It is now a simple matter to form the desired hash. Once again I will use array decomposition to advantage.
by_company.each_with_object({}) do |(company_name, *employees),h|
h[company_name] = positions.zip(employees).to_h
end
#=> {"company1"=>{"hr_admin"=>"Tom", "manager"=>"Philip"},
# "company2"=>{"hr_admin"=>"Joane", "manager"=>"Daemon"},
# "company3"=>{"hr_admin"=>"Kris", "manager"=>"Kristy"}}
When, for example,
company_name, *employees = ["company1", "Tom", "Philip"]
company_name
#=> "company1"
employees
#=> ["Tom", "Philip"]
so
h[company_name] = positions.zip(employees).to_h
h["company1"] = ["hr_admin", "manager"].zip(["Tom", "Philip"]).to_h
= [["hr_admin", "Tom"], ["manager", "Philip"]].to_h
= {"hr_admin"=>"Tom", "manager"=>"Philip"}
Note that these calculations do not depend on the numbers of rows or columns of arr.
1. As is common practice, I used the special variable _ to signal to the reader that its value is not used in subsequent calculations.

Create a new hash from an existing array of hashes

I am newbie to ruby . I have an array of hashes input_array
[{
"name"=>"test1",
"zone_status"=>"valid",
"certificate_status"=>"valid",
"users"=>1000,
"name"=>"test2",
"zone_status"=>"valid",
"certificate_status"=>"valid",
"users"=>5000,
"name"=>"test3",
"zone_status"=>"valid",
"certificate_status"=>"valid",
"users"=>3000,
"name"=>"test4",
"zone_status"=>"valid",
"certificate_status"=>"valid",
"users"=>2000}]
and an array
existing_names_array = ["test1","test2"]
The below line gets me all the names into input_names
input_array.each_with_index {|val, index| input_names << input_array[index]['name'] }
But how can I get the input_names to be a hash with name as key and its respective users as value?
Because my final goal is to check the names which are in input_names, but not in existing_names_array and get a count of those names and users
As the names tes1,test2 exists in existing_names_array, I need the count of rest of names and count of their respective users in input_array
Expected output:
output_names = test3, test 4
total_output_names = 2
output_users = 3000,2000
total_output_users = 5000
If you use ActiveSupport's Enumerable#index_by with Ruby core's Hash#transform_values it's pretty easy, if I'm understanding your question correctly:
# add gem 'activesupport' to Gemfile, or use Rails, then ..
require 'active_support/all'
users_and_counts = input_array.
index_by { |hsh| hsh["name"] }.
transform_values { |hsh| hsh["users"] }
# => { "test1" => 1000, "test2" => 5000, ... }
You can do this with Enumerable#reduce (or Enumerable#each_with_object) as well:
users_and_counts = input_array.reduce({}) do |memo, hsh|
memo[hsh["name"]] = hsh["users"]
memo
end
Or, the simplest way, with good old each:
users_and_counts = {}
input_array.each do |hsh|
users_and_counts[hsh["name"]] = hsh["users"]
end
in response to comment
In general, there are a few ways to check whether an element is found in an array:
array = ["foo", "bar"]
# 1. The simple standard way
array.include?("foo") # => true
# 2. More efficient way
require 'set'
set = Set.new(array)
set.member?("foo") # => true
So with this knowledge we can break up our task into a few steps:
Make a new hash which is a copy of the one we built above, but without the key-vals corresponding to users in the existing_names_array (see Hash#select and Hash#reject):
require 'set'
existing_names_set = Set.new(existing_names_array)
new_users_and_counts = users_and_counts.reject do |name, count|
existing_names_set.member?(name)
end
# => { "test3" => 3000, "test4" => 2000 }
Use Hash#keys to get the list of user names:
new_user_names = new_users_and_counts.keys
# => ["test3", "test4"]
Use Hash#values to get the list of counts:
new_user_counts = new_users_and_counts.values
# => [3000, 2000]
I assume input_array is to be as follows.
input_array = [
{ "name"=>"test1", "zone_status"=>"valid", "certificate_status"=>"valid",
"users"=>1000 },
{ "name"=>"test2", "zone_status"=>"valid", "certificate_status"=>"valid",
"users"=>5000 },
{ "name"=>"test3", "zone_status"=>"valid", "certificate_status"=>"valid",
"users"=>3000 },
{ "name"=>"test4", "zone_status"=>"valid", "certificate_status"=>"valid",
"users"=>2000}
]
We are also given:
names = ["test1", "test2"]
The information of interest can be represented nicely by the following hash (which I will construct):
summary = { "test1"=>1000, "test2"=>5000 }
We might use that to compute the following.
names = summary.keys
#=> ["test1", "test2"]
users = summary.values
#=> [1000, 5000]
total_users = users.sum
#=> 6000
There are many ways to construct the hash summary. I prefer the following.
summary = input_array.each_with_object({}) do |g,h|
key = g["name"]
h[key] = g["users"] if names.include?(key)
end
#=> {"test1"=>1000, "test2"=>5000}
Another way is as follows.
summary = input_array.map { |g| g.values_at("name", "users") }.
.to_h
.slice(*names)
See [Hash#values_at(https://ruby-doc.org/core-2.7.0/Hash.html#method-i-values_at), Array#to_h and Hash#slice. The steps are as follows.
arr = input_array.map { |g| g.values_at("name", "users") }
#=> [["test1", 1000], ["test2", 5000], ["test3", 3000], ["test4", 2000]]
h = arr.to_h
#=> {"test1"=>1000, "test2"=>5000, "test3"=>3000, "test4"=>2000}
summary = h.slice(*names)
#=> {"test1"=>1000, "test2"=>5000}
The splat operator converts h.slice(*names), which is h.slice(*["test1", "test2"]), to h.slice("test1", "test2"). That's because Hash#slice was defined to accept a variable number of keys as arguments, rather than an array of keys as the argument.

Ruby: Scanning strings for matching adjacent vowel groups

I am building a script to randomly generate words that sound like english. I have broken down a large number of english words into VCV groups.
...where the V's represent ALL the adjacent vowels in a word and the C represents ALL the adjacent consonants. For example, the English word "miniature" would become
"-mi", "inia", "iatu", and "ure". "school" would become "-schoo" and "ool".
These groups will be assembled together with other groups from other words with
the rule being that the complete set of adjacent ending vowels must match the
complete set of starting vowels for the attached group.
I have constructed a hash in the following structure:
pieces = {
:starters => { "-sma" => 243, "-roa" => 77, "-si" => 984, ...},
:middles => { "iatu" => 109, "inia" => 863, "aci" => 229, ...},
:enders => { "ar-" => 19, "ouid-" => 6, "ude" => 443, ...}
}
In order to construct generated words, a "starter" string would need to end with the same vowel grouping as the "middle" string. The same applies when connecting the "middle" string with the "ender" string. One possible result using the examples above would be "-sma" + "aba" + "ar-" to give "smabar". Another would be "-si" + "inia" + "iatu" + "ude" to give "siniatude".
My problem is that when I sample any two pieces, I don't know how to ensure that the ending V group of the first piece exactly matches the beginning V group of the second piece. For example, "utua" + "uailo" won't work together because "ua" is not the same as "uai". However, a successful pair would be "utua" + "uado" because "ua" = "ua".
def match(first, second)
end_of_first = first[/[aeiou]+$|[^aeiou]+$/]
start_of_second = second[/^[aeiou]+|^[^aeiou]+/]
end_of_first == start_of_second
end
match("utua", "uailo")
# => false
match("inia", "iatu")
# => true
EDIT: I apparently can't read, I thought you just want to match the group (whether vowel or consonant). If you restrict to vowel groups, it's simpler:
end_of_first = first[/[aeiou]+$/]
start_of_second = second[/^[aeiou]+/]
Since you're already pre-processing the dictionary, I suggest doing a little more preprocessing to make generation simpler. I have two suggestions. First, for the starters and middles, separate each into a tuple (for which, in Ruby, we just use a two-element array) of the form (VC, V), so e.g. "inia" becomes ["in", "ia"]:
starters = [
[ "-sm", "a" ],
[ "-r", "oa" ],
[ "-s", "i" ],
# ...
]
We store the starters in an array since we just need to choose one at random, which we can do with Array#sample:
starter, middle1_key = starters.sample
puts starter # => "-sm"
puts middle1_key # => "a"
We want to be able to look up middles by their initial V groups, so we put those tuples in a Hash instead, with their initial V groups as keys:
middles = {
"ia" => [
[ "iat", "u" ],
[ "iabl", "e" ],
],
"i" => [
[ "in", "ia" ],
# ...
],
"a" => [
[ "ac", "i" ],
# ...
],
# ...
}
Since we stored the starter's final V group in middle1_key above, we can now use that as a key to get the array of middle tuples whose initial V group matches, and choose one at random as we did above:
possible_middles1 = middles[middle1_key]
middle1, middle2_key = possible_middles1.sample
puts middle1 # => "ac"
puts middle2_key => "i"
Just for kicks, let's pick a second middle:
middle2, ender_key = middles[middle2_key].sample
puts middle2 # => "in"
puts ender_key # => "ia"
Our enders we don't need to store in tuples, since we won't be using any part of them to look anything up like we did with middles. We can just put them in a hash whose keys are the initial V groups and whose values are arrays of all of the enders with that initial V group:
enders = {
"a" => [ "ar-", ... ],
"oui" => [ "ouid-", ... ],
"u" => [ "ude-", ... ],
"ia" => [ "ial-", "iar-", ... ]
# ...
}
We stored the second middle's final V group in ender_key above, which we can use to get the array of matching enders:
possible_enders = enders[ender_key]
ender = possible_enders.sample
puts ender # => "iar-"
Now that we have four parts, we just put them together to form our word:
puts starter + middle1 + middle2 + ender
# => -smaciniar-
Edit
The data structures above omit the relative frequencies (I wrote the above before I had a chance to read your answer to my question about the numbers). Obviously it's trivial to also store the relative frequencies alongside the parts, but I don't know off the top of my head a fast way to then choose parts in a weighted fashion. Hopefully my answer is of some use to you regardless.
You can do that using the methods Enumerable#flat_map, String#partition, Enumerable#chunk and a few more familiar ones:
def combine(arr)
arr.flat_map { |s| s.partition /[^aeiou-]+/ }.
chunk { |s| s }.
map { |_, a| a.first }.
join.delete('-')
end
combine ["-sma", "aba", "ar-"]) #=> "smabar"
combine ["-si", "inia", "iatu", "ude"] #=> "siniatude"
combine ["utua", "uailo", "orsua", "uav-"] #=> "utuauailorsuav"
To see how this works, let's look at the last example:
arr = ["utua", "uailo", "orsua", "uav-"]
a = arr.flat_map { |s| s.partition /[^aeiou-]+/ }
#=> ["u", "t", "ua", "uai", "l", "o", "o", "rs", "ua", "ua", "v", "-"]
enum = a.chunk { |s| s }
#=> #<Enumerator: #<Enumerator::Generator:0x007fdd14963888>:each>
We can see the elements of this enumerator by converting it to an array:
enum.to_a
#=> [["u", ["u"]], ["t", ["t"]], ["ua", ["ua"]], ["uai", ["uai"]],
# ["l", ["l"]], ["o", ["o", "o"]], ["rs", ["rs"]], ["ua", ["ua", "ua"]],
# ["v", ["v"]], ["-", ["-"]]]
b = enum.map { |_, a| a.first }
#=> ["u", "t", "ua", "uai", "l", "o", "rs", "ua", "v", "-"]
s = b.join
#=> "utuauailorsuav-"
s.delete('-')
#=> "utuauailorsuav"

Mapping two dimensional array to one dimensional array with indexes

I have 2d array like this:
ary = [
["Source", "attribute1", "attribute2"],
["db", "usage", "value"],
["import", "usage", "value"],
["webservice", "usage", "value"]
]
I want to pull out the following in hash:
{1 => "db", 2 => "import", 3 => "webservice"} // keys are indexes or outer 2d array
I know how to get this by looping trough 2d array. But since I'm learning ruby I thought I could do it with something like this
ary.each_with_index.map {|element, index| {index => element[0]}}.reduce(:merge)
This gives me :
{0=> "Source", 1 => "db", 2 => "import", 3 => "webservice"}
How do I get rid of 0 element from my output map?
I'd write:
Hash[ary.drop(1).map.with_index(1) { |xs, idx| [idx, xs.first] }]
#=> {1=>"db", 2=>"import", 3=>"webservice"}
ary.drop(1) drops the first element, returns the rest.
You could build the hash directly without the merge reduction using each_with_object
ary.drop(1)
.each_with_object({})
.with_index(1) { |((source,_,_),memo),i| memo[i] = source }
Or map to tuples and send to the Hash[] constructor.
Hash[ ary.drop(1).map.with_index(1) { |(s,_,_),i| [i, s] } ]

Convert cartesian product to nested hash in ruby

I have a structure with a cartesian product that looks like this (and could go out to arbitrary depth)...
variables = ["var1","var2",...]
myhash = {
{"var1"=>"a", "var2"=>"a", ...}=>1,
{"var1"=>"a", "var2"=>"b", ...}=>2,
{"var1"=>"b", "var2"=>"a", ...}=>3,
{"var1"=>"b", "var2"=>"b", ...}=>4,
}
... it has a fixed structure but I'd like simple indexing so I'm trying to write a method to convert it to this :
nested = {
"a"=> {
"a"=> 1,
"b"=> 2
},
"b"=> {
"a"=> 3,
"b"=> 4
}
}
Any clever ideas (that allow for arbitrary depth)?
Maybe like this (not the cleanest way):
def cartesian_to_map(myhash)
{}.tap do |hash|
myhash.each do |h|
(hash[h[0]["var1"]] ||= {}).merge!({h[0]["var2"] => h[1]})
end
end
end
Result:
puts cartesian_to_map(myhash).inspect
{"a"=>{"a"=>1, "b"=>2}, "b"=>{"a"=>3, "b"=>4}}
Here is my example.
It uses a method index(hash, fields) that takes the hash, and the fields you want to index by.
It's dirty, and uses a local variable to pass up the current level in the index.
I bet you can make it much nicer.
def index(hash, fields)
# store the last index of the fields
last_field = fields.length - 1
# our indexed version
indexed = {}
hash.each do |key, value|
# our current point in the indexed hash
point = indexed
fields.each_with_index do |field, i|
key_field = key[field]
if i == last_field
point[key_field] = value
else
# ensure the next point is a hash
point[key_field] ||= {}
# move our point up
point = point[key_field]
end
end
end
# return our indexed hash
indexed
end
You can then just call
index(myhash, ["var1", "var2"])
And it should look like what you want
index({
{"var1"=>"a", "var2"=>"a"} => 1,
{"var1"=>"a", "var2"=>"b"} => 2,
{"var1"=>"b", "var2"=>"a"} => 3,
{"var1"=>"b", "var2"=>"b"} => 4,
}, ["var1", "var2"])
==
{
"a"=> {
"a"=> 1,
"b"=> 2
},
"b"=> {
"a"=> 3,
"b"=> 4
}
}
It seems to work.
(see it as a gist
https://gist.github.com/1126580)
Here's an ugly-but-effective solution:
nested = Hash[ myhash.group_by{ |h,n| h["var1"] } ].tap{ |nested|
nested.each do |v1,a|
nested[v1] = a.group_by{ |h,n| h["var2"] }
nested[v1].each{ |v2,a| nested[v1][v2] = a.flatten.last }
end
}
p nested
#=> {"a"=>{"a"=>1, "b"=>2}, "b"=>{"a"=>3, "b"=>4}}
You might consider an alternative representation that is easier to map to and (IMO) just as easy to index:
paired = Hash[ myhash.map{ |h,n| [ [h["var1"],h["var2"]], n ] } ]
p paired
#=> {["a", "a"]=>1, ["a", "b"]=>2, ["b", "a"]=>3, ["b", "b"]=>4}
p paired[["a","b"]]
#=> 2

Resources