How to modify Map's output style? - java

I have a method like this:
public static void main(String[] args) {
Map<String, Long> var = Files.walk(Paths.get("text"), number)
.filter(path -> !Files.isDirectory(path))
.map(Path::getFileName)
.map(Object::toString)
.filter(fileName -> fileName.contains("."))
.map(fileName -> fileName.substring(fileName.lastIndexOf(".") + 1))
.collect(Collectors.groupingBy(
extension -> extension,
Collectors.counting()
)
);
System.out.println(var);
}
As we know, output will be like:
{text=1, text=2}
Is it possible to change the output to:
text = 1
text = 2
I want to have some more freedom, e. g. remove brackets and commas, add new lines after number etc.

You can iterate over the results of the map, and just print them out:
for (Map.Entry<String, Long> entry : var.entrySet())
{
System.out.println(entry.getKey() + " = " + entry.getValue());
}
Then if you want more control, you can just modify how the line gets printed, since you have the raw key and value objects.

What about something as simple as:
map.toString().replace(", ", "\n");

Related

Finding duplicate using HashMap in java not working

I am new to java.I searched this problem in SO but I tried in my own way.I have map and it is printing following :
Key = MX Week Email Pulls 010521 -010621_22780_1, Value = 010521010621
Key = MX Week Email Pulls 010721 -010921_23122, Value = 010721010921
Key = MX Week Email Pulls 010321 -010421_22779, Value = 010321010421
Key = MX Week Email Pulls 010521 -010621_22780, Value = 010521010621
Since,key is different,I want to find duplicate of these keys by using values. Since the duplicate value above is:
010521010621
010521010621
I tried to find duplicate by increasing count value:
public void doAnalysis(Map<String,String> mapAll) {
List<String> listOf=new ArrayList<>();
Map<String,Integer> putDupli=new HashMap<String, Integer>();
for (Map.Entry<String,String> entry : mapAll.entrySet()) {
System.out.println("Key = " + entry.getKey() +
", Value = " + entry.getValue());
if(!putDupli.containsValue(entry.getValue())) {
putDupli.put(entry.getValue(),0);
}
else {
putDupli.put(entry.getValue(),putDupli.get(entry.getKey())+1);
}
}
System.out.println(putDupli);
}
The line System.out.println(putDupli); is printing
{010521010621=0, 010721010921=0, 010321010421=0}
My expected output was:
{010521010621=2, 010721010921=0, 010321010421=0}
Should be if(!putDupli.containsKey(entry.getValue())) {
instead of
if(!putDupli.containsValue(entry.getValue())) {
as the other map is putting the value as key.
Also, putDupli.put(entry.getValue(),putDupli.get(entry.getKey())+1);
should be
putDupli.put(entry.getValue(), putDupli.get(entry.getValue()) + 1);.
Furthermore, you probably want the initial count to be 1 instead of 0.
If catching those bugs is hard at first, try using a debugger.
If you are using java 8, you can use Stream to get the count
Map<String, Long> dupliMap = mapAll.values().stream()
.collect(Collectors.groupingBy(
x -> x,
Collectors.counting()
));
try this:
!putDupli.containsValue(entry.getValue().toString)
As your comapring the values of an Integer to String.
You might want to consider a different approach. Create a Map<Integer,List<Long>> to store the values as follows:
String[] data = { "010521,010521010621",
"010721,010721010921", "010321,010321010421",
"010521,010521010621" };
Map<Integer, List<Long>> map =
Arrays.stream(data).map(str -> str.split(","))
.collect(Collectors.groupingBy(
arr -> Integer.valueOf(arr[0]),
Collectors.mapping(
arr -> Long.valueOf(arr[1]),
Collectors.toList())));
Then you can find the count of each value.
for (List<Long> list : map.values()) {
System.out.println(list.get(0) + "=" + list.size());
}
Prints
10321010421=1
10721010921=1
10521010621=2

How to print a single Key-Value pair in a HashMap in java?

Is it possible to do so? If yes, what is the required syntax/method?
I tried it by using,
System.out.println(key+"="+HM.get(key));
but it prints it in this format,
key= value
whereas, I need
key=value
(Due to outputting format in HackerRank)
while(sc.hasNextLine())
{
String s=sc.nextLine();
if(a.containsKey(s))
{
System.out.println(s+"="+a.get(s));
}
else
System.out.println("Not found");
}
EDIT 1:
I saw the solution given, the person used the following code,
while(scan.hasNext()){
String s = scan.next();
Integer phoneNumber = phoneBook.get(s);
System.out.println(
(phoneNumber != null)
? s + "=" + phoneNumber
: "Not found"
);
}
Now, why does this not have white space in the answer?
The only visible change I see is that this person used an object instead of primitive data type.
Also,this person used int instead of string in accepting the phone number, I initially did the same but it gave me an InputMismatchException .
There is no avalilable method in https://docs.oracle.com/javase/7/docs/api/java/util/Map.html to get Entity from map if there is any key or value available. Try below code if it help :
if (map.containsKey(key)) {
Object value = map.get(key);
System.out.println("Key : " + key +" value :"+ value);
}
You can use entrySet() (see entrySet) to iterate over your map.
You will then have access to a Map Entry which contains the methods getValue() and getKey() to retreive both the value and the key of your mapped object.
entrySet() returns a Set, and Set extends Collection, which offers the stream() method, so you can use stream to loop over and process your entrySet :
map
.entrySet() // get a Set of Entries of your map
.stream() // get Set as Stream
.forEach( // loop over Set
entry -> // lambda as looping over set entries implicitly returns an Entry
System.out.println(
entry.getKey() // get the key
+ "="
+ entry.getValue() // get the value
);
If needed, you can add .filter() to process only elements that matches your conditions in your Stream.
Working example here
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
Map<String, String> myMap = new HashMap<String, String>() {
private static final long serialVersionUID = 1L;
{
for (int i = 1 ; i < 10 ; i++) {
put("key"+i, "value"+i);
}
}
};
myMap.entrySet().stream().forEach(
entry -> System.out.println(entry.getKey() + "=" + entry.getValue())
);
}
}
Output :
key1=value1
key2=value2
key5=value5
key6=value6
key3=value3
key4=value4
key9=value9
key7=value7
key8=value8

Java 8 Stream to determine a maximum count in a text file

For my assignment I have to replace for loops with streams that count the frequency of words in a text document, and I am having trouble figuring the TODO part out.
String filename = "SophieSallyJack.txt";
if (args.length == 1) {
filename = args[0];
}
Map<String, Integer> wordFrequency = new TreeMap<>();
List<String> incoming = Utilities.readAFile(filename);
wordFrequency = incoming.stream()
.map(String::toLowerCase)
.filter(word -> !word.trim().isEmpty())
.collect(Collectors.toMap(word -> word, word -> 1, (a, b) -> a + b, TreeMap::new));
int maxCnt = 0;
// TODO add a single statement that uses streams to determine maxCnt
for (String word : incoming) {
Integer cnt = wordFrequency.get(word);
if (cnt != null) {
if (cnt > maxCnt) {
maxCnt = cnt;
}
}
}
System.out.print("Words that appear " + maxCnt + " times:");
I have tried this:
wordFrequency = incoming.parallelStream().
collect(Collectors.toConcurrentMap(w -> w, w -> 1, Integer::sum));
But that is not right and I'm not sure how to incorporate maxCnt into the stream.
Assuming you have all the words extracted from a file in a List<String> this word count for each word can be computed using this approach,
Map<String, Long> wordToCountMap = words.stream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
The most freequent word can then be computed using the above map like so,
Entry<String, Long> mostFreequentWord = wordToCountMap.entrySet().stream()
.max(Map.Entry.comparingByValue())
.orElse(new AbstractMap.SimpleEntry<>("Invalid", 0l));
You may change the above two pipelines together if you wish like this,
Entry<String, Long> mostFreequentWord = words.stream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
.entrySet().stream()
.max(Map.Entry.comparingByValue())
.orElse(new AbstractMap.SimpleEntry<>("Invalid", 0l));
Update
As per the following discussion it is always good to return an Optional from your computation like so,
Optional<Entry<String, Long>> mostFreequentWord = words.stream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
.entrySet().stream()
.max(Map.Entry.comparingByValue());
Well, you have done almost everything you needed with that TreeMap, but it seems you don't know that it has a method called lastEntry and that is the only one you need to call after you computed wordFrequency to get the word with the highest frequency.
The only problem is that this is not very optimal, since TreeMap sorts the data on each insert and you don't need sorted data, you need the max. Sorting in case of TreeMap is O(nlogn), while inserting into a HashMap is O(n).
So instead of using that TreeMap, all you need to change is to a HashMap:
wordFrequency = incoming.stream()
.map(String::toLowerCase)
.filter(word -> !word.trim().isEmpty())
.collect(Collectors.toMap(
Function.identity(),
word -> 1,
(a, b) -> a + b,
HashMap::new));
Once you have this Map, you need to find max - this operation is O(n) in general and could be achieved with a stream or without one:
Collections.max(wordFrequency.entrySet(), Map.Entry.comparingByValue())
This approach with give you O(n) for HashMap insert, and O(n) for finding the max - thus O(n) in general, so it's faster than TreeMap
Ok, first of all, your wordFrequency line can make use of Collectors#groupingBy and Collectors#counting instead of writing your own accumulator:
List<String> incoming = Arrays.asList("monkey", "dog", "MONKEY", "DOG", "giraffe", "giraffe", "giraffe", "Monkey");
wordFrequency = incoming.stream()
.filter(word -> !word.trim().isEmpty()) // filter first, so we don't lowercase empty strings
.map(String::toLowerCase)
.collect(Collectors.groupingBy(s -> s, Collectors.counting()));
Now that we got that out of the way... Your TODO line says use streams to determine maxCnt. You can do that easily by using max with naturalOrder:
int maxCnt = wordFrequency.values()
.stream()
.max(Comparator.naturalOrder())
.orElse(0L)
.intValue();
However, your comments make me think that what you actually want is a one-liner to print the most frequent words (all of them), i.e. the words that have maxCnt as value in wordFrequency. So what we need is to "reverse" the map, grouping the words by count, and then pick the entry with highest count:
wordFrequency.entrySet().stream() // {monkey=3, dog=2, giraffe=3}
.collect(groupingBy(Map.Entry::getValue, mapping(Map.Entry::getKey, toList()))).entrySet().stream() // reverse map: {3=[monkey, giraffe], 2=[dog]}
.max(Comparator.comparingLong(Map.Entry::getKey)) // maxCnt and all words with it: 3=[monkey, giraffe]
.ifPresent(e -> {
System.out.println("Words that appear " + e.getKey() + " times: " + e.getValue());
});
This solution prints all the words with maxCnt, instead of just one:
Words that appear 3 times: [monkey, giraffe].
Of course, you can concatenate the statements to get one big do-it-all statement, like this:
incoming.stream() // [monkey, dog, MONKEY, DOG, giraffe, giraffe, giraffe, Monkey]
.filter(word -> !word.trim().isEmpty()) // filter first, so we don't lowercase empty strings
.map(String::toLowerCase)
.collect(groupingBy(s -> s, counting())).entrySet().stream() // {monkey=3, dog=2, giraffe=3}
.collect(groupingBy(Map.Entry::getValue, mapping(Map.Entry::getKey, toList()))).entrySet().stream() // reverse map: {3=[monkey, giraffe], 2=[dog]}
.max(Comparator.comparingLong(Map.Entry::getKey)) // maxCnt and all words with it: 3=[monkey, giraffe]
.ifPresent(e -> {
System.out.println("Words that appear " + e.getKey() + " times: " + e.getValue());
});
But now we're stretching the meaning of "one statement" :)
By piecing together information I was able to successfully replace the for loop with
int maxCnt = wordFrequency.values().stream().max(Comparator.naturalOrder()).get();
System.out.print("Words that appear " + maxCnt + " times:");
I appreciate all the help.

Is there a way to change the double `for` loop into java8's lamda expression?

for example
Map<String, List<String>> headers = connection.getHeaderFields();
for (String key: headers.keySet()){
for (String value: headers.get(key)){
System.out.println(key+":"+value);
}
}
Can this code change to the (Method References) somehow like this?
Consumer<String> consumer = headers::get;
headers.keySet().forEach(consumer);
But this is not correct.I think there is a way to do this:
Consumer<String> consumer = headers::get;
BiConsumer<String, String> header = (key, value) -> System.out.println(key+":"+value);
combine 1,2
Is my though right?
Looks like a case where you can utilize the flatMap:
headers.entrySet().stream()
.flatMap(k -> k.getValue().stream().map(v -> k + ":" + v))
.forEach(System.out::println);
Regarding your question about Consumer and BiConsumer, it's totally missed - those are just Functional Interfaces and can be used only for representing functions - they need to be passed somewhere to be used.
First, change these for loops into a loop on map entries to improve efficiency (Q&A on iterating maps in Java):
for (Map.Entry<String, List<String>> entry : headers.entrySet()){
for (String value: entry.getValue()){
System.out.println(entry.getKey()+":"+value);
}
}
Now you are ready to convert it to stream-based iteration:
headers.entrySet().forEach(entry -> {
entry.getValue().forEach(value -> System.out.println(entry.getKey()+":"+value))
});
You can use something like this
headers.forEach((key, value) -> {
value.forEach((current) -> {
System.out.println(key + ":" + current);
});
});

grouping List of Objects and counting using Java collection

Which Java Collection class is better to group the list of objects?
I have a list of messages from users like below:
aaa hi
bbb hello
ccc Gm
aaa Can?
CCC yes
ddd No
From this list of message object I want to count and display aaa(2)+bbb(1)+ccc(2)+ddd(1). Any code help?
You can use Map<String, Integer> where the keys represent the individual strings, and the map value is the counter for each one.
So you can do something like:
// where ever your input comes from: turn it into lower case,
// so that "ccc" and "CCC" go for the same counter
String item = userinput.toLowerCase();
// as you want a sorted list of keys, you should use a TreeMap
Map<String, Integer> stringsWithCount = new TreeMap<>();
for (String item : str) {
if (stringsWithCount.contains(item)) {
stringsWithCount.put(item, stringsWithCount.get(item)+1));
} else {
stringsWithCount.put(item, 0);
}
}
And then you can iterate the map when done:
for (Entry<String, Integer> entry : stringsWithCount.entrySet()) {
and build your result string.
That was like the old-school implementation; if you want to be fancy and surprise your teachers, you can go for the Java8/lambda/stream solution.
( where i wouldn't recommend that unless you really invest the time to completely understand the following solution; as this is untested from my side)
Arrays.stream(someListOrArrayContainingItems)
.collect(Collectors
.groupingBy(s -> s, TreeMap::new, Collectors.counting()))
.entrySet()
.stream()
.flatMap(e -> Stream.of(e.getKey(), String.valueOf(e.getValue())))
.collect(Collectors.joining())
Putting the pieces together from a couple of the other answers, adapting to your code from the other question and fixing a few trivial errors:
// as you want a sorted list of keys, you should use a TreeMap
Map<String, Integer> stringsWithCount = new TreeMap<>();
for (Message msg : convinfo.messages) {
// where ever your input comes from: turn it into lower case,
// so that "ccc" and "CCC" go for the same counter
String item = msg.userName.toLowerCase();
if (stringsWithCount.containsKey(item)) {
stringsWithCount.put(item, stringsWithCount.get(item) + 1);
} else {
stringsWithCount.put(item, 1);
}
}
String result = stringsWithCount
.entrySet()
.stream()
.map(entry -> entry.getKey() + '(' + entry.getValue() + ')')
.collect(Collectors.joining("+"));
System.out.println(result);
This prints:
aaa(2)+bbb(1)+ccc(2)+ddd(1)
You need a MultiSet from guava. That collection type is tailor-made for this kind of task:
MultiSet<String> multiSet = new MultiSet<>();
for (String line : lines) { // somehow you read the lines
multiSet.add(line.split(" ")[0].toLowerCase());
}
boolean first = true;
for (Multiset.Entry<String> entry : multiset.entrySet()) {
if (!first) {
System.out.println("+");
}
first = false;
System.out.print(entry.getElement() + "(" + entry.getCount() + ")");
}
Assuming that you use Java 8, it could be something like this using the Stream API:
List<Message> messages = ...;
// Convert your list as a Stream
// Extract only the login from the Message Object
// Lowercase the login to be able to group ccc and CCC together
// Group by login using TreeMap::new as supplier to sort the result alphabetically
// Convert each entry into login(count)
// Join with a +
String result =
messages.stream()
.map(Message::getLogin)
.map(String::toLowerCase)
.collect(
Collectors.groupingBy(
Function.identity(), TreeMap::new, Collectors.counting()
)
)
.entrySet()
.stream()
.map(entry -> entry.getKey() + '(' + entry.getValue() + ')')
.collect(Collectors.joining("+"))
System.out.println(result);
Output:
aaa(2)+bbb(1)+ccc(2)+ddd(1)
If you want to group your messages by login and have the result as a collection, you can proceed as next:
Map<String, List<Message>> groupedMessages =
messages.stream()
.collect(
Collectors.groupingBy(
message -> message.getLogin().toLowerCase(),
TreeMap::new,
Collectors.toList()
)
);

Categories