2016-02-01 13 views
8

Podczas konfigurowania pętli-pętli chcę mieć możliwość przechodzenia w pętlę przez nieznaną liczbę parametrów.Dynamicznie zmieniaj pętle for w MATLAB

Dzięki plikowi wejściowemu użytkownik może ustawić dowolną liczbę parametrów pętli zgodnie z własnym życzeniem i chcę mieć możliwość ich przewracania bez względu na liczbę ustawionych parametrów. Loop Przykład Wejście: (Zauważ, że moje wejścia mogą być listy ciągów i kombinacji liczb, jak również)

Przypadek 1:

  • Waga 45000: 5000: 75000
  • Wysokość 10000
  • Prędkość 0,2: 0,1: 0,9

Przypadek 2:

  • Waga 30000
  • wysokości 1000: 1000: 10000
  • klapy 10, 20
  • temperatury około 10: 1: 10

długość listy może się różnić i może obejmować gdziekolwiek od 0-15 zmiennych. Znam sposób obejścia tego problemu, ale naprawdę nieuporządkowane jest używanie w tym celu mnóstwa zagnieżdżonych pętli for-loop. Szukam sposobu na skonfigurowanie metody, być może z programem Recursion, gdzie kod ustawi odpowiedni system pętli for bez względu na liczbę parametrów i nadal będzie śledzić te zmienne.

+2

Szukasz [coś w tym stylu] (http://stackoverflow.com/a/34840222/2732801)? – Daniel

+2

Chciałbym parsować plik wejściowy i przekazać wyniki jako argumenty do funkcji. – excaza

+0

Naprawdę nie jest konieczne wykonywanie żadnych analiz. po prostu napisz kod, aby użyć maksymalnej liczby pętli (np. pętla na wadze, wysokość, prędkość, temperatura, klapy itp.). w przypadku, gdy jeden z parametrów jest stały, to w rzeczywistości nie będzie pętli, ale wystarczy raz ustawić indeks i przejść do następnej pętli. –

Odpowiedz

1

Rekursja.

Nie określono żadnych wymagań dotyczących prędkości. Ta odpowiedź jest powolna, wykorzystuje słabo pamięci, ale jest najprostszą możliwą implementacją tego pomysłu. Istnieje wiele lepszych implementacji, które są bardziej skomplikowane, zużywają znacznie mniej pamięci i są znacznie szybsze. Zależy od tego jak mocno trzeba wewnętrzne pętle być ...

parms.weight = [45000:5000:75000]; 
parms.alt = 10000; 
parms.Speed = 0.2:0.1:0.9; 

Następnie zdefiniować symulatora, jak coś takiego:

function result = simulation(parms) 
    fieldNames = fieldnames(parms) 
    result = []; 
    for ix = 1 : numel(fieldNames) 
     if 1 < numel(parms.(fieldNames{ix})) 
      list = parms.(fieldNames{ix}); 
      for jx = 1 : numel(list) 
       tmpParms = parms; 
       tmpParms.(fieldNames{ix}) = list(jx); 
       tmpResult = simulation(tmpParms); 
       result = [result; tmpResult]; 
      end 
      return; 
     end 
    end 

    if 0 == numel(result) 
     % Do the real simulation here. 
    end 
+2

Często miałem do czynienia z tym problemem, ale z jakiegoś powodu twoje pytanie zachęciło mnie do zupełnie nowego sposobu myślenia o tym problemie. Doskonałe pytanie. – John

+2

Coś jest nie tak z twoim kodem, dostaję zdecydowanie wiele kombinacji. Po prostu spróbuj 'struct ('a', [1 2], 'b', [3], 'c', [4,5], 'e', ​​[6,7])'. Oczekuje 6 wywołań symulacji, ale generuje znacznie więcej połączeń. – Daniel

+1

@ Daniel, dzięki. Masz rację, jest błąd. Naprawiony. btw. ze swoimi danymi wejściowymi, oczekuj 8 wyników w ostatecznej odpowiedzi. Uwaga: Liczba różnych wywołań do symulacji będzie większa niż 8. – John

1

Doing zagnieżdżone for -loops dla wszystkich parametrów jest proste rozwiązanie stałe parametry spowodują po prostu pojedynczą iterację dla tej konkretnej pętli.

Można również użyć ndgrid stworzyć zestaw wielowymiarowych tablic, które są parametry dla każdego połączenia z twoich computeFunction(...) i iteracyjne nad wyjściem ngrid's Zamiast budować zagnieżdżone pętle dla każdego parametru potencjalnie zmiennym, jeśli oczekujesz zmienić liczbę parametrów.

Należy jednak pamiętać, że może się to wiązać z kosztem wydajności, ponieważ w zależności od tego, co dokładnie robimy w wersji matlab, może nie skorzystać z wbudowanych optymalizacji, które otrzymuje "prosta" wersja pętli for.

+1

Podniesiesz bardzo przydatny punkt dotyczący wydajności. Wielkie dzięki za twój wkład! – arthream

+1

Ponadto, i popraw mnie, jeśli się mylę, wydaje się, że ndgrid ogranicza się tylko do wartości liczbowych, co nie pomaga mojej sytuacji. Mogłabym zbudować indeksowane wyszukiwanie dla ciągów wejściowych, ale to więcej komplikacji, które chciałbym dodać do kodu. – arthream

+1

@arthream: Podstawową wadą tego rozwiązania jest to, że tworzy matrycę z pełną przestrzenią parametrów (każda kombinacja), która może zużywać dużo pamięci. – Daniel

1

Poniżej funkcji rekurencyjnej, który wytwarza wszystkich możliwych zestawów parametrów jako wektory kolumny tablicy:

function idx=dynloop(varargin) 
    if nargin==1 
     idx=varargin{1}; 
    else 
     idx=dynloop(varargin{2:end}); 
     idx=[repelem(varargin{1},size(idx,2)); 
      repmat(idx,[1,length(varargin{1})])]; 
    end 
return 

Przykład Wydajność: pętla ponad 5:7, następnie 8, następnie [2,3,5,7]:

>> idx = dynloop(5:7,8,[2,3,5,7]) 

idx = 

    5  5  5  5  6  6  6  6  7  7  7  7 
    8  8  8  8  8  8  8  8  8  8  8  8 
    2  3  5  7  2  3  5  7  2  3  5  7 

Dane wyjściowe są równoważne z danymi z ndgrid (z tym, że moim zdaniem jest to bardziej intuicyjny kształt). (Również niektóre Wstępna analiza porównawcza wykazała, może to być nieznacznie szybciej niżndgrid ... Do ustalenia?!)

Główna pętla: Teraz wystarczy użyć jednej pętli do iterację kolumny idx. Zauważ, że ta koncepcja rozwiązania jest podobna do this solution, jak skomentował @ Daniel dla OP.

Weight = 45000:5000:75000; 
Altitude = 10000; 
Speed = 0.2:0.1:0.9; 

idx = dynloop(Weight,Altitude,Speed); 

for ii=1:size(idx,2) 
    params = idx(:,ii); 

    %// do stuff with params! 
    %// ... 

end 

EDIT: aby obsługiwać zarówno ciąg i wejść numerycznych

prosty mod do pierwotnej funkcji rekurencyjnej napisałem pozwala mu powrócić indeksów, zamiast rzeczywistych elementów, więc komórki smyczkowe są w porządku:

function idx=dynloop(varargin) 
    if nargin==1 
     idx=1:length(varargin{1}); 
    else 
     idx=dynloop(varargin{2:end}); 
     idx=[repelem(1:length(varargin{1}),size(idx,2)); 
      repmat(idx,[1,length(varargin{1})]);]; 
    end 
return 

Więc w sumie, czynność przypuszczalnie działać tak:

function yourMainFcn(varargin) 
    idx=dynloop(varargin{:}); 
    for ii=1:size(idx,2) 
     params = cellfun(@(x,k) numORcell(x,k),... 
       varargin,num2cell(idx(:,ii).'),... %//' 
       'UniformOutput',0); 

     %// do stuff with params! 
     %// ... 
     disp(params) 

    end 
end 

gdzie funkcja numORcell odpowiednio analizuje numeryczny w porównaniu z danymi komórki:

function y=numORcell(x,k) 
    if iscell(x) 
     y=x{k}; 
    else 
     y=x(k); 
    end 
end 

przykład z łańcuchów:

Weight = 45000:5000:75000; 
Altitude = 10000; 
Speed = 0.2:0.1:0.9; 
Names = {'Foo','Bar'}; 

>> yourMainFcn(Names,Altitude,Weight) 
'Foo' [10000] [45000] 

'Foo' [10000] [50000] 

'Foo' [10000] [55000] 

'Foo' [10000] [60000] 

'Foo' [10000] [65000] 

'Foo' [10000] [70000] 

'Foo' [10000] [75000] 

'Bar' [10000] [45000] 

'Bar' [10000] [50000] 

'Bar' [10000] [55000] 

'Bar' [10000] [60000] 

'Bar' [10000] [65000] 

'Bar' [10000] [70000] 

'Bar' [10000] [75000] 

lub zamiast:

>> yourMainFcn(Names,Names,Speed,Names) 
'Foo' 'Foo' [0.2] 'Foo' 

'Foo' 'Foo' [0.2] 'Bar' 

'Foo' 'Foo' [0.3] 'Foo' 

'Foo' 'Foo' [0.3] 'Bar' 

'Foo' 'Foo' [0.4] 'Foo' 

... 

'Foo' 'Foo' [0.9] 'Bar' 

'Foo' 'Bar' [0.2] 'Foo' 

... 

'Foo' 'Bar' [0.9] 'Bar' 

'Bar' 'Foo' [0.2] 'Foo' 

... 

'Bar' 'Foo' [0.9] 'Bar' 

... 

'Bar' 'Bar' [0.8] 'Foo' 

'Bar' 'Bar' [0.8] 'Bar' 

'Bar' 'Bar' [0.9] 'Foo' 

'Bar' 'Bar' [0.9] 'Bar' 

Zadanie lewej do czytnika : Jeśli przechowywanie wszystkich indeksów w idx jest problemem z pamięcią, musisz wykonać dość ciężką pętlę. Mimo to możesz utworzyć funkcję, która określa z bieżącego zestawu indeksów następny zestaw indeksów leksykograficznych, co oznacza, że ​​będziesz musiał tylko przechowywać jeden zestaw indeksów i powtarzać je na końcu każdej pętli.

+0

Interesujące. Powinienem również określić, że moje parametry wejściowe mogą zawierać łańcuchy i wartości alfanumeryczne, a ndgrid nie jest najlepszą metodą z wejściami łańcuchowymi, chyba że wymyślę skomplikowany schemat indeksowania opcji łańcuchowych na dużych listach. Ale klucz jest skomplikowany. Priorytetem jest uczynienie kodu zrozumiałym dla przyszłych użytkowników. – arthream

+0

@arthream moje rozwiązanie zostało zmodyfikowane, aby obsługiwać również wejścia łańcuchowe. Nie wymaga samodzielnego indeksowania opcji napisów. Mam nadzieję, że pomaga – Geoff

+0

wielkie dzięki! Ta edycja powinna dobrze działać, z tym wyjątkiem, że korzystam z wersji MATLAB 2014b (repelem został wprowadzony w 2015a), a mój kod ma być kompatybilny z co najmniej od 2012a. Jest to z pewnością wnikliwe i będę o tym pamiętać! – arthream

2

Kod generacji rozwiązanie

No, masz już szereg bardzo dobrych rozwiązań. Po prostu wrzucę tam jednego, który wymaga generowania kodu. MATLAB nie ma zbyt wielu narzędzi, ale można go sfałszować za pomocą kilku pętli i fprintf.Oto mój skrypt generowania kodu:

s = struct() ; 
s.weight = 45000:5000:75000 ; 
s.altitude = 10000 ; 
s.engine = {'ge','rolsroyce'} ; 

h = fopen('thisrun.m','w+') ; 

mydisp = @(varargin)disp(transpose(varargin(:))) ; % dummy function body 

vars = fields(s) ; 
nv = numel(vars) ; 

for ii = 1:nv 
    if isnumeric(s.(vars{ii})) 
     lb = '(' ; 
     rb = ')' ; 
    else 
     lb = '{' ; 
     rb = '}' ; 
    end 
    fprintf(h,'for i%g = 1:numel(s.(vars{%g})) \n',ii,ii) ; 
    fprintf(h,'i%gval = s.(vars{%g})%si%g%s ; \n',ii,ii,lb,ii,rb) ; 
end 

fprintf(h,'mydisp(') ; 
for ii = 1:numel(vars) 
    fprintf(h,'i%gval',ii) ; 
    if ii<nv 
     fprintf(h,',') ; 
    end 
end 
fprintf(h,') ; \n') ; 

for ii = 1:nv 
    fprintf(h,'end \n') ; 
end 

fclose(h) ; 
run thisrun.m 

wygenerowany kod (thisrun.m):

for i1 = 1:numel(s.(vars{1})) 
    i1val = s.(vars{1})(i1) ; 
    for i2 = 1:numel(s.(vars{2})) 
     i2val = s.(vars{2})(i2) ; 
     for i3 = 1:numel(s.(vars{3})) 
      i3val = s.(vars{3}){i3} ; 
      mydisp(i1val,i2val,i3val) ; 
     end 
    end 
end 

Wynikiem działania wygenerowany kod:

>> 
    [45000] [10000] 'ge' 

    [45000] [10000] 'rolsroyce' 

    [50000] [10000] 'ge' 

    [50000] [10000] 'rolsroyce' 

    [55000] [10000] 'ge' 

    [55000] [10000] 'rolsroyce' 

    [60000] [10000] 'ge' 

    [60000] [10000] 'rolsroyce' 

    [65000] [10000] 'ge' 

    [65000] [10000] 'rolsroyce' 

    [70000] [10000] 'ge' 

    [70000] [10000] 'rolsroyce' 

    [75000] [10000] 'ge' 

    [75000] [10000] 'rolsroyce' 

Generowanie kodu wymaga czasu, ale jeśli trzeba uruchamiaj plik wiele razy, może nadal być wydajnym rozwiązaniem.

Powiązane problemy