2

I have an array of strings like this, each string is separated by comma. They can be in any order. I would like to build a nested hash out of it. If an item already exists, then subsequent elements should be nested. Example:

["a", "a,b", "d", "d,e", "d,e,f", "a,b,foobar"]

I want the output to be -

  {
    a => {
      b => foobar
    },
    d => {
      e => f
    }
  }

I was thinking to iterate over each element, then split it on , and add to a temporary Hash in some recursive way, but I can't figure out how to get them nested.

6
  • Where did f come from in the result? Is there supposed to be a "d,e,f" in the input, perhaps? Commented May 3, 2022 at 18:14
  • yep, thats right, sorry, updated Commented May 3, 2022 at 18:17
  • Are they guaranteed to be in order? Commented May 3, 2022 at 18:25
  • nope, it can be any string, input. Basically, if a letter/string from the split (",") was already "seen" before (of the temp hash), then all the next elements should be nested under it Commented May 3, 2022 at 18:26
  • You need to include quotes in your example's desired result. For example, "a"=>{"b"=>"foobar"}. Commented May 3, 2022 at 18:39

2 Answers 2

2

First, a function to loop through the array and split those strings into an array which is much easier to work with.

def nest_array(array)
  return array.each_with_object({}) do |string, hash|
    values = string.split(/,/)

    do_nesting(values, hash)
  end
end

Then a recursive function to deal with each individual entry. ['a'] becomes { 'a' => nil }, ['a', 'b'] becomes { 'a' => 'b' } and ['a', 'b', 'c'] recurses to make a new hash from ['b', 'c'].

def do_nesting(values, hash)
  if values.size <= 2
    hash[values[0]] = values[1]
  else
    hash[values.shift] = do_nesting(values, {})
  end

  return hash
end
Sign up to request clarification or add additional context in comments.

1 Comment

@CarySwoveland Explicit returns make it clear what's supposed to be returned, that's one where I'm happy to disagree with the style guide. The parsing is a placeholder, I expect the real input to be complicated.
1

This should get you started.

def hashify(arr)
  # Get the first character from everything and make the hash.
  prefixes = arr.group_by { |x| x[0] }

  prefixes.transform_values do |inner|
    # Exclude the value that's just "a", i.e. that just shows the key
    # and no values inside.
    values = inner.map { |x| x[2..] }.grep_v(nil)
    # Recursively hashify the subvalues.
    hashify values
  end

end

Given the input

["a", "a,b", "d", "d,e", "d,e,f"]

This will produce

{"a"=>{"b"=>{}}, "d"=>{"e"=>{"f"=>{}}}}

Note that this isn't exactly what you want. Namely, "b" has been replaced with {"b" => {}} and the same for "f". It's not yet clear how you want to do that transformation. For instance, if the input is

["a,b", "a,c"]

Then what should the key a map to in the resulting hash? In my function above, it'll map to a hash {"b" => {}, "c" => {}}. It's up to you if this makes sense, or if you want to take the "first" encountered value, or perhaps make an array of all of them. Since that's not specified in the question, I'll leave that as an exercise to the reader.

2 Comments

this is actually close! thanks. I think i should have used a better example, since the string can be a word or a letter. for isntance ["foo", "foo,bar", "foo,bar,bazz"]. I am trying to see if i can work with what you have
Yeah, in that case, your idea with split should fix it right up, but the recursion should still basically be the same.

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.