This is nearly the same question as this one, but in the opposite direction.
I know there's no FloatStream
in Java 8, and there're no many use cases for float[], but I have one:
Dealing with TriangleMesh
in JavaFX 3D, you have to provide an ObservableFloatArray
for the 3D coordinates of the vertices of the whole mesh.
As a result of some calculations I'll have all these coordinates in a List
, and to add all of them at once to the mesh, I'll call triangleMesh.getPoints().addAll()
, using one of the following methods:
- addAll(ObservableFloatArray src)
- addAll(float... elements)
- addAll(ObservableFloatArray src, int srcIndex, int length)
- addAll(float[] src, int srcIndex, int length)
where ObservableFloatArray
can be created using FXCollections.observableFloatArray()
, FXCollections.observableFloatArray(ObservableFloatArray array)
or FXCollections.observableFloatArray(float... values)
.
Let's say I have this pojo for each vertex:
private class Vertex {
private final float x;
private final float y;
private final float z;
public Vertex(float x, float y, float z){
this.x=x; this.y=y; this.z=z;
}
public float[] getCoordenates(){
return new float[]{x,y,z};
}
}
and after performing some calculations I have List<Vertex> listVertices
. I'll need to generate float[] arrayVertices
to finally call triangleMesh.getPoints().addAll(arrayVertices);
.
For now this is what I'm doing:
listVertices.forEach(vertex->triangleMesh.getPoints().addAll(vertex.getCoordenates()));
But this triggers the associated listener on every new vertex added to the observable array, and for high number of vertices this affects performance.
Should FloatStream
and flatMapToFloat()
exist, I'd do something like this:
float[] arrayVertices = listVertices.stream()
.map(vertex->FloatStream.of(vertex.getCoordenates()))
.flatMapToFloat(f->f).toArray();
triangleMesh.getPoints().addAll(arrayVertices);
like I actually do with a list of int[] for face indices:
int[] arrayFaces = listFaces.stream()
.map(face->IntStream.of(face.getFaceIndices()))
.flatMapToInt(i->i).toArray();
triangleMesh.getFaces().addAll(arrayFaces);
But as far as I know, there's no way using streams.
Thanks in advance for any possible solution involving streams.
Keep in mind that a Stream
defines an operation rather that a storage. So for most operations, using a float
provides only little benefit over double
values when CPU registers are used. There might be a theoretical improvement for operations that could be accelerated using SSE or GPU, but that’s not relevant here.
So you can use a DoubleStream
for that operation, the only thing you need is a collector capable of collecting a DoubleStream
into a float[]
array:
float[] arrayVertices = listVertices.stream()
.flatMapToDouble(vertex->DoubleStream.of(vertex.x, vertex.y, vertex.z))
.collect(FaCollector::new, FaCollector::add, FaCollector::join)
.toArray();
static class FaCollector {
float[] curr=new float[64];
int size;
void add(double d) {
if(curr.length==size) curr=Arrays.copyOf(curr, size*2);
curr[size++]=(float)d;
}
void join(FaCollector other) {
if(size+other.size > curr.length)
curr=Arrays.copyOf(curr, size+other.size);
System.arraycopy(other.curr, 0, curr, size, other.size);
size+=other.size;
}
float[] toArray() {
if(size!=curr.length) curr=Arrays.copyOf(curr, size);
return curr;
}
}
This supports parallel processing, however, for an operation that merely consist of data copying only, there is no benefit from parallel processing.
I don't think there's any way around the fact that you have to create one data structure (e.g. a double[]
or a List<Float>
) and then map it into a float[]
. (But maybe I am missing something.)
If you want to do this using a Stream
-like API, you can use a Collector
to do the mapping at the end:
import java.util.List;
import java.util.ListIterator;
import java.util.Random;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import javafx.scene.shape.TriangleMesh;
public class StreamFloatsTest {
public static void main(String[] args) {
// The following declaration works in Eclipse 4.4
// however it won't compile from the command line:
// Collector<Float, List<Float>, float[]> toFloatArray =
// This declaration works:
Collector<Float, ?, float[]> toFloatArray =
Collectors.collectingAndThen(Collectors.toList(), floatList -> {
float[] array = new float[floatList.size()];
for (ListIterator<Float> iterator = floatList.listIterator(); iterator.hasNext();) {
array[iterator.nextIndex()] = iterator.next();
}
return array ;
});
// Random vertex list for demo purposes:
Random rng = new Random();
List<Vertex> vertices = IntStream.range(0, 100)
.mapToObj(i -> new Vertex(rng.nextFloat(), rng.nextFloat(), rng.nextFloat()))
.collect(Collectors.toList());
float[] vertexArray = vertices.stream()
.flatMap(v -> Stream.of(v.getX(), v.getY(), v.getZ()))
.collect(toFloatArray);
TriangleMesh mesh = new TriangleMesh();
mesh.getPoints().addListener(obs -> System.out.println("mesh invalidated"));
mesh.getPoints().addListener((array, sizeChanged, from, to) -> System.out.println("mesh changed"));
mesh.getPoints().addAll(vertexArray);
}
public static class Vertex {
private final float x ;
private final float y ;
private final float z ;
public Vertex(float x, float y, float z) {
this.x = x ;
this.y = y ;
this.z = z ;
}
public float getX() {
return x;
}
public float getY() {
return y;
}
public float getZ() {
return z;
}
public float[] getCoordinates() {
return new float[] {x, y, z};
}
}
}