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);
}
Related
I have a class Records as follows:
public class Records {
private BigDecimal price;
private BigDecimal tos;
private BigDecimal share;
}
And have a Map of type Map<String, List<Records>> where each Key can be associated with a group of Records.
I want to perform cumulative multiplication on those Records instances associated with the same Key and produce the resulting Map where every Key is mapped to a single Records instance.
For example, in my map the data, if I store data, is like this:
Map<String, List<Records>> has data
{
"Id1": [ { "price": 3, "share": 4, "tos": 5}, { "price": 2, "share": 3, "tos": 6} ],
"Id2": [ { "price": 1, "share": 7, "tos": 4} ]
}
I want to perform cumulative multiplication on data of Id1 and achieve output like:
Map<String, Records>
{
"Id1": { "price": 6, "share": 12, "tos": 30},
"Id2": { "price": 1, "share": 7, "tos": 4}
}
My attempt:
private static Map<String, Records> getCumulativeSafFafPaf(Map<String, List<Records>> recordsMap) {
Map<String, Records> recordData = null;
Records output = null;
for (Map.Entry<String, List<Records>> data : recordsMap.entrySet()) {
List<Records> dbData = data.getValue();
for (String pid : recordsMap.keySet()) {
output = new Records();
if(recordsMap.get(pid).size() > 1) {
BigDecimal price = BigDecimal.ONE, share = BigDecimal.ONE, tos= BigDecimal.ONE;
for (Records abc : dbData) {
price = DecimalUtil.resetValueWithDefaultScale(price .multiply(abc.getPrice()));
share = DecimalUtil.resetValueWithDefaultScale(share .multiply(abc.getShare()));
tos= DecimalUtil.resetValueWithDefaultScale(tos.multiply(abc.getTos()));
}
output.setPrice(price);
output.setShare(share);
output.setTos(tos);
}
recordData.put(pid, output);
}
}
return recordData;
}
What I'm stuck on:
I'm not fully understand how I to store the multiplied data and along with that store the rest of the data.
It would be handy to define a method containing accumulation logic inside the Records class instead of manually manipulating the data outside the Records instance and then reassigning it via setter, which is less maintainable and violates the Information expert principle.
public static class Records {
private BigDecimal price = BigDecimal.ONE;
private BigDecimal tos = BigDecimal.ONE;
private BigDecimal share = BigDecimal.ONE;
public void merge(Records other) {
price = DecimalUtil.resetValueWithDefaultScale(price.multiply(other.getPrice()));
share = DecimalUtil.resetValueWithDefaultScale(share.multiply(other.getShare()));
tos = DecimalUtil.resetValueWithDefaultScale(fl.multiply(other.getTos()));
}
// getters, constructors, etc.
}
Now the code in the method that operates with a map of records can be made leaner.
Note
That in order to perform the transformation, you've illustrated with the sample data you don't need a nested for-loop (the inner loop iterating over the keys in your code is redundant).
Since your objects are mutable, it might not be a good idea to share object between different collections (for that reason in the implementations provided below all Records instances are not reused, even if there's only one record mapped to a particular key).
Plural nouns like Records are rarely used as names-class. And in such cases it's either a utility class (like Collections) which is meant to facilitate various actions which can be performed with instances of a particular type or class which is meant to store multiple instances of particular type. But Records is neither of these, therefore I would rather call this class Record.
That's your code can be reimplemented using classic Java features:
private static Map<String, Records> getCumulativeSafFafPaf(Map<String, List<Records>> recordsMap) {
Map<String, Records> recordData = new HashMap<>();
for (Map.Entry<String, List<Records>> entry : recordsMap.entrySet()) {
Records value = new Records();
recordData.put(entry.getKey(), value);
for (Records next : entry.getValue()) {
value.merge(next);
}
}
return recordData;
}
That's how it can be implemented with Stream IPA in a more concise way using collector toMap() and three-args collect() to perform mutable reduction:
private static Map<String, Records> getCumulativeSafFafPaf(Map<String, List<Records>> recordsMap) {
return recordsMap.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
entry -> entry.getValue().stream()
.collect( Records::new, Records::merge, Records::merge )
));
}
A link to Online Demo
You basically have the right idea. You just seem to be making it more complicated than it needs to be.
The below code was developed using JDK 19.
(More notes after the code.)
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Records {
private BigDecimal price;
private BigDecimal $float;
private BigDecimal share;
public Records(BigDecimal thePrice, BigDecimal theFloat, BigDecimal theShare) {
price = thePrice;
$float = theFloat;
share = theShare;
}
public BigDecimal getPrice() {
return price;
}
public BigDecimal getFloat() {
return $float;
}
public BigDecimal getShare() {
return share;
}
public String toString() {
return String.format("price: %.2f , float: %.2f , share: %.2f", price, $float, share);
}
public static void main(String[] args) {
List<Records> id1List = List.of(new Records(new BigDecimal(3),
new BigDecimal(4),
new BigDecimal(5)),
new Records(new BigDecimal(2),
new BigDecimal(3),
new BigDecimal(6)));
List<Records> id2List = List.of(new Records(new BigDecimal(1),
new BigDecimal(7),
new BigDecimal(4)));
Map<String, List<Records>> map = new HashMap<>();
map.put("Id1", id1List);
map.put("Id2", id2List);
for (Map.Entry<String, List<Records>> entry : map.entrySet()) {
List<Records> list = entry.getValue();
int size = list.size();
if (size > 1) {
Records record = list.get(0);
BigDecimal thePrice = record.getPrice();
BigDecimal theFloat = record.getFloat();
BigDecimal theShare = record.getShare();
for (int i = 1; i < size; i++) {
record = list.get(i);
thePrice = thePrice.multiply(record.getPrice());
theFloat = theFloat.multiply(record.getFloat());
theShare = theShare.multiply(record.getShare());
}
map.put(entry.getKey(), List.of(new Records(thePrice, theFloat, theShare)));
}
}
System.out.println(map);
}
}
You can't use float as an identifier in Java since it is a primitive type. Therefore I changed it to $float.
You don't need to iterate the set of keys (from the Map), you can iterate the entries. Each entry contains both the key and the value.
I am not familiar with class DecimalUtil but class BigDecimal can do the multiplication.
Running the above code produces the following output:
{Id2=[price: 1.00 , float: 7.00 , share: 4.00], Id1=[price: 6.00 , float: 12.00 , share: 30.00]}
The user of the api sends a json like this:
{ "0": "3♥", "1": "5♣", "2": "4♣",“3”: “9♥”, … }
im trying to save the the value of each index (3♥,5♣,4♣,9♥) in a list.
all I have now is the POST method but I dont know how to read it) OR i don't know if i need to use another type of request
#RequestMapping(value="/start", method = RequestMethod.POST, consumes= "application/json" )
public String getData(#RequestBody ?? ) { }
thank you in advance
Try below
#RequestMapping(value="/start", method = RequestMethod.POST, consumes= "application/json" )
public String getData(#RequestBody HashMap<String, String> data) {
List<String> result = new ArrayList<>();
for (String val: data.values()){
result.add(val);
}
}
We are storing the user input into a HashMap and then extracting its values in the for loop. You can ofcourse collect the data returned by data.values() into an ArrayList or any collection of your choice to avoid the for loop.
You can use EntrySet if you need both key and value like below
for (Map.Entry<String, String> entry : data.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
// ...
}
Try this:
#PostMapping("/saveData")
public ResponseEntity<String> saveData(#RequestBody Map<Integer, Object> data) {
List<Object> values = new ArrayList<>();
data.forEach(values::add);
//Additonal code here, e.g. save
return ResponseEntity.ok().build();
}
#RequestBody Map<Integer, Object> ensures that indexes will always be Integers. Value type can be changed to String.
If indexes are not int, 400 Bad Request will be returned. Indexes must be positive integers.
You can also user longer notation for adding elements to the list (might be more clear):
data.forEach((key, value) -> values.add(key, value));
For this payload:
{
"0": "3♥",
"1": "5♣",
"2": "4♣",
"3": "9♥"
}
That's the outcome:
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()
I have a data structure like below. I'm trying to group the objects in such a way like Map<String, List<String>> where key is the entryId and value is the List of groups it belongs to. entryId is always unique inside a group.
Example: entryId "1111" belongs to group1,group2,group3. I'm using the old java 7 way to iterate through the lists and checking. Is there any best possible way using Java8 Collectors/grouping to achieve this.
List<Group> where each Group object will have a list of Entry objects.
[
{
"id":"group1",
"entries":[
{
"entryId":"1111",
"name":"test1"
},
{
"entryId":"2222",
"name":"test2"
},
{
"entryId":"3333",
"name":"test3"
}
]
},
{
"id":"group2",
"entries":[
{
"entryId":"4444",
"name":"test1"
},
{
"entryId":"1111",
"name":"test2"
},
{
"entryId":"2222",
"name":"test3"
}
]
},
{
"id":"group3",
"entries":[
{
"entryId":"1111",
"name":"test1"
},
{
"entryId":"5555",
"name":"test2"
},
{
"entryId":"3333",
"name":"test3"
}
]
}
]
So the expected out put is this :
[
{
"1111":[
"group1",
"group2",
"group3"
]
},
{
"2222":[
"group1",
"group2"
]
},
{
"3333":[
"group1",
"group3"
]
},
{
"4444":[
"group2"
]
},
{
"5555":[
"group3"
]
}
]
I'm using below way currently. which is working as expected, but is there a much simpler way in Java 8 I can achieve this.
public Map<String, List<String>> mapEntries(List<Group> groups) {
Map<String, List<String>> entryMaps = new HashMap<>();
for (Group group : groups) {
for (Entry entry : group.getEntries()) {
List<String> groupsEntryBelongs = new ArrayList<>();
if (groups.iterator().hasNext() && !entryMaps.keySet().contains(entry.getEntryId())) {
updateGroups(groups, entry.getEntryId(), groupsEntryBelongs, entryMaps);
}
}
}
return entryMaps;
}
void updateGroups(List<Group> groups, String id, List<String> groupsEntryBelongs, Map<String, List<String>> entryMaps) {
for (Group group : groups) {
for (Entry entry : group.getEntries()) {
if (entry.getEntryId().equalsIgnoreCase(id)) {
groupsEntryBelongs.add(group.getId());
}
}
}
entryMaps.put(id, groupsEntryBelongs);
}
You can do it as follows:
Map<String, Set<String>> entryMaps = new LinkedHashMap<>();
groups.forEach(group ->
group.getEntries().forEach(entry ->
entryMaps.computeIfAbsent(
entry.getEntryId().toLowerCase(),
k -> new LinkedHashSet<>())
.add(group.getId())));
This iterates the groups, then each group's entries and uses Map.computeIfAbsent to put an entry with a new, empty LinkedHashSet if the key wasn't present, returning either this empty set or the one matching that key. Then, the group id is added to this returned set.
Note: I'm using a Set instead of a List for values, to avoid possible duplicates. And LinkedHashMap and LinkedhashSet guarantee insertion-order.
Something like this ought to work, it requires making some sort of intermediate tuple object:
groups.stream()
.flatMap(group -> group.getEntries().stream()
.map(entry -> Map.entry(entry.getEntryId(), group.getId())))
.collect(Colectors.groupingBy(Map.Entry::getKey,
Colectors.mapping(Map.Entry::getValue, toList())));
List<List<EmployeeHourLogDetails>> resultLisSalesman = new ArrayList<>
(employeeHourLogHeader.getEmployeeHourLogDetails().stream()
.collect(Collectors.groupingBy(d ->
d.getEmployeeId())).values());
I'm trying to parse a JSON ArrayNode in Java but I'm having some issues.
The object is as follows:
{
"type": "type",
"id": "id",
"attributes": {
"x": [ "x.value" ],
"y": [ "y.value" ],
"z": [ "z.value" ]
}
}
I'm parsing it as follows:
Map<String, Map<String, String>> users = new HashMap<>();
Iterator<JsonNode> arrayIterator = dataArray.elements();
while (arrayIterator.hasNext())
{
JsonNode r = arrayIterator.next();
String id = r.get("id").asText();
users.put(id, new HashMap<>());
Iterator<JsonNode> attributeIterator = r.path("attributes").elements();
while (attributeIterator.hasNext())
{
JsonNode attribute = attributeIterator.next();
users.get(id).put(attribute.asText(),
attribute.elements().next().asText());
}
}
But I'm getting a map like this:
"" => z.value
I found out in Java' documentation that the attribute .asText() will return empty if it is not a value node. How can I get that name so my map is instead:
x => x.value
y => y.value
z => z.value
Well the first thing you need the keys of your JSON. So I tried with fields instead of only elements
Iterator<Map.Entry<String, JsonNode>> attributeIterator = dataArray.path("attributes").fields();
while (attributeIterator.hasNext())
{
Map.Entry<String, JsonNode> attribute = attributeIterator.next();
users.get(id).put(attribute.getKey(),
attribute.getValue().get(0).asText());
}
I didn't like to get an array So I change to this
Iterator<Map.Entry<String, JsonNode>> attributeIterator = dataArray.path("attributes").fields();
while (attributeIterator.hasNext())
{
Map.Entry<String, JsonNode> attribute = attributeIterator.next();
users.get(id).put(attribute.getKey(),
attribute.getValue().elements().next().textValue());
}
The reason I used fields because I needed the key value :
Iterator that can be used to traverse all key/value pairs for object
nodes; empty iterator (no contents) for other types
And elements doesn't include keys:
Method for accessing all value nodes of this Node, iff this node is a
JSON Array or Object node. In case of Object node, field names
(keys) are not included, only values. For other types of nodes,
returns empty iterator.
From Java Docs
This is getting the map filled. I used jackson 2.9.4