|
import java.util.*; |
|
import java.util.function.*; |
|
import java.util.stream.*; |
|
|
|
class IdenticalPairs { |
|
|
|
public enum Approach { |
|
TRIANGULAR_FORMULA_STREAM, |
|
TRIANGULAR_FORMULA_LOOP, |
|
COUNTING_NESTED_LOOP, |
|
ONE_LINE, |
|
} |
|
|
|
public static final int MAX_IDENTICAL_PAIRS = 1_000_000_000; |
|
|
|
|
|
// result is a map of distinct values and the counts of their occurrences |
|
// we actually only need the values, we can ignore the keys after we have the distinct counts |
|
public static final Function< int[], Collection< Long > > COUNT_DISTINCT = |
|
in -> IntStream.of( in ) |
|
.boxed().collect( |
|
Collectors.groupingBy( |
|
i -> i, Collectors.counting() ) ).values(); |
|
|
|
/* |
|
* |
|
* |
|
*/ |
|
public static final IntUnaryOperator TRIANGULAR_FORMULA = x -> x * ( x + 1 ) / 2; |
|
|
|
/* |
|
* the triangular formula is based on zero-index sequences, i.e. 0=0, 1=0, 2=1, 3=3, 4=6, 5=10, 6=15... |
|
* since we're getting the counts via a count-distinct, the occurrences of each distinct number will not show up |
|
* in the counts unless there is at least 1. So we have to subtract 1 to align with the triangular formula. |
|
* i.e. if there are zero of a number in the input, we won't have a count for it |
|
*/ |
|
public static final IntUnaryOperator SUBTRACT_ONE = x -> x - 1; |
|
|
|
|
|
/* |
|
* combining the subtraction and the formula into one operation ( subtraction first ) that can be applied to a count |
|
*/ |
|
public static final IntUnaryOperator IDENTICAL_PAIRS_FROM_OCCURRENCE_COUNT = TRIANGULAR_FORMULA.compose( SUBTRACT_ONE ); |
|
|
|
/* |
|
* here's a non-formula version that uses a loop to calculate the pairs |
|
*/ |
|
public static final IntUnaryOperator IDENTICAL_PAIRS_FROM_OCCURRENCE_COUNT_VIA_LOOP = distinctCount -> { |
|
|
|
int pairs = 0; |
|
int count = 2; |
|
|
|
while ( count <= distinctCount ) { |
|
pairs = pairs + count - 1; |
|
count++; |
|
} |
|
return pairs; |
|
}; |
|
|
|
public int solution( int[] A ) { |
|
|
|
if ( A.length < 2 ) // can't have a pair from 0 or 1 elements; |
|
return 0; |
|
|
|
Approach approach = Approach.TRIANGULAR_FORMULA_LOOP; |
|
|
|
switch ( approach ) { |
|
|
|
case ONE_LINE: { |
|
|
|
// explicit, not using any of the Function constants |
|
|
|
return Math.min( MAX_IDENTICAL_PAIRS, |
|
|
|
IntStream.of( A ).boxed() |
|
.collect( Collectors.groupingBy( i -> i, Collectors.counting() ) ).values().stream() |
|
.mapToInt( Long::intValue ) |
|
.map( x -> x - 1 ) |
|
.map( x -> x * ( x + 1 ) / 2 ) |
|
.sum() |
|
); |
|
|
|
|
|
} |
|
|
|
case TRIANGULAR_FORMULA_STREAM: { |
|
|
|
// still explicit but using Function constants for readability and clarity |
|
|
|
return Math.min( MAX_IDENTICAL_PAIRS, |
|
|
|
COUNT_DISTINCT.apply( A ).stream() |
|
.mapToInt( Long::intValue ) |
|
.map( IDENTICAL_PAIRS_FROM_OCCURRENCE_COUNT ) |
|
.sum() |
|
); |
|
|
|
} |
|
|
|
case TRIANGULAR_FORMULA_LOOP: { |
|
|
|
// breaking things up a bit.. getting the distinctCounts first and looping over them |
|
// can optimize a bit here, if the ceiling is reached before we've handled all the counts, |
|
// we can exit early |
|
|
|
int retval = 0; |
|
Collection< Long > distinctCounts = COUNT_DISTINCT.apply( A ); |
|
|
|
for ( long distinctCount : distinctCounts ) { |
|
if ( retval > MAX_IDENTICAL_PAIRS ) |
|
return MAX_IDENTICAL_PAIRS; // exit early if the ceiling is reached |
|
|
|
int countForKey = Math.toIntExact( distinctCount ); |
|
retval += IDENTICAL_PAIRS_FROM_OCCURRENCE_COUNT.applyAsInt( countForKey ); |
|
|
|
|
|
} |
|
return Math.min( MAX_IDENTICAL_PAIRS, retval ); |
|
|
|
} |
|
|
|
case COUNTING_NESTED_LOOP: { |
|
|
|
// breaking things up more.. accumulating the distinct counts with a loop instead of a stream |
|
// lets us check whether we've reached the ceiling earlier. |
|
// it's cheap to do so if we're using the triangular formula (i.e. not the loop version); |
|
|
|
int retval = 0; |
|
Map< Integer, Integer > distinctCounts = new HashMap<>(); |
|
|
|
for ( int i = 0; i < A.length; i++ ) { |
|
int count = distinctCounts.getOrDefault( A[ i ], 0 ); |
|
distinctCounts.put( A[ i ], ++count ); |
|
|
|
if ( IDENTICAL_PAIRS_FROM_OCCURRENCE_COUNT.applyAsInt( retval + count ) > MAX_IDENTICAL_PAIRS ) |
|
return MAX_IDENTICAL_PAIRS; // exit early if the ceiling is reached |
|
|
|
} |
|
|
|
for ( int distinctCount : distinctCounts.values() ) { |
|
if ( retval > MAX_IDENTICAL_PAIRS ) |
|
return MAX_IDENTICAL_PAIRS; |
|
|
|
retval += IDENTICAL_PAIRS_FROM_OCCURRENCE_COUNT.applyAsInt( distinctCount ); |
|
} |
|
return Math.min( MAX_IDENTICAL_PAIRS, retval ); |
|
|
|
} |
|
default: { |
|
|
|
return 0; |
|
} |
|
|
|
} |
|
} |
|
|
|
|
|
} |