2

Any suggestion to simplify the following code block using Java 8 features?

int[] ans = new int[2];
list.forEach(i -> {
    if (i > 0) {
        ans[0] += 1;
    } else if (i < 0) {
        ans[1] += 1;
    }
});

P.S. Not sure if I should post this here

Naman
  • 27,789
  • 26
  • 218
  • 353
Saikat
  • 14,222
  • 20
  • 104
  • 125
  • 1
    Are you looking for something like this https://stackoverflow.com/questions/49857232/use-java-lambda-instead-of-if-else ? – Naman Oct 13 '18 at 05:56

5 Answers5

4

If you don't want to count zeros, you code is as simple as it can get. If you wanted to count zeros as positive, however then you could shorten it to this.

int[] ans = new int[2];
for (int i : list) ans[i < 0 ? 1 : 0] += 1;
Leo Aso
  • 11,898
  • 3
  • 25
  • 46
  • @Eugene There is no `<<<` perhaps you meant `>>>` If you don't know the type of `i` you can use `i >>> -1` which works for `int` and `long`. – Peter Lawrey Oct 13 '18 at 09:43
  • 1
    @PeterLawrey ha! had to go to the specification to understand if this is specified or a hack, nice. thank u – Eugene Oct 14 '18 at 15:57
2
 ans[0] = (int)list.stream().filter(x -> x < 0).count();
 ans[1] = (int)list.stream().filter(x -> x > 0).count();

but I hardly think this is a simplification as such, your solution is already as simple as it can get.

In case you do want to count zeros, this could be simplified to:

 list.forEach(x -> ++ans[x >>> 31])
Eugene
  • 117,005
  • 15
  • 201
  • 306
1

I'd suggest

int[] ans = new int[2];
list.forEach(i -> ans[i >>> 31] += i==0 ? 0 : 1);

where i >>> 31 discards all but the sign bit (i.e., is the same as i<0 ? 1 : 0) and the second conditional handles zero.

I don't claim, it's really better than the original.

maaartinus
  • 44,714
  • 32
  • 161
  • 320
  • You can do it entirely branch-free with `.forEach(i -> ans[i >>> 31] += i>>>31|-i>>>31)` – Holger Oct 15 '18 at 12:57
  • @Holger Nice and simple. I guess, I can hope for my conditional being translated using `cmov`, but your expression can be even then faster because of the common subexpression. – maaartinus Oct 15 '18 at 17:03
  • 1
    @maaartinus that is *almost* the trick behind `Integer::signum` btw, very good answer still, even without this simplification...1+ – Eugene Oct 19 '18 at 16:03
1

To distinguish two cases like i > 0 and i < 0 we can use Stream.partition:

Map<Boolean, List<Integer>> partitioned = list.stream()
  .filter(i -> i != 0)
  .collect(Collectors.partitioningBy(i -> i > 0));
ans[0] = partitioned.get(true).size();
ans[1] = partitioned.get(false).size();

Is it simplified? At least it's still readable and easy to understand.

EDIT
Or as @saka1029 suggests:

Map<Boolean, Long> partitioned = list.stream()
  .filter(i -> i != 0)
  .collect(Collectors.partitioningBy(i -> i > 0, Collectors.counting()));
ans[0] = partitioned.get(true);
ans[1] = partitioned.get(false);

EDIT
And a further Stream solution which returns the desired array. But I would argue it's not simpler. So it's about to compare.

int[] ans = list.stream().filter(i -> i != 0).collect(
  () -> new int[2], 
  (arr, i) -> arr[i > 0 ? 0 : 1]++, 
  (l, r) -> {  l[0] += r[0]; l[1] += r[1]; });
LuCio
  • 5,055
  • 2
  • 18
  • 34
  • 1
    You can collect counts by `.collect(Collectors.partitioningBy(i -> i > 0, Collectors.counting()))` –  Oct 13 '18 at 08:32
  • @saka1029 Thank you for your suggestion. I included it in my answer. – LuCio Oct 13 '18 at 09:02
0

Here's a way that counts zeroes as well:

int[] ans = new int[3];
list.forEach(i -> ans[Integer.signum(i) + 1] += 1);

The ans array now holds 3 values: index 0 for negative count, index 1 for zero count and index 2 for positive count.

i.e. for the following input: [1, 2, 3, 0, -4, -5], the output would be: [2, 1, 3].


A more functional way, excluding zeroes:

Map<Integer, Long> bySignum = list.stream()
    .filter(i -> i != 0)
    .collect(Collectors.groupingBy(Integer::signum, Collectors.counting()));

Which produces this output: {-1=2, 1=3}.

fps
  • 33,623
  • 8
  • 55
  • 110
  • 1
    well, if you happen to know how `Integer::signum` is implemented, `i >> 31 | -i >>> 31` - which is sort of crazy how smart it is, you could say `ans[i >>> 31] = i >>> 31 | -i >>> 31` as Holger has shown above. The explanation for this is interesting too. if `i` is negative, `i >>> 31` will give `1` (index), if `i` is positive `i >>> 31` will give `0`. that is the index computation. and the part `i >>> 31 | -i >>> 31` is even funner!! – Eugene Oct 19 '18 at 17:03
  • @Eugene Well, yeah, it's clever, I guess. I wouldn't sacrifice code readability for this, it's OK if we're just having fun (this is the modern equivalent of playing crosswords), And it is `ans[i >>> 31] += i >>> 31 | -i >>> 31` (you forgot the `+`sign) – fps Oct 19 '18 at 17:31