Ruby: Scanning strings for matching adjacent vowel groups - ruby

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"

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.

Sorting Multiple Directory Entries in Ruby

If I have the following files And they have the following paths (changed for simplicity)
array = ["root_path/dir1/a/file.jpg",
"root_path/dir1/a/file2.jpg",
"root_path/dir1/b/file3.jpg",
"root_path/dir2/c/file4.jpg"]
How can I sort them to get this sort of hash like this?
sort_directory(array)
#=>
{
"dir1" => {
"a" => [
"root_path/dir1/a/file.jpg",
"root_path/dir1/a/file2.jpg"
],
"b" => [
"root_path/dir1/b/file3.jpg"
]
},
"dir2" => {
"c" => [
"root_path/dir2/c/file4.jpg"
]
}
}
one way of doing it using group_by, split and/or some regex
array.group_by{ |dir| dir.split('/')[1] }.map{ |k,v| {k => v.group_by{ |file| file[/\/([^\/]+)(?=\/[^\/]+\/?\Z)/, 1]} } }
Here is how you can use recursion to obtain the desired result.
If s = "root_path/dir1/a/b/c/file.jpg", we can regard "root_path" as being at "position" 0, "dir1" at position 1 and so on. The example given by the OP has desired grouping on values at positions 1 and 2, which I will write positions = [1,2].
There is no limit to the number of positions on which to group or their order. For the string above we could write, for example, positions = [2,4,1], so the first grouping would be on position 2, the next on position 4 and the last on position 1 (though I have no idea if that could be of interest).
Code
def hashify(arr, positions)
recurse(positions, arr.map { |s| s.split("/") })
end
def recurse(positions, parts)
return parts.map { |a| a.join('/') } if positions.empty?
pos, *positions = positions
h = parts.group_by { |a| a[pos] }.
each_with_object({}) { |(k,a),g| g[k]=recurse(positions, a) }
end
Example
arr = ["root_path/dir1/a/file.jpg",
"root_path/dir1/a/file2.jpg",
"root_path/dir1/b/file3.jpg",
"root_path/dir2/c/file4.jpg"]
hashify(arr, [1, 2])
#=>{"dir1"=>{"a"=>["root_path/dir1/a/file.jpg", "root_path/dir1/a/file2.jpg"],
# "b"=>["root_path/dir1/b/file3.jpg"]},
# "dir2"=>{"c"=>["root_path/dir2/c/file4.jpg"]}}
Explanation
Recursive methods are difficult to explain. The best way, in my opinion, is to insert puts statements to show the sequence of calculation. I've also indented a few spaces whenever the method calls itself. Here is how the code might be modified for that purpose.
INDENT = 4
def hashify(arr, positions)
recurse(positions, arr.map { |s| s.split("/") }, 0)
end
def recurse(positions, parts, lvl)
puts
"lvl=#{lvl}".pr(lvl)
"positions=#{ positions }".pr(lvl)
if positions.empty?
"parts=#{parts}".pr(lvl)
return parts.map { |a| a.join('/') }
end
pos, *positions = positions
"pos=#{pos}, positions=#{positions}".pr(lvl)
h = parts.group_by { |a| a[pos] }
"h=#{h}".pr(lvl)
g = h.each_with_object({}) { |(k,a),g| g[k]=recurse(positions, a, lvl+1) }
"rv=#{g}".pr(lvl)
g
end
class String
def pr(lvl)
print "#{ ' ' * INDENT * lvl}"
puts self
end
end
We now execute this method for the data given in the example.
hashify(arr, [1, 2])
lvl=0
positions=[1, 2]
pos=1, positions=[2]
h={"dir1"=>[["root_path", "dir1", "a", "file.jpg"],
["root_path", "dir1", "a", "file2.jpg"],
["root_path", "dir1", "b", "file3.jpg"]],
"dir2"=>[["root_path", "dir2", "c", "file4.jpg"]]}
lvl=1
positions=[2]
pos=2, positions=[]
h={"a"=>[["root_path", "dir1", "a", "file.jpg"],
["root_path", "dir1", "a", "file2.jpg"]],
"b"=>[["root_path", "dir1", "b", "file3.jpg"]]}
lvl=2
positions=[]
parts=[["root_path", "dir1", "a", "file.jpg"],
["root_path", "dir1", "a", "file2.jpg"]]
lvl=2
positions=[]
parts=[["root_path", "dir1", "b", "file3.jpg"]]
rv={"a"=>["root_path/dir1/a/file.jpg", "root_path/dir1/a/file2.jpg"],
"b"=>["root_path/dir1/b/file3.jpg"]}
lvl=1
positions=[2]
pos=2, positions=[]
h={"c"=>[["root_path", "dir2", "c", "file4.jpg"]]}
lvl=2
positions=[]
parts=[["root_path", "dir2", "c", "file4.jpg"]]
rv={"c"=>["root_path/dir2/c/file4.jpg"]}
rv={"dir1"=>{"a"=>["root_path/dir1/a/file.jpg",
"root_path/dir1/a/file2.jpg"],
"b"=>["root_path/dir1/b/file3.jpg"]},
"dir2"=>{"c"=>["root_path/dir2/c/file4.jpg"]}}

Ruby idiom for sorting on two fields

I need the Ruby idiom for sorting on two fields. In Python if you sort a list of two-element tuples, it sorts based on the first element, and if two elements are equal then the sort is based on the second element.
One example is the following sorting code in Python (word sort from longest to shortest and consider second element to break ties) from http://www.pythonlearn.com/html-008/cfbook011.html
txt = 'but soft what light in yonder window breaks'
words = txt.split()
t = list()
for word in words:
t.append((len(word), word))
t.sort(reverse=True)
res = list()
for length, word in t:
res.append(word)
print res
What I came up in Ruby is the following code that uses structs
txt = 'but soft what light in yonder window breaks'
words = txt.split()
t = []
tuple = Struct.new(:len, :word)
for word in words
tpl = tuple.new
tpl.len = word.length
tpl.word = word
t << tpl
end
t = t.sort {|a, b| a[:len] == b[:len] ?
b[:word] <=> a[:word] : b[:len] <=> a[:len]
}
res = []
for x in t
res << x.word
end
puts res
I would like to know if there are better ways (less code) to achieve this stable sort.
I think you're overthinking this.
txt = 'but soft what light in yonder window breaks'
lengths_words = txt.split.map {|word| [ word.size, word ] }
# => [ [ 3, "but" ], [ 4, "soft" ], [ 4, "what" ], [ 5, "light" ], ... ]
sorted = lengths_words.sort
# => [ [ 2, "in" ], [ 3, "but" ], [ 4, "soft" ], [ 4, "what" ], ... ]
If you really want to use Struct, you can:
tuple = Struct.new(:length, :word)
tuples = txt.split.map {|word| tuple.new(word.size, word) }
# => [ #<struct length=3, word="but">, #<struct length=4, word="soft">, ... ]
sorted = tuples.sort_by {|tuple| [ tuple.length, tuple.word ] }
# => [ #<struct length=2, word="in">, #<struct length=3, word="but">, ... ]
This is equivalent:
sorted = tuples.sort {|tuple, other| tuple.length == other.length ?
tuple.word <=> other.word : tuple.length <=> other.length }
(Note that it's sort this time, not sort_by.)
...but since we're using a Struct we can make this nicer by defining our own comparison operator (<=>), which sort will invoke (the same works in any Ruby class):
tuple = Struct.new(:length, :word) do
def <=>(other)
[ length, word ] <=> [ other.length, other.word ]
end
end
tuples = txt.split.map {|word| tuple.new(word.size, word) }
tuples.sort
# => [ #<struct length=2, word="in">, #<struct length=3, word="but">, ... ]
There are other options for more complex sorting. If you wanted to get longest words first, for example:
lengths_words = txt.split.map {|word| [ word.size, word ] }
sorted = lengths_words.sort_by {|length, word| [ -length, word ] }
# => [ [ 6, "breaks" ], [ 6, "window" ], [ 6, "yonder" ], [ 5, "light" ], ... ]
Or:
tuple = Struct.new(:length, :word) do
def <=>(other)
[ -length, word ] <=> [ -other.length, other.word ]
end
end
txt.split.map {|word| tuple.new(word.size, word) }.sort
# => [ #<struct length=6, word="breaks">, #<struct length=6, word="window">, #<struct length=6, word="yonder">, ... ]
As you can see, I'm relying a lot on Ruby's built-in ability to sort arrays based on their contents, but you can also "roll your own" if you prefer, which might perform better with many, many items. Here's a comparison method that's equivalent to your t.sort {|a, b| a[:len] == b[:len] ? ... } code (plus a bonus to_s method):
tuple = Struct.new(:length, :word) do
def <=>(other)
return word <=> other.word if length == other.length
length <=> other.length
end
def to_s
"#{word} (#{length})"
end
end
sorted = txt.split.map {|word| tuple.new(word.size, word) }.sort
puts sorted.join(", ")
# => in (2), but (3), soft (4), what (4), light (5), breaks (6), window (6), yonder (6)
Finally, a couple comments on your Ruby style:
You pretty much never see for in idiomatic Ruby code. each is the idiomatic way to do almost all iteration in Ruby, and "functional" methods like map, reduce and select are also common. Never for.
A great advantage of Struct is that you get accessor methods for each property, so you can do tuple.word instead of tuple[:word].
Methods with no arguments are called without parentheses: txt.split.map, not txt.split().map
Ruby makes this easy, using Enumerable#sort_by will and Array#<=> for sorting.
def sort_on_two(arr, &proc)
arr.map.sort_by { |e| [proc[e], e] }.reverse
end
txt = 'but soft what light in yonder window breaks'
sort_on_two(txt.split) { |e| e.size }
#=> ["yonder", "window", "breaks", "light", "what", "soft", "but", "in"]
sort_on_two(txt.split) { |e| e.count('aeiou') }
#=> ["yonder", "window", "breaks", "what", "soft", "light", "in", "but"]
sort_on_two(txt.split) { |e| [e.count('aeiou'), e.size] }
#=> ["yonder", "window", "breaks", "light", "what", "soft", "but", "in"]
Note that in recent versions of Ruby, proc.call(e) can be written proc[e], proc.yield(e) or proc.(e).
UPDATE: my first answer was wrong (this time!), thanks to #mu is too short comment
Your code is ok to sort on two criteria, but if you just want to achieve the same result, the best is to do the following:
txt.split.sort_by{|a| [a.size,a] }.reverse
=> ["breaks", "window", "yonder", "light", "soft", "what", "but", "in"]
The first check will use the size operator, and if the result is zero, it will use the second one....
If you really want to keep your data structure, it's same principle:
t.sort_by{ |a| [a[:len],a[:word]] }.reverse

Determining if a prefix exists in a set

Given a set of strings, say:
"Alice"
"Bob"
"C"
"Ca"
"Car"
"Carol"
"Caroling"
"Carousel"
and given a single string, say:
"Carolers"
I would like a function that returns the smallest prefix not already inside the array.
For the above example, the function should return: "Caro". (A subsequent call would return "Carole")
I am very new to Ruby, and although I could probably hack out something ugly (using my C/C++/Objective-C brain), I would like to learn how to properly (elegantly?) code this up.
There's a little known magical module in Ruby called Abbrev.
require 'abbrev'
abbreviations = Abbrev::abbrev([
"Alice",
"Bob",
"C",
"Ca",
"Car",
"Carol",
"Caroling",
"Carousel"
])
carolers = Abbrev::abbrev(%w[Carolers])
(carolers.keys - abbreviations.keys).sort.first # => "Caro"
Above I took the first element but this shows what else would be available.
pp (carolers.keys - abbreviations.keys).sort
# >> ["Caro", "Carole", "Caroler", "Carolers"]
Wrap all the above in a function, compute the resulting missing elements, and then iterate over them yielding them to a block, or use an enumerator to return them one-by-one.
This is what is generated for a single word. For an array it is more complex.
require 'pp'
pp Abbrev::abbrev(['cat'])
# >> {"ca"=>"cat", "c"=>"cat", "cat"=>"cat"}
pp Abbrev::abbrev(['cat', 'car', 'cattle', 'carrier'])
# >> {"cattl"=>"cattle",
# >> "catt"=>"cattle",
# >> "cat"=>"cat",
# >> "carrie"=>"carrier",
# >> "carri"=>"carrier",
# >> "carr"=>"carrier",
# >> "car"=>"car",
# >> "cattle"=>"cattle",
# >> "carrier"=>"carrier"}
Your question still doesn't match what you are expecting as a result. It seems that you need prefixes, not the substrings (as "a" would be the shortest substring not already in the array). For searching the prefix, this should suffice:
array = [
"Alice",
"Bob",
"C",
"Ca",
"Car",
"Carol",
"Caroling",
"Carousel",
]
str = 'Carolers'
(0..str.length).map{|i|
str[0..i]
}.find{|s| !array.member?(s)}
I am not a Ruby expert, but I think you may want to approach this problem by converting your set into a trie. Once you have the trie constructed, your problem can be solved simply by walking down from the root of the trie, following all of the edges for the letters in the word, until you either find a node that is not marked as a word or walk off the trie. In either case, you've found a node that isn't part of any word, and you have the shortest prefix of your word in question that doesn't already exist inside of the set. Moreover, this would let you run any number of prefix checks quickly, since after you've built up the trie the algorithm takes time at most linear in the length of the string.
Hope this helps!
I'm not really sure what you're asking for other than an example of some Ruby code to find common prefixes. I'll assume you want to find the smallest string which is a prefix of the most number of strings in the given set. Here's an example implementation:
class PrefixFinder
def initialize(words)
#words = Hash[*words.map{|x|[x,x]}.flatten]
end
def next_prefix
max=0; biggest=nil
#words.keys.sort.each do |word|
0.upto(word.size-1) do |len|
substr=word[0..len]; regex=Regexp.new("^" + substr)
next if #words[substr]
count = #words.keys.find_all {|x| x=~regex}.size
max, biggest = [count, substr] if count > max
#puts "OK: s=#{substr}, biggest=#{biggest.inspect}"
end
end
#words[biggest] = biggest if biggest
biggest
end
end
pf = PrefixFinder.new(%w(C Ca Car Carol Caroled Carolers))
pf.next_prefix # => "Caro"
pf.next_prefix # => "Carole"
pf.next_prefix # => "Caroler"
pf.next_prefix # => nil
No comment on the performance (or correctness) of this code but it does show some Ruby idioms (instance variables, iteration, hashing, etc).
=> inn = ["Alice","Bob","C","Ca","Car","Carol","Caroling","Carousel"]
=> y = Array.new
=> str="Carolers"
Split the given string to an array
=> x=str.split('')
# ["C","a","r","o","l","e","r","s"]
Form all the combination
=> x.each_index {|i| y << x.take(i+1)}
# [["c"], ["c", "a"], ["c", "a", "r"], ["c", "a", "r", "o"], ["c", "a", "r", "o", "l"], ["c", "a", "r", "o", "l", "e"], ["c", "a", "r", "o", "l", "e", "r"], ["c", "a", "r", "o", "l", "e", "r", "s"]]
Using Join to concatenate the
=> y = y.map {|s| s.join }
# ["c", "ca", "car", "caro", "carol", "carole", "caroler", "carolers"]
Select the first item from the y thats not available in the input Array
=> y.select {|item| !inn.include? item}.first
You will get "caro"
Putting together all
def FindFirstMissingItem(srcArray,strtocheck)
y=Array.new
x=strtocheck.split('')
x.each_index {|i| y << x.take(i+1)}
y=y.map {|s| s.join}
y.select {|item| !srcArray.include? item}.first
end
And call
=> inn = ["Alice","Bob","C","Ca","Car","Carol","Caroling","Carousel"]
=> str="Carolers"
FindFirstMissingItem inn,str
Very simple version (but not very Rubyish):
str = 'Carolers'
ar = %w(Alice Bob C Ca Car Carol Caroling Carousel)
substr = str[0, n=1]
substr = str[0, n+=1] while ar.include? substr
puts substr

Alias for array or hash element in Ruby

Example for array
arr = ["a", "b", "c"]
# TODO create an alias for arr[1] as x
x = "X"
# arr should be ["a", "X", "c"] here
Example for hash
hash = { :a => "aaa", :b => "bbb" , :c => "ccc" }
# TODO create an alias for hash[:b] as y
y = "YYY"
# hash should be { :a => "aaa", :b => "YYY" , :c => "ccc" } here
And also an alias for a variable?
var = 5
# TODO create an alias for var as z
z = 7
# var should be 7 here
Motivation: I have a big large deep construct of data, and you can imagine the rest. I want to use it in a read-only manner, but due to performance reasons copy is not permissible.
Metaphor: I want to choose context from a larger data structure and I want to access it with a short and simple name.
UPDATE: Problem solved as sepp2k advised. I just want to draw a summarizing picture here about the solution.
irb(main):001:0> arr = [ { "a" => 1, "b" => 2}, { "x" => 7, "y" => 8 } ]
=> [{"a"=>1, "b"=>2}, {"x"=>7, "y"=>8}]
irb(main):002:0> i = arr[0]
=> {"a"=>1, "b"=>2}
irb(main):004:0> j = arr[1]
=> {"x"=>7, "y"=>8}
irb(main):007:0> j["z"] = 9
=> 9
irb(main):008:0> j
=> {"x"=>7, "y"=>8, "z"=>9}
irb(main):009:0> arr
=> [{"a"=>1, "b"=>2}, {"x"=>7, "y"=>8, "z"=>9}]
What you want is not possible. There is no feature in ruby that you could use to make your examples work like you want.
However since you're saying you want to only use it read-only, there is no need for that. You can just do x = myNestedStructure[foo][bar][baz]. There will be no copying involved when you do that. Assignment does not copy the assigned object in ruby.
You would have to create a method that is your alias, which would update the data.
def y=(value)
arr[:b]=value
end
Then call it.
self.y="foo"
Edit: updated second code snippet.

Resources