Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is There a More Efficient Java 8 Stream approach for finding the index in an int[]?

Based upon a BlackJack Question, I got to wondering about how to indicate all of the winning hands. The original question simply asked, in effect, about the maximum of two numbers not greater than 21. So a method like

public int blackjack(int a, int b);

However, what if one wished to return all of the winning hands (assuming the location in the input array was a seat at a table), so a signature such as:

/**
 * returns an array indicate the index in the specified hands that
 * correspond to the winning locations. Will return an empty array if
 * there are no winners. The length of the returned array is how many
 * winning hands there were
 * @param hands The total for each hand, where the index is the seat
 * @return the index/"seat" where a winning hand was found; may return
 *    an empty array
 */
public int[] blackjack(int[] hands) { ... }

So based upon input data such as (just using 3 "players" at "seats" 0, 1, 2):

{ 17, 15, 23 }
{ 23, 25, 22 }
{18, 16, 18 }
{16, 21, 20}

I would expect output along the lines of:

Hands: [17, 15, 23] has winners at [0]
Hands: [23, 25, 22] has no winners
Hands: [18, 16, 18] has winners at [0, 2]
Hands: [16, 21, 20] has winners at [1]

In times past, I would have iterated the hands[] array, found the maximum that was <= 21, and then iterated again finding each index that was equal to the maximum. So something like this:

public static int[] blackjackByIteration(int[] hands)
{
    int max = 0;
    int numAtMax = 0;
    for (int i = 0; i < hands.length; ++i) {
        if (hands[i] <= 21 && hands[i] > max) {
            max = hands[i];
            numAtMax = 1;
        }
        else if (hands[i] == max) {
            ++numAtMax;
        }
    }

    int[] winningSeats = new int[numAtMax];

    int loc = 0;
    for (int i = 0; i < hands.length; ++i) {
        if (hands[i] == max) {
            winningSeats[loc++] = i;
        }
    }

    return winningSeats;
}

However, I was wondering if there was a more efficient way to implement it via streams. I am cognizant that using Lambdas is not the solution to all problems. I believe, if I have read correctly, that is not possible to find the index of an int[] array directly, so the approach must rely upon using a List<Integer>, as suggested in the question referenced above.

I made an initial solution using Streams, but was wondering if there was a more efficient approach. I fully admit my understand of streams is limited.

public static int[] blackjackByStreams(int[] hands)
{
    // set to an empty array; no winners in this hand
    int[] winningSeats = new int[0];

    // get the maximum that is <= 21
    OptionalInt oi = Arrays.stream(hands).filter(tot -> tot <= 21).max();

    // if there are any hands that are <= 21
    if (oi.isPresent()) {
        // have to make a list (?) 
        List<Integer> list = Arrays.stream(hands)
                                    .boxed()
                                    .collect(Collectors.toList());

        // find the location(s) in the list
        winningSeats = IntStream.range(0, list.size())
                      .filter(i -> list.get(i) == oi.getAsInt())
                      .toArray();
    }

    return winningSeats;
}

The two approaches return the same data, so it is not a question of functionality per se. Rather, is there any way to make the blackjackByStreams better? Especially, is there a way to eliminate the creation of the List<Integer> list?

Edit: I did read this question here, in which one answer suggested creating a custom collector. Not sure if that would be the only alternative approach.

Thank you for providing any insight.

like image 792
KevinO Avatar asked Apr 25 '16 20:04

KevinO


1 Answers

You're missing the simple solution when you have found the maximum element. Just create a Stream over the indexes of the array directly instead of having an intermediate list:

public static int[] blackjackByIteration(int[] hands) {
    OptionalInt oi = Arrays.stream(hands).filter(i -> i <= 21).max();
    if (oi.isPresent()) {
        int value = oi.getAsInt();
        return IntStream.range(0, hands.length).filter(i -> hands[i] == value).toArray();
    }
    return new int[0];
}
like image 158
Tunaki Avatar answered Nov 03 '22 20:11

Tunaki