Re-index a list to nested map in kotlin - java

I have a variables of type var students = List<StudentData>. I need to create a map with the following structure: Map<String, Map<Int, StudentData>>.
StudentData has information like: studentID, classId, courseA, courseB, courseC etc.
So for the map would be the following: Map<studentID, Map <classId, StudentData>>. Meaning that for every student, I need a map of his classId and all the information.
How can I achieve this using kotlin ?
var indexByCourse = Map<String, Map<Int, StudentData>>
students.forEach {
student -> indexByCourse.getOrPut(student.studentID, {mutableSetOf()}.add(??)
}
Not sure how to approach this problem. Any guidance is appreciated!

You can try the below way to map classId with it's corresponding information.
fun testFunction() {
//for holding student data
val students = ArrayList<StudentData>()
students.add(StudentData(1, 10, "course a", "course 3"))
students.add(StudentData(2, 11, "course b", "course 6"))
students.add(StudentData(3, 12, "course c", "course 2"))
students.add(StudentData(4, 13, "course d", "course 5"))
//For holding course
val indexByCourse = hashMapOf<Int, HashMap<Int, StudentData>>()
students.forEachIndexed { index, studentData ->
val studentSubData = hashMapOf(studentData.classId to studentData)
indexByCourse[studentData.studentID] = studentSubData
}
//Fetch the student data based on studentID
println(indexByCourse[3])
}
and StudentData class for holding the values
data class StudentData(var studentID: Int, var classId: Int, var courseA: String, var courseB: String)

data class StudentData(
val studentID: String,
val classId: Int,
val courseA: String? = "",
val courseB: String? = "",
val courseC: String? = ""
)
val students = listOf(
StudentData("1", 100, "Course 1"),
StudentData("2", 100, "Course 1", "Course 2", "Course 3"),
StudentData("3", 100, "Course 2", "Course 3"),
StudentData("4", 200, "Course 1", "Course 3", "Course 3"),
StudentData("5", 200, "Course 1", "Course 2")
)
val result = students
.groupBy { studentData -> studentData.studentID }
.map { (studentID, values) ->
studentID to values
.groupBy { studentData -> studentData.classId }
.map { (classId, values) -> classId to values.first() }
.toMap()
}
.toMap()
result.forEach(::println)
Output:
1={100=StudentData(studentID=1, classId=100, courseA=Course 1, courseB=, courseC=)}
2={100=StudentData(studentID=2, classId=100, courseA=Course 1, courseB=Course 2, courseC=Course 3)}
3={100=StudentData(studentID=3, classId=100, courseA=Course 2, courseB=Course 3, courseC=)}
4={200=StudentData(studentID=4, classId=200, courseA=Course 1, courseB=Course 3, courseC=Course 3)}
5={200=StudentData(studentID=5, classId=200, courseA=Course 1, courseB=Course 2, courseC=)}

Related

How to convert a list object to other list object in kotlin

I have a list of products and a list of types, but to know what type the product belongs to I need to rely on the ID in the type list, so I need to convert it to a new NewProducts list and its type is a String, not an Integer.
data class Product(val id:String,val name:String,val price:Float,val type:Int)
data class Type(val id:Int,val name:String)
corresponding to the above data is the JSON snippet below:
Products:
[
{
"id":1,
"name":"Product 1",
"price":3444,
"type":1
},
{
"id":2,
"name":"Product 2",
"price":3444,
"type":2
},
{
"id":3,
"name":"Product 3",
"price":3444,
"type":3
},
{
"id":4,
"name":"Product 4",
"price":3444,
"type":1
},
{
"id":5,
"name":"Product 5",
"price":3444,
"type":2
}
]
and Type model:
[
{
"id": 1,
"type": "A"
},
{
"id": 2,
"type": "B"
},
{
"id": 3,
"type": "C"
},
]
So I want to convert them to a new object like this:
data class NewProduct(val id:String,val name:String,val price:Float,val type:String)
It looks like:
var products = mutableListOf<Product>(
Product("1", "Product 1", 34f, 1),
Product("2", "Product 2", 34f, 2),
Product("3", "Product 3", 34f, 3),
Product("4", "Product 4", 34f, 1),
Product("5", "Product 5", 34f, 2),
)
var types = mutableListOf<Type>(
Type(1, "A"), Type(2, "B"),
Type(3, "C"),
)
// i want to convert to NewProduct list
products.map { products-> }
Can you help me with this problem?
You can do
products.map { product ->
NewProduct(
product.id,
product.name,
product.price,
types.first { type -> type.id == product.type }.name
)
}
You can first convert types into a map to access the types by id much faster:
val typeMap: Map<Int, String> = types.associateBy(Type::id, Type::name)
// {1=Type(id=1, name=A), 2=Type(id=2, name=B), 3=Type(id=3, name=C)}
Then map the products
val productsByType: List<NewProduct> = products.map {
NewProduct(
id = it.id,
name = it.name,
price = it.price,
type = typeMap[it.type] ?: ""
)
}
/**
[NewProduct(id=1, name=Product 1, price=3444.0, type=A),
NewProduct(id=2, name=Product 2, price=3444.0, type=B),
NewProduct(id=3, name=Product 3, price=3444.0, type=C),
NewProduct(id=4, name=Product 4, price=3444.0, type=A),
NewProduct(id=5, name=Product 5, price=3444.0, type=B)]
*/
This way you can do it with O(n) complexity.

Java - Read data from a json with the JSON library according to an object

I have a JSON with some data and I would like to print as follows
10 REGISTER 1, KP SUM 2081,606
20 REGISTER 2 CH SUM 0,22
Where the general sum is calculated by the total sum of the items according to the code.
Following the rule, first multiply the quantity by the unit and then add all the items that have the same code.
Example:
code 10
SUM = 0,0200000 * 7,40 + 10,0000000 * 200,31 + 0,5690000 * 40,19 + 0,7890000 * 70,33
The same goes for the other codes that appear in JSON
My JSON
[
{
"code": 10,
"description": "REGISTER 1",
"unity": "KP",
"typeItem": "I",
"itemCode": 1,
"descriptionItem": "ITEM",
"unityItem": "UN",
"quantity": "0,0200000",
"valueUnity": "7,40"
},
{
"code": 10,
"description": "REGISTER 1",
"unity": "KP",
"typeItem": "I",
"codeItem": 2,
"descriptionItem": "ITEM 2",
"unityItem": "UN",
"quantity": "10,0000000",
"valueUnity": "200,31"
},
{
"code": 10,
"description": "REGISTER 1",
"unity": "KP",
"typeItem": "I",
"codeItem": 88248,
"descriptionItem": "ITEM 3",
"unityItem": "H",
"quantity": "0,5690000",
"valueUnity": "40,19"
},
{
"code": 10,
"description": "REGISTER 1",
"unity": "KP",
"typeItem": "I",
"codeItem": 88267,
"descriptionItem": "ITEM 4",
"unityItem": "N",
"quantity": "0,7890000",
"valueUnity": "70,33"
},
{
"code": 20,
"description": "REGISTER 2",
"unity": "CH",
"typeItem": "I",
"codeItem": 1,
"descriptionItem": "ITEM 1",
"unityItem": "H",
"quantity": "30,0000000",
"valueUnity": "0,17"
},
{
"code": 20,
"description": "REGISTER 2",
"unity": "CH",
"typeItem": "I",
"codeItem": 2,
"descriptionItem": "ITEM 2",
"unityItem": "H",
"quantity": "3,0000000",
"valueUnity": "0,07"
}
]
My class Java
public class MyJson {
#SerializedName("code")
#Expose
private Integer code;
#SerializedName("description")
#Expose
private String description;
#SerializedName("unity")
#Expose
private String unity;
#SerializedName("typeItem")
#Expose
private String typeItem;
#SerializedName("codeItem")
#Expose
private Integer codeItem;
#SerializedName("descriptionItem")
#Expose
private String descriptionItem;
#SerializedName("unityItem")
#Expose
private String unityItem;
#SerializedName("quantity")
#Expose
private String quantity;
#SerializedName("valueUnity")
#Expose
private String valueUnity;
private Double total;
}
My Program
public static void main(String[] args) {
Gson gson = new Gson();
try {
File jsonFile = new File("C:\\my_json.json");
InputStreamReader reader = new InputStreamReader(new FileInputStream(jsonFile), "UTF-8");
BufferedReader jsonBuffer = new BufferedReader(reader);
MyJson[] myJsonArray = gson.fromJson(jsonBuffer, MyJson[].class);
BigDecimal valueUnity = BigDecimal.ZERO;
BigDecimal sumTotal = BigDecimal.ZERO;
//
Set<MyJson> list = new HashSet<>();
for(MyJson myJson : myJsonArray) {
if(checkStringNullOrEmpty(myJson.getQuantity()) && checkStringNullOrEmpty(myJson.getValueUnity())) {
if(myJson.getCode().equals(myJson.getCode())) {
String value1 = myJson.getQuantity().replaceAll( "," , "." ).trim();
String value2 = myJson.getValueUnity.replaceAll( "," , "." ).trim();
BigDecimal quantity = new BigDecimal(value1);
BigDecimal valueUnit = new BigDecimal(value2);
valueUnity = quantity.multiply(valueUnit);
somaTotal = sumTotal.add(valueUnity);
String resultado = String.format(Locale.getDefault(), "%.2f", valueUnity);
String sumTotal2 = String.format(Locale.getDefault(), "%.2f", sumTotal);
myJson.setTotal(new Double(sumTotal2.replaceAll( "," , "." ).trim()));
list.add(myJson);
}
}
}
for(MyJson myJson : list) {
StringBuilder builer = new StringBuilder();
builer.append(myJson.getCode()).append(" ");
builer.append(myJson.getDescription().toUpperCase()).append(" ");
builer.append(myJson.getUnity().toUpperCase()).append(" ");
builer.append(myJson.getTotal());
System.out.println(builer.toString());
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static boolean checkStringNullOrEmpty(String value) {
if(!value.isEmpty()) {
return true;
} else {
return false;
}
}
Exit program
The calculation is being done wrong when using the Set
10 REGISTER 1, KP SUM 130,33
20 REGISTER 2 CH SUM 439,18
You cannot keep track of multiple running totals (i.e. one for each code) using one total. Instead you will need one total for each different code.
I would recommend that you use a Map<Integer, MyJson> for this purpose. This would store a number of MyJson objects which you could look up by their code. When handling each MyJson object, you check to see if you already have a MyJson object with the same code: if you do then you add to its total, otherwise you add your MyJson object to the map.
Get rid of your Set<MyJson> variable (which you have somewhat confusingly named list) and replace it with the following
Map<Integer, MyJson> jsonsByCode = new LinkedHashMap<>();
(You can use a HashMap<> instead of a LinkedHashMap<> here: I chose to use a LinkedHashMap<> because it keeps its entries in the same order they were inserted into it.)
Then, replace all lines from somaTotal = sumTotal.add(valueUnity); to list.add(myJson); with
if (jsonsByCode.containsKey(myJson.getCode())) {
// We've seen this code before, so add the value
// to the total.
MyJson matchingJson = jsonsByCode.get(myJson.getCode());
matchingJson.setTotal(matchingJson.getTotal() + valueUnity.doubleValue());
} else {
// First time seeing this code, so set its total
// and add it to the map.
myJson.setTotal(valueUnity.doubleValue());
jsonsByCode.put(myJson.getCode(), myJson);
}
(Note that BigDecimal values such as valueUnity have a .doubleValue() method on them, which is the easiest way to convert them to a double.)
Then, in the for loop below, where you are printing out the values, replace list with jsonsByCode.values().
I made these changes to your program and it generated the following output:
10 REGISTER 1 KP 2081.60648
20 REGISTER 2 CH 5.31
Incidentally, your code also contains the following if statement:
if(myJson.getCode().equals(myJson.getCode())) {
// ....
}
You are comparing myJson.getCode() against itself, so this condition will always be true (unless of course myJson.getCode() returns null, in which case you get a NullPointerException). You can just get rid of this check, it doesn't do anything useful.

Cannot make json valid for recyclerview

I'm trying to JSON using Gson and recyclerview. My JSON isnt completely valid. In my JSON, the food field has only one of the string quoted leaving the other unquoted. Kindly see my JSON below...
[
{"quantity" = 2,
"price" = 15,
"food" = "Fried" Rice},
{"quantity" = 2,
"price" = 20,
"food" = "Rice" and Stew}
]
You can see that Fried is in quotes and Rice isnt in quotes, likewise the same for Rice and Stew in the other too. Initially it was like this ...
[
{quantity = 2,
price = 15,
food = Fried Rice},
{quantity = 2,
price = 20,
food = Rice and Stew}
]
My activity class code...
Bundle extras = getIntent().getExtras();
if (extras != null) {
String listOfFood = extras.getString("foods");
listOfFood = listOfFood.replaceAll("([\\w]+)[ ]*=", "\"$1\" ="); // to quote before = value
listOfFood = listOfFood.replaceAll("=[ ]*([\\w#\\.]+)", "= \"$1\""); // to quote after = value, add special character as needed to the exclusion list in regex
listOfFood = listOfFood.replaceAll("=[ ]*\"([\\d]+)\"", "= $1"); // to un-quote decimal value
listOfFood = listOfFood.replaceAll("\"true\"", "true"); // to un-quote boolean
listOfFood = listOfFood.replaceAll("\"false\"", "false"); // to un-quote boolean
Log.d(TAG, "onCreate: "+listOfFood);
GsonBuilder builder = new GsonBuilder();
Gson mGson = builder.create();
List<FoodOrder> posts = new ArrayList<FoodOrder>();
posts = Arrays.asList(mGson.fromJson(listOfFood, FoodOrder[].class));
adapter = new RecyclerViewAdapter(FoodsOrderedActivity.this, posts);
recyclerView.setAdapter(adapter);
}
I need the food field that has Fried Rice to be inbetween quotes as one and the same for rice and stew or if theres a workaround, I would like to know.
Thank you
Base on your code. (Try to convert listOfFood to JSON )
I modify 2 lines of your code as bellow
listOfFood = listOfFood.replaceAll("(\\s*)([^{,\\s]+)(\\s*)=","$1\"$2\"$3:"); // to quote before = value and replace = by :
listOfFood = listOfFood.replaceAll("(:\\s*)([^\\s,{}](\\s*[^\\s,{}]+)*)", "$1\"$2\""); // to quote after = value (= now became :)
The json structure should look like as below
[
{
"Key1" : "value1",
"Key2" : "value2",
},
{
"Key1" : "value1",
"Key2" : "value2",
}
]
The json structure should be like this :
[{"quantity" : 2,
"price" : 15,
"food" : "Fried Rice"},
{"quantity" : 2,
"price" : 20,
"food" : "Rice and Stew"}]

How to transform a flattened LinkedHashMap to one that represents a hierarchy

I'm a Java newbie and I'm having problems conceptualizing how to solve the problem of trying to create a LinkedHashMap that represents some hierarchical data.
This is as far as I've gotten. In this example, I've got a LinkedHashMap that shows the flattened result:
public static Map<String, String> myDataSet = new LinkedHashMap() {
{
put("MY_KEY_1", "My First Label");
put("MY_KEY_2", "My Second Label");
put("MY_KEY_3", "My Third Label");
put("MY_KEY_4", "My Fourth Label");
put("MY_KEY_5", "My Fifth Label");
}
};
This is only how I create the LinkedHashMap. At this point I iterate over the body of my response and capture the values with the keys...but you don't need to see all that ugly code. In the end I've got this response where the content is an array of objects:
{
data:{
content:[
{
name: "My First Label",
value: 500,
},
{
name: "My Second Label",
value: 1500,
},
{
name: "My Third Label",
value:2500,
},
{
name: "My Fourth Label",
value: 3500,
},
{
name: "My Fifth Label",
value: 4500,
}
]
}
},
Which is great, a flattened data set. But what I really want to learn is how to create a LinkedHashMap in such a way that this is the result:
{
data:{
content:[
{
name: "My First Label",
props:[
{
name: "Sub label 1",
value: 500,
},
{
name: "Sub label 2",
value: 1500,
},
]
},
{
name: "My Second Label",
props:[
{
name: "Sub label 1",
value: 2500,
},
]
},
{
name: "My Fourth Label",
props: [
{
name: "Sub label 1",
value: 500,
},
],
},
{
name: "My Fifth Label",
props: [
{
name: "Sub label 1"
value: 4500
}
],
}
]
}
}
And I'm a little lost as to how to get started to transform this. Any tips are appreciated. Thanks.
This can be accomplished by setting the type of the mapped value to another Map - e.g. Map<String, Map<String, Integer>>.
public static Map<String, Map<String, Integer>> myDataSet =
new LinkedHashMap<String, Map<String, Integer>>() {{
this.put("My First Label", new LinkedHashMap<String, Integer>() {{
this.put("Sub label 1", 500);
this.put("Sub label 2", 1500);
}});
this.put("My Second Label", new LinkedHashMap<String, Integer>() {{
this.put("Sub label 1", 2500);
}});
}};

.net list group by in java 8?

I got a task that migrate code from C# to java 8.
And I have a problem with the C# codes below.
List<Log> lst = LogRepository.GetLogs(DateTime.Now.AddDays(-2), DateTime.Now);
return lst
.GroupBy(x => new { x.Title, x.ID })
.Select(x => x.OrderByDescending(y => y.DataChangeTime).FirstOrDefault())
.ToList();
Yes ,the method GroupBy is easy,I know what is it doing.
But ,I can't figure out what is this series methods doing and what results will it return ?
Finally,can anyone give me a java version solution ?
The C#-code is already explained by #Rango. Assuming the following design for the Log-class in C#
class Log
{
public String title;
public String ID;
public DateTime dataChangeTime;
public String whatever;
...
}
and an analogous Java-class (with e.g. LocalDateTime instead of DateTime), a Java-expression providing the same result is:
Comparator<Log> comparator = (Log l1, Log l2) -> l2.dataChangeTime.compareTo(l1.dataChangeTime); // sort descending
List<Log> resultantList = initialList.stream()
.collect(Collectors.groupingBy(l -> l.title + l.ID)).values().stream() // group according to title and id
.map(logs -> logs.stream().sorted(comparator).findFirst().get()) // sort and take the first
.collect(Collectors.toList()); // create the list
The expression groups all Log-objects together having the same title and ID, i.e. the same value of l.title + l.ID. If the grouping-condition is more complex then it might make more sense to define a class which represents the grouping, e.g.
class LogGroup {
private String Title;
private String ID;
public LogGroup(String Title, String ID) {
this.Title = Title;
this.ID = ID;
}
#Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof LogGroup)) {
return false;
}
LogGroup logGroup = (LogGroup) o;
return Objects.equals(Title, logGroup.Title) &&
Objects.equals(ID, logGroup.ID);
}
#Override
public int hashCode() {
return Objects.hash(Title, ID);
}
}
It's crucial that the class implements both, the equals- and the hashCode-method (i.e the implementation of the equals-method alone is not enough).
Using that class the Java-expression becomes:
Comparator<Log> comparator = (Log l1, Log l2) -> l2.dataChangeTime.compareTo(l1.dataChangeTime);
List<Log> resultantList = initialList.stream()
.collect(Collectors.groupingBy(l -> new LogGroup(l.title, l.ID))).values().stream()
.map(logs -> logs.stream().sorted(comparator).findFirst().get())
.collect(Collectors.toList());
A list like
private static List<Log> getInitialList() {
List<Log> initialList = new ArrayList<>();
initialList.add(new Log("Title 6", "ID 6", LocalDateTime.of(2017, 1, 18, 23, 15, 12), "A"));
initialList.add(new Log("Title 3", "ID 3", LocalDateTime.of(2005, 4, 20, 16, 10, 10), "B"));
initialList.add(new Log("Title 1", "ID 1", LocalDateTime.of(2010, 10, 25, 3, 5, 2), "C"));
initialList.add(new Log("Title 2", "ID 2", LocalDateTime.of(2018, 2, 18, 21, 13, 32), "D"));
initialList.add(new Log("Title 3", "ID 3", LocalDateTime.of(2016, 5, 16, 15, 23, 15), "E"));
initialList.add(new Log("Title 1", "ID 1", LocalDateTime.of(2012, 2, 8, 14, 46, 28), "F"));
initialList.add(new Log("Title 6", "ID 6", LocalDateTime.of(1996, 1, 28, 22, 26, 34), "G"));
initialList.add(new Log("Title 3", "ID 3", LocalDateTime.of(2007, 4, 15, 2, 5, 55), "H"));
initialList.add(new Log("Title 6", "ID 3", LocalDateTime.of(2018, 1, 15, 20, 15, 10), "I"));
return initialList;
}
is processed by both expressions as follows
Title 1 ID 1 2012-02-08 14:46:28 F
Title 3 ID 3 2016-05-16 15:23:15 E
Title 2 ID 2 2018-02-18 21:13:32 D
Title 6 ID 6 2017-01-18 23:15:12 A
Title 6 ID 3 2018-01-15 20:15:10 I
The resultant list itself isn't sorted (which would be easy to implement with Collections.sort(...)), but that applies also to the C#-output.

Categories