How to GROUP BY same strings in Java - java

I have got an Arraylist of strings and I need to return the Arraylist indexes of strings that are the same.
For example
Arraylist[0]: IPAddress
Arraylist[1]: DomainName
Arraylist[2]: IPAddress
Arraylist[3]: Filesize
The output should be:
Arraylist[0]
IPAddress|0,2 //0,2 denotes the arraylist index that is of the same
Arraylist[1]
DomainName|1
Arraylist[2]
Filesize|3
Any idea how can this be achieved?
What I have done is:
for(int i=0; i<arr.size(); i++){
if(arr.get(i).equals(arr.size()-1)){
//print index
}
}

With Java8 streams
List<String> strings = Arrays.asList("IPAddress", "DomainName", "IPAddress", "Filesize");
Map<String, List<Integer>> map = IntStream.range(0, strings.size()).boxed().collect(Collectors.groupingBy(strings::get));
System.out.println(map);
output
{DomainName=[1], Filesize=[3], IPAddress=[0, 2]}
To get the results in ordered
Map<String, List<Integer>> map = IntStream.range(0, strings.size())
.boxed()
.collect(Collectors.groupingBy(strings::get, LinkedHashMap::new, Collectors.toList()));

The mechanical steps are fairly straightforward:
Get a collection which can support a key (which is the string in your list) and a list of values representing the indexes in which they occur (which would be another ArrayList).
If the element exists in the collection, simply add the index to its value.
Otherwise, create a new list, add the index to that, then add that to the collection.
Here is some sample code below.
final List<String> list = new ArrayList<String>() {{
add("IPAddress");
add("DomainName");
add("IPAddress");
add("Filesize");
}};
final Map<String, List<Integer>> correlations = new LinkedHashMap<>();
for (int i = 0; i < list.size(); i++) {
final String key = list.get(i);
if (correlations.containsKey(key)) {
correlations.get(key).add(i);
} else {
final List<Integer> indexList = new ArrayList<>();
indexList.add(i);
correlations.put(key, indexList);
}
}
Any optimizations to the above are left as an exercise for the reader.

Related

Remove duplicates from list based on duplicate index in another list

I have 2 Lists: Names & IDs.
There are cases where the same name will appear multiple times. For example:
Names = {'ben','david','jerry','tom','ben'}
IDs = {'123','23456','34567','123','123'}
I know I can use
Set<String> set = new LinkedHashSet<>( Names );
Names .clear();
Names .addAll( set );
In order to remove duplicates, however, it not what I want.
What I would like to do is to check where Names has a duplicate value which in this case will be the last value and then remove from IDs the value at that position so the final result will be:
Names = {'ben','david','jerry','tom'}
IDs = {'123','23456','34567','123'}
How can I get the index of the duplicated value in order to remove it from the second list? or is there some easy and fast way to do it?
I'm sure I can solve it by using a loop but I try to avoid it.
SOLUTION:
I changed the code to use:
Map<String, String> map = new HashMap<>();
When using:
map.put(name,id);
It might not do the job since there are cases where the same name has different it and it won't allow duplicate in the name so just changed to map.put(id,name) and it did the job.
Thank you
You could collect the data from both input arrays/lists into a set of pairs and then recollect the pairs back to two new lists (or clear and reuse existing names/IDs lists):
List<String> names = Arrays.asList("ben","david","jerry","tom","ben");
List<String> IDs = Arrays.asList("123","23456","34567","123","123");
// assuming that both lists have the same size
// using list to store a pair
Set<List<String>> deduped = IntStream.range(0, names.size())
.mapToObj(i -> Arrays.asList(names.get(i), IDs.get(i)))
.collect(Collectors.toCollection(LinkedHashSet::new));
System.out.println(deduped);
System.out.println("-------");
List<String> dedupedNames = new ArrayList<>();
List<String> dedupedIDs = new ArrayList<>();
deduped.forEach(pair -> {dedupedNames.add(pair.get(0)); dedupedIDs.add(pair.get(1)); });
System.out.println(dedupedNames);
System.out.println(dedupedIDs);
Output:
[[ben, 123], [david, 23456], [jerry, 34567], [tom, 123]]
-------
[ben, david, jerry, tom]
[123, 23456, 34567, 123]
You can collect as a map using Collectors.toMap then get the keySet and values from map for names and ids list.
List<String> names = Arrays.asList("ben","david","jerry","tom","ben");
List<String> ids = Arrays.asList("123","23456","34567","123","123");
Map<String, String> map =
IntStream.range(0, ids.size())
.boxed()
.collect(Collectors.toMap(i -> names.get(i), i -> ids.get(i),
(a,b) -> a, LinkedHashMap::new));
List<String> newNames = new ArrayList<>(map.keySet());
List<String> newIds = new ArrayList<>(map.values());
You can do map creation part using loop also
Map<String, String> map = new LinkedHashMap<>();
for (int i = 0; i < names.size(); i++) {
if(!map.containsKey(names.get(i))) {
map.put(names.get(i), ids.get(i));
}
}
You could add your names one by one to a set as long as Set.add returns true and if it returns false store the index of that element in a list (indices to remove). Then sort the indices list in reverse order and use List.remove(int n) on both your names list and id list:
List<String> names = ...
List<String> ids = ...
Set<String> set = new HashSet<>();
List<Integer> toRemove = new ArrayList<>();
for(int i = 0; i< names.size(); i ++){
if(!set.add(names.get(i))){
toRemove.add(i);
}
}
Collections.sort(toRemove, Collections.reverseOrder());
for (int i : toRemove){
names.remove(i);
ids.remove(i);
}
System.out.println(toRemove);
System.out.println(names);
System.out.println(ids);

Map data between two array list

I have data:
Item: {String name,String count}
List<Item> listA =[{"a",10},{"b",10},{"c",10},{"d",10},{"e",10},{"f",10}]
List<Item> listB =[{"b",1},{"d",3},{"f",4},{"h",5}]
I want map data from listB to listA so I used code:
for (int i = 0; i < listB.size(); i++) {
Item item= listB.get(i); // get element in listB
for (int j = 0; j < listA.size(); j++) {
if (item.getName().equals(listA.get(j).getName())) {
listA.get(j).setCount(item.getCount());
}
}
}
My result:
listA =[{"a",10},{"b",1},{"c",10},{"d",3},{"e",10},{"f",4}]
My code working but I want do it better. Because it will duplicate item in for of listA. How I can do it better? Please help me. Thank you so much.
I'm not sure of your Java version,
but if you are using a higher version than Java 8, could you try this code below?
// Map is useful to remove duplicate data,
// so we will convert the list type to map.
Map<String, Integer> mapA = listA.stream()
.collect(Collectors.toMap(Item::getName, Item::getCount));
Map<String, Integer> mapB = listB.stream()
.collect(Collectors.toMap(Item::getName, Item::getCount));
// Let's put the data from mapA to mapB
mapB.entrySet().stream()
.filter(entry -> mapA.containsKey(entry.getKey()))
.forEach(entry -> mapA.put(entry.getKey(), entry.getValue()));
// Your expected result is list type, like below,
// [{"a": 10},{"b": 1},{"c": 10},{"d": 3},{"e": 10},{"f": 4}]
// convert it to list again!
List<Item> list = mapA.entrySet().stream()
.map(o -> new Item(o.getKey(), o.getValue())).collect(Collectors.toList());
Instead of a List try to create a HashMap. Then, loop through the entries of the mapB
and update the mapA. It will automatically replace the values for keys that exist in the map and generate the entries that don't exist.
Example Code:
Map<String, Integer> mapA = createMapA() , mapB = createMapB();
mapB.entrySet().foreach(entry -> mapA.put(entry.getKey(), entry.getValue());
The lambda code style is in Java8 but the general idea remains the same if you have Java7.

Collect to map the order/position value of a sorted stream

I am sorting a populated set of MyObject (the object has a getName() getter) in a stream using a predefined myComparator.
Then once sorted, is there a way to collect into a map the name of the MyObject and the order/position of the object from the sort?
Here is what I think it should look like:
Set<MyObject> mySet; // Already populated mySet
Map<String, Integer> nameMap = mySet.stream()
.sorted(myComparator)
.collect(Collectors.toMap(MyObject::getName, //HowToGetThePositionOfTheObjectInTheStream));
For example, if the set contain three objects (object1 with name name1, object2 with name name2, object3 with name name3) and during the stream they get sorted, how do I get a resulting map that looks like this:
name1, 1
name2, 2
name3, 3
Thanks.
A Java Stream doesn't expose any index or positioning of elements, so I know no way of replacing /*HowToGetThePositionOfTheObjectInTheStream*/ with streams magic to obtain the desired number.
Instead, one simple way is to collect to a List instead, which gives every element an index. It's zero-based, so when converting to a map, add 1.
List<String> inOrder = mySet.stream()
.sorted(myComparator)
.map(MyObject::getName)
.collect(Collectors.toList());
Map<String, Integer> nameMap = new HashMap<>();
for (int i = 0; i < inOrder.size(); i++) {
nameMap.put(inOrder.get(i), i + 1);
}
Try this one. you could use AtomicInteger for value of each entry of map. and also to guarantee order of map use LinkedHashMap.
AtomicInteger index = new AtomicInteger(1);
Map<String, Integer> nameMap = mySet.stream()
.sorted(myComparator)
.collect(Collectors
.toMap(MyObject::getName, value -> index.getAndIncrement(),
(e1, e2) -> e1, LinkedHashMap::new));
The simplest solution would be a loop, as a formally correct stream solution that would also work in parallel requires a nontrivial (compared to the rest) merge functions:
Map<String,Integer> nameMap = mySet.stream()
.sorted(myComparator)
.collect(HashMap::new, (m, s) -> m.put(s.getName(), m.size()),
(m1, m2) -> {
int offset = m1.size();
m2.forEach((k, v) -> m1.put(k, v + offset));
});
Compare with a loop/collection operations:
List<MyObject> ordered = new ArrayList<>(mySet);
ordered.sort(myComparator);
Map<String, Integer> result = new HashMap<>();
for(MyObject o: ordered) result.put(o.getName(), result.size());
Both solutions assume unique elements (as there can be only one position). It’s easy to change the loop to detect violations:
for(MyObject o: ordered)
if(result.putIfAbsent(o.getName(), result.size()) != null)
throw new IllegalStateException("duplicate " + o.getName());
Dont use a stream:
List<MyObject> list = new ArrayList<>(mySet);
list.sort(myComparator);
Map<String, Integer> nameMap = new HashMap<>();
for (int i = 0; i < list.size(); i++) {
nameMap.put(list.get(i).getName(), i);
}
Not only will this execute faster than a stream based approach, everyone knows what's going on.
Streams have their place, but pre-Java 8 code does too.

Counting same Strings from Array in Java

How can I count the same Strings from an array and write them out in the console?
The order of the items should correspond to the order of the first appearance of the item. If there are are two or more items of a kind, add an "s" to the item name.
String[] array = {"Apple","Banana","Apple","Peanut","Banana","Orange","Apple","Peanut"};
Output:
3 Apples
2 Bananas
2 Peanuts
1 Orange
I tried this:
String[] input = new String[1000];
Scanner sIn = new Scanner(System.in);
int counter =0;
String inputString = "start";
while(inputString.equals("stop")==false){
inputString = sIn.nextLine();
input[counter]=inputString;
counter++;
}
List<String> asList = Arrays.asList(input);
Map<String, Integer> map = new HashMap<String, Integer>();
for (String s : input) {
map.put(s, Collections.frequency(asList, s));
}
System.out.println(map);
But I don't know how to get the elements out of the Map and sort them like I would like.
You can use a Map to put your result, here is a simple example:
public static void main(String args[]){
String[] array = {"Apple","Banana","Apple","Peanut","Banana","Orange","Apple","Peanut"};
Map<String, Integer> result = new HashMap<>();
for(String s : array){
if(result.containsKey(s)){
//if the map contain this key then just increment your count
result.put(s, result.get(s)+1);
}else{
//else just create a new node with 1
result.put(s, 1);
}
}
System.out.println(result);
}
Use Java streams groupingBy and collect the results into a Map<String, Long> as shown below:
String[] array = {"Apple","Banana","Apple","Peanut","Banana","Orange","Apple", "Peanut"};
Map<String, Long> map = Stream.of(array).collect(Collectors.
groupingBy(Function.identity(), //use groupingBy array element
Collectors.counting())); //count number of occurances
System.out.println(map);//output the results of the Map
Java 8 would allow a pretty elegant way of doing this with groupingBy and counting. Using a LinkedHashMap instead of the default map should handle the ordering:
Arrays.stream(array)
.collect(Collectors.groupingBy(Function.identity(),
LinkedHashMap::new,
Collectors.counting()))
.entrySet()
.forEach(e -> System.out.println(e.getValue() +
"\t" +
e.getKey() +
(e.getValue() > 1 ? "s" : "")));
use java 8
Map<String, Long> myMap = Stream.of(array).collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));

Convert Hash Map to 2D Array

What is the easiest way to convert a HashMap into a 2D array?
HashMap map = new HashMap();
Object[][] arr = new Object[map.size()][2];
Set entries = map.entrySet();
Iterator entriesIterator = entries.iterator();
int i = 0;
while(entriesIterator.hasNext()){
Map.Entry mapping = (Map.Entry) entriesIterator.next();
arr[i][0] = mapping.getKey();
arr[i][1] = mapping.getValue();
i++;
}
This can only be done when the types of both key and value are the same.
Given:
HashMap<String,String> map;
I can create an array from this map with this simple loop:
String[][] array = new String[map.size()][2];
int count = 0;
for(Map.Entry<String,String> entry : map.entrySet()){
array[count][0] = entry.getKey();
array[count][1] = entry.getValue();
count++;
}
How about
Object[][] array = new Object[][]{map.keySet.toArray(), map.entrySet.toArray()};
Or, to be more specific about the types, let's say they're Strings: Set's toArray takes a hint argument, so that
String[][] array = new String[][]{map.keySet.toArray(new String[0]), map.entrySet.toArray(new String[0])};
Edit: I just realized a couple of days later that while this may work by chance, in general it shouldn't. The reason is the intermediate Set; although it is "backed by the map", there seems to be no explicit guarantee that it will iterate in any particular order. Thus the key- and entry-arrays might not be in the same order, which is a disaster for sure!
Using Java 8 stream:
#Test
void testMapToArray() {
Map<String, Object> map = new HashMap<>();
map.put("key1", "value1");
map.put("key2", 2);
Object[][] arr =
map.entrySet().stream()
.map(e -> new Object[]{e.getKey(), e.getValue()})
.toArray(Object[][]::new);
System.out.println(Arrays.deepToString(arr));
}
Output:
[[key1, value1], [key2, 2]]
Iterate over your Map using entrySet, and fill your array record with the Entry object

Categories