Įdėkite =VLOOKUP(A1,B:B,1) į langelį stulpelyje B, ir Excel apskaičiuoja ją be nusiskundimų. Pateikę tą pačią darbo knygą priklausomybių grafo perskaičiavimo varikliui, greičiausiai gausite žiedinės nuorodos klaidą, nes formulė priklauso nuo srities, kurioje yra pati formulė. HotXLS pranešdavo būtent tai iki v2.361.98. Pataisa nėra ypatingas atvejis viso stulpelio sritims; tai išskirtis tarp dviejų priklausomybės briaunų rūšių, kurių reikia skaičiuoklės varikliui ir kurių neturi paprastas nukreiptasis grafas
Paieškos masyvo argumentas paieškų šeimai — LOOKUP, MATCH, HLOOKUP, VLOOKUP, XLOOKUP ir XMATCH — dabar žymimas kaip skenavimo nuoroda. Skenavimo nuoroda vis tiek sėja nešvarumą, todėl langelio srities viduje keitimas perskaičiuoja formulę, bet ji niekada nededa į ciklų aptikimą ar vertinimo eiliškumą. Tikri ciklai vis dar randami; klaidingieji dingo
Kodėl Excel leidžia paieškos sričiai turėti formulę?
Nes tas argumentas nevartojamas taip, kaip aritmetinis operandas. Paieškų šeima skenuoja sritį dėl podėlio reikšmių ir grąžina atitikmenį; jai nereikalaujama, kad sritis būtų įvertinta iki pabaigos pirmiausia. Excel save dengiančią paieškos sritį traktuoja kaip skaitantį tai, ką tie langeliai šiuo metu laiko, — ta pati semantika, kurią jis taiko bet kuriai neiteracinei darbo knygai: langeliai, šioje eigoje neperskaičiuoti, atiduoda savo paskutinę apskaičiuotą reikšmę
Viso stulpelio nuorodos daro tai įprastu atveju, o ne egzotišku. B:B yra idiominis būdas parašyti „visą paieškos lentelę“ lape, kuriame eilutės pridedamos, ir bet kuri formulė, gyvenanti stulpelyje B, tada yra savo paties paieškos srities viduje. Finansiniai modeliai, susitaikymo lakštai ir audito darbo knygos tai daro nuolat, paprastai be to, kad kas nors pastebėtų srities perdengimą
Ką priklausomybių grafas daro su ta pačia formule
HotXLS perskaičiuoja prieauginiais žingsniais, kas reikalauja tikro priklausomybių grafo: mazgų langeliams, briaunų nuorodoms, topologinės tvarkos vertinimui ir stipriai susijusių komponentų eigos ciklams klasifikuoti. Ta mechanika aprašyta prieauginio perskaičiavimo straipsnyje, ir būtent todėl pasirodė klaidingasis teigiamas
Ištraukus priklausomybes iš =VLOOKUP(A1,B:B,1) langelyje B7, antrasis argumentas duoda sritį, turinčią patį B7. Grafas dabar turi savęs kilpą. To mazgo įeinantysis laipsnis niekada nepasiekia nulio, todėl topologinė eiga niekada jo negali suplanuoti, o komponentų eiga klasifikuoja jį kaip ciklą. Variklis samprotavoja teisingai apie grafą, kurį gavo. Grafas yra neteisingas modelis, nes jis užkoduoja vieną briaunų tipą ten, kur skaičiuoklė turi du
Dvi briaunų klasės, vienas grafas
Pakeitimas prideda vėliavėlę išanalizuotų nuorodų įrašui — TXLSDepRange.LookupScan, kurią priklausomybių ištraukėjas nustato, apeidamas vienos iš šešių funkcijų paieškos masyvo argumentą. Toliau briaunos, kilusios iš tų nuorodų, saugomos atskirai nuo paprastų briaunų: grafo mazgas turi ScanDependents ir ScanPrecedents sąrašus greta savo įprastų priklausomųjų ir pirmtakų sąrašų
Atskyrimas yra tai, kas daro semantiką teisingą. Skenavimo briaunos pereinamos nešvarumų plitimo, todėl keitimas bet kur B:B viduje vis tiek žymi B7 kaip nešvarų, ir B7 perskaičiuoja. Skenavimo briaunos niekada neįskaitomos į įeinantįjį laipsnį ir niekada neįeina į komponentų kūrėją, todėl negali sukurti topologinės aklavietės ir negali būti klasifikuotos kaip ciklas. Abi bibliotekos grafo įgyvendinimai — klasikinis pagal darbo knygą grafas ir tarpdarbknygis darbo erdvės grafas, nešantis komponentų analizę — buvo keičiami kartu; leisti jiems skirtis duotų darbo knygą, kuri perskaičiuoja skirtingai pagal tai, ar atidaryta viena, ar kaip darbo erdvės dalis
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
begin
Book := TXLSXWorkbook.Create;
try
Sheet := Book.Sheets.Add('Ledger');
Sheet.Cells[1, 1].Value := 'ACC-4471';
Sheet.Cells[1, 2].Value := 1200.00;
// Paieškos sritis dengia stulpelį B, ir ši formulė gyvena jame
Sheet.Cells[7, 2].Formula := 'VLOOKUP(A1,B:B,1)';
case Book.Recalculate of
lxOk:
// Iki v2.361.98 ši šaka buvo nepasiekiama šiam lakštui
SaveReport(Book);
lxErrorRef:
LogWarning('Genuine circular reference - review model inputs');
end;
finally
Book.Free;
end;
end;
Ko atsisakote, išimdami skenavimo briaunas iš eiliškumo
Lygiai vieną dalyką, ir verta tai pasakyti atvirai, o ne slėpti. Kadangi skenavimo briaunos nedalyvauja topologinėje tvarkoje, paieškos formulė gali būti įvertinta toje pačioje eigoje prieš kai kuriems jos paieškos srities langeliams buvus perskaičiuotiems, ir tada ji perskaitys jų ankstesnes reikšmes. Rezultatas konverguoja kito perskaičiavimo metu
Tai priimtina, nes būtent tai daro Excel. Darbo knygai be įjungto iteracinio skaičiavimo Excel pats atsakymas dėl reikšmės, esamoje eigoje dar neperskaičiuotos, yra paskutinė apskaičiuota reikšmė, todėl variklis, atkuriantis šią elgseną, atitinka atskaitos įgyvendinimą, o ne jo artėja. Jeigu reikia tikrai susikoncentravusio atsakymo save nurodančiam modeliui, tam mechanizmas yra iteracinis skaičiavimas su aiškia iteracijų riba, dengiamas iteracinio skaičiavimo straipsnyje, ir jis taikomas tikriems ciklams, o ne skenavimo perdengimams
Regresijos pavojus, slepimasis pataisoje
LookupScan pridėjimas prie TXLSDepRange įvedė riziką, neturinčią nieko bendro su paieškomis ir viską turinčią bendro su Pascal. TXLSDepRange yra nevaldomas įrašas, todėl to tipo vietinis kintamasis nėra nuliu inicializuojamas. Kiekviena kodų bazės vieta, rankomis konstruojanti tokį, įskaitant duomenų lentelės priklausomybių blokus ir kelis testų pagalbininkus, todėl turėjo būti atnaujinta, kad aiškiai nustatytų naują lauką. Praleidus vieną, baitas, atsitiktinai buvęs krūvoje, nusprendžia, ar ta nuoroda traktuotina kaip skenavimo briauna, kas duoda perskaičiavimo klaidą, pasirodančią ir dingstančią su nesusijusiais kodo pakeitimais
// Naujas Boolean laukas nevaldomame įraše padaro kiekvieną rankinę
// konstrukcijos vietą latenta klaida. Du saugūs būdai:
var
R: TXLSDepRange;
begin
FillChar(R, SizeOf(R), 0); // nuliuoti viską, tada užpildyti
R.Sheet1 := SheetIndex;
R.Sheet2 := SheetIndex;
R.Row1 := Row; R.Col1 := Col;
R.Row2 := Row; R.Col2 := Col;
// arba nustatyti kiekvieną lauką, įskaitant naująjį, kiekvienoje vietoje
R.LookupScan := False;
end;
Bendra taisyklė, kurios tai užsitarnavo: lauko pridėjimas prie įrašo, konstruojamo krūvoje daugiau nei saujelėje vietų, yra didesnės rizikos pakeitimas, nei atrodo, ir kompiliatorius nepadės jums rasti vietų. Jeigu įrašas pasiekiamas iš karšto kelio, teikite pirmenybę pagalbininkui, jį visiškai inicijuojančiam, o ne pasitikėjimui, kad kiekviena iškvietimo vieta bus atnaujinta
Tikro ciklo atskyrimas nuo skenavimo perdengimo
Nieko šiame pakeitime nesilpnina ciklų aptikimo. =B7+1 langelyje B7 vis dar yra ciklas, trijų formulių grandinė, užsiverianti į save, vis dar yra ciklas, ir abu vis dar pranešami per perskaičiavimo rezultatą, ciklo nariams išlaikant ankstesnes podėlio reikšmes, kol viskas už ciklo ribų lieka dabartinė. Pasikeitė tik tai, kad paieškos masyvo argumentas nebegamina ciklų, kurių Excel nemato
Jeigu audituojate darbo knygą ir norite žinoti, kurias nuorodas variklis iš tikrųjų išanalizavo ir kokia tvarka, vertinimo seklys yra tam įrankis; formulių vertinimo seklio straipsnis dengia, kaip skaityti jo išvestį. HotXLS yra savoji Delphi ir C++Builder skaičiuoklės komponentė, skaitanti ir rašanti XLS, XLSX, ODS ir CSV be įdiegto Excel, o perskaičiavimo variklis tas pats kiekvienam formatui; esama funkcijų ir variklio dengimas išvardytas HotXLS Delphi spreadsheet component produkto puslapyje