Java ArrayList adding values with same Id - java

So I have a an array List that looks like this: <id,value,id,value,...>and I should find all the elements with the same id and add together there respected values into a result array list (value always comes after the id). At first I thought the problem was easy and went ahead and tried this:
List<String> resultList = new ArrayList<>();
for(int i = 0; i < myList.size(); i+=2){
BigInteger currentID = new BigInteger(myList.get(i));
BigInteger currentSum = new BigInteger(myList.get(i+1));
String currentId = myList.get(i);
int j = 2;
while(j < myList.size()){
if(currentId.equals(myList.get(j))){
BigInteger value2 = new BigInteger(myList.get(j+1));
currentSum = currentID.add(value2);
}
resultList.add(currentId);
resultList.add(String.valueOf(currentSum));
j+=2;
}
}
Needless to say it isn't correct, one of the many problems is that values which haven been already added together will be readded into the result array so I think they should be removed somehow in the loop. Do you guys have any suggestions how to go around this?

Assuming you can use a Map:
My initial thought it to use a Map<String, String> map = new HashMap();
Pseudo code:
For each (id,value pair) in resultList
if map.get(id) exists
add the value to the existing entry in the map
else
add a new entry in the map for (id,value)
As code (NOTE: untested and might not compile, wouldn't copy & paste directly):
Map<String, String> map = new HashMap<>();
for(int i = 0; i < myList.size(); i+=2){
String listId = resultList.get(i); //get the Id from the resultList
String listValue = resultList.get(i+1) //get the value from the resultList
if(map.get(listId) != null) { // if the map has this Id
map.put(listId, map.get(listId)+ listValue); // add the Id's list value
}
else { // if the map doesn't have this Id
map.put(listId, listValue) // add the entry to the map
}
}
You can then take the resulting map and transform is back into a list.
I'll let you write the code to take each entry in the map and add it to a new List. Google can be helpful here.

If you want to use streams you can do it like this.
Given a list like the following:
List<BigInteger> list = List.of(1,10,3,20,3,40,1,5,4,7,1,8,4,9,6,20)
.stream().mapToLong(a -> a).mapToObj(
BigInteger::valueOf).collect(Collectors.toList());
First accumulate the sums in a map with the key being the id.
Map<BigInteger, BigInteger> mapfinal =
IntStream.iterate(0, i -> i < list.size(), i -> i += 2).mapToObj(
i -> new BigInteger[] { list.get(i), list.get(i + 1)
}).collect(Collectors.groupingBy(i -> i[0],
Collectors.reducing(BigInteger.ZERO,
a -> a[1],
(a, b) -> a.add(b))));
You can stop there and just use the map, or you can convert it to a list like the one you started with of alternating id/value pairs.
List<BigInteger> listfinal = mapfinal
.entrySet()
.stream()
.flatMap(e -> Stream.of(e.getKey(), e.getValue()))
.collect(Collectors.toList());
And print the list.
System.out.println(listfinal);
[1, 23, 3, 60, 4, 16, 6, 20]

It is easiest to do this using a Map. But it can also be done without changing the input data structure, or using any intermediate data structure.
Here is an untested code sample. I have simplified the problem by using List<Integer>
List<Integer> myList = ...
List<Integer> resultList = new ArrayList<>();
for (int i = 0; i < myList.size(); i += 2) {
int id = myList.get(i);
// Check that we haven't accumulated this id already
boolean seen = false;
for (int j = 0; j < i && !seen; j += 2) {
seen = myList.get(j) == id;
}
if (seen) {
continue;
}
// Accumulate values for 'id'
int sum = myList.get(i + 1);
for (int j = i + 2; j < myList.size(); j += 2) {
if (myList.get(j) == id) {
sum += myList.get(j + 1);
}
}
// Send to output ...
resultList.add(id);
resultList.add(sum);
}
This solution is O(N^2). And since you are actually using String and BigInteger rather than Integer, that makes it even more expensive.
A solution using a Map should be O(NlogN) or O(N) depending on the map type.
Aside: It would be faster in some cases if you used resultList rather than myList in the first inner loop. But the way I did it above is more obvious ...

Related

Interview Question - Printing the pattern formed by Characters of the given String sorted in Ascending order

I was asked this question during an interview.
Print the pattern of characters from the given word by arranging the characters in ascending order based on the number of occurrencies.
Input: "abbccbddddeeeee"
Output:
a
cc
bbb
dddd
eeeee
I was not able to solve it on the spot, and I can't make my solution working for now.
Specifically, I'm having trouble with arranging in ascending order after populating a HashMap.
My code:
String test = "abbccbdddddddeeeee";
List<Character> orderedList = new ArrayList<Character>();
List<Integer> orderedNumber = new ArrayList<Integer>();
char[] testing = test.toCharArray();
int value = 1;
Map<Character, Integer> pattern = new HashMap<>();
for (int i = 0; i < testing.length; i++) {
value = 1;
if (!pattern.containsKey(testing[i])) {
pattern.put(testing[i], value);
for (int j = i + 1; j < test.length(); j++) {
if (testing[i] == testing[j]) {
value = pattern.get(testing[i]);
value += 1;
pattern.replace(testing[i], value);
}
}
orderedList.add(testing[i]);
orderedNumber.add(value);
}
}
This is for the printing part, but as you can see. I have little success in it.
System.out.println(orderedList);
System.out.println(orderedNumber);
int minNumber = 0;
int minIndex = 0;
for (int i = 0; i < orderedList.size(); i++) {
minNumber = pattern.get(orderedList.get(i));
int maxNumber = 0;
for (int j = i +1; j < orderedList.size(); j++) {
if (minNumber > pattern.get(orderedList.get(j))) {
minNumber = pattern.get(orderedList.get(j));
maxNumber = pattern.get(orderedList.get(i));
}
}
System.out.println(minNumber);
}
With your idea of using Map you were on the right track.
Imperative approach
Main steps:
Generate a map of frequencies for each symbol in the given string.
Sort the map entries based on values. Obviously, it's not doable withing a map, we need a different collection for that. We can dump map entries into a List and sort it.
Print the contents of the list.
That's how it can be implemented:
public static void printFrequencyPattern(String str) {
Map<Character, Integer> frequencies = getFrequencies(str);
List<Map.Entry<Character, Integer>> sortedEntries = toSortedEntryList(frequencies);
for (Map.Entry<Character, Integer> entry: sortedEntries) {
System.out.println(entry.getKey().toString().repeat(entry.getValue()));
}
}
public static Map<Character, Integer> getFrequencies(String str) {
Map<Character, Integer> frequencies = new HashMap<>();
for (int i = 0; i < str.length(); i++) {
frequencies.merge(str.charAt(i), 1, Integer::sum);
}
return frequencies;
}
public static List<Map.Entry<Character, Integer>> toSortedEntryList(Map<Character, Integer> frequencies) {
List<Map.Entry<Character, Integer>> sortedEntries = new ArrayList<>(frequencies.entrySet());
sortedEntries.sort(Map.Entry.comparingByValue());
return sortedEntries;
}
main()
public static void main(String[] args) {
String test = "abbccbdddddddeeeee";
printFrequencyPattern("abbccbdddddddeeeee");
}
Output:
a
cc
bbb
eeeee
ddddddd
Stream-based Solution
In case if you're comfortable with using Stream IPA, here's a one-liner which everything as the code above within a single statement.
The idea behind the code is mostly the same, collector toMap() generates a map of frequencies. Then a stream of its entries gets created and sorted by value of each entry.
str.codePoints()
.boxed()
.collect(Collectors.toMap(
Function.identity(),
ch -> 1,
Integer::sum
))
.entrySet().stream()
.sorted(Map.Entry.comparingByValue())
.forEachOrdered(entry ->
System.out.println(Character.toString(entry.getKey()).repeat(entry.getValue()))
);
Using a map for a frequency count is a common practice and a good start. To demonstrate:
First, create some random data. I wanted there to be the same number of some elements to allow for sorting in lexical order if the counts were equal (not required based on your question but easy to do).
String s = "abbcccddddeeeeffffffggggggg";
List<String> list =
new ArrayList<>(Arrays.stream(s.split("")).toList());
Collections.shuffle(list);
Now define a comparator to use in the sort - first on count, then on lexical key. Doing it outside the stream avoids clutter.
Comparator<Entry<String, Long>> comp = Entry
.<String, Long>comparingByValue()
.thenComparing(Entry.comparingByKey());
Then stream the list and do a frequency count. The key will be the letter and the count will be its frequency of occurrences. Take the resulting entrySet of the map and sort first based on count, then based on lexical order. Then print using the Entry fields.
list.stream()
.collect(Collectors.groupingBy(a -> a,
Collectors.counting()))
.entrySet().stream().sorted(comp)
.forEach(e -> System.out.println(
e.getKey().repeat(e.getValue().intValue())));
prints
a
bb
ccc
dddd
eeee
ffffff
ggggggg
One way you can do this is by storing occurrence number per character in a map and then sort the map. After that you can print the map as your pattern.
You have already stored the occurrences in a map.
// sort the map. You can do it in any way you like
Map<Character, Integer> sortedMap = pattern.entrySet().stream()
.sorted(Map.Entry.comparingByValue())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,(e1, e2) -> e1, LinkedHashMap::new));
//print the pattern
System.out.println(sortedMap);
for(Character key : sortedMap.keySet()){
for(int i=1; i<= sortedMap.get(key);i++){
System.out.print(key);
}
System.out.println();
}
This is a basic counting sort. You use an array of int to count the occurrence of each character. Then use the array to print them in ascending order. The indices 0-26 map to 'a' to 'z'.
Then use counting sort again to sort by occurrence. This works well only if your string isn't too long.
import java.util.*;
public class Main{
public static void main(String[] args) {
String s = "abbccbddddeeeee";
int[] count = new int[26];
for(char c: s.toCharArray())
count[c-'a']++;
char[] sort = new char[s.length()];
for(int i=0; i<count.length; i++)
sort[count[i]]=(char)(i+'a');
for(int i=0; i<sort.length; i++){
if ('a' <= sort[i] && sort[i] <= 'z'){
for(int j=0; j<i; j++)
System.out.print(sort[i]);
System.out.println();
}
}
}
}

Is there a way to Dynamically populate values into hashmap

Im trying to populate values dynamically into a hashmap and it is taking only the last values that im keeping into the map
for (int i = 0; i < 22; i++) {
for (int j = 0; j < col.length; j++) {
String[] Temp = finval.get(i);
ls[i] = JasperRepo.getMapObject();
ls[i].put(columnsList[j], Temp[j]);
}
m.add(ls[i]);
}
Is there any way to add values dynamically?
Here is a simple demonstration. You can make this even more concise by using some advanced features of map, but the idea is the same. It separates even and odd values.
Map<String, List<Integer>> map = new HashMap<>();
String key;
for (int i = 0; i < 10; i++) {
// assign key based on value
if (i % 2 == 0) {
key = "evens";
} else {
key = "odds";
}
// retrieve or create the list for the items.
List<Integer> list = map.get(key);
if (list == null) {
list = new ArrayList<>();
map.put(key, list);
}
// add the item to the list
list.add(i);
}
map.forEach((k,v)-> System.out.println(k + " -> " + v));
It prints the following:
odds -> [1, 3, 5, 7, 9]
evens -> [0, 2, 4, 6, 8]
Do you wish to update the values of ls[i], so that they are being incremented if there are duplicate keys?
If yes, you could try this:
if (!ls[i].containsKey(columnsList[j])) {
ls[i].put(columnsList[j], Temp[j]);
} else {
ls[i].put(columnsList[j], ls[i].get(columnsList[j]) + Temp[j]);
}

Problem finding duplicate numbers in array, is my technique bad?

I used a HashMap to store the occurrences of each element, and then iterated over the hash map to get duplicated element, but something doesn't feel right about this solution.
Problem statement in Firecode.io:
Write a method duplicate to find the repeated or duplicate elements in an array. This method should return a list of repeated integers in a string with the elements sorted in ascending order (as illustrated below).
duplicate({1,3,4,2,1}) --> "[1]"
duplicate({1,3,4,2,1,2,4}) --> "[1, 2, 4]"
Note: You may use toString() method to return the standard string representation of most data structures, and Arrays.sort() to sort your result.*
Here is my code:
public String duplicate(int[] numbers) {
HashMap < Integer, Integer > hs = new HashMap < Integer, Integer > ();
for (int i = 0; i < numbers.length; i++) {
if (hs.get(numbers[i]) == null) {
hs.put(numbers[i], 1);
} else hs.put(numbers[i], (Integer) hs.get(numbers[i]) + 1);
}
int size = 0;
for (int i: hs.keySet()) {
if (hs.get(i) > 1) {
size++;
}
}
int j = 0;
int[] a = new int[size];
for (int i: hs.keySet()) {
if (hs.get(i) > 1) {
a[j++] = i;
}
}
Arrays.sort(a);
return Arrays.toString(a);
}
Here's the way I would do it: (comments for educational purposes, would probably not have them in production code.)
public String duplicate(int[] numbers) {
// holds the items we've encountered more than once.
// TreeSet<> keeps things in sorted order for us.
final SortedSet<Integer> duplicates = new TreeSet<>();
// keeps track of items we've encountered.
final Set<Integer> encountered = new HashSet<>();
// iterate over every number
for (final int number : numbers) {
// Add the item to encountered. Set.add() will return true if
// the element is new to the set.
if (!encountered.add(number)) {
// Since the element wasn't new, ensure this item exists in the duplicates collection.
duplicates.add(number);
}
}
return duplicates.toString();
}
Since you don't have to tell how many times an element is duplicated you only need a Set to remember which elements are unique and which not. If you know the element values (e.g. numbers between 1 and 10) you could further simplify Set to boolean[] or a bit vector:
int[] numbers = {1, 3, 4, 2, 2, 1, 2, 4, 4};
Set<Integer> unique = new HashSet<>();
Set<Integer> duplicates = new HashSet<>();
for (int n : numbers) {
if (!unique.add(n)) {
duplicates.add(n);
}
}
List<Integer> result = new ArrayList<>(duplicates);
result.sort(Integer::compareTo);
System.out.println(result); // [1, 2, 4]
If you are using Java 8 or beyond you can try:
public String duplicate(int[] numbers) {
Map<Integer, Integer> hs = new HashMap<>();
for ( int i : numbers ) {
hs.merge( i, 1, Integer::sum);
}
return '[' +
hs.entrySet()
.stream()
.filter( e -> e.getValue() > 1 )
.map(Entry::getKey)
.sorted()
.map(i -> i.toString())
.collect(Collectors.joining(", ")) +
']';
}

How to change following nested FOR loops into Java8 Stream API? [duplicate]

This question already has answers here:
How can I turn a List of Lists into a List in Java 8?
(12 answers)
Closed 6 years ago.
The following example may be trivial, but I have created it to show what I need to achieve using different data (not integers). This code is runnable etc.
List<List<Integer>> master = new ArrayList<>();
for (int i = 0; i < 10; i++) {
List<Integer> inner = new ArrayList<Integer>();
master.add(inner);
for (int j = 0; j < 10; j++) {
inner.add(i * 10 + j);
}
}
System.out.println(master);
//lets make single collections from that but add 1000 to every element - this represents some intermediate operations to generate part of final result
List<Integer> finalAccumulated = new ArrayList<Integer>(); // this will represent our accumulated, final result
for (List<Integer> topLvl : master) {
ArrayList<Integer> intermedialeAccumulated = new ArrayList<>(); //this step is important as it represents returning a collection of results for stream#map not single result
for (Integer nested : topLvl) { // this represents stream#map (or maybe collector?)
intermedialeAccumulated.add(nested + 1000);
}
finalAccumulated.addAll(intermedialeAccumulated); // this represent accumulation of collection of results, not just single result like stream#map do
}
System.out.println(finalAccumulated);
How to get the same result as in finalAccumulated using single Stream. By single I mean that in call chain there can be only single terminate action so the resulting form would be
finalAccumulated=master.stream()...intermediateCalls()...terminateCollectingCall();
Online ide with running code here
Just use a flat map:
List<Integer> finalAccumulated = master
.stream()
.flatMap((x) -> x.stream())
.map((i) -> i + 1000)
.collect(Collectors.toList());
private static void test() {
List<List<Integer>> master = new ArrayList<>();
for (int i = 0; i < 10; i++) {
List<Integer> inner = new ArrayList<Integer>();
master.add(inner);
for (int j = 0; j < 10; j++) {
inner.add(i * 10 + j);
}
}
System.out.println(master);
//lets make single collections from that but add 1000 to every element - this represents some intermediate operations to generate part of final result
List<Integer> finalAccumulated = new ArrayList<Integer>(); // this will represent our accumulated, final result
for (List<Integer> topLvl : master) {
ArrayList<Integer> intermedialeAccumulated = new ArrayList<>(); //this step is important as it represents returning a collection of results for stream#map not single result
for (Integer nested : topLvl) { // this represents stream#map (or maybe collector?)
intermedialeAccumulated.add(nested + 1000);
}
finalAccumulated.addAll(intermedialeAccumulated); // this represent accumulation of collection of results, not just single result like stream#map do
}
//map then using flatmap
List<Integer> finalAccumulated2 = master.stream().map(topLvl -> {
ArrayList<Integer> intermedialeAccumulated = new ArrayList<>();
for (Integer nested : topLvl) {
intermedialeAccumulated.add(nested + 1000);
}
return intermedialeAccumulated;
}).flatMap(intermedialeAccumulated -> intermedialeAccumulated.stream())
.collect(Collectors.toList());
}
Here's how to produce the same finalAccumulated using a single Stream
List<Integer> finalAccumulated = IntStream.range(1000, 1000 + 10 * 10).boxed().collect(Collectors.toList());
Here would be an over-engineered and generic approach for such an operation
private static <T> List<T> flattenWith(List<List<T>> master, Function<T, T> transform) {
return master.stream()
.flatMap(List::stream)
.map(transform)
.collect(Collectors.toList());
}
Usage
flattenWith(master, x -> x + 1000)

How do I collect multiple maximum values from a List

How to get max from an ArrayList that has more than one max? For example, if an ArrrayList contains max = 20 stored at index 2, 3 and 6, how do you get all that indicies?
The obvious way is to first get maximum value by Collections.max(), then collect indicies where items are equal to max:
public <T extends Comparable<? super T>> List<Integer> maxIndicies(List<T> input) {
if (input.isEmpty()) // avoid exception thrown by Collections.max() if input is empty
return Collections.emptyList();
final T max = Collections.max(input);
return IntStream.range(0, input.size())
.filter(i -> input.get(i).compareTo(max) == 0)
.boxed()
.collect(Collectors.toList());
}
Additionally, I'd like to propose another solution where iteration is performed only once. During iteration, you need to check two things for each item: 1) if it is greater than current max, set a new max and reset result list, 2) if it is equal to current max, add its index to result list:
public <T extends Comparable<? super T>> List<Integer> maxIndicies(List<T> input) {
T max = null;
List<Integer> res = new ArrayList<>();
for (int i = 0; i < input.size(); i++) {
T item = input.get(i);
if (max == null || item.compareTo(max) > 0) { // item > max => reset
res.clear();
max = item;
res.add(i);
} else if (item.compareTo(max) == 0) // item equals current max
res.add(i);
}
return res;
}
This won't give you value of max item itself, but you can get it by any returned index, simply as:
List<Integer> maxInd = maxIndicies(list);
maxValue = maxInd.isEmpty() ? null : list.get(maxInd.get(0));
This sounds like a homework for your programming course. You should do it yourself but anyway here is the solution.
private List<Integer> getAllMaxIndices(List<Integer> aList) {
List<Integer> result = new ArrayList<Integer>();
// check argument
if (aList == null || aList.isEmpty()) {
return result;
}
// initialize the list with the index of the first element
result.add(0);
Integer tmpInt;
Integer tmpFirstIndexOfMaxInt;
Integer tmpMaxInt;
for (int i = 0; i < aList.size(); i++) {
// save the current integer and the currently maximum integer
tmpInt = aList.get(i);
tmpFirstIndexOfMaxInt = result.get(0);
tmpMaxInt = aList.get(tmpFirstIndexOfMaxInt);
// if the current element is greater than the last found
if (tmpInt > tmpMaxInt) {
// empty the result
result.clear();
// start collecting indices again
result.add(i);
}
// if the current element is equal to the last found
else if (tmpInt.intValue() == tmpMaxInt.intValue()) {
// insert the current index in the result
result.add(i);
}
}
return result;
}
I will leave it to you to write the code which tests this function.
Another approach using streams. That solution assumes that you want to know how often the max occurs (not the indices).
public static Map.Entry<Integer, Long> getMaxWithOccurrences(
List<Integer> list) {
return list
.stream()
.collect(
Collectors.groupingBy(i -> i, TreeMap::new,
Collectors.counting())).lastEntry();
}
I'd use a simple and easy to read for loop.
public List<Integer> getMaxIndices(List<Integer> values) {
Integer max = Collections.max(values);
List<Integer> maxIndices = new ArrayList<>();
for (int i = 0; i < values.size(); i++) {
if (values.get(i).equals(max)) {
maxIndices.add(Integer.valueOf(i));
}
}
return maxIndices;
}
Integer maxValue = Collections.max(list);
int numberofMax = Collections.frequency(list, maxValue);
this "numberofMax" will return how many maximum values the "list" has.
usual max finders only store the maximum met value, here you will have to maintain a list of indexes matching the maximum value.
You can do it in following way:
public void findMaxIndices() {
//Your list with numbers
List<Integer> list = new ArrayList<Integer>(Arrays.asList(1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9));
//Sorted Map which will contain key as numbers and value as list of indices where your 'key' exists in the list
SortedMap<Integer, List<Integer>> indexMapping = new TreeMap<Integer, List<Integer>>();
for(int i = 0; i< list.size(); i++) {
//Get the number at index i
int number = list.get(i);
//Check if any index corresponding to 'number' as index has been added to your map
List<Integer> mapping = indexMapping.get(number);
if(mapping == null) {
//instantiate the list if no index has been added yet
mapping = new ArrayList<Integer>();
//Key as your 'number'
indexMapping.put(number, mapping);
}
//Add the index of the 'number' to the mapping list, which is mapped by key as 'number'
mapping.add(i);
}
//Last key in sorted map will be your highest number in the list, get the value corresponding to it. Following prints: [8,17]
int maxNumber = indexMapping.lastKey(); //Maximum number found
System.out.println(indexMapping.get(maxNumber)); //Indices where maximum number exists
}
This way, you can also find indices with lowest values easily:
indexMapping.get(indexMapping.firstKey());

Categories