• Global community
    • Language:
      • Deutsch
      • English
      • Español
      • Français
      • Português
  • 日本語コミュニティ
    Dedicated community for Japanese speakers
  • 한국 커뮤니티
    Dedicated community for Korean speakers
Exit
0

Flattening a 2-dimensional array

People's Champ ,
Jan 12, 2017 Jan 12, 2017

Copy link to clipboard

Copied

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

TOPICS
Scripting

Views

1.1K

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines

correct answers 1 Correct answer

Guide , Jan 12, 2017 Jan 12, 2017

(…)

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

Votes

Translate

Translate
Guide ,
Jan 12, 2017 Jan 12, 2017

Copy link to clipboard

Copied

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

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Guide ,
Jan 12, 2017 Jan 12, 2017

Copy link to clipboard

Copied

(…)

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

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
People's Champ ,
Jan 12, 2017 Jan 12, 2017

Copy link to clipboard

Copied

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

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Guide ,
Jan 12, 2017 Jan 12, 2017

Copy link to clipboard

Copied

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

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jan 13, 2017 Jan 13, 2017

Copy link to clipboard

Copied

LATEST

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 instanceof Array) {

      flatten (list, flattened);

    } else {

      flattened.push (list);

    }

  }

  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

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
People's Champ ,
Jan 12, 2017 Jan 12, 2017

Copy link to clipboard

Copied

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-)

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines