6 Replies Latest reply on Jan 13, 2017 6:29 AM by Peter Kahrel

    Flattening a 2-dimensional array

    TᴀW Adobe Community Professional & MVP

      I'd be interested to know what you think of the following line for "flattening" a 2-dimensional array.

       

      The problem is a familiar one: Sometimes InDesign returns an array of arrays and sometimes just a simple array. For instance, the result of findGrep() is sometimes an array, and sometimes an array of arrays. Likewise, I just got bitten by this for the findHyperlinks() method of a text object. I didn't realize it could return a 2-dimensional array, but sometimes it seems it does.

       

      In all these cases, I want to know that I've got a simple array that I can loop through and deal with each element, and I was thinking of the simplest, quickest way of turning a possible 2d array into a 1d array. So I came up with this:

      a = eval(a.toSource().replace("[[", "[").replace("]]", "]"));
      

       

      It simply converts the array to a string, removes any double-[[ and ]] and changes them to single brackets, then evals() the string back into an array.

       

      Do you think it's okay and safe? I guess if one of the elements of the array is a string containing "[[" or "]]" this will screw things up. But otherwise it should be fine, no?

       

      Ariel

        • 1. Re: Flattening a 2-dimensional array
          Marc Autret Level 4

          Hi Ariel,

           

          If an “array of arrays” is defined as anything having the form [ [a0, a1, a2…], [b0, b1, b2…], …, [z0, z1, z2…] ], then I don't see how your regex could flatten it. (?)

           

          Are you only talking about a singleton whose single element is an array?

           

          @+

          Marc

          • 2. Re: Flattening a 2-dimensional array
            Marc Autret Level 4

            (…)

             

            In the general case, that is, a being either an Array of Things or an Array of Arrays of Things, a simple flattening trick is:

             

            a = [].concat.apply([],a);
            

             

            @+

            Marc

            • 3. Re: Flattening a 2-dimensional array
              TᴀW Adobe Community Professional & MVP

              Marc Autret wrote:

               

              Are you only talking about a singleton whose single element is an array?

              Hi Marc,

               

              Thank you. No, I was hoping for a general solution. But of course you're right, my suggestion doesn't work -- but it worked for my test case, which was indeed as you describe.

               

              So (sticking to my approach), I think this should work?

               

              a = eval ("[" + (a.toSource().replace(/\[/g, "").replace(/\]/g, "")) + "]");
              

               

              ... well, that's no good. Running toSource on a simple insertionPoint gives something like this:

               

              resolve("/document[@id=1]//story[@id=941021]/insertion-point[874]")

               

              ... so clearly stripping out the brackets will destroy that.

               

              Oh well, it seemed a good idea at the time! 8-)

              • 4. Re: Flattening a 2-dimensional array
                TᴀW Adobe Community Professional & MVP

                Marc Autret wrote:

                 

                (…)

                 

                In the general case, that is, a being either an Array of Things or an Array of Arrays of Things, a simple flattening trick is:

                a=[].concat.apply([],a);

                 

                Well, like magic, it works! How it works, though, I do not know.

                 

                My reference says about apply(): "applies a method of an object, substituting another object for the current object."

                 

                But I fail to see what's going on here.

                 

                Well done!

                Ariel

                • 5. Re: Flattening a 2-dimensional array
                  Marc Autret Level 4

                  Hi again Ariel,

                   

                  > But I fail to see what's going on here.

                   

                  It takes a good understanding of JS to get the trick in depth, but let's try to dissect it:

                   

                  1. The method Array.prototype.concat supports as many arguments as desired, and each of these arguments can be either an Array or a simple item to be added to the result. That's the very key point. (The normalized algorithm of concat is described in the spec http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf#page=440)

                   

                  2. Then, given an array X and a set of arguments arg0, arg1…, argN whose each may indifferently be an array or a single item, the syntax X.concat(arg0, arg1…, argN) would return the full concatenation Xarg0arg1⊕…⊕argN—and this always works whatever the array-ness of arg_i.

                   

                  3. Note also that in the above expression the caller object, X, can be the empty array [] so the result would simply be the concatenation arg0arg1⊕…⊕argN.

                   

                  4. Now the problem can be put as follows: let a be the array [arg0, arg1…, argN] which you don't know whether its elements are either arrays or simple elements. (This array a is exactly the one of your original problem.) What would be great would be to write out the expression “[].concat(arg0, arg1…, argN)” since that would exactly return what you're looking after. But this expression is not literally available, because you only have a as an identifier, not as a developed set of arguments. So you need an additional trick:

                   

                  5. Given any function F and any array a, we can call F as a method (F), from a context obj, and supplying A's elements as formal arguments, using the syntax F.apply(obj, a). In easier words, the apply scheme used on F emulates the expression obj.F(arg0, arg1…, argN)” as if a's elements were taken as arguments. For deeper details on this point, see http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf#page=352

                   

                  6. Thus, using the empty array [] as the calling context (obj) and Array.prototype.concat as the called function (F) we can now see what happens with Array.prototype.concat.apply([], a). It mimicks the literal expression seen in point 4 and therefore solves our problem as pictured in point 3.

                   

                  7. Finally—and that's nothing but a refinement—the reference to the function Array.prototype.concat can be abbreviated [].concat, this is just a lazy shortcut pointing out to the prototyped method.

                   

                  Hence the expression:

                  [].concat.apply([], a)

                   

                   

                  Hope that helps a bit,

                  @+

                  Marc

                  • 6. Re: Flattening a 2-dimensional array
                    Peter Kahrel Adobe Community Professional & MVP

                    The [].concat.apply() method does not work with arrays embedded deeper than one level. That's no problem for Ariel's purposes, assuming that findGrep() never returns deeply embedded arrays, but in general the method is not general enough. For example, this script:

                     

                    a = [1,2,[3,4],5,[6,[7,8]],9];
                    b = [].concat.apply ([], a);
                    $.writeln (b.length);
                    $.writeln (b.join('\r'));
                    

                     

                    writes 8 in the console, not 9, as you might expect. When you do b.join('\r') you can see why:

                     

                    a = [1,2,[3,4],5,[6,[7,8]],9];
                    b = [].concat.apply ([], a);
                    $.writeln(b.join('\r'));
                    

                     

                    which prints

                     

                    1

                    2

                    3

                    4

                    5

                    6

                    7,8

                    9

                     

                    To flatten an array completely you need a function like this one:

                     

                    function flatten (list, flattened) {
                      for (var i = 0; i < list.length; i++) {
                        if (list[i] instanceof Array) {
                          flatten (list[i], flattened);
                        } else {
                          flattened.push (list[i]);
                        }
                      }
                      return flattened
                    }
                    
                    a = [1,2,[3,4],5,[6,[7,8]],9];
                    flat = flatten (a, []);
                    

                     

                    Which does return the expected output of length 9.

                     

                    Peter