问题
I have a two dimensional integer array. Row and Column information (locations of numbers) is important for me. So, I don't want to sort an array (matrix actually). How can I find the highest 5 value from this two dimensional array?
Here is my code:
for (int row = 0; row < matirx.length; row++) {
for (int col = 0; col < matirx[row].length; col++) {
if (matirx[row][col] > maxValue) {
maxValue = matirx[row][col];
}
}
}
回答1:
First, I went for a streams solution that is very similar to other Answers. I didn't like the boxing and unboxing variations, but since IntStream
doesn't have a fancy method that makes sorting with a Comparator
straight out of the box, the IntStream
has to be converted into a Stream
in order to sort the values in reverse order. I didn't think it was important to return an int[]
array, since we're only really interested in the values.
public static Integer[] streamIt(int[][] matrix, int n){
Integer[] result =
Arrays.stream(matrix) // stream the arrays
// This is the same as using .flatMaptoInt(..) and then .boxed()
.flatMap(a -> Arrays.stream(a) // stream the array in arrays
.mapToObj(i -> Integer.valueOf(i))) // turn the ints into Integers
.sorted(Comparator.reverseOrder()) // sort by higest values
.limit(n) // only pick n
.toArray(i -> new Integer[i]); // put then in Integer array
return result;
}
If you want them in an int[]
array instead, look at the Answer by shadow.sabre that uses mapToInt()
do to that.
While the streams solution is very neat and clean looking, I felt that the problem was really just to get the set of highest values, so inserting them into a standard java sorted Set
made sense to me. I start off by inserting the values into the set until there are 5 elements in there. Then I check to see if the new value is higher than the lowest value, and if so, I just remove the lowest value while inserting the new one. Finding the lowest value is easy when using TreeSet
as it's a sorted set.
The trick is to also check that the new value isn't already in the set. If there's already 5, 4, 3, 2, 1 in the set, and the new value is 5, then I don't want to remove the lowest value 1, since adding the new value wouldn't actually add any new elements to the Set. Remember a Set
cannot contain duplicate values:
public static Set<Integer> useSet(int[][] matrix, int n){
TreeSet<Integer> max = new TreeSet<>(Comparator.<Integer>naturalOrder().reversed());
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[i].length; j++) {
// Keep adding values until there's n elements in the Set
if (max.size() < n) {
max.add(matrix[i][j]);
} else {
// if the new value is higher than the lowest value
// ..and the new values isn't already there.
if (max.last() < matrix[i][j] && !max.contains(matrix[i][j])) {
max.pollLast();
max.add(matrix[i][j]);
}
}
}
}
return max;
}
Note that this solution obviously never contains the same values, but always the top distinct ones.
Looking at the set solution it was easy to add the additional functionality of keeping track of where in the matrix the values were found. I created a class, Element
, to contain the value and its location. Every element in the matrix that's to be inserted into the TreeSet
is created as an Element
.
The Element
needs to either implement Comparable
or the TreeSet
has to be initialized with a Comparator
in order to sort the elements. This example of Element
has both, and I just used the static Comparator
in the implementation of compareTo(Element that)
to make it a Comparable<Element>
. Normally you'd implement the class with private fields using getters to fetch the values, but for this purpose is seemed a little verbose. Making the fields final
also ensures the class is immutable so I have no scruples about it.
Since the comparison is done using both the value and the location, every element from the matrix will be distinct:
class Element implements Comparable<Element> {
final int value;
final int x;
final int y;
static Comparator<Element> comparator =
Comparator.comparing((Element e) -> e.value)
.thenComparing((Element e) -> e.x)
.thenComparing((Element e) -> e.y)
.reversed();
Element(int value, int x, int y) {
this.value = value;
this.x = x;
this.y = y;
}
public int compareTo(Element that){
return comparator.compare(this, that);
}
public String toString(){
return value + " at [" + x + "][" + y + "]";
}
}
If the Element
didn't implement the Comparable
interface, this would be the initialization of the TreeSet
:
TreeSet<Element> maxElement = new TreeSet<>(Element.comparator);
But since Element
does implement the Comparable
interface, the set implementation can be initialized without it:
public static Set<Element> useSetElements(int[][] matrix, int n){
TreeSet<Element> maxElement = new TreeSet<>();
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[i].length; j++) {
if (maxElement.size() < n) {
maxElement.add(new Element(matrix[i][j],i,j));
} else {
if (maxElement.last().value < matrix[i][j]) {
maxElement.pollLast();
maxElement.add(new Element(matrix[i][j],i,j));
}
}
}
}
return maxElement;
}
Note that because every element is distinct, there's no need to also check that the new value isn't already in the set.
Running the three solutions with given input:
int n = 5;
int[][] matrix = {{16, -20, 22, 19},
{ 2, 5, 6, 8},
{17, 25, 16, 19},
{ 7, 18, 4, 17}};
System.out.println("streamIt: \n "
+ Arrays.toString(streamIt(matrix,n)));
System.out.println("useSet: \n "
+ useSet(matrix,n));
System.out.println("useSetElements: \n "
+ useSetElements(matrix,n));
..gives this:
streamIt:
[25, 22, 19, 19, 18]
useSet:
[25, 22, 19, 18, 17]
useSetElements:
[25 at [2][1], 22 at [0][2], 19 at [2][3], 19 at [0][3], 18 at [3][1]]
But what about Performance..?
The three different implementations had me wondering about the performance, so I added a method to time the execution:
static void timeMethod(Runnable toRun){
long start = System.nanoTime();
try{
toRun.run();
} finally {
long end = System.nanoTime();
System.out.println(" Time: " + (end - start)/1.0e6 + " miliseconds");
}
}
And ran the three solutions:
timeMethod(() -> System.out.println("streamIt: \n "
+ Arrays.toString(streamIt(matrix,n))));
timeMethod(() -> System.out.println("useSet: \n "
+ useSet(matrix,n)));
timeMethod(() -> System.out.println("useSetElements: \n "
+ useSetElements(matrix,n)));
..giving this result:
streamIt:
[25, 22, 19, 19, 18]
Time: 1.2759 miliseconds
useSet:
[25, 22, 19, 18, 17]
Time: 0.9343 miliseconds
useSetElements:
[25 at [2][1], 22 at [0][2], 19 at [2][3], 19 at [0][3], 18 at [3][1]]
Time: 1.16 miliseconds
It seems that the three solutions have roughly the same performance. The streams solution seems slightly slower. The Set
solution looks promising, expect the solution using Element
seems to take a toll. But to look at it more deeply I decided to run them on a much larger matrix, which I build using random integers:
Random random = new Random();
int[][] largerMatrix =
IntStream.range(0,10000) // 10000 on the first dimension
.mapToObj(i -> random.ints(0,128) // values between 0 and 128 (not included)
.limit(10000) // 10000 on the second dimension
.toArray()) // make the second 1D arrays
.toArray(int[][]::new); // put them into a 2D array
Running the test with a 10000 by 10000 matrix:
timeMethod(() -> System.out.println("streamIt: \n "
+ Arrays.toString(streamIt(largerMatrix,n))));
timeMethod(() -> System.out.println("useSet: \n "
+ useSet(largerMatrix,n)));
timeMethod(() -> System.out.println("useSetElements: \n "
+ useSetElements(largerMatrix,n)));
..gave this result:
streamIt:
[127, 127, 127, 127, 127]
Time: 90374.6995 miliseconds
useSet:
[127, 126, 125, 124, 123]
Time: 2465.2448 miliseconds
useSetElements:
[127 at [0][310], 127 at [0][277], 127 at [0][260], 127 at [0][81], 127 at [0][61]]
Time: 1839.7323 miliseconds
Here the streams solution seems incredibly slow! The Element
solution is the winner of the two Set
solutions. I expect it's due to the fact that Element
s are only created when they're needed for inserting into the Set
and it's doing a straight up int
comparison, while the other Set
solution is unboxing every time the values are compared. I didn't further test my hypothesis though.
My curiosity of the other solutions in this thread got me testing out those as well. The solutions tested were:
- Answer by Arvind Kumar Avinash
- Answer by Anurag Jain
- Answer by Michael Chatiskatzi
Running the tests on both the small and the large array:
System.out.println("--- Testing performance ---");
timeMethod(() -> System.out.println("ArvindKumarAvinash: \n "
+ Arrays.toString(ArvindKumarAvinash(matrix,n))));
timeMethod(() -> System.out.println("AnuragJain: \n "
+ AnuragJain(matrix,n)));
timeMethod(() -> System.out.println("MichaelChatiskatzi: \n "
+ Arrays.toString(MichaelChatiskatzi(matrix,n))));
System.out.println();
System.out.println("--- Testing performance with largeMatrix---");
timeMethod(() -> System.out.println("ArvindKumarAvinash: \n "
+ Arrays.toString(ArvindKumarAvinash(largerMatrix,n))));
timeMethod(() -> System.out.println("AnuragJain: \n "
+ AnuragJain(largerMatrix,n)));
timeMethod(() -> System.out.println("MichaelChatiskatzi: \n "
+ Arrays.toString(MichaelChatiskatzi(largerMatrix,n))));
..gave these results:
--- Testing performance ---
ArvindKumarAvinash:
[25, 22, 19, 19, 18]
Time: 0.9076 miliseconds
AnuragJain:
[25, 22, 19, 19, 18]
Time: 6.2277 miliseconds
MichaelChatiskatzi:
[18, 19, 19, 22, 25]
Time: 1.2204 miliseconds
--- Testing performance with largeMatrix---
ArvindKumarAvinash:
[127, 127, 127, 127, 127]
Time: 3381.1387 miliseconds
AnuragJain:
[127, 127, 127, 127, 127]
Time: 120244.7063 miliseconds
MichaelChatiskatzi:
[127, 127, 127, 127, 127]
Time: 51.4259 miliseconds
It seems that solutions using streams are not very performant at all. Michael Chatiskatzi's solution is by far the better performant one.
All the code
If you want to run it yourself, here a complete class for copy'n'paste'n'run:
import java.util.Arrays;
import java.util.Comparator;
import java.util.stream.IntStream;
import java.util.Set;
import java.util.TreeSet;
import java.util.Comparator;
import java.util.Random;
import java.util.List;
import java.util.ArrayList;
import java.util.stream.Collectors;
public class GettingTheTopN {
public static void main(String[] args) {
int n = 5;
int[][] matrix = {{16, -20, 22, 19},
{ 2, 5, 6, 8},
{17, 25, 16, 19},
{ 7, 18, 4, 17}};
System.out.println("streamIt: \n "
+ Arrays.toString(streamIt(matrix,n)));
System.out.println("useSet: \n "
+ useSet(matrix,n));
System.out.println("useSetElements: \n "
+ useSetElements(matrix,n));
System.out.println();
System.out.println("--- Testing performance ---");
timeMethod(() -> System.out.println("streamIt: \n "
+ Arrays.toString(streamIt(matrix,n))));
timeMethod(() -> System.out.println("useSet: \n "
+ useSet(matrix,n)));
timeMethod(() -> System.out.println("useSetElements: \n "
+ useSetElements(matrix,n)));
timeMethod(() -> System.out.println("ArvindKumarAvinash: \n "
+ Arrays.toString(ArvindKumarAvinash(matrix,n))));
timeMethod(() -> System.out.println("AnuragJain: \n "
+ AnuragJain(matrix,n)));
timeMethod(() -> System.out.println("MichaelChatiskatzi: \n "
+ Arrays.toString(MichaelChatiskatzi(matrix,n))));
System.out.println();
System.out.println("--- Testing performance with largeMatrix---");
Random random = new Random();
int[][] largerMatrix =
IntStream.range(0,10000) // 10000 on the first dimension
.mapToObj(i -> random.ints(0,128) // values between 0 and 128 (not included)
.limit(10000) // 10000 on the second dimension
.toArray()) // make the second 1D arrays
.toArray(int[][]::new); // put them into a 2D array
timeMethod(() -> System.out.println("streamIt: \n "
+ Arrays.toString(streamIt(largerMatrix,n))));
timeMethod(() -> System.out.println("useSet: \n "
+ useSet(largerMatrix,n)));
timeMethod(() -> System.out.println("useSetElements: \n "
+ useSetElements(largerMatrix,n)));
timeMethod(() -> System.out.println("ArvindKumarAvinash: \n "
+ Arrays.toString(ArvindKumarAvinash(largerMatrix,n))));
timeMethod(() -> System.out.println("AnuragJain: \n "
+ AnuragJain(largerMatrix,n)));
timeMethod(() -> System.out.println("MichaelChatiskatzi: \n "
+ Arrays.toString(MichaelChatiskatzi(largerMatrix,n))));
}
public static Integer[] streamIt(int[][] matrix, int n){
Integer[] result =
Arrays.stream(matrix) // stream the arrays
// This is the same as using .flatMaptoInt(..) and then .boxed()
.flatMap(a -> Arrays.stream(a) // stream the array in arrays
.mapToObj(i -> Integer.valueOf(i))) // turn the ints into Integers
.sorted(Comparator.reverseOrder()) // sort by higest values
.limit(n) // only pick n
.toArray(i -> new Integer[i]); // put then in Integer array
return result;
}
public static Set<Integer> useSet(int[][] matrix, int n){
TreeSet<Integer> max = new TreeSet<>(Comparator.<Integer>naturalOrder().reversed());
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[i].length; j++) {
// Keep adding values until there's n elements in the Set
if (max.size() < n) {
max.add(matrix[i][j]);
} else {
// if the new value is higher than the lowest value
// ..and the new values isn't already there.
if (max.last() < matrix[i][j] && !max.contains(matrix[i][j])) {
max.pollLast();
max.add(matrix[i][j]);
}
}
}
}
return max;
}
public static Set<Element> useSetElements(int[][] matrix, int n){
TreeSet<Element> maxElement = new TreeSet<>();
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[i].length; j++) {
if (maxElement.size() < n) {
maxElement.add(new Element(matrix[i][j],i,j));
} else {
if (maxElement.last().value < matrix[i][j]) {
maxElement.pollLast();
maxElement.add(new Element(matrix[i][j],i,j));
}
}
}
}
return maxElement;
}
// ----------------- Performance
static void timeMethod(Runnable toRun){
long start = System.nanoTime();
try{
toRun.run();
} finally {
long end = System.nanoTime();
System.out.println(" Time: " + (end - start)/1.0e6 + " miliseconds");
}
}
// [Answer to "How to find first 5 highest value in a two dimensional array?"](https://stackoverflow.com/a/65374950/12695027) by [Arvind Kumar Avinash](https://stackoverflow.com/users/10819573/arvind-kumar-avinash)
static int[] ArvindKumarAvinash(int[][] matrix, int MAX_N) {
// Find count as the total number of elements
int count = 0, row, col;
for (row = 0; row < matrix.length; row++) {
count += matrix[row].length;
}
// Create flattened = new int[count] and fill it with all elements of matrix[][]
int[] flattened = new int[count];
int i = 0;
for (row = 0; row < matrix.length; row++) {
for (col = 0; col < matrix[row].length; col++) {
flattened[i++] = matrix[row][col];
}
}
// Create max = new int[MAX_N] to store maximum n numbers.
// Also, create maxPos = new int[MAX_N] to store the position of the maximum numbers.
int[] max = new int[MAX_N];
int[] maxPos = new int[MAX_N];
// Loop MAX_N times. In each iteration, assume flattened[0] is the largest number.
for (i = 0; i < max.length; i++) {
max[i] = flattened[0];
for (int j = 1; j < flattened.length; j++) {
// If flattened[j] >= max[i], check if the position, j has already been
// processed. If not assign flattened[j] to max[i] and j to maxPos[i].
if (flattened[j] >= max[i]) {
boolean posAlreadyProcessed = false;
for (int k = 0; k <= i; k++) {
if (maxPos[k] == j) {
posAlreadyProcessed = true;
break;
}
}
if (!posAlreadyProcessed) {
max[i] = flattened[j];
maxPos[i] = j;
}
}
}
}
return max;
// System.out.println("Largest " + MAX_N + " values: " + Arrays.toString(max));
}
// [Answer to "How to find first 5 highest value in a two dimensional array?"](https://stackoverflow.com/a/65380541/12695027) by [Anurag Jain](https://stackoverflow.com/users/5825625/anurag-jain)
static List<Integer> AnuragJain(int[][] matrix, int n) {
List<Integer> allVal = new ArrayList<>();
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[i].length; j++) {
allVal.add(matrix[i][j]);
}
}
allVal = allVal.stream()
.sorted(Comparator.reverseOrder())
.limit(n).collect(Collectors.toList());
return allVal;
// System.out.println(allVal);
}
// [Answer to "How to find first 5 highest value in a two dimensional array?"](https://stackoverflow.com/a/65379921/12695027) by [Michael Chatiskatzi](https://stackoverflow.com/users/11263320/michael-chatiskatzi)
static int[] MichaelChatiskatzi(int[][] matrix, int n) {
// int[] highestNumbers = new int[5];
int[] highestNumbers = new int[n];
Arrays.fill(highestNumbers, Integer.MIN_VALUE);
for (int row = 0; row < matrix.length; row++) {
for (int column = 0; column < matrix[row].length; column++) {
int currentEntry = matrix[row][column];
if (currentEntry > highestNumbers[0]) {
highestNumbers[0] = currentEntry;
Arrays.sort(highestNumbers);
}
}
}
return highestNumbers;
// System.out.println(Arrays.toString(highestNumbers));
}
}
// -------------------------------------------
// -------------------------------------------
class Element implements Comparable<Element> {
final int value;
final int x;
final int y;
static Comparator<Element> comparator =
Comparator.comparing((Element e) -> e.value)
.thenComparing((Element e) -> e.x)
.thenComparing((Element e) -> e.y)
.reversed();
Element(int value, int x, int y) {
this.value = value;
this.x = x;
this.y = y;
}
public int compareTo(Element that){
return comparator.compare(this, that);
}
public String toString(){
return value + " at [" + x + "][" + y + "]";
}
}
回答2:
- Let
MAX_N = 5
. - Find
count
as the total number of elements inmatrix[][]
. - Create
flattened = new int[count]
and fill it with all elements ofmatrix[][]
. - Create max = new int[MAX_N] to store maximum n numbers. Also, create maxPos = new int[MAX_N] to store the position of the maximum numbers.
- Loop
MAX_N
times and in each iteration, assumeflattened[0]
is the largest number. - If
flattened[j] >= max[i]
, check if the position,j
has already been processed. If not assignflattened[j]
tomax[i]
andj
tomaxPos[i]
.
Demo:
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
final int MAX_N = 5;
int[][] matrix = {
{16, -20, 11, 19},
{2, 5, 6, 8},
{17, 25, 16, 19},
{7, 17, 4, 17}};
// Find count as the total number of elements
int count = 0, row, col;
for (row = 0; row < matrix.length; row++) {
count += matrix[row].length;
}
// Create flattened = new int[count] and
// fill it with all elements of matrix[][]
int[] flattened = new int[count];
int i = 0;
for (row = 0; row < matrix.length; row++) {
for (col = 0; col < matrix[row].length; col++) {
flattened[i++] = matrix[row][col];
}
}
// Create max = new int[MAX_N] to store maximum
// n numbers. Also, create maxPos = new int[MAX_N]
// to store the position of the maximum numbers.
int[] max = new int[MAX_N];
int[] maxPos = new int[MAX_N];
// Loop MAX_N times. In each iteration,
// assume flattened[0] is the largest number.
for (i = 0; i < max.length; i++) {
max[i] = flattened[0];
for (int j = 1; j < flattened.length; j++) {
// If flattened[j] >= max[i], check if the
// position, j has already been processed.
// If not assign flattened[j] to max[i]
// and j to maxPos[i].
if (flattened[j] >= max[i]) {
boolean posAlreadyProcessed = false;
for (int k = 0; k <= i; k++) {
if (maxPos[k] == j) {
posAlreadyProcessed = true;
break;
}
}
if (!posAlreadyProcessed) {
max[i] = flattened[j];
maxPos[i] = j;
}
}
}
}
System.out.println("Largest " + MAX_N +
" values: " + Arrays.toString(max));
}
}
Output:
Largest 5 values: [25, 19, 19, 17, 17]
回答3:
With Java8 streams it can be done with this (one) line of code. It will leave the original matrix untouched.
Arrays.stream(matrix) // create a stream of the matrix
.flatMapToInt(Arrays::stream) //Reduce 2d matrix to 1d
.boxed() //Convert int to Integer so we can sort reversed order
.sorted(Collections.reverseOrder()) //sort array in reversed order highest first
.limit(5) //Limit stream to 5 entries, the five top results
.forEach(System.out::println); //Print the result
回答4:
Without sorting array itself, you can make sorted the stream over this array. Or you can implement a kind of selection sort in descending order. These two code examples do the same thing - find the first 5 highest distinct values in 2D array, if they are present:
-
int[][] arr = { {1, 4, 7, 7}, {2, 5, 8, 3}, {5, 5, 1, 2}, {3, 6, 0, 9}}; int[] max = Arrays.stream(arr) .flatMapToInt(Arrays::stream) .boxed().sorted(Comparator.reverseOrder()) .mapToInt(Integer::intValue) .distinct() .limit(5) .toArray(); System.out.println(Arrays.toString(max)); // [9, 8, 7, 6, 5]
-
int[][] arr = { {1, 4, 7, 7}, {2, 5, 8, 3}, {5, 5, 1, 2}, {3, 6, 0, 9}}; int[] max = new int[5]; for (int m = 0; m < max.length; m++) { int prev_max = m > 0 ? max[m - 1] : Integer.MAX_VALUE; for (int i = 0; i < arr.length; i++) { for (int j = 0; j < arr[i].length; j++) { if (arr[i][j] > max[m] && prev_max > arr[i][j]) { max[m] = arr[i][j]; } } } } System.out.println(Arrays.toString(max)); // [9, 8, 7, 6, 5]
See also: Selection sort of array
回答5:
Now that the question is open again, I will present my comment as an answer.
Instead of iterating over the same matrix multiple times I fill the int[] highestNumbers
with Integer.MIN_VALUE
, iterate over the matrix once, and replace the smallest entry of max each time the current integer is greater, by updating the first entry of highestNumbers
and sort it.
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[][] matrix = {
{10, -5, 15},
{8, 20, 12},
{27, -3, 14},
{7, 17, 4}};
int[] highestNumbers = new int[5];
Arrays.fill(highestNumbers, Integer.MIN_VALUE);
for (int row = 0; row < matrix.length; row++) {
for (int column = 0; column < matrix[row].length; column++) {
int currentEntry = matrix[row][column];
if (currentEntry > highestNumbers[0]) {
highestNumbers[0] = currentEntry;
Arrays.sort(highestNumbers);
}
}
}
System.out.println(Arrays.toString(highestNumbers));
}
}
Output:
[14, 15, 17, 20, 27]
回答6:
You can use below way this will give you below benefits:
- Keeping very simple logic, As you want highest 5 values and you will not loose any index location/order for existing array,
- Using this you will get better performance,
- Clean Code.
public static void main(String[] args) {
int[][] matrix = {
{10, -5, 15},
{8, 20, 12},
{27, -3, 14},
{7, 17, 4}};
List<Integer> allVal = new ArrayList<>();
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[i].length; j++) {
allVal.add(matrix[i][j]);
}
}
allVal = allVal.stream()
.sorted(Comparator.reverseOrder())
.limit(5)
.collect(Collectors.toList());
System.out.println(allVal);
}
Output:
[27, 20, 17, 15, 14]
来源:https://stackoverflow.com/questions/65374379/how-to-find-first-5-highest-value-in-a-two-dimensional-array