How to set array to tree fabric using Ruby - ruby

I have an array with values:
list = [["a"], ["a", "b"], ["a", "b", "c"], ["a", "b", "c", "d"]]
I would like to convert this array to print a tree struct, just like computer directory struct.
Im trying to using recursive function to resolve this question. and expect result is Hash type, like this:
{ "a" => { "b" => { "c" => { "d" => {} } } } }
This's question will help me show the redis keys with tree shape, it's folding.

Using brilliant Hashie::Mash and Kernel.eval:
input = [%w|a|, %w|a b|, %w|a b c|, %w|a b c d|]
require 'hashie/mash'
input.each_with_object(Hashie::Mash.new) do |e, acc|
eval ["acc", e.map{ |k| "#{k}!" }].join(".")
end
#⇒ { "a" => { "b" => { "c" => { "d" => {} } } } }

You didn't show any code, so I won't either.
You're looking for a Trie, not just a Tree.
Pick any gem.

Related

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 non consistent results with scanned string's length

I may not be having the whole picture here but I am getting inconsistent results with a calculation: I am trying to solve the run length encoding problem so that if you get an input string like "AAABBAAACCCAA" the encoding will be: "3A2B3A3C2A" so the functions is:
def encode(input)
res = ""
input.scan(/(.)\1*/i) do |match|
res << input[/(?<bes>#{match}+)/, "bes"].length.to_s << match[0].to_s
end
res
end
The results I am getting are:
irb(main):049:0> input = "AAABBBCCCDDD"
=> "AAABBBCCCDDD"
irb(main):050:0> encode(input)
(a) => "3A3B3C3D"
irb(main):051:0> input = "AAABBBCCCAAA"
=> "AAABBBCCCAAA"
irb(main):052:0> encode(input)
(b) => "3A3B3C3A"
irb(main):053:0> input = "AAABBBCCAAA"
=> "AAABBBCCAAA"
irb(main):054:0> encode(input)
(c) => "3A3B2C3A"
irb(main):055:0> input = "AAABBBCCAAAA"
=> "AAABBBCCAAAA"
irb(main):056:0> encode(input)
(d) => "3A3B2C3A"
irb(main):057:0> input = 'WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB'
=> "WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB"
irb(main):058:0> encode(input)
(e) => "12W1B12W1B12W1B"
As you can see, results (a) through (c) are correct, but results (d) and (e) are missing some repetitions and the resulting code is several letters short, can you give a hint as to where to check, please? (I am learning to use 'pry' right now)
Regular expressions are great, but they're not the golden hammer for every problem.
str = "AAABBAAACCCAA"
str.chars.chunk_while { |i, j| i == j }.map { |a| "#{a.size}#{a.first}" }.join
Breaking down what it does:
str = "AAABBAAACCCAA"
str.chars # => ["A", "A", "A", "B", "B", "A", "A", "A", "C", "C", "C", "A", "A"]
.chunk_while { |i, j| i == j } # => #<Enumerator: #<Enumerator::Generator:0x007fc1998ac020>:each>
.to_a # => [["A", "A", "A"], ["B", "B"], ["A", "A", "A"], ["C", "C", "C"], ["A", "A"]]
.map { |a| "#{a.size}#{a.first}" } # => ["3A", "2B", "3A", "3C", "2A"]
.join # => "3A2B3A3C2A"
to_a is there for illustration, but isn't necessary:
str = "AAABBAAACCCAA"
str.chars
.chunk_while { |i, j| i == j }
.map { |a| "#{a.size}#{a.first}" }
.join # => "3A2B3A3C2A"
how do you get to know such methods as Array#chunk_while? I am using Ruby 2.3.1 but cannot find it in the API docs, I mean, where is the compendium list of all the methods available? certainly not here ruby-doc.org/core-2.3.1/Array.html
Well, this is off-topic to the question but it's useful information to know:
Remember that Array includes the Enumerable module, which contains chunk_while. Use the search functionality of http://ruby-doc.org to find where things live. Also, get familiar with using ri at the command line, and try running gem server at the command-line to get the help for all the gems you've installed.
If you look at the Array documentation page, on the left you can see that Array has a parent class of Object, so it'll have the methods from Object, and that it also inherits from Enumerable, so it'll also pull in whatever is implemented in Enumerable.
You only get the count of the matched symbol repetitions that occur first. You need to perform a replacement within a gsub and pass the match object to a block where you can perform the necessary manipulations:
def encode(input)
input.gsub(/(.)\1*/) { |m| m.length.to_s << m[0] }
end
See the online Ruby test.
Results:
"AAABBBCCCDDD" => 3A3B3C3D
"AAABBBCCCAAA" => 3A3B3C3A
"AAABBBCCAAA" => 3A3B2C3A
"AAABBBCCAAAA" => 3A3B2C4A
"WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB" => 12W1B12W3B24W1B

How to use keep_if with string array

I'm trying to use keep_if in my Rails 5 app with Ruby 2.3.1.
a = ["a", "b", "c", "d" ]
b = ["b", "d"]
a.keep_if { |v| v=~ /["#{b}"]/ }
#=> ["b", "d"]
Real project:
a = ["apple", "banana", "orange"]
b = ["mangoes", "banana", "pear"]
a.keep_if { |v| v=~ /["#{b}"]/ }
#=> ["mangoes", "banana", "pear"]
What I expected:
#=> ["banana"]
I'm guessing some sort of regex to be used? How to get what I expected?
keep_if() deletes every element of self for which block evaluates to false. See Array#select!
If no block is given, an enumerator is returned instead.
#Cary Swoveland has mentioned in a comment that the following should work if you want to use keep_if():
a.keep_if { |v| b.include?(v) } #=> ["banana"]
The following would work if you wanted to use Array#select! instead for perhaps a different scenario:
c = a+b
c.select { |x| c.count(x) == 2 }.uniq #=> ["banana"]
# (use .uniq > 2 for values that appear more than once)

Enumerator `Array#each` 's {block} can't always change array values?

Ok maybe this is simple but...
given this:
arr = ("a".."z").to_a
arr
=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
..and that I'm trying to change all "arr" values to "bad"
why isn't this working ?
arr.each { |v| v = "bad" }
arr
=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
Answers suggested that "v" is a local variable to the block (a "copy" of the array value) and I fully understand that (and never puzzled me before) but then
.. why it is working if array elements are objects ?
class Person
def initialize
#age = 0
end
attr_accessor :age
end
kid = Person.new
man = Person.new
arr = [kid, man]
arr.each { |p| p.age = 50 }
arr[0]
=> #<Person:0xf98298 #age=50>
isn't here "p" still local to the block here?
but then it really affects the objects, how come ?
I'll expand upon #pst's comment:
why isn't this working ?
arr.each { |v| v = "bad" }
Because each iterates through the array and puts each item into the block you've given as a local variable v, as v is not a reference to the array arr.
new_arr = arr.each { |v| v = "bad" }
each does not give back an array, for that you would use map (see #benjaminbenben's answer). Therefore assigning it does not "work".
arr.each { |v| arr[arr.index v] = "bad" }
Here you put each item in arr into the local variable v, but you've also referred to the array itself in the block, hence you are able to assign to the array and use the local variable v to find an index that corresponds to the contents of v (but you may find this wouldn't work as you expect when the items are not all unique).
arr.each { |p| p.age = 50 }
kid.age #-> 50
Here, again you've filled the local variable p with each item/object in arr, but then you've accessed each item via a method, so you are able to change that item - you are not changing the array. It's different because the reference is to the contents of the local variable, which you've mixed up with being a reference to the array. They are separate things.
In response to the comment below:
arr[0]
# => #<Person:0xf98298 #age=50>
It's all about who's referring to whom when.
Try this:
v = Person.new
# => #<Person:0x000001008de248 #age=0>
w = Person.new
# => #<Person:0x000001008d8050 #age=0>
x = v
# => #<Person:0x000001008de248 #age=0>
v = Person.new
# => #<Person:0x00000100877e80 #age=0>
arr = [v,w,x]
# => [#<Person:0x00000100877e80 #age=0>, #<Person:0x000001008d8050 #age=0>, #<Person:0x000001008de248 #age=0>]
v referred to 2 different objects there. v is not a fixed thing, it's a name. At first it refers to #<Person:0x000001008de248 #age=0>, then it refers to #<Person:0x00000100877e80 #age=0>.
Now try this:
arr.each { |v| v = "bad" }
# => [#<Person:0x00000100877e80 #age=0>, #<Person:0x000001008d8050 #age=0>, #<Person:0x000001008de248 #age=0>]
They are all objects but nothing was updated or "worked". Why? Because when the block is first entered, v refers to the item in the array that was yielded (given). So on first iteration v is #<Person:0x00000100877e80 #age=0>.
But, we then assign "bad" to v. We are not assigning "bad" to the first index of the array because we aren't referencing the array at all. arr is the reference to the array. Put arr inside the block and you can alter it:
arr.each { |v|
arr[0] = "bad" # yes, a bad idea!
}
Why then does arr.each { |p| p.age = 50 } update the items in the array? Because p refers to the objects that also happen to be in the array. On first iteration p refers to the object also known as kid, and kid has an age= method and you stick 50 in it. kid is also the first item in the array, but you're talking about kid not the array. You could do this:
arr.each { |p| p = "bad"; p.age }
NoMethodError: undefined method `age' for "bad":String
At first, p referred to the object that also happened to be in the array (that's where it was yielded from), but then p was made to refer to "bad".
each iterates over the array and yields a value on each iteration. You only get the value not the array. If you want to update an array you either do:
new_arr = arr.map{|v| v = "bad" }
new_arr = arr.map{|v| "bad" } # same thing
or
arr.map!{|v| v = "bad"}
arr.map!{|v| "bad"} # same thing
as map returns an array filled with the return value of the block. map! will update the reference you called it on with an array filled with the return value of the block. Generally, it's a bad idea to update an object when iterating over it anyway. I find it's always better to think of it as creating a new array, and then you can use the ! methods as a shortcut.
In example
arr.each { |v| v = "bad" }
"v" is just reference to string, when you do v = "bad", you reassign local variable. To make everything bad you can do like that:
arr.each { |v| v.replace "bad" }
Next time you can play with Object#object_id
puts arr[0].object_id #will be save as object_id in first iteration bellow
arr.each { |v| puts v.object_id }
You might be looking for .map - which returns a new array with the the return value of the block for each element.
arr.map { "bad" }
=> ["bad", "bad", "bad", "bad", …]
using .map! will alter the contents of the original array rather than return a new one.
How about this
arry = Array.new(arry.length,"bad")
This will set the a default value of "bad" to the arry.length

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