MATLAB Language
Handige trucs
Zoeken…
Handige functies die werken op cellen en arrays
Dit eenvoudige voorbeeld geeft een uitleg over enkele functies die ik uiterst nuttig vond sinds ik MATLAB ben gaan gebruiken: cellfun
, arrayfun
. Het idee is om een array- of celklasse-variabele te nemen, door alle elementen te lopen en een speciale functie op elk element toe te passen. Een toegepaste functie kan anoniem zijn, wat meestal een geval is, of een normale functie definiëren in een * .m-bestand.
Laten we beginnen met een eenvoudig probleem en zeggen dat we een lijst met * .mat-bestanden moeten vinden in de map. Laten we voor dit voorbeeld eerst enkele * .mat-bestanden maken in een huidige map:
for n=1:10; save(sprintf('mymatfile%d.mat',n)); end
Na het uitvoeren van de code zouden er 10 nieuwe bestanden met de extensie * .mat moeten zijn. Als we een opdracht uitvoeren om alle * .mat-bestanden weer te geven, zoals:
mydir = dir('*.mat');
we zouden een reeks elementen van een dir-structuur moeten krijgen; MATLAB zou een soortgelijke output moeten geven als deze:
10x1 struct array with fields:
name
date
bytes
isdir
datenum
Zoals je kunt zien, is elk element van deze array een structuur met een paar velden. Alle informatie is inderdaad belangrijk voor elk bestand, maar voor 99% ben ik eerder geïnteresseerd in bestandsnamen en niets anders. Om informatie uit een structuurarray te extraheren, gebruikte ik een lokale functie waarbij tijdelijke variabelen met de juiste grootte moesten worden gemaakt, voor lussen, een naam uit elk element moest worden opgehaald en opgeslagen in de gemaakte variabele. Veel eenvoudiger manier om exact hetzelfde resultaat te bereiken, is door een van de bovengenoemde functies te gebruiken:
mydirlist = arrayfun(@(x) x.name, dir('*.mat'), 'UniformOutput', false)
mydirlist =
'mymatfile1.mat'
'mymatfile10.mat'
'mymatfile2.mat'
'mymatfile3.mat'
'mymatfile4.mat'
'mymatfile5.mat'
'mymatfile6.mat'
'mymatfile7.mat'
'mymatfile8.mat'
'mymatfile9.mat'
Hoe deze functie werkt? Meestal zijn er twee parameters nodig: een functie-ingang als eerste parameter en een array. Een functie werkt dan op elk element van een gegeven array. De derde en vierde parameters zijn optioneel maar belangrijk. Als we weten dat een uitvoer niet regelmatig is, moet deze worden opgeslagen in een cel. Dit moet wijzen op het instellen van zijn false
te UniformOutput
. Standaard probeert deze functie een reguliere uitvoer te retourneren, zoals een vector van getallen. Laten we bijvoorbeeld informatie extraheren over hoeveel schijfruimte wordt ingenomen door elk bestand in bytes:
mydirbytes = arrayfun(@(x) x.bytes, dir('*.mat'))
mydirbytes =
34560
34560
34560
34560
34560
34560
34560
34560
34560
34560
of kilobytes:
mydirbytes = arrayfun(@(x) x.bytes/1024, dir('*.mat'))
mydirbytes =
33.7500
33.7500
33.7500
33.7500
33.7500
33.7500
33.7500
33.7500
33.7500
33.7500
Deze keer is de uitvoer een regelmatige dubbele vector. UniformOutput
was standaard ingesteld op true
.
cellfun
is een soortgelijke functie. Het verschil tussen deze functie en arrayfun
is dat cellfun
werkt op cellfun
variabelen. Als we alleen namen willen extraheren die een lijst met bestandsnamen in een cel 'mydirlist' hebben gekregen, moeten we deze functie als volgt uitvoeren:
mydirnames = cellfun(@(x) x(1:end-4), mydirlist, 'UniformOutput', false)
mydirnames =
'mymatfile1'
'mymatfile10'
'mymatfile2'
'mymatfile3'
'mymatfile4'
'mymatfile5'
'mymatfile6'
'mymatfile7'
'mymatfile8'
'mymatfile9'
Nogmaals, omdat een uitvoer geen reguliere vector van getallen is, moet een uitvoer worden opgeslagen in een celvariabele.
In het onderstaande voorbeeld combineer ik twee functies in één en retourneer ik alleen een lijst met bestandsnamen zonder extensie:
cellfun(@(x) x(1:end-4), arrayfun(@(x) x.name, dir('*.mat'), 'UniformOutput', false), 'UniformOutput', false)
ans =
'mymatfile1'
'mymatfile10'
'mymatfile2'
'mymatfile3'
'mymatfile4'
'mymatfile5'
'mymatfile6'
'mymatfile7'
'mymatfile8'
'mymatfile9'
Het is gek maar heel goed mogelijk omdat arrayfun
een cel retourneert die naar verwachting input van cellfun
; een kanttekening hierbij is dat we elk van die functies kunnen dwingen om resultaten in een UniformOutput
te geven door UniformOutput
expliciet op false te zetten. We kunnen altijd resultaten in een cel krijgen. We kunnen mogelijk geen resultaten krijgen in een reguliere vector.
Er is nog een soortgelijke functie die op velden een structuur werkt: structfun
. Ik heb het niet zo nuttig gevonden als de andere twee, maar het zou in sommige situaties schijnen. Als u bijvoorbeeld wilt weten welke velden numeriek of niet-numeriek zijn, kan de volgende code het antwoord geven:
structfun(@(x) ischar(x), mydir(1))
Het eerste en het tweede veld van een richtstructuur zijn van het type char. Daarom is de output:
1
1
0
0
0
De uitvoer is ook een logische vector van true
/ false
. Bijgevolg is het regelmatig en kan het in een vector worden opgeslagen; geen behoefte aan een celklasse.
Voorkeuren voor codevouwen
Het is mogelijk om de codevouwvoorkeur aan uw behoefte aan te passen. Codevouwen kan dus worden ingesteld voor niet-specifieke / niet-specifieke constructies (bijvoorbeeld: if block
, for loop
, Sections
...).
Om vouwvoorkeuren te wijzigen, ga naar Voorkeuren -> Code vouwen:
Vervolgens kunt u kiezen welk deel van de code kan worden gevouwen.
Wat informatie:
- Merk op dat u ook alle code in een bestand kunt uitvouwen of samenvouwen door uw cursor ergens in het bestand te plaatsen, met de rechtermuisknop te klikken en vervolgens Code vouwen> Alles uitvouwen of Code vouwen> Alles vouwen te selecteren in het contextmenu.
- Merk op dat vouwen blijvend is, in die zin dat een deel van de code die is uitgevouwen / samengevouwen hun status behoudt nadat Matlab of het m-bestand is gesloten en opnieuw wordt geopend.
Voorbeeld: vouwen inschakelen voor secties:
Een interessante optie is om secties te vouwen. Secties worden gescheiden door twee procenttekens (
%%
).Voorbeeld: schakel het selectievakje "Secties" in:
In plaats van een lange broncode te zien die lijkt op:
U kunt secties vouwen voor een algemeen overzicht van uw code:
Cijfergegevens extraheren
Een paar keer heb ik een interessant cijfer gehad dat ik heb opgeslagen, maar ik ben de toegang tot de gegevens kwijtgeraakt. Dit voorbeeld toont een truc om informatie uit een figuur te halen.
De belangrijkste functies zijn findobj en get . findobj retourneert een handler naar een object met gegeven kenmerken of eigenschappen van het object, zoals Type
of Color
, enz. Zodra een lijnobject is gevonden, kan get elke waarde retourneren die door eigenschappen wordt vastgehouden. Het blijkt dat de Line
objecten alle gegevens bevatten met de volgende eigenschappen: XData
, YData
en ZData
; de laatste is meestal 0 tenzij een figuur een 3D-plot bevat.
De volgende code maakt een voorbeeldfiguur met twee regels, een sin-functie en een drempelwaarde en een legenda
t = (0:1/10:1-1/10)';
y = sin(2*pi*t);
plot(t,y);
hold on;
plot([0 0.9],[0 0], 'k-');
hold off;
legend({'sin' 'threshold'});
Het eerste gebruik van findobj retourneert twee handlers naar beide regels:
findobj(gcf, 'Type', 'Line')
ans =
2x1 Line array:
Line (threshold)
Line (sin)
Om het resultaat te beperken, kan findobj ook een combinatie van logische operatoren -and
, -or
en eigenschapnamen gebruiken. Ik kan bijvoorbeeld een lijnobject vinden waarvan DiplayName
sin
en de XData
en YData
.
lineh = findobj(gcf, 'Type', 'Line', '-and', 'DisplayName', 'sin');
xdata = get(lineh, 'XData');
ydata = get(lineh, 'YData');
en controleer of de gegevens gelijk zijn.
isequal(t(:),xdata(:))
ans =
1
isequal(y(:),ydata(:))
ans =
1
Op dezelfde manier kan ik mijn resultaten verfijnen door de zwarte lijn (drempel) uit te sluiten:
lineh = findobj(gcf, 'Type', 'Line', '-not', 'Color', 'k');
xdata = get(lineh, 'XData');
ydata = get(lineh, 'YData');
en de laatste controle bevestigt dat de gegevens uit dit cijfer hetzelfde zijn:
isequal(t(:),xdata(:))
ans =
1
isequal(y(:),ydata(:))
ans =
1
Functioneel programmeren met behulp van anonieme functies
Anonieme functies kunnen worden gebruikt voor functioneel programmeren. Het belangrijkste probleem om op te lossen is dat er geen native manier is om een recursie te verankeren, maar dit kan nog steeds in een enkele regel worden geïmplementeerd:
if_ = @(bool, tf) tf{2-bool}();
Deze functie accepteert een Booleaanse waarde en een celarray van twee functies. De eerste van die functies wordt geëvalueerd als de booleaanse waarde als waar wordt geëvalueerd en de tweede als de booleaanse waarde als onwaar wordt geëvalueerd. We kunnen de faculteit nu eenvoudig schrijven:
fac = @(n,f) if_(n>1, {@()n*f(n-1,f), @()1});
Het probleem is hier dat we niet direct een recursieve aanroep kunnen aanroepen, omdat de functie nog niet is toegewezen aan een variabele wanneer de rechterkant wordt geëvalueerd. We kunnen deze stap echter wel voltooien door te schrijven
factorial_ = @(n)fac(n,fac);
Nu evacueert @(n)fac(n,fac)
facultaire functie recursief. Een andere manier om dit te doen in functioneel programmeren met behulp van een y-combinator, die ook eenvoudig kan worden geïmplementeerd:
y_ = @(f)@(n)f(n,f);
Met deze tool is de faculteit nog korter:
factorial_ = y_(fac);
Of direct:
factorial_ = y_(@(n,f) if_(n>1, {@()n*f(n-1,f), @()1}));
Sla meerdere figuren op in hetzelfde .fig-bestand
Door meerdere figuurhandvatten in een grafische array te plaatsen, kunnen meerdere figuren in hetzelfde .fig-bestand worden opgeslagen
h(1) = figure;
scatter(rand(1,100),rand(1,100));
h(2) = figure;
scatter(rand(1,100),rand(1,100));
h(3) = figure;
scatter(rand(1,100),rand(1,100));
savefig(h,'ThreeRandomScatterplots.fig');
close(h);
Dit creëert 3 spreidingsplots van willekeurige gegevens, elk deel van de grafische array h. Vervolgens kan de grafische array worden opgeslagen met savefig zoals bij een normale figuur, maar met de greep van de grafische array als een extra argument.
Een interessante kanttekening is dat de cijfers de neiging hebben om op dezelfde manier geordend te blijven als ze waren opgeslagen toen u ze opent.
Commentaarblokken
Als u een deel van uw code wilt becommentariëren, kunnen commentaarblokken nuttig zijn. Commentaarblok begint met een %{
in een nieuwe regel en eindigt met %}
in een andere nieuwe regel:
a = 10;
b = 3;
%{
c = a*b;
d = a-b;
%}
Hiermee kun je de secties die je hebt becommentarieerd vouwen om de code schoner en compacter te maken.
Deze blokken zijn ook handig voor het in- en uitschakelen van delen van uw code. Het enige dat u hoeft te doen om de blokkering op te heffen, is nog een %
voordat het strats:
a = 10;
b = 3;
%%{ <-- another % over here
c = a*b;
d = a-b;
%}
Soms wil je commentaar geven op een deel van de code, maar zonder de inspringing te beïnvloeden:
for k = 1:a
b = b*k;
c = c-b;
d = d*c;
disp(b)
end
Meestal, wanneer u een codeblok markeert en op Ctrl + r drukt om het te becommentariëren (door het %
automatisch aan alle regels toe te voegen, en wanneer u later op Ctrl + i drukt voor automatische inspringing, wordt het codeblok verplaatst van de juiste hiërarchische plaats en verplaatst te veel naar rechts:
for k = 1:a
b = b*k;
% c = c-b;
% d = d*c;
disp(b)
end
Een manier om dit op te lossen is het gebruik van commentaarblokken, zodat het binnenste deel van het blok correct ingesprongen blijft:
for k = 1:a
b = b*k;
%{
c = c-b;
d = d*c;
%}
disp(b)
end