DEV Community

Discussion on: Reinventing ruby flatten method

Collapse
 
johncip profile image
jmc • Edited

I've been enjoying Exercism too, and am getting close to done with the Clojure track.

My one gripe is that I've unfortunately noticed that the reviewers won't help you if you're on independent mode (which makes sense, but the site claims that you can have up to one solution reviewed in that mode, even though in practice the reviewers seem to ignore them).

I like your concat solution, and obviously it's better not to mutate the input array. But FYI if you ever want to push one array onto another, you can use the splat operator:

> a = []; b = [1, 2]
> a.push(*b)
=> [1, 2]

My solution for flatten-array on the Clojure track used reduce for the outer loop and into (which is like a.push(*b) in Ruby except that it doesn't modify the original). But otherwise it's similar to what you have.

I was thinking that one could also write something iterative that uses a stack (perhaps of array indices), but didn't bother to try it on this one.

Also, if you're interested, out of curiosity I looked up what they do in Rubinius. It's a mostly-complete Ruby implementation written in Ruby itself and when writing Ruby I occasionally found their code useful to glance at. Theirs appears to also be recursive, and besides handling a max_depth argument, they also mention "checking for recursive structures" which I assume is for cycle detection, but I'm not 100% certain.

github.com/rubinius/rubinius/blob/...

Collapse
 
kodekage profile image
Prosper Opara

When you say Independent Mode do you mean the Personal Edition mode?

From my experience, i mostly come up with my own solution to the task, and the reviewers mostly point me tho new methods that can make my solution cleaner.

I totally appreciate your pointing out splat operator, using it with push() makes push work like concat

I was thinking that one could also write something iterative that uses an explicit stack (perhaps of array indices) if you absolutely had to avoid recursion, but I've never bothered to try.

I did this in one of my solutions, but you'd be doing a lot of deeply nested iterations, because the array you want to flatten might for example be 5 dimension while you might have catered for only 3-4 dimensions.

Collapse
 
johncip profile image
jmc • Edited

Also I don't remember seeing a "Personal Edition" specifically but I know that when I picked a track on Exercism I was asked to choose between mentored or independent. Maybe it's the same thing?

Thread Thread
 
kodekage profile image
Prosper Opara

I checked out the modes for using exercism:

Mentors not helping out in the Independent mode is just what it's intended for.

But opting for the mentored mode is really the best way to get maximum benefit from the platform (at least for a beginner)

Collapse
 
johncip profile image
jmc • Edited

I did this in one of my solutions, but you'd be doing a lot of deeply nested iterations, because the array you want to flatten might for example be 5 dimension while you might have catered for only 3-4 dimensions.

Very true, the problem with explicit nested loops is that you can run out of loops :P

That's where a stack helps: I was specifically picturing a stack of indices, for instance [1, 5, 3], which means you'd next read the element at arr[1][5][3].

If index 3 of the grandchild holds the final element, then after reading it, you'd pop the 3 off the stack, and increment the new tail: the stack becomes [1, 6] and you next read arr[1][6].

And if that element happens to be an array, then you push a new index onto the stack: [1, 6, 0] (it grows on the tail end).

Hopefully I'm explaining myself well. In general, walking a nested thing requires "bookmarking" your place in the outer structures, in order to return to them when an inner structure is done. And for that you need a data structure that grows and shrinks as the nesting level changes. In recursive code, the call stack does it for you.

Thread Thread
 
kodekage profile image
Prosper Opara

I honestly don't understand this yet.

Thread Thread
 
johncip profile image
jmc • Edited

Hey, thanks for letting me know. Here's a code example in JS, hopefully some code will be clearer than a description of code:

// think of each element in the nested structure as having a list
// of indices. (sort of like "street, city, state, country" in
// a real address.)

// given arr = [["a", ["b"]], "c"]:
//   "a" is at [0, 0]
//   "b" is at [0, 1, 0]
//   "c" is at [1]
//
// note that the numbers correspond to how you'd access them:
// [0, 1, 0] -> arr[0][1][0] -> "b"

// we're going to create these lists, using an array that we'll modify
// as we go. it will grow or shrink as we enter or exit subarrays.
// we'll increment the last number to move forward in the input.

function flatten (arr) {
  const result = []
  const stack = [0]

  while (stack.length) {
    // get the current value
    let el = arr; stack.forEach(i => el = el[i])

    // new subarray; grow stack
    if (el instanceof Array) {
      stack.push(0)

    // subarray ended; shrink stack
    } else if (el === undefined) {
      stack.pop()
      stack[stack.length - 1]++

    // non-array; add to result
    } else {
      result.push(el)
      stack[stack.length - 1]++
    }
  }

  return result
}

console.log(flatten([[1, 2], 3, [[4, [5]]]])) // [1, 2, 3, 4, 5]

The recursive solution is cleaner, of course. And this will skip items if the input contains undefined as an element anywhere, but you could avoid that by checking the subarray lengths.