Omar Ham
Omar Ham

Reputation: 150

How to filter and map a 2D IntArray into a Set using streams

I have a 2D IntArray which represent a game board where -1 means a blank space, and some value grater or equals to 0 means that cell belongs to some player. Something like below (-1 are represented by dots(.))

. . . . . 
1 0 . . 2
0 . 1 3 3
4 3 . . 0

I want to get a Set with the location of cells already occupied by any player. Something like this:

[Cell{1,0}, Cell{1,1}, ..., Cell{3,4}]

I know the first approach would be iterate over the 2D array:

val set = HashSet<Cell>();
for(row in 0 until HEIGHT){
    for (col in 0 until WIDTH){
        if(board[row][col] >= 0)
            set.add(Cell(row, col))
    }
}

But... is it somehow more efficient if I use streams? could it be achieved in less code and more efficiently?

IntStream.range(0, HEIGHT)
            .mapToObj { row -> IntStream.range(0, WIDTH)
                    .filter{ col -> board[row][col] >= 0}
                    .mapToObj { col -> Cell(row, col) } }
            .flatMap { point -> point }
            .collect(Collectors.toSet())

Upvotes: 2

Views: 1052

Answers (2)

msrd0
msrd0

Reputation: 8390

First of all, it is recommended to use the Kotlin standard library and not Java streams. With the Kotlin standard library, s1m0nw1 gave a good solution where I'd only replace the flatMap with a flatMapTo call to avoid creating a list that is later discarded:

val set = HashSet<Cell>()
(0 until board.size).flatMapTo(set) { row ->
    (0 until board[row].size).filter { col ->
        board[row][col] >= 0
    }.map { col ->
        Cell(row, col)
    }
}

If you really need a Java8 stream solution, go with the following code:

fun IntRange.stream() : Stream<Int>
    = StreamSupport.stream(spliterator(), false)

val set = (0 until board.size).stream().flatMap { row ->
    (0 until board[row].size).stream().filter { col ->
        board[row][col] >= 0
    }.map { col ->
        Cell(row, col)
    }
}.collect(Collectors.toSet())

As of performance, both solutions given here should be quite similar (I haven't tested it though). However, Java8 streams allow you do operations in parallel (see StreamSupport.stream). This is using some thread pool that does all the magic for you. Kotlin, as far as I know, does not offer any parallel operations out of the box, but it has Coroutines that are a lot faster than Java threads - but come together with more code on your side.

Upvotes: 1

s1m0nw1
s1m0nw1

Reputation: 81989

This can be made a bit more Kotlin-natural if the Java streams are replaced by ranges.

val cells = (0 until board.size).flatMap { row ->
    (0 until board[row].size)
            .filter { col -> board[row][col] >= 0 }.map { Cell(row, it) }
}.toSet()

Upvotes: 1

Related Questions