MATLAB - Sort struct by substruct field - matlab

I'm a new Matlab user and a I have some doubts about structs.
My case is:
I have a struct P1 that have 3 "substructs"( A1, A2, A3) with two fields(Name and Age) and I want to sort my "substructs" by age. So, I have it:
P1.A1.age = 33
P1.A2.age = 23
P1.A3.age = 31
and I want this results:
P1.A2.age = 23
P1.A3.age = 31
P1.A1.age = 33
Any idea?
I try to use the function orderfields, but I didn't have the results that I wish.
Thanks !!!

First get the required permutation for sorting using sort and structfun. Then apply that permutation using orderfields:
[~, I] = sort(structfun(#(x) x.age, P1));
P1 = orderfields(P1, I);

Will this work for you?
>> [val idx]=sort(arrayfun(#(x) P1.(sprintf('A%d',x)).age,1:3))
val =
23 31 33
idx =
2 3 1

Related

Adding a datapoint to datastruct in matlab

I am trying to add a datapoint to an existing data struct. I have created the following data struct.
ourdata.animal= {'wolf', 'dog', 'cat'}
ourdata.height = [110 51 32]
ourdata.weight = [55 22 10]
say I want to add another one to the data struct with name 'fish' height 3 and weight 1, how do I go about this?
You can simply attach it to the end of the structure:
ourdata.animal{end+1} = 'fish'
ourdata.height(end+1) = 3
ourdata.weight(end+1) = 1
If you want to work with multiple structures, you can write a little function to combine the values of fields in multiple structs. Here's one, using fieldnames() to discover what fields exist:
function out = slapItOn(aStruct, anotherStruct)
% Slap more data on to the end of fields of a struct
out = aStruct;
for fld = string(fieldnames(aStruct))'
out.(fld) = [aStruct.(fld) anotherStruct.(fld)];
end
end
Works like this:
>> ourdata
ourdata =
struct with fields:
animal: {'wolf' 'dog' 'cat'}
height: [110 51 32]
weight: [55 22 10]
>> newdata = slapItOn(ourdata, struct('animal',{{'bobcat'}}, 'height',420, 'weight',69))
newdata =
struct with fields:
animal: {'wolf' 'dog' 'cat' 'bobcat'}
height: [110 51 32 420]
weight: [55 22 10 69]
>>
BTW, I'd suggest that you use string arrays instead of cellstrs for storing your string data. They're better in pretty much every way (except performance). Get them with double quotes:
>> strs = ["wolf" "dog" "cat"]
strs =
1×3 string array
"wolf" "dog" "cat"
>>
Also, consider using a table array instead of a struct array for tabular-looking data like this. Tables are nice!
>> animal = ["wolf" "dog" "cat"]';
>> height = [110 51 32]';
>> weight = [55 22 10]';
>> t = table(animal, height, weight)
t =
3×3 table
animal height weight
______ ______ ______
"wolf" 110 55
"dog" 51 22
"cat" 32 10
>>

q - apply function on table rowwise

Given a table and a function
t:([] c1:1 2 3; c2:`a`b`c; c3:13:00 13:01 13:02)
f:{[int;sym;date]
symf:{$[x=`a;1;x=`b;2;3]};
datef:{$[x=13:00;1;x=13:01;2;3]};
r:int + symf[sym] + datef[date];
r
};
I noticed that when applying the function f onto columns of t, then the entire columns are passed into f and if they can be operated on atomically then the output will be of the same length as the inputs and a new column is produced. However in our example this wont work:
update newcol:f[c1;c2;c3] from t / 'type error
because the inner functions symf and datef cannot be applied to the entire column c2, c3, respectively.
If I dont want to change the function f at all, how can I apply it row by row and collect the values into a new column in t.
What's the most q style way to do this?
EDIT
If not changing f is really inconvenient one could workaround like so
f:{[arglist]
int:arglist 0;
sym:arglist 1;
date:arglist 2;
symf:{$[x=`a;1;x=`b;2;3]};
datef:{$[x=13:00;1;x=13:01;2;3]};
r:int + symf[sym] + datef[date];
r
};
f each (t`c1),'(t`c2),'(t`c3)
Still I would be interested how to get the same result when working with the original version of f
Thanks!
You can use each-both for this e.g.
q)update newcol:f'[c1;c2;c3] from t
c1 c2 c3 newcol
------------------
1 a 13:00 3
2 b 13:01 6
3 c 13:02 9
However you will likely get better performance by modifying f to be "vectorised" e.g.
q)f2
{[int;sym;date]
symf:3^(`a`b!1 2)sym;
datef:3^(13:00 13:01!1 2)date;
r:int + symf + datef;
r
}
q)update newcol:f2[c1;c2;c3] from t
c1 c2 c3 newcol
------------------
1 a 13:00 3
2 b 13:01 6
3 c 13:02 9
q)\ts:1000 update newcol:f2[c1;c2;c3] from t
4 1664
q)\ts:1000 update newcol:f'[c1;c2;c3] from t
8 1680
In general in KDB, if you can avoid using any form of each and stick to vector operations, you'll get much more efficiency

Matlab: structures with variable name as index

I am not sure this is possible in Matlab but wanted to make sure.
I have structures as:
x = struct();
x.val1 = 5;
x.val2 = 7;
y = struct();
y.val1 = 15;
y.val2 = 17;
I want to create a structure DataStore as:
DataStore = struct;
DataStore(x).val1 = 5
DataStore(x).val2 = 7
DataStore(y).val1 = 15
DataStore(y).val2 = 17
OR
DataStore = struct;
DataStore('x').val1 = 5
DataStore('x').val2 = 7
DataStore('y').val1 = 15
DataStore('y').val2 = 17
So, I am using the name of the original structure variables as index for DataStore.
Is the above feasible ?
Edit:
I aim to use DataStore as following:
disp( DataStore('x').val1 )
disp( DataStore('y').val2 )
Use a struct, maybe with dynamic field names.
Either:
DataStore.x.val1=6
DataStore.x.val2=9
Alternative with dynamic filed names (result is the same):
f='x'
DataStore.(f).val1=6
DataStore.(f).val2=9
In case val1 and val2 are not just placeholders, concider replacing them with an array:
DataStore.(f).val(1)=6
DataStore.(f).val(2)=9

Matlab - read a specific format line

I have a file which contains data in the following format 0,"20 300 40 12".
How can I read this data with sscanf function such that I store 0 in a separate variable and 20 300 40 12 in another variable. The problem is that the array within the " " changes its size, so I cannot use a fix length array. So I can have something like this within my file:
0,"20 300 40 12"
0,"20 300 43 40 12"
1,"22 40 12"
Can you give me a hint of how to read this?
Have you tried with this:
fid = fopen(filename,'r');
A = textscan(fid,'%d,%q','Delimiter','\n');
Here's another way to do it:
[a,b] = textread('ah.txt','%d,"%[^"]"');
fun = #(x) split(' ',x);
resb = cellfun(fun,b,'UniformOutput',false)
res = {a resb};
function l = split(d,s)
%split string s on string d
out = textscan(s,'%s','delimiter',d,'multipleDelimsAsOne',1);
l = out{1};

T-SQL Decimal Division Accuracy

Does anyone know why, using SQLServer 2005
SELECT CONVERT(DECIMAL(30,15),146804871.212533)/CONVERT(DECIMAL (38,9),12499999.9999)
gives me 11.74438969709659,
but when I increase the decimal places on the denominator to 15, I get a less accurate answer:
SELECT CONVERT(DECIMAL(30,15),146804871.212533)/CONVERT(DECIMAL (38,15),12499999.9999)
give me 11.74438969
For multiplication we simply add the number of decimal places in each argument together (using pen and paper) to work out output dec places.
But division just blows your head apart. I'm off to lie down now.
In SQL terms though, it's exactly as expected.
--Precision = p1 - s1 + s2 + max(6, s1 + p2 + 1)
--Scale = max(6, s1 + p2 + 1)
--Scale = 15 + 38 + 1 = 54
--Precision = 30 - 15 + 9 + 54 = 72
--Max P = 38, P & S are linked, so (72,54) -> (38,20)
--So, we have 38,20 output (but we don use 20 d.p. for this sum) = 11.74438969709659
SELECT CONVERT(DECIMAL(30,15),146804871.212533)/CONVERT(DECIMAL (38,9),12499999.9999)
--Scale = 15 + 38 + 1 = 54
--Precision = 30 - 15 + 15 + 54 = 84
--Max P = 38, P & S are linked, so (84,54) -> (38,8)
--So, we have 38,8 output = 11.74438969
SELECT CONVERT(DECIMAL(30,15),146804871.212533)/CONVERT(DECIMAL (38,15),12499999.9999)
You can do the same math if follow this rule too, if you treat each number pair as
146804871.212533000000000 and 12499999.999900000
146804871.212533000000000 and 12499999.999900000000000
To put it shortly, use DECIMAL(25,13) and you'll be fine with all calculations - you'll get precision right as declared: 12 digits before decimal dot, and 13 decimal digits after.
Rule is: p+s must equal 38 and you will be on safe side!
Why is this?
Because of very bad implementation of arithmetic in SQL Server!
Until they fix it, follow that rule.
I've noticed that if you cast the dividing value to float, it gives you the correct answer, i.e.:
select 49/30 (result = 1)
would become:
select 49/cast(30 as float) (result = 1.63333333333333)
We were puzzling over the magic transition,
P & S are linked, so:
(72,54) -> (38,29)
(84,54) -> (38,8)
Assuming (38,29) is a typo and should be (38,20), the following is the math:
i. 72 - 38 = 34,
ii. 54 - 34 = 20
i. 84 - 38 = 46,
ii. 54 - 46 = 8
And this is the reasoning:
i. Output precision less max precision is the digits we're going to throw away.
ii. Then output scale less what we're going to throw away gives us... remaining digits in the output scale.
Hope this helps anyone else trying to make sense of this.
Convert the expression not the arguments.
select CONVERT(DECIMAL(38,36),146804871.212533 / 12499999.9999)
Using the following may help:
SELECT COL1 * 1.0 / COL2