Java - compare 2 lists based on highest value and subsequent values? - java

I have two lists of:
List<Person> persons;
each Person has the attribute - score
e.g.
list1:
person.score = 3
person.score = 5
person.score = 8
list2:
person.score = 8
person.score = 4
person.score = 7
I want to compare each list to find the highest score in each. If both highest are the same, then I want to compare the second highest etc and so on.
How can I do so?

You can make a copy of each List, sort these copies, then compare them element by element.
Comparator<List<Person>> comp = (a, b) -> {
List<Person> s1 = a.stream().sorted(Comparator.comparingInt(Person::getScore).reversed()).toList(),
s2 = b.stream().sorted(Comparator.comparingInt(Person::getScore).reversed()).toList();
for (int i = 0; i < Math.min(a.size(), b.size()); i++) {
int c = Integer.compare(s1.get(i).getScore(), s2.get(i).getScore());
if (c != 0) return c;
}
return Integer.compare(a.size(), b.size());
};

Define a comparator to compare the lists. After, sorting each list, The comparator short circuits as soon corresponding scores are unequal, returning the value of the the last comparison. Otherwise, if the two lists are equal or if if their sizes are different, the shortest list will return as the smaller.
Details
Since the score is the target of the comparison, I decided to grab it early and place in int arrays which are sorted. Then I iterate over the arrays in reverse order to ensure I am testing the largest elements first (IntStreams can't take a comparator for sorting and I didn't want to box the int values).
Iterating over the lists, the scores are compared and the result is mapped to the stream. Then the result of the comparison (-1,0, or 1) is filtered to ignore zero values, capturing the result in an OptionalInt.
Then either the OptionalInt value is returned, or the comparison of the array sizes. Then, all comparisons being equal, the smaller array length would return -1, larger 1, and equal lengths 0
Comparator<List<Person>> listComp = (lst1, lst2) -> {
int[] scores1 = lst1.stream().mapToInt(Person::getScore).sorted()
.toArray();
int[] scores2 = lst2.stream().mapToInt(Person::getScore).sorted()
.toArray();
OptionalInt diff = IntStream
.iterate(Math.min(scores1.length-1, scores2.length-1),
i -> i >= 0, i -> i-1)
.map(i -> Integer.compare(scores1[i], scores2[i]))
.filter(delta -> delta != 0).findFirst();
return diff.orElse(Integer.compare(scores1.length, scores2.length));
};
Demos
Some Data - comparing every two lists in the list of lists to each other. For demo purposes, I am using a record with a modified toString().
record Person(int getScore) {
#Override
public String toString() {
return "%d".formatted(getScore);
}
}
List<List<Person>> lists = List.of(
// first is smaller
List.of(new Person(3), new Person(5), new Person(8)),
List.of(new Person(8), new Person(4), new Person(7)),
// equal lists
List.of(new Person(3), new Person(7), new Person(8)),
List.of(new Person(8), new Person(3), new Person(7)),
// first is smaller due to length
List.of(new Person(3), new Person(7), new Person(8)),
List.of(new Person(8), new Person(3), new Person(7),
new Person(2)));
To compare the lists, use int result = listComp.compare(list1, list2);
int v;
for (int i = 0; i < lists.size(); i += 2) {
System.out.println(lists.get(i)
+ ((v = listComp.compare(lists.get(i), lists.get(i + 1))) > 0
? " > "
: (v < 0) ? " < " : " == ")
+ lists.get(i + 1));
}
prints the following:
[3, 5, 8] < [8, 4, 7]
[3, 7, 8] == [8, 3, 7]
[3, 7, 8] > [8, 3, 7, 2]

Related

Reduce the number of distinct elements in array

I have an array of numbers and another number K.
My task is to reduce the number of distinct elements in the array. For that, I can update the array several times. For updating the array, I have to follow these steps:
Select an element at index i and add that element by K, and reduce all other remaining elements by K.
For updating an array I can select the same index several times.
Example:
K = 1
Array: [3,1,3]
Answer: 3
I am picking index = 1, as [3-1, 1+1, 3-1] = [2,2,2] so we have number 2 that appears 3 times so this element occurs maximum number of times. So answer is 3.
Another example:
K = 1
Array: [1,2,2]
Answer: 2
It's not possible to make all elements same, so we have number 2 that appears 2 times, so answer is 2.
Array size can be [1, 1000], and the value of K and elements in array is in range [0, 1000]
Here is my code that I tried, my my approach is not correct.
public static int process(int K, int[] A) {
Map<Integer, Integer> map = new TreeMap<>();
for (int key : A) {
map.put(key, map.getOrDefault(key, 0) + 1);
}
int result = 0;
boolean flag = false;
int last = -1, cur = -1;
for (int key : map.keySet()) {
if (flag == false) {
flag = true;
last = key;
continue;
}
cur = key;
int a = map.get(last), b = map.get(cur);
if (Math.abs(last - cur) > K) {
result += a + b;
} else {
result += Math.max(a, b);
}
}
last = cur;
return result;
}
When looking at the examples with K = 1, it is clear that the answer
depends on the parity of the elements. Only elements with same parity can be set to the same level,
and all elements with same parity can be joined.
For example:
[2 4 6] -> [1 5 5] -> [2 4 4] -> [3 3 3]
[1 2 2] -> [2 1 1] ... no progress
With K = 1, we have to consider value modulo 2, i.e. modulo 2*K.
When K is different of one, for example K = 2, two numbers can be joined only there are separated by a distance multiple of 4, i.e. of 2*K.
[2 6 6] -> [4 4 4]
For K different from 1, instead of creating buckets for numbers with same parity,
we just create buckets according to value modulo 2K.
We just have to pay attention to use the modulo and not the remainder, the values are different for negative values.
Then the answer if simply the highest size of a bucket.
Output:
K = 1 Array : 3 1 3 -> 3
K = 1 Array : 1 2 2 -> 2
K = 1 Array : 2 3 4 7 4 9 11 -> 4
K = 1 Array : -3 -1 2 3 -> 3
K = 3 Array : -7 -1 0 1 2 4 5 -> 3
Here is a simple code in C++ to illustrate the algorithm.
In this code, the value val_modulo modulo 2K of each element is calculated.
Then, the orresponding counter is increased
Bucket[val_modulo] = Bucket[val_modulo] + 1
At the end, the highest value corresponds to the number of repetitions of the most repeated final value.
We may note that the number of non empty bucket corrresponds to the number of different
final values (not used in this code).
#include <iostream>
#include <vector>
#include <string>
#include <map>
void print (const std::vector<int> &A, const std::string &after = "\n", const std::string &before = "") {
std::cout << before;
for (int x: A) {
std::cout << x << " ";
}
std::cout << after;
}
int Modulo (int n, int mod) {
int ans = n % mod;
if (ans < 0) ans += mod;
return ans;
}
int max_equal(int K, std::vector<int> A) {
K = std::abs(K); // useful befoe taking the modulo
std::map<int, int> Buckets;
int nmax = 0;
int mod = 2*K;
for (int x: A) {
int val_modulo = Modulo (x, mod); // and not x*mod, as x can be negative
Buckets[val_modulo]++;
}
for (auto x: Buckets) {
if (x.second > nmax) {
nmax = x.second;
}
}
return nmax;
}
int main() {
std::vector<std::vector<int>> examples = {
{3, 1, 3},
{1, 2, 2},
{2, 3, 4, 7, 4, 9, 11},
{-3, -1, 2, 3},
{-7, -1, 0, 1, 2, 4, 5}
};
std::vector<int> tab_K = {1, 1, 1, 1, 3};
for (int i = 0; i < examples.size(); ++i) {
std::cout << "K = " << tab_K[i] << " Array : ";
print (examples[i], " -> ");
auto ans = max_equal (tab_K[i], examples[i]);
std::cout << ans << "\n";
}
return 0;
}
The problem is conceptual, and translating it in somewhat computing code.
Let's look:
We pick a number (by index, which is irrelevant), and for all the occurrences we add K. All others we subtract K And then the number of same occurrences must be maximal.
The same occurrences can only grow when the picked number + K is equal to another number - K.
The data structure:
A map with the array numbers as key, and mapped to frequency (how often the number occurs in the array).
So:
pickedNumber.value + K = otherNumber.value - K
=> otherNumber.value = pickedNumber.value + 2*K
Note that as there is only one single otherNumber, derived from the pickedNumber.
(It might occur more than once.)
And we want maximal:
pickedNumber.frequency + otherNumber.frequency maximal.
Though map is not really needed, a sorted array would do too, let's do a map:
The algorithm:
Kept simple.
int[] numbers = {3, 1, 3};
int index = pickedIndexOfBestSolution(numbers, 1);
System.out.println("Index: " + index);
int pickedIndexOfBestSolution(int[] numbers, int k) {
Map<Integer, Long> frequencyTable = IntStream.of(numbers)
.boxed()
.collect(Collectors.groupingBy(Function.identity(),
Collectors.counting()));
int bestNumber = frequencyTable.entrySet().stream()
.sorted(Comparator.comparingLong(e -> -e.getValue()
- frequencyTable.getOrDefault(e.getKey() + 2*k, 0L)))
.findFirst()
.map(e -> e.getKey())
.orElseThrow();
int index = -1;
while (numbers[++index] != bestNumber) {
}
return index;
}
The frequency table I filled using an IntStream and groupingBy (just as SQL).
As counting is done with long, I just kept that.
To find the max I counted the new occurrence count trying to add the "other" number's frequency too; 0 when no other number.
Instead of using .reverse() to reverse the comparison (largest, max, first), I took the negative value, which to me seems more calculatory.
Notice that a Stream with findFirst to find the max is probably optimal too: no need that the stream creates an intermediate list.
For the index I used brute force (while loop), a kind of indexOf.
Notice if there is no other number, it returns the index of a number with the most occurrences. Which is fine.
In short:
You see the different approach. Actually simpler, and more solid. In fact applying
some (minor) intelligence first. Trying to nail down the problem first.

Create a list of List from an Array

How can i create a list of List from Array eg:
int[] arr = {3, 1, 5, 8, 2, 4}.
Such that the lists in the List have only two elements eg:
[[3,1], [5,8], [2,4]].
So far i have tried code below but it return only lists with one element,I can't figure out where i went wrong.
class ListList {
public static List<List<Integer>> listOfList(int[] num){
List<List<Integer>> arrList = new ArrayList<>();
for(int i = 0 ; i<num.length;i++){
List<Integer> list = new ArrayList<>();
if(list.size() !=2){
list.add(num[i]);
}
arrList.add(list);
}
return arrList;
}
}
Result: [[3], [1], [5], [8], [2], [4]].
Here's a generic one:
var arr = new int[] {3, 1, 5, 8, 2, 4};
var batchSize = 2;
List<List<Integer>> lists = IntStream.range(0, arr.length)
.mapToObj(index -> Map.entry(index, arr[index]))
.collect(Collectors.groupingBy(e -> e.getKey() / batchSize))
.values().stream()
.map(entries -> entries.stream().map(Map.Entry::getValue).toList())
.toList();
System.out.println(lists);
Output:
[[3, 1], [5, 8], [2, 4]]
You are basically creating a mapping of index->value and subsequently grouping by the batchSize to make splits
You are creating an empty list on each iteration, then you check if its size != 2 (of course it is) and add 1 element, finally you add list with 1 element to result list, which is not what you need.
Move list creation out of loop and add elements to it. When its size == 2, add current list to result list and create a new one.
class ListList {
public static List<List<Integer>> listOfList(int[] num) {
List<List<Integer>> arrList = new ArrayList<>();
List<Integer> list = new ArrayList<>();
for(int i = 0; i < num.length; i++) {
if(list.size() == 2) {
arrList.add(list);
list = new ArrayList<>();
}
list.add(num[i]);
}
if(list.size() != 0) {
arrList.add(list);
}
return arrList;
}
}
If your input size can be odd, then you would also add list of length 1 to result list. If you don't want to, add aditional checks.
If you're certain that the list has an even number of values, you can do it like this.
create a list of lists.
taking two at a time, put each in a separate list
add that list to the list of lists
int [] arr ={3, 1, 5, 8, 2, 4};
List<List<Integer>> list = new ArrayList<>();
for (int i = 0; i < arr.length; i+=2) {
List<Integer> temp = Arrays.asList(arr[i], arr[i+1]);
list.add(temp);
}
System.out.println(list);
prints
[[3, 1], [5, 8], [2, 4]]
If you list is of odd length, change the assignment to
List<Integer> temp = arr.length - i >= 2 ? Arrays.asList(arr[i], arr[i+1]) :
Arrays.asList(arr[i]);
And here is a different take on the idea suggested by Dhrubajyoti Gogoi.
stream the indices.
use integer math to divide into groups, mapping to a list
and return the values as a collection of lists.
int groupSize = 2;
Collection<List<Integer>> result =
IntStream.range(0, arr.length)
.mapToObj(Integer::valueOf)
.collect(Collectors.groupingBy(
i -> i / groupSize,
Collectors.mapping(i -> arr[i],
Collectors.toList())))
.values();
Try this:
public static List<List<Integer>> listOfList(int[] num){
List<List<Integer>> arrList = new ArrayList<>();
for (int i = 0; i < num.length; i += 2) {
if (num.length > i + 1) {
arrList.add(List.of(num[i], num[i+1]));
} else {
arrList.add(List.of(num[i]));
}
}
return arrList;
}
In your example you always create a fresh list in the loop, so size is always 0 and never 2.

How to copy a Set<Set<Integer>> into a List<Integer[]> in Java?

I want to copy a Set<Set<Integer>> of array to a List of Integer[]. I want to do that because I want to change the first element of every array when it is 0 , and put it at the end.
I have manage to do that but in a List<Set> , and when looping throw , I can't make changes because the array are still a Set.
No other questions make this clear, instead they explained when we have only Set<SomeWrapperClass>
Maybe some other solutions? Remark: I cannot change the Set<Set<Integer>>
My Set:
Set<Set<Integer>> indexs = new HashSet<>();
I can convert to a List only in this way:
List<Set<Integer>> arrangedIndexOfCourses = new ArrayList<>();
static Set<Set<Integer>> coursesIndex = Sets.powerSet(Sets.newHashSet(1, 2, 3, 4, 5, 6, 7, 8, 9, 0));
static List<Set<Integer>> arrangedIndexOfCourses = new ArrayList<>();
The following method return all the occurence of unique numbers made from 0123456789 ( Guava library )
private void indexSet(Set<Set<Integer>> coursesIndex) {
Set<Set<Integer>> indexs = new HashSet<>();
for (Set<Integer> token : coursesIndex) {
indexs.add(token);
arrangedIndexOfCourses.add(token);
count++;
}
}
And the code where I tried to change the first 0 of the arrays and put at last:
for (Set<Integer> i : arrangedIndexOfCourses) {
if (i.contains(0)) {
Collections.rotate(arrangedIndexOfCourses, -1);
}
}
It appears, the main trick in this task is how to implement rotation of the first element if it is 0.
As mentioned in the comments, not all types of sets maintain order and therefore have a "first" element. But upon converting a set to stream, even for a HashSet there's a first element in the stream unless it's empty.
Therefore, the set of integer sets may be converted to a list of integer arrays as follows:
Take the first element of the stream using limit or findFirst
Compare to 0, if needed put it to the end
Else keep the stream of the set as is
Set<Set<Integer>> data = Set.of(
Collections.emptySet(),
new LinkedHashSet<>(Arrays.asList(0, 1, 2, 3)),
new LinkedHashSet<>(Arrays.asList(4, 5, 6)),
new LinkedHashSet<>(Arrays.asList(10, 0, 100, 1000)),
new TreeSet<>(Arrays.asList(25, 0, 1, 16, 4, 9)),
new HashSet<>(Arrays.asList(0, 5, 50))
);
List<Integer[]> rotatedZeros = data
.stream()
.map(s ->
(s.stream().findFirst().orElse(-1) == 0 // or limit(1).findAny()
? Stream.concat(s.stream().skip(1), Stream.of(0))
: s.stream())
.toArray(Integer[]::new)
)
.collect(Collectors.toList());
rotatedZeros.stream().map(Arrays::toString).forEach(System.out::println);
Output:
[]
[4, 5, 6]
[1, 2, 3, 0]
[10, 0, 100, 1000]
[1, 4, 9, 16, 25, 0]
[50, 5, 0]
the 2-step solution with a lambda w/o external library
(1) convert the Set<Set<Integer>> to a List<Integer[]>
List<Integer[]> arrangedIndexOfCourses = coursesIndex.stream()
.map(s -> s.toArray(Integer[]::new)).collect(toList());
(2) iterate over the arrays and change the zero-arrays in the traditional way
for (Integer[] indices : arrangedIndexOfCourses) {
if (indices.length > 0 && indices[0] == 0) {
for (int i = 0; i < indices.length - 1; i++)
indices[i] = indices[i + 1];
indices[indices.length - 1] = 0;
}
}
a lambda based on Alex's mind blowing solution
moving the zero-rotating-stuff upstream makes the lambda less trickier and You can work with collections instead of arrays
List<Integer[]> arrangedIndexOfCourses = coursesIndex.stream()
.map(s -> {
if (s.stream().findFirst().orElse(-1) == 0) {
List<Integer> l = new ArrayList<>();
l.addAll(s);
l.remove(0);
l.add(0);
return l.toArray(Integer[]::new);
} else
return s.toArray(Integer[]::new);
}).collect(toList());

Delete duplicates from table (List<List<Integer>> table = new ArrayList ...)

I got table like
List<List<Integer>> table = new ArrayList<>()
Where List's within are table rows, i need to set all duplicate values ALL OVER THE TABLE to null, how can do it via only ArrayList && foreach loop || λ - expressions?
Must work somehow like this:
1 2 3 null 2 3
4 1 5 -> 4 null 5
1 6 9 null 6 9
Sorry for my poor English and thank you for fast response!
Using java-8,
//Find duplicates
Map<Integer, Long> counts =
table.stream()
.flatMap(Collection::stream)
.collect(Collectors.groupingBy(i->i, Collectors.counting()));
// and remove them
table.stream().forEach(row -> row.replaceAll(i-> counts.get(i) > 1 ? null : i));
Interpreting the (slightly unclear) question as a deduplication (removal rather than "nulling"):
List<List<Integer>> dedup = table.stream().distinct().collect(toList());
or java 7:
List<List<Integer>> dedup = new ArrayList<>(new LinkedHashSet<>(table));
which is generally more useful.
However, if nulling is really required:
Set<List<Integer>> set = new HashSet<>();
List<List<Integer>> dedup = table.stream()
.filter(list -> set.add(list) ? list : null)
.collect(toList());
The add() method of a Set returns true is adding the element changed the set - ie if it's an element not seen before.
Try this, this approach is to build an index map for all the elements present in the List and setting the value to null if more than one value found for a key
List<List<Integer>> table = new ArrayList<>();
table.add(Arrays.asList(1, 2, 3));
table.add(Arrays.asList(4, 1, 5));
table.add(Arrays.asList(1, 6, 9));
System.out.println("Before " + table);
Map<Integer, List<List<Integer>>> indices = new HashMap<>();
for (int i = 0; i < table.size(); i++) {
for (int j = 0; j < table.get(i).size(); j++) {
int el = table.get(i).get(j);
if (indices.get(el) == null) {
indices.put(el, new ArrayList<>());
}
indices.get(el).add(Arrays.asList(i, j));
}
}
indices.keySet().stream()
.filter(k -> indices.get(k).size() > 1)
.flatMap(k -> indices.get(k).stream())
.forEach(li -> table.get(li.get(0)).set(li.get(1), null));
System.out.println("After " + table);
output
Before [[1, 2, 3], [4, 1, 5], [1, 6, 9]]
After [[null, 2, 3], [4, null, 5], [null, 6, 9]]

Retrieve all value pairs from List<Integer>

For example I have a List<Integer> object with the following:
3, 6, 5, 3, 3, 6
The result would be 3 and 6. How can I create a function that tests for duplicates and then returns 1 value of the duplicate (not the pair, just one in the pair)? One problem that might occur is if there are quadruple values: 3, 4, 5, 3, 8, 3, 3 Then I would like to return 3 and 3. How can I accomplish this?
I would go through the list counting the number of instances of each one (storing them in a map) and then create a new list from the map:
List<Integer> values = // the list of values you have
Map<Integer,Integer> counts = new HashMap<Integer,Integer>();
for(Integer value : values) {
if(counts.containsKey(value)) {
counts.put(value, counts.get(value)+1);
} else {
counts.put(value, 1);
}
}
List<Integer> resultValues = new ArrayList<Integer>();
for(Integer value : counts.keySet()) {
Integer valueCount = counts.get(value);
for(int i=0; i<(valueCount/2); i++) { //add one instance for each 2
resultValues.add(value);
}
}
return resultValues;
This avoids the O(nlogn) behavior of sorting the values first, working in O(n) instead.
I think the "map" way by #RHSeeger is good enough. Here I just suggest another way, just for 'fun', so that u may take a look. It kind of give a stable result: first completed pairs appears first:
List<Integer> values = ....;
List<Integer> result = new ArrayList<Integer>();
Set<Integer> unpairedValues = new HashSet<Integer>();
for (int i : values) {
if (unpairedValues.contains(i)) {
result.add(i);
unpairedValues.remove(i);
} else {
unpairedValues.add(i);
}
}
// result contains what u want
One possible way in pseudo code:
sortedList = sort (list)
duplicates = empty list
while (length (list) > 1)
{
if (list [0] == list [1] )
{
duplicates.append (list [0] )
list.removeAt (0)
}
list.removeAt (0);
}
// ArrayList<Integer> list = {3, 6, 5, 3, 3, 6}
Collections.sort(list);
ArrayList<Integer> pairs = new ArrayList<Integer>();
int last = Integer.MIN_VALUE; // some value that will never appear in the list
for (Integer cur : list) {
if (last == cur) {
pairs.add(cur);
last = Integer.MIN_VALUE; // some value that will never appear in the list
} else {
last = cur;
}
}
System.out.println(Arrays.toString(pairs.toArray()));
Will output
[3, 6]
** Edit **
Slightly better algorithm, modifies the given list
// ArrayList<Integer> list = {3, 6, 5, 3, 3, 6}
Collections.sort(list);
int index = 1, last;
while (index < list.size()) {
last = list.remove(index - 1);
if (list.get(index - 1) == last) {
index++;
}
if (index == list.size()) {
list.remove(index - 1);
}
}

Categories