enum Matter {
AIR, STEAM, LAVA
}
static class Point {
public final int x, y, z;
private final Set<BiFunction<Point, Integer, Point>> makeNeighbor = Set.of(
(p, add) -> new Point(p.x + add, p.y, p.z),
(p, add) -> new Point(p.x, p.y + add, p.z),
(p, add) -> new Point(p.x, p.y, p.z + add)
);
public Point(int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
}
public boolean isNeighbor(Point c) {
return Math.abs(this.x - c.x) + Math.abs(this.y - c.y)
+ Math.abs(this.z - c.z) == 1;
}
public List<Point> makeNeighbours() {
return makeNeighbor.stream()
.flatMap(f -> IntStream.of(-1, 1).mapToObj(i -> f.apply(this, i)))
.collect(Collectors.toList());
}
}
static List<Point> findVoids(Point start, int[][][] cube) {
Deque<Point> stack = new ArrayDeque<>();
Set<Point> visited = new HashSet<>();
stack.push(start);
while (!stack.isEmpty()) {
Point cur = stack.pop();
visited.add(cur);
if (cube[cur.x][cur.y][cur.z] == Matter.AIR.ordinal()) {
cube[cur.x][cur.y][cur.z] = Matter.STEAM.ordinal();
for (Point neighbour : cur.makeNeighbours()) {
if (!visited.contains(neighbour) && isBelongCube(neighbour, cube)) {
stack.push(neighbour);
}
}
}
}
return IntStream.range(0, cube.length)
.mapToObj(x -> IntStream.range(0, cube[x].length)
.mapToObj(y -> IntStream.range(0, cube[x][y].length)
.mapToObj(z -> new Point(x, y, z))
)
)
.flatMap(it -> it.flatMap(Function.identity()))
.filter(p -> cube[p.x][p.y][p.z] == Matter.AIR.ordinal())
.collect(Collectors.toList());
}
static boolean isBelongCube(Point p, final int[][][] cube) {
return (p.x >= 0 && p.y >= 0 && p.z >= 0)
&& (p.x < cube.length && p.y < cube[0].length && p.z < cube[0][0].length);
}
static int[][][] makeCube(List<Point> points) {
int maxCoord = points.stream()
.flatMapToInt(p -> IntStream.of(p.x, p.y, p.z))
.summaryStatistics().getMax();
int[][][] cube = new int[maxCoord + 1][maxCoord + 1][maxCoord + 1];
points.forEach(p -> cube[p.x][p.y][p.z] = Matter.LAVA.ordinal());
return cube;
}
static long area(List<Point> points) {
return (points.size() * 6L) - IntStream.range(0, points.size())
.mapToObj(i -> IntStream.range(i, points.size())
.mapToObj(j -> points.get(i).isNeighbor(points.get(j)))
)
.flatMap(b -> b)
.filter(Boolean.TRUE::equals)
.count() * 2;
}