Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
342 views
in Technique[技术] by (71.8m points)

java - Convert ArrayList into 2D array containing varying lengths of arrays

So I have:

ArrayList<ArrayList<String>> 

Which contains an x number of ArrayLists which contain another y number of Strings.. To demonstrate:

Index 0:
  String 1
  String 2
  String 3
Index 1:
  String 4
Index 2:
Index 3:
  String 5
  String 6

Where index refers to the array index containing a string.

How can I transform this into a 2D array which looks like:

{{String1, String2, String3},{String4}, {}, {String5, String6}}

Thank you so much.

Question&Answers:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Welcome to a world with Java 8!

It only took me all night with no sleep to learn what was needed to write this one freaking line of code. I'm sure it is already out there somewhere but I couldn't find it. So I'm sharing my hours and hours of research, enjoy. Woot!

Assuming:

ArrayList<ArrayList<String>> mainList = new ArrayList<ArrayList<String>>();
// populate this list here

(Or, rather, in Java 8:

ArrayList<ArrayList<String>> mainList = new ArrayList();
//Populate

)

Then all you need is:

String[][] stringArray = mainList.stream().map(u -> u.toArray(new String[0])).toArray(String[][]::new);

Bam! One line.

I'm not sure how fast it is compared to the other options. But this is how it works:

  1. Take a stream of the mainList 2D ArrayList. This stream is a bit like a Vector hooked up with a LinkedList and they had a kid. And that kid, later in life, dosed up on some NZT-48. I digress; mainList.stream() is returning a stream of ArrayList<String> elements. Or in even geekier speak: mainList.stream() returns a Stream<ArrayList<String>>, sorta.

  2. Call the .map function on that stream which will return a new stream with contents that match a new type specified by the parameters passed into map. This map function will covert each element in our stream for us. It has a built in foreach statement. In order to accomplish this; the map function takes a lambda expression as its parameter. A Lambda expression is like a simple inline one-line function. Which has two data types gettin' Jiggy wit it. First is the type of data in the stream upon which it was called (mainList.stream()). The next type is the type of data it will map it out to, which is in the right half of the lambda expression: u -> u.toArray(new String[0]). Here u is an identifier you choose just like when using a foreach statement. The first half declares this like so: u ->. And like a foreach, the variable u will now be each element in the stream as it iterates through the stream. Thus, u is of the data type that the elements of the original stream are because it is them. The right half of the Lambda expression shows what to do with each element: u.toArray(new String[0]). With the results being stored in their rightful place in a new stream. In this case we convert it to a String[].. because after all, this is a 2D array of String.. or rather from this point in the code, a 1D array of String[] (string arrays). Keep in mind that u is ultimately an ArrayList. Note, calling toArray from an ArrayList object will create a new array of the type passed into it. Here we pass in new String[0]. Therefore it creates a new array of type String[] and with length equal to the length of the ArrayList u. It then fills this new array of strings with the contents of the ArrayList and returns it. Which leaves the Lambda expression and back into map. Then, map collects these string arrays and creates a new stream with them, it has the associated type String[] and then returns it. Therefore, map returns a Stream<String[]>, in this case. (Well, actually it returns a Stream<Object[]>, which is confusing and needs conversion, see below)

  3. Therefore we just need to call toArray on that new stream of arrays of strings. But calling toArray on a Stream<Object[]> is a bit different than calling it on an ArrayList<String>, as we did before. Here, we have to use a function reference confusing thing. It grabs the type from this: String[][]::new. That new function has type String[][]. Basically, since the function is called toArray it will always be an [] of some sort. In our case since the data inside was yet another array we just add on another []. I'm not sure why the NZT-48 wasn't working on this one. I would have expected a default call to toArray() would be enough, seeing that its a stream and all. A stream thats specifically Stream<String[]>. Anyone know why map actually returns a Stream<Object[]> and not a stream of the type returned by the Lambda expression inside?

  4. Now that we got the toArray from our mainList stream acting properly. We can just dump it into a local variable easy enough: String[][] stringArray = mainList.stream...

Convert 2D ArrayList of Integers to 2D array of primitive ints

Now, I know some of you are out there going. "This doesn't work for ints!" As was my case. It does however work for "Ents", see above. But, if you want a 2D primitive int array from a 2D ArrayList of Integer (ie. ArrayList<ArrayList<Integer>>). You gotta change around that middle[earth] mapping. Keep in mind that ArrayLists can't have primitive types. Therefore you can't call toArray on the ArrayList<Integer> and expect to get a int[]. You will need to map it out... again.

int[][] intArray = mainList.stream().map(  u  ->  u.stream().mapToInt(i->i).toArray()  ).toArray(int[][]::new);

I tried to space it out for readability. But you can see here that we have to go through the same whole mapping process again. This time we can't just simply call toArray on the ArrayList u; as with the above example. Here we are calling toArray on a Stream not an ArrayList. So for some reason we don't have to pass it a "type", I think its taking brain steroids. Therefore, we can take the default option; where it takes a hit of that NZT-48 and figures out the obvious for us this [run]time. I'm not sure why it couldn't just do that on the above example. Oh, thats right.... ArrayLists don't take NZT-48 like Streams do. Wait... what am I even talking about here?

Annnyhoow, because streams are sooo smart. Like Sheldon, we need a whole new protocol to deal with them. Apparently advanced intelligence doesn't always mean easy to deal with. Therefore, this new mapToInt is needed to make a new Stream which we can use its smarter toArray. And the i->i Lambda expression in mapToInt is a simple unboxing of the Integer to int, using the implicit auto-unboxing that allows int = Integer. Which, now, seems like a dumb trivial thing to do, as if intelligence has its limits. During my adventure learning all this: I actually tried to use mapToInt(null) because I expected a default behavior. Not an argument!! (cough Sheldon cough) Then I say in my best Husker valley girl accent, "Afterall, it is called mapToInt, I would guess that, like, 84% of the time (42x2) it will be, like, passed i->i by, like, everyone, so like, omgawd!" Needless to say, I feel a bit... like.. this guy. I don't know, why it doesn't work that way.

Well, I'm red-eye and half delirious and half asleep. I probably made some mistakes; please troll them out so I can fix them and let me know if there is an even better way!

PT


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

1.4m articles

1.4m replys

5 comments

57.0k users

...