I'm currently having troubles figuring out how to use Java 8 streams.
I'm trying to go from lista_dottori (Map<Integer, Doctor>) to a new map patientsPerSp where to every medical specialization (method getSpecialization) I map the number of patients wich have a doctor with this specialization (method getPatients in class Doctor returns a List of that doctor's patients). I can't understand how to use the counting method for this purpose, and I can't seem to find any examples nor explanations for this kind of problems on the internet.
That's what i've written, it does give me error in the counting section:
public Collection<String> countPatientsPerSpecialization(){
patientsPerSp=
lista_dottori.values().stream()
.map(Doctor::getSpecialization)
.collect(groupingBy(Doctor::getSpecialization, counting(Doctor::getPatients.size())))
;
}
Seems that you want to sum the sizes of patients lists. This can be done by summingInt() collector, not counting() (which just counts occurences; doctors in this case). Also mapping seems to be unnecessary here. So you cuold write:
patientsPerSp = lista_dottori.values().stream()
.collect(groupingBy(Doctor::getSpecialization,
summingInt(doctor -> doctor.getPatients().size())));
Note that the results will be incorrect if several doctors have the same patient (this way it will be counted several times). If it's possible in your case, then it would be probably better to make a set of patients:
patientsPerSp = lista_dottori.values().stream()
.collect(groupingBy(Doctor::getSpecialization,
mapping(Doctor::getPatients(), toSet())));
This way you will have a map which maps specialization to the set of patients, so the size of this set will be the count which you want. If you just need the count without set, you can add a final step via collectingAndThen():
patientsPerSp = lista_dottori.values().stream()
.collect(groupingBy(Doctor::getSpecialization,
collectingAndThen(
mapping(Doctor::getPatients(), toSet()),
Set::size)));
I solved the problem avoiding using the streams. That's the solution I used:
public Collection<String> countPatientsPerSpecialization(){
int numSpec = 0;
Map<String, Integer> spec = new HashMap<>();
for(Doctor d : lista_dottori.values()){
if(!spec.containsKey(d.getSpecialization())){
spec.put(d.getSpecialization(), d.getPatients().size());
numSpec++;
}
else{ //cioè se la specializzazione c'è già
spec.replace(d.getSpecialization(), +d.getPatients().size());
}
}
patientsPerSp.add(spec.keySet() + ": " + spec.values());
for(String s : patientsPerSp)
System.out.println(s);
return patientsPerSp;
}
I couldn't seem to be able to solve it using your solutions, although they were very well exposed, sorry.
Thank you anyway for taking the time to answer
Map<String, Integer> patientsPerSpecialization =
doctors.values()
.stream()
.collect(Collectors.groupingBy(Doctor::getSpecialization,
Collectors.summingInt(Doctor::nberOfAssignedPatients)));
Related
Given I have a stream or list of double properties that should all be bind together with the add operation, what is the right way to do this in JavaFX?
The following doesn't work since DoubleExpression::add returns a DoubleBinding instead of a DoubleProperty.
DoubleBinding value = doubleProperties.stream()
.reduce(doubleProperty, doubleProperty2) -> doubleProperty.add(doubleProperty2)).get();
DoubleBinding value = doubleProperties.stream()
.reduce(DoubleExpression::add).get();
The following works but is a hack that I don't think should be necessary:
DoubleBinding value = doubleProperties.stream()
.map(doubleProperty -> doubleProperty.add(0))
.reduce(DoubleExpression::add)
.get();
The most use able solution for me is using DoubleExpression instead of DoubleProperty or DoubleBinding.
DoubleExpression value = doubleProperties.stream()
.map(DoubleProperty::doubleExpression)
.reduce(DoubleExpression::add)
.get();
I don't actually understand the nuances between these three classes.
If someone knows if this construct does what I probably expect and what the differences are, please feel free to comment or answer.
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.
Is there an example anywhere of a form that performs running totals in a column located within a grid. The user ordering and filtering of the grid would affect the running totals column.
I can easily perform the above if it was ordering only by transaction date, but including the user ordering and filtering I presume that we would have to use the datasource range() and rangecount() functions (see SysQuery::mergeRanges() for an example) then iterate over these to apply the filtering, then include the dynalinks. The same for the ordering, albeit this is now more complicated.
Any suggestions appreciated. Any appreciations suggested (as in: vote the question up!).
You could implement it as a form datasource display method using this strategy:
Copy the form's datasource query (no need for SysQuery::mergeRanges):
QueryRun qr = new QueryRun(ledgerTrans_qr.query());
Iterate and sum over your records using qr, stop after the current record:
while (qr.next())
{
lt = qr.getNo(1);
total += lt.AmountMST;
if (lt.RecId == _lt.RecId)
break;
}
This could be made more performant if the sorting order was fixed (using sum(AmountMST) and adding a where constraint).
Return the total
This is of cause very inefficient (subquadratic time, O(n^2)).
Caching the results (in a map) may make it usable if there are not too many records.
Update: a working example.
Any observations or criticisms to the code below most welcome. Jan's observation about the method being slow is still valid. As you can see, it's a modification of his original answer.
//BP Deviation Documented
display AmountMST XXX_runningBalanceMST(LedgerTrans _trans)
{
LedgerTrans localLedgerTrans;
AmountMST amountMST;
;
localLedgerTrans = this.getFirst();
while (localLedgerTrans)
{
amountMST += localLedgerTrans.AmountMST;
if (localLedgerTrans.RecId == _trans.RecId)
{
break;
}
localLedgerTrans = this.getNext();
}
return amountMST;
}
We're looking into refining our User Groups in Dynamics AX 2009 into more precise and fine-tuned groupings due to the wide range of variability between specific people within the same department. With this plan, it wouldn't be uncommon for majority of our users to fall user 5+ user groups.
Part of this would involve us expanding the default length of the User Group ID from 10 to 40 (as per Best Practice for naming conventions) since 10 characters don't give us enough room to adequately name each group as we would like (again, based on Best Practice Naming Conventions).
We have found that the main information seems to be obtained from the UserGroupInfo table, but that table isn't present under the Data Dictionary (it's under the System Documentation, so unavailable to be changed that way by my understanding). We've also found the UserGroupName EDT, but that is already set at 40 characters. The form itself doesn't seem to restricting the length of the field either. We've discussed changing the field on the SQL directly, but again my understanding is that if we do a full synchronization it would overwrite this change.
Where can we go to change this particular setting, or is it possible to change?
The size of the user group id is defined as as system extended data type (here \System Documentation\Types\userGroupId) and you cannot change any of the properties including the size 10 length.
You should live with that, don't try to fake the system using direct SQL changes. Even if you did that, AX would still believe that length is 10.
You could change the SysUserInfo form to show the group name only. The groupId might as well be assigned by a number sequence in your context.
I wrote a job to change the string size via X++ and it works for EDTs, but it can't seem to find the "userGroupId". From the general feel of AX I get, I'd be willing to guess that they just have it in a different location, but maybe not. I wonder if this could be tweaked to work:
static void Job9(Args _args)
{
#AOT
TreeNode treeNode;
Struct propertiesExt;
Map mapNewPropertyValues;
void setTreeNodePropertyExt(
Struct _propertiesExt,
Map _newProperties
)
{
Counter propertiesCount;
Array propertyInfoArray;
Struct propertyInfo;
str propertyValue;
int i;
;
_newProperties.insert('IsDefault', '0');
propertiesCount = _propertiesExt.value('Entries');
propertyInfoArray = _propertiesExt.value('PropertyInfo');
for (i = 1; i <= propertiesCount; i++)
{
propertyInfo = propertyInfoArray.value(i);
if (_newProperties.exists(propertyInfo.value('Name')))
{
propertyValue = _newProperties.lookup(propertyInfo.value('Name'));
propertyInfo.value('Value', propertyValue);
}
}
}
;
treeNode = TreeNode::findNode(#ExtendedDataTypesPath);
// This doesn't seem to be able to find the system type
//treeNode = treeNode.AOTfindChild('userGroupId');
treeNode = treeNode.AOTfindChild('AccountCategory');
propertiesExt = treeNode.AOTgetPropertiesExt();
mapNewPropertyValues = new Map(Types::String, Types::String);
mapNewPropertyValues.insert('StringSize', '30');
setTreeNodePropertyExt(propertiesExt, mapNewPropertyValues);
treeNode.AOTsetPropertiesExt(propertiesExt);
treeNode.AOTsave();
info("Done");
}
I'll show you the function first.
private var areaCollection:ArrayCollection;
private function generateAreaCollection():void
{
areaCollection = new ArrayCollection();
areaCollection.addItem({Areal: "Totalareal:", Verdi: int(totalArea * 100) / 100 + " kvm"});
areaCollection.addItem({Areal: "Hovedtakets areal:", Verdi: int(result.area* 100) / 100 + " kvm"});
//Lots of other stuff omitted (just more addItems).
}
As you see, the order i put the items in the ArrayCollection is Areal, then Verdi (area, value)
When i loop through the collection later, the order changes. Now it is Verdi then Areal (value, area).
Does anyone have an idea of what might be the problem?
It really ruins things when I pass it over to a PHP-script for table-making in html later.
(I have several different dynamic DataGrids that differs in size and "values", so I can't really point directly to f.ex "Areal" in my PHP-script)
And by the way, does anyone know how i can remove that pesky mx_internal_uid?
Raw objects in AS3 (Object class, or things defined by {} ), have no sorting. That is, the order is unpredictable. You're confusing two ideas here I think. The Areal and Verdi strings are keys within an object map. The array collection is a list composed of two such objects. So any sorting applied to the array collection will sort those objects, but not within the object.
So you need to refer to areaCollection.source[0].Areal to get "Totalareal". And areaCollection.source[1].Verdi to get int(result.area* 100) / 100 + " kvm"
If you do for(var s:String in areaCollection.source[0]) { } you will iterate twice, with the value of "s" being "Areal" and "Verdi" or vice-versa (e.g, order not guaranteed).
Make sense? If you need order, you can make the object an array instead ["Totalareal", (int(totalArea * 100) / 100 + " kvm")], and then access "Verdi" using [1].
P.s. not sure how to remove the mx_internal_id.