Sort LinkedHashMap<String, Json> by value without losing keys - java

I have this retrofit response body as LinkedHashMap< String, Json>
"-M74DWOrW0w07BpfmBVo": {
"noteContent": "Note 1 content",
"noteCurrentTime": 1589225588206,
"noteTitle": "Note 1"
},
"-M74Dc2dDAZgVk6q86Rs": {
"noteContent": "Note 2 content",
"noteCurrentTime": 1589225990674,
"noteTitle": "Note 2"
},
"-M74DmbSNQnjEU0Hw4yQ": {
"noteContent": "Note 3 content",
"noteCurrentTime": 1589225658614,
"noteTitle": "Note 3"
}
}
I need to sort by 'noteCurrentTime' value. So far, this is how get the array of sorted values.
private fun sortJsonArray(valuesArray: JSONArray): JSONArray? {
val sortedValues: MutableList<JSONObject> = ArrayList()
for (i in 0 until valuesArray.length()) {
sortedValues.add(valuesArray.getJSONObject(i))
}
sortedValues.sortWith(Comparator { lhs, rhs ->
val lid: Long = lhs.getLong("noteCurrentTime")
val rid: Long = rhs.getLong("noteCurrentTime")
lid.compareTo(rid)
})
return JSONArray(sortedValues)
}
However, this only returns sorted values, without keys, which are now in a wrong order. Is there a way to sort values of LinkedHashMap and keep the correct order of keys? Any and all help would be appreciated.

You can convert the map to a list (of key-value pairs), sort that, and then convert it back.
I don't really know what type is Json in your example Map value type. But you would convert it in the sortedBy lambda however is necessary to get your Long date.
val response: LinkedHashMap<String, Json> = //...
val sorted: Map<String, Json> = response.toList().sortBy { (_, jsonValue) ->
jsonValue.getLong("noteCurrentTime")
}.toMap()

Related

How to flatten list of list values in groupBy in Kotlin

I have an object structure
data class File(val travelBatch: List<TravelBatch>) {
data class TravelBatch(
val currency: String,
val transactions: List<Transaction>
)
}
I want to have a map of currency to transactions. The below code I tried gives
Map<String, List<List<Transaction>> I want Map<String, List<Transaction>
file.travelBatch.groupBy({it.currency}, {it.transactions})
Need help to flatten the values in the map in kotlin?
You can use mapValues
val result = file.travelBatch
.groupBy({ it.currency }, { it.transactions })
.mapValues { it.value.flatten() }

Hazelcast Jet vs Java 8 streams

I'm trying sort List<Map<String,Object>> object based on max date using Hazelcast Jet.
Here is my java 8 code that works:
public static List<Map<String, Object>> extractDate1(List<Map<String, Object>> data) {
return data.stream().map(value -> new Object() {
Map<String, Object> theMap = value;
LocalDate date = extractDate(value);
}).sorted(Comparator.comparing(obj -> obj.date)).map(obj -> obj.theMap).collect(Collectors.toList());
}
public static LocalDate extractDate(Map<String, Object> value) {
DateTimeFormatter formatter1 = DateTimeFormatter.ofPattern("dd-MM-yyyy");
DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyy-MM-dd");
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("d-MM-yyyy");
return LocalDate.parse(LocalDate.parse(value.get("effectiveDate").toString(), formatter2).format(formatter1),
formatter);
}
The above java 8 code sorts the map objects from low to high:
Below is the Jet code that I'm trying to extract also giving proper output. But I just want to make use of hazelcast jet aggregate/rolling functions
// fetching jsonb type data from db
BatchStage<Object> jobJson = dbValue
// this model holds the string json value
// converting json data to Map object
.map(model -> JsonUtil.mapFrom(model.getJosnValue())
.filter(map -> map.size() != 0)
.map(map -> {
// each json/map object will be having an array and again an array will I have multiple json objects in the
// I'm filtering json objects based on max date
List<Map<String, Object>> extractedDateValue;
if (map.containsKey("records")) {
//Here I'm calling external function (above java 8 code)
extractedDateValue = extractMapBasedOnMax(
(List<Map<String, Object>>) map.get("records"));
}
return extractedDateValue.get(extractedDateValue.size() - 1);
});
JSON data example:
{
"id": "01",
"records": [{
"location": "xyz1",
"effectiveDate": "02-03-2021"
}, {
"location": "xyz2",
"effectiveDate": "02-04-2021"
}]
}
Expeceted Output:
{
"location": "xyz2",
"effectiveDate": "02-04-2021"
}
Is it possible to achieve this through Hazelcast Jet rolling aggregations? Or any suggestions would be helpful.. Thanks
Consider flatMapping the pipeline and finding the maximum using topN. flatMap would convert each JSON structure to series of [id, location, effectiveDate] records. See the documentation of flatMap for code sample.
It's not clear whether you want to find max element in the whole collection or max element per id. Adding a groupingKey would find maximum per id.
The pipeline shape in a "metacode":
source // stream of JSON structures
.flatMap // stream [id, location, effectiveDate]
.groupingKey // for maximum per id, remove for global max
.aggregate(AggregateOpperations.topN) // finds max
.sink;

Passing JSON keys as String containing a range of numbers

I am trying to have a set of keys to point to a value.
For example the key 0 is to point to the value "EXAMPLE_1"
keys 1,2,3,4,5 is to point to the value "EXAMPLE_2"
and keys 6,7,8,9,10 is to point to the value "EXAMPLE_3"
This is the JSON structure I came up with (which will exist in an external file).
{
"0" : "EXAMPLE_1",
"1,5" : "EXAMPLE_2",
"6,10" : "EXAMPLE_3"
}
Using following code to read and fetch correct value.
private String getValue(String count){
Map<String, String> map = // code to fetch data from the file and get above map. Works.
for (Map.Entry<String, String> entry : map.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
String[] keysInRange = key.split(",");
if(Arrays.asList(keysInRange).contains(count)){
return value;
}
}
}
This technically works but is there a better way to do this.
Looking to improve the JSON structure.
Finding it silly to be passing in the keys in this manner.
Note that the keys would be a single number or always in a range.
You could try below. This is assuming, Keys in range are like this 1,2,3,4,5 for 1,5
private String getValue(String count){
Map<String, String> map = // code to fetch data from the file and get above map. Works.
If(map.containsKey(count)){
return map.get(count);
}
for (Map.Entry<String, String> entry : map.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
if(key.contains(","+count) || key.contains(","+count+",") || key.contains(count+",") ){
return value;
}
}
}
You could change your JSON structure to an array of elements:
{[ {
"name": "EXAMPLE_1",
"from": "0",
"to": "0"
},
{
"name": "EXAMPLE_2",
"from": "1",
"to": "5"
},
{
"name": "EXAMPLE_3",
"from": "6",
"to": "10"
}
]}
and parse them with a JSON parser like Jackson oder GSON in data objects like
class Example {
private String name;
private int from;
private int to;
// ommitted getters & setters for brevity
}
Your method then becomes (using Java 8+ and the streams api):
private String getValue(int count) {
Set<Example> examples = ... // code to fetch data from the file
Optional<Example> match = examples.stream()
.filter(example -> example.getFrom() >= count)
.filter(example -> example.getTo() <= count)
.findFirst();
// or return the Optional<Example> directly
return match.map(Example::getValue).orElse(null);
}

How to convert List of a POJO to Map<String,List> in Java Stream?

I want to convert a List of Java POJO into a Map in a non-static method with Java 8 Stream API.
My line chart needs a list of date String values for axis x and a list of numeric values for axis y. It's a typical Map format. But my database return a List of POJOs for me. I feel dislike looping without the help of Java 8 Stream API.
I've tried the method in this [ask}(Java 8 List of Objects to Map<String, List> of values). However, I am faced with two problems.First, My POJO MoreCustomDTO contains Integer besides String. Second, When I try to use method reference IDEA complains about non-static method cannot be referenced from a static context.
POJO:
#Data
MoreCustomDTO {
private String date;
private Integer money;
}
DAO query method:
public List<MoreCustomDTO > getMoreCustomCount(#Param("format") String format, #Param("startTime") String startTime, #Param("endTime") String endTime);
Solution before Java 8:
List<MoreCustomCountDTO> customList = customDao.getMoreCustomCount(SqlUtils.DATE_TYPE_FORMAT[Integer.valueOf(type)],startTime,endTime);
Map<String, List> map = new HashMap();
List<String> dateList = new ArrayList<>();
List<Integer> moneyList = new ArrayList<>();
for (MoreCustomCountDTO data : customList) {
dates.add(data.getDate());
dailyAmounts.add(data.getMoney());
}
map.put("date", dateList);
map.put("money", moneyList);
Failure code segment:
Map<String,List> map =
customList.stream()
.flatMap(element -> {
Map<String,String> um = new HashMap<>();
um.put("date",element.getDate());
um.put("money",element.getMoney());
return um.entrySet().stream();
})
.collect(Collectors.groupingBy(Map.Entry::getKey,
Collectors.mapping(Map.Entry::getValue,
Collectors.toList())));
I get a List from my database. And it is a Object array in JSON foramt.
response:
{
"rows": [
{
"date": "2019-09-01",
"money": 0.00
},
{
"date": "2019-09-02",
"money": 0.00
}
]
}
But I want a one(key)-to-many(values) Map format.
response:
{
"map": {
"date": [
"2019-09-01",
"2019-09-02"
],
"money": [
0.00,
0.00
]
}
}
Honestly, I think your initial solution was ok. Sometimes forcing a solution to be implemented using the most fancy features of the language ends up with less clear code, which is always a bad thing.
Having said that, I think what you intended to do is something like:
Map<Object, Object> map = Stream.of(
new SimpleEntry<>(
"date",
customList.stream().map(MoreCustomDTO::getDate).collect(Collectors.toList())
),
new SimpleEntry<>(
"money",
customList.stream().map(MoreCustomDTO::getMoney).collect(Collectors.toList())
)
).collect(Collectors.toMap(SimpleEntry::getKey, SimpleEntry::getValue));
use SimpleEntry instead like this:
Map<String, List<Object>> map= customList.stream().
flatMap(element ->
Stream.of(new AbstractMap.SimpleEntry<String, Object>("date", element.getDate()),
new AbstractMap.SimpleEntry<String, Object>("money", element.getMoney()))).
collect(Collectors.groupingBy(AbstractMap.SimpleEntry::getKey, Collectors.mapping(AbstractMap.SimpleEntry::getValue, Collectors.toList())));

Java 8 stream.collect(Collectors.toMap()) analog in kotlin

Suppose I have a list of persons and would like to have Map<String, Person>, where String is person name. How should I do that in kotlin?
Assuming that you have
val list: List<Person> = listOf(Person("Ann", 19), Person("John", 23))
the associateBy function would probably satisfy you:
val map = list.associateBy({ it.name }, { it.age })
/* Contains:
* "Ann" -> 19
* "John" -> 23
*/
As said in KDoc, associateBy:
Returns a Map containing the values provided by valueTransform and indexed by keySelector functions applied to elements of the given array.
If any two elements would have the same key returned by keySelector the last one gets added to the map.
The returned map preserves the entry iteration order of the original array.
It's applicable to any Iterable.
Many alternatives in Kotlin :)
val x: Map<String, Int> = list.associate { it.name to it.age }
val y: Map<String, Int> = list.map { it.name to it.age }.toMap()
var z: Map<String, Int> = list.associateBy({ it.name }, { it.age })
The best option for this particular case IMO is the first as Lambdas are not needed but simple transformation.

Categories