Swapping and flattening a Java Map<Integer, List<Integer>> using Stream API - collections

Having this Map<Integer, List>:
Map<Integer, List<Integer>> forwardMap = Map.of(
100, List.of(6),
300, List.of(49, 52),
500, List.of(293)
);
I would like to 'flatten' the value Lists and swap the key and value in the Map, ending up with this:
Map<Integer, Integer> reverseMap = Map.of(
6, 100,
49, 300
52, 300,
293, 500
);
My cannot-compile attempt, where I attempt to stream the Set<Map.Entry> and then the nested List:
Map<Integer, Integer> reverseMap = forwardMap.entrySet().stream().map(
entry -> entry.getValue().stream().collect(Collectors.toMap(Integer::getInteger, entry.getKey()));
);
Perhaps I need to avoid using stream() twice - possibly by using flatMap() somewhere and somehow. I have also tried first swapping swapping the key and value - but still end up not having a reference to the 'outer' key and the 'inner' nested Integers in the Lists, at the same time.
What am I missing or downright misunderstanding?

Here is a similar answer with a few things added.
the keys in your result are sorted so I sorted them in ascending order.
In your case, there are no duplicate keys in the final result. If that happens the process will throw an exception. There are three options.
keep the first duplicate and its associated value encountered which results in loss of data.
keep the last duplicate which has the same effect.
return values in a list for duplicate keys.
in this exercise, I chose the first option via (first, next)->first merge function.
I also return the items in a LinkedHashMap to preserve the sorted order.
Map<Integer, Integer> result = forwardMap.entrySet().stream()
.flatMap(e -> e.getValue().stream()
.map(v -> Map.entry(v, e.getKey())))
.sorted(Entry.comparingByKey())
.collect(Collectors.toMap(Entry::getKey,
Entry::getValue, (first, next) -> first,
LinkedHashMap::new));
result.entrySet().forEach(System.out::println);
prints
6=100
49=300
52=300
293=500
Here is how it would work if you had a duplicate value and wanted to keep it. The only differences are in the final collector.
groupingBy is used to create a list for each key
a LinkedHashMap is specified to preserve the sorted order.
and a mapping collector is used to extract the desired value from the entry.
Map<Integer, List<Integer>> forwardMap =
Map.of(100, List.of(6), 300, List.of(49, 52), 500,
List.of(293, 52));
Map<Integer, List<Integer>> result2 =
forwardMap.entrySet().stream()
.flatMap(e -> e.getValue().stream()
.map(v -> Map.entry(v, e.getKey())))
.sorted(Entry.comparingByKey())
.collect(Collectors.groupingBy(
Map.Entry::getKey, LinkedHashMap::new,
Collectors.mapping(
Map.Entry::getValue,
Collectors.toList())));
result2.entrySet().forEach(System.out::println);
prints
6=[100]
49=[300]
52=[500, 300]
293=[500]

As part of your goal is to flatten the values, you're correct you'll probably need a flatMap operation somewhere. For example:
Map<Integer, Integer> reverseMap =
forwardMap.entrySet().stream()
.flatMap(
entry -> entry.getValue().stream().map(value -> Map.entry(value, entry.getKey())))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

Related

How to write Key Value Pairs in Map in a Loop without overwriting them?

the following is my problem:
I do a select on my database and want to write the values of every record i get in a map. When i do this, i only have the values of one record in my map, because the put() function overwrites the entries everytime the loop starts again. Since I have to transfer the Key:Value pairs via JSON into my Javascript and write something into a field for every Key:Value pair, an ArrayList is not an option.
I've already tried to convert a ArrayList, which contains the Map, to a Map or to a String and then to Map, but i failed.
EDIT:
HereĀ“s my Code
def valueArray = new ArrayList();
def recordValues = [:]
while (rs.next())
{
fremdlKorr = rs.getFloatValue(1)
leistungKorr = rs.getFloatValue(2)
materialKorr = rs.getFloatValue(3)
strid = rs.getStringValue(4)
recordValues.put("strid", strid);
recordValues.put("material", materialKorr);
recordValues.put("fremdl", fremdlKorr);
recordValues.put("leistung", leistungKorr);
valueArray.add(korrekturWerte);
}
The ArrayList was just a test, i dont want to have an ArrayList, i need a Map.
The code as written, will give you a list of maps, but the maps will all contain the values of the last row. The reaons is the def recordValues = [:] that is outside of the while loop. So you basically add always the same map to the list and the map values get overwritten each loop.
While moving the code, would fix the problem, I'd use Sql instead. That all boils down to:
def valueArray = sql.rows("select strid, material, fremdl, leistung from ...")

Changing values to combobox from an array

I need to load values from an array to a combobox, with this code it loads only the last value of the array? Can anyone help me please.
for(int i =0; i<lines.size(); i++) {
resultArray[i] = lines.get(i).split("\t");
Laptops[i] = resultArray[i][0];
ObservableList<String> option = FXCollections.observableArrayList(Laptops[i].toString());
cbx1.setValue("");
cbx1.setItems(option);
cbx2.setValue("");
cbx2.setItems(option);
cbx3.setValue("");
cbx3.setItems(option);
}
In your loop, you are creating a brand new List on each iteration. So when you call setItems() on your ComboBox, the option list only has that one item in it.
There are several other issues with your loop, but once you have a valid array, populating a ComboBox with it is quite simple using the Arrays.asList() method:
ObservableList<String> option = FXCollections.observableList(Arrays.asList(resultArray));
cbx1.setItems(option);
That being said, I doubt you're getting a proper array with your line:
resultArray[i] = lines.get(i).split("\t");
Without knowing what lines actually is in your code, it's difficult to address this specifically, but it looks like you could skip the whole resultArray altogether and just use lines.
Calling the split() method returns an array of Strings anyway. Perhaps you could remove the loop altogether and just try:
ObservableList<String> options =
FXCollections.observableArrayList(Arrays.asList(lines.split("\t"));

How map from a stream the sum of a Duration field in java?

I am trying to create a map that holds an activity and the total duration of that activity, knowing that the activity appears more times with different durations.
Normally, I would have solved it like this:
Map<String,Duration> result2 = new HashMap<String,Duration>();
for(MonitoredData m: lista)
{
if(result2.containsKey(m.getActivity())) result2.replace(m.getActivity(),result2.get(m.getActivity()).plus(m.getDuration()));
else result2.put(m.getActivity(), m.getDuration());
}
But I am trying to do this with a stream, but I can't figure out how to put the sum in there.
Function<Duration, Duration> totalDuration = x -> x.plus(x);
Map<String, Duration> result2 = lista.stream().collect(
Collectors.groupingBy(MonitoredData::getActivity,
Collectors.groupingBy(totalDuration.apply(), Collectors.counting()))
);
I tried in various ways to group them, to map them directly, or to sum them directly in the brackets, but i'm stuck.
Use the 3-argument version of toMap collector:
import static java.util.stream.Collectors.toMap;
Map<String,Duration> result = lista.stream()
.collect(toMap(MonitoredData::getActivity, MonitoredData::getDuration, Duration::plus));
Also, note that Map interface got some nice additions in Java 8. One of them is merge. With that, even your iterative for loop can be rewritten to be much cleaner:
for (MonitoredData m: lista) {
result.merge(m.getActivity(), m.getDuration(), Duration::plus);
}

Flink: enrich a data set with a new column based on some computation

I am trying to do a simple processing with a data set.
Consider a data set with two columns of type String. To this data set I want to add a third column of type Long, which accumulates the number of records so far seen in the data set.
Example:
Input:
a,b
b,c
c,d
Output:
a,b,1
b,c,2
c,d,3
I have tried the following solution but I get a strange result:
DataSet<Tuple2<String, String>> csvInput = env.readCsvFile("src/main/resources/data_file")
.ignoreFirstLine()
.includeFields("11")
.types(String.class,String.class);
long cnt=0;
DataSet<Tuple3<String, String, Long>> csvOut2 = csvInput.map(new MyMapFunction(cnt));
private static class MyMapFunction implements MapFunction<Tuple2<String, String>, Tuple3<String, String, Long>> {
long cnt;
public MyMappingFunction(long cnt) {
this.cnt = cnt;
}
#Override
public Tuple3<String, String, Long> map(Tuple2<String, String> m) throws Exception {
Tuple3 <String ,String, Long> resultTuple = new Tuple3(m.f0,m.f1, Long.valueOf(cnt));
cnt++;
return resultTuple;
}
}
When I apply this solution for a file with 100 entries I get a count of 47 instead of 100. The counter is restarted at 53. Similarly, when I apply it for even a larger file the counter is somehow reset from time to time so I don't get the total number of the lines.
Could you please explain why is my implementation behaving in this way? Also, what could be a possible solution to my problem?
Thanks!
This is a multithreading issue. How many tasks slots do you have?
I had to clean up your code before running it - I suggest posting full working examples in future so that you have a chance of getting more answers.
The way you are keeping track of the count is not thread-safe, and so if you have more than one task slot you will have problems with the count value being inaccurate.
The proper way to count, as shown in the data artisans word count example, would be to use the 3rd slot in your tuple to simply store the value 1, and then sum the dataset.
resultTuple = new Tuple3(m.f0,m.f1, 1L);
then
csvOut2.sum(2).print();
where 2 is the index of the tuple containing the value 1.

What is wrong with this code? why the List does not identify?

what is wrong with this code?
bool claimExists;
string currentClaimControlNo = "700209308399870";
List<string> claimControlNo = new List<string>();
claimControlNo.Add("700209308399870");
if (claimControlNo.Contains(currentClaimControlNo.Substring(0, 14)))
claimExists = true;
else
claimExists = false;
Why the claimControlNo above is coming into false?
Since I know the value exists, how can i tune the code?
It's reporting false because you aren't asking whether the list contains the currentClaimControlNo, you're asking whether it contains a string that is the first fourteen characters of the fifteen-character string currentClaimControlNo.
Try this instead:
claimExists = claimControlNo.Any(ccn => ccn.StartsWith(currentClaimControlNo.Substring(0,14)));
Your count is wrong. There are 15 characters. Your substring is cutting off the last 0 which fails the condition.
Because you're shaving off the last digit in your substring.
if you change the line
if (claimControlNo.Contains(currentClaimControlNo.Substring(0, 14)))
to
if (claimControlNo.Contains(currentClaimControlNo.Substring(0, 15)))
it works.
Because contains on a list looks for the whole item, not a substring:
currentClaimControlNo.Substring(0, 14)
"70020930839987"
Is not the same as
700209308399870
You're missing a digit, hence why your list search is failing.
I think you are trying to find something in the list that contains that substring. Don't use the lists contain method. If you are trying to find something in the list that has the subset do this
claimExists = claimControlNo.Any(item => item.Contains(currentClaimControlNo.Substring(0, 14)))
This goes through each item in claimControlNo and each item can then check if it contains the substring.
Why do it this way? The Contains method on a string
Returns a value indicating whether the specified System.String object occurs within this string.
Which is what you want.
Contains on a list, however
Determines whether an element is in the System.Collections.Generic.List.
They aren't the same, hence your confusion
Do you really need this explaining?
You are calling Substring for 14 characters when the string is of length 15. Then you are checking if your list (which only has one item of length 15) contains an item of length 14. It doesn;t event need to check the value, the length is enough to determine it is not a match.
The solution of course is to not do the Substring, it makes not sense.
Which would look like this:
if (claimControlNo.Contains(currentClaimControlNo))
claimExists = true;
else
claimExists = false;
Then again, perhaps you know you are trimming the search, and are in fact looking for anything that has a partial match within the list?
If this is the case, then you can simply loop the list and do a Contains on each item. Something like this:
bool claimExists = false;
string searchString = currentClaimControlNo.Substring(0, 14);
foreach(var s in claimControlNo)
{
if(s.Contains(searchString))
{
claimExists = true;
break;
}
}
Or use some slightly complex (certainly more complex then I can remember off the top of my head) LINQ query. Quick guess (it's probably right to be fair, I am pretty freaking awesome):
bool claimExists = claimControlNo.Any(x => x.Contains(searchString));
Check it:
// str will be equal to 70020930839987
var str = currentClaimControlNo.Substring(0, 14);
List<string> claimControlNo = new List<string>();
claimControlNo.Add("700209308399870");
The value str isn't contained in the list.

Resources