0

I know this is a shot in the dark, but here it goes. I've been trying to build an unordered list using ruby similar to the one below, but here is the catch: the data that I am receiving from an API is not in a easily parsable format.

I know that I'm going to have to use recursion, but I'm not sure how I can convert it properly into a nested (parent - children) ruby hash.

Any suggestions or references to resources to solve my issue will be greatly appreciated. Thank you for your time for reading this question.

Ruby Array

[ [ "Lost in Translation 2003 BDRip 1080p AAC x264-tomcat12\\Lost in Translation 2003 BDRip 1080p AAC x264-tomcat12.mp4", 3450211337 ],
  [ " Lost in Translation 2003 BDRip 1080p AAC x264-tomcat12\\video VC-1 BD TEST SAMPLE\\video VC-1 BD SAMPLE.mkv", 249150757 ],
  [ " Lost in Translation 2003 BDRip 1080p AAC x264-tomcat12\\subs idx\\English.idx", 62582 ],
  [ " Lost in Translation 2003 BDRip 1080p AAC x264-tomcat12\\subs idx\\French.idx", 43725 ],
]

Desired Unordered List

<ul>
  <li>Lost in Translation 2003 BDRip 1080p AAC x264-tomcat12
    <ul>
      <li>Lost in Translation 2003 BDRip 1080p AAC x264-tomcat12.mp4 - (3450211337)</li>
    </ul>
    <ul>
      <li>video VC-1 BD TEST SAMPLE</li>
      <ul>
        <li>video VC-1 BD SAMPLE.mkv - (249150757)</li>
      </ul>  
    </ul>
    <ul>
      <li>subs idx
        <ul>
         <li>English.idx - (62582)</li>
         <li>French.idx - (43725)</li>
        </ul>  
      </li>
    </ul>
   </li>
</ul>

Just wanted to say thank you all for the guidance and help. I used the assistance to launch a tree view of the torrent files on my site: moviemagnet.net

tree view of torrent files

2 Answers 2

2

The following block will produce a hash that follows the above layout. It is not recursive, it goes to depth "1" only. I hope this is enough.

array = [ [ "Lost in Translation 2003 BDRip 1080p AAC x264-tomcat12\\Lost in Translation 2003 BDRip 1080p AAC x264-tomcat12.mp4", 3450211337 ],
  [ " Lost in Translation 2003 BDRip 1080p AAC x264-tomcat12\\video VC-1 BD TEST SAMPLE\\video VC-1 BD SAMPLE.mkv", 249150757 ],
  [ " Lost in Translation 2003 BDRip 1080p AAC x264-tomcat12\\subs idx\\English.idx", 62582 ],
  [ " Lost in Translation 2003 BDRip 1080p AAC x264-tomcat12\\subs idx\\French.idx", 43725 ]]
hash={}

array.each do |i|
  j=i.join(', ').split('\\').map(&:strip) # split at '//'
  k=j[0] 
  j.shift
  if hash[k].blank?
    hash[k]=j
  else
    l = hash[k].map{|v| v[0]} # check the first element of each item in the array
    p = l.index j[0]
    if p.nil? # see if current item already there
      hash[k] << j
    else
      j.shift
      hash[k][p][1] = Array(hash[k][p][1]) # if it's string, make it array
      hash[k][p][1] << j.join(" ")
    end
  end
end

# hash
{"Lost in Translation 2003 BDRip 1080p AAC x264-tomcat12"=>
    [
        "Lost in Translation 2003 BDRip 1080p AAC x264-tomcat12.mp4, 3450211337", 
        ["video VC-1 BD TEST SAMPLE", "video VC-1 BD SAMPLE.mkv, 249150757"], 
        ["subs idx", ["English.idx, 62582", "French.idx, 43725"]]
    ]
}
Sign up to request clarification or add additional context in comments.

5 Comments

I appreciate you taking your time to answer my question. I've spent a few hours trying to wrap my head around the second if else conditional using the index method, and it is finally makes sense. Thank you.
Sorry for my shorthand writing, I was low on time yet I couldn't resist a good problem. Please, feel free to ask if there is anything you wish for me to clear out.
Instead of k=j[0]; j.delete j[0] you should just do k = j.shift. (In addition to just being simpler, delete will delete all matching elements from the array, which could cause undesired behavior.)
Also hash[k].map{|v| v[0]} should just be hash.keys.
Ok for first comment, I'll replace from pc. But v is an array
2

The recursive method below should work for any number of levels. This code produces a hash, which you can use to easily format the text as desired.

Code

def doit(a)
   return a.first if a.size == 1
   sz = common_prefix_size(a)
   {a.first.first[0, sz]=>
     a.map { |b| [b.first[sz..-1], b.last] }
      .group_by { |b| b.first[0] }
      .values
      .map { |b| doit(b) } }
end

def common_prefix_size(a)
  i = 0
  loop do
    return i if (a.map { |b| b.first[i] }.uniq.size > 1) || a.first.first == nil
    i += 1
  end
end

Example

I've eliminated the leading space in all but the first element of "Ruby Array" (assuming they were typos).

a = [ [ "Lost in Translation 2003 BDRip 1080p AAC x264-tomcat12\\Lost in Translation 2003 BDRip 1080p AAC x264-tomcat12.mp4", 3450211337 ],
      [ "Lost in Translation 2003 BDRip 1080p AAC x264-tomcat12\\video VC-1 BD TEST SAMPLE\\video VC-1 BD SAMPLE.mkv", 249150757 ],
      [ "Lost in Translation 2003 BDRip 1080p AAC x264-tomcat12\\subs idx\\English.idx", 62582 ],
      [ "Lost in Translation 2003 BDRip 1080p AAC x264-tomcat12\\subs idx\\French.idx", 43725 ],
    ]

doit(a)
  #=> {"Lost in Translation 2003 BDRip 1080p AAC x264-tomcat12\\"=>
  #     [ ["Lost in Translation...tomcat12.mp4", 3450211337],
  #       ["video VC-1...SAMPLE.mkv", 249150757], 
  #       {"subs idx\\"=>
  #         [ ["English.idx", 62582],
  #           ["French.idx", 43725]
  #         ]
  #       }
  #     ]
  #   } 

Explanation

Consider first the helper method common_prefix_size(a). This takes an array of two-element arrays as an argument. The first element of each two-element array is a string. This method returns the largest integer n such that the first n characters of all the strings are equal. n can be between 0 and the size of the shortest string. Note that if s.size => n for a string s, s[m] => nil for m >= n.

For a above:

common_prefix_size(a) #=> 55

meaning the common string is:

a.first.first[0,55]
  #=> "Lost in Translation 2003 BDRip 1080p AAC x264-tomcat12\\"

doit returns a.first if a.size == 1, where a, its argument, is a subarray of the initial a, except the first so-many characters of each string have been removed, as explained below.

If a.size > 1, doit obtains sz = common_prefix_size to determine the sole key for the hash it will return, that key being:

a.first.first[0, sz]

It then maps a into an array for which the first element of each element is a string obtained by removing the first sz characters. The elements of the array obtained are then grouped on the first character of the first element of each (a string). (Making the elements of a to be two-element arrays certainly complicates this explanation. :-) ). This grouping is a hash, whose keys are the distinct first letters referred to above. Each value of this hash, a subarray of a after lopping off the beginning of the strings) is then passed to doit. Whew!

I appreciate that this explanation may require a second reading.

2 Comments

Thank you for your answer and explanation. After the initial read through, I knew this answer had to come from a professor. This is going to take a few hours to completely comprehend (which isn't a bad thing).
Yes, I was once a professor, but that was 40 years ago!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.