Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

503 lines
18 KiB

  1. #include <windows.h>
  2. #include "lexicon.h"
  3. #include "rulelex.h"
  4. #include "LexMgr.h"
  5. #include "CHTBrKr.h"
  6. #include "BaseLex.h"
  7. CCHTWordBreaker::CCHTWordBreaker(void)
  8. {
  9. m_pcLexicon = NULL;
  10. m_ppWordLattice = NULL;
  11. m_pdwCandidateNumber = NULL;
  12. m_dwSentenceLength = 0;
  13. m_dwLatticeLength = 0;
  14. m_pdwMaxWordLength = NULL;
  15. m_psBreakResult = NULL;
  16. m_pcRuleLex = NULL;
  17. }
  18. CCHTWordBreaker::~CCHTWordBreaker(void)
  19. {
  20. DWORD i;
  21. if (m_pcLexicon) {
  22. delete m_pcLexicon;
  23. m_pcLexicon = NULL;
  24. }
  25. if (m_ppWordLattice) {
  26. for (i = 0; i < m_dwSentenceLength; ++i) {
  27. if (m_ppWordLattice[i]) {
  28. delete m_ppWordLattice[i];
  29. }
  30. }
  31. delete m_ppWordLattice;
  32. m_ppWordLattice = NULL;
  33. }
  34. if (m_pdwCandidateNumber) {
  35. delete m_pdwCandidateNumber;
  36. m_pdwCandidateNumber = NULL;
  37. }
  38. if (m_pdwMaxWordLength) {
  39. delete m_pdwMaxWordLength;
  40. m_pdwMaxWordLength = NULL;
  41. }
  42. if (m_psBreakResult) {
  43. if (m_psBreakResult->puWordLen) {
  44. delete m_psBreakResult->puWordLen;
  45. }
  46. if (m_psBreakResult->pbTerminalCode) {
  47. delete m_psBreakResult->pbTerminalCode;
  48. }
  49. if (m_psBreakResult->puWordAttrib) {
  50. delete m_psBreakResult->puWordAttrib;
  51. }
  52. }
  53. m_dwSentenceLength = 0;
  54. m_dwLatticeLength = 0;
  55. }
  56. BOOL CCHTWordBreaker::AllocLattice(
  57. DWORD dwLength)
  58. {
  59. BOOL fRet = FALSE;
  60. DWORD i;
  61. m_pdwMaxWordLength= new DWORD[dwLength];
  62. if (!m_pdwMaxWordLength) { goto _exit; }
  63. m_pdwCandidateNumber = new DWORD[dwLength];
  64. if (!m_pdwCandidateNumber) { goto _exit; }
  65. m_ppWordLattice = new PSLatticeNode[dwLength];
  66. if (!m_ppWordLattice) { goto _exit; }
  67. for (i = 0; i < dwLength; ++i) {
  68. m_ppWordLattice[i] = NULL;
  69. }
  70. for (i = 0; i < dwLength; ++i) {
  71. m_ppWordLattice[i] = new SLatticeNode[MAX_CHAR_PER_WORD];
  72. if (!m_ppWordLattice[i]) { goto _exit; }
  73. m_pdwCandidateNumber[i] = 0;
  74. }
  75. m_dwLatticeLength = dwLength;
  76. fRet = TRUE;
  77. _exit:
  78. if (!fRet) {
  79. DestroyLattice();
  80. }
  81. return fRet;
  82. }
  83. void CCHTWordBreaker::DestroyLattice()
  84. {
  85. DWORD i;
  86. if (m_pdwCandidateNumber) {
  87. delete m_pdwCandidateNumber;
  88. m_pdwCandidateNumber = NULL;
  89. }
  90. if (m_pdwMaxWordLength) {
  91. delete m_pdwMaxWordLength;
  92. m_pdwMaxWordLength = NULL;
  93. }
  94. if (m_ppWordLattice) {
  95. for (i = 0; i < m_dwLatticeLength; ++i) {
  96. if (m_ppWordLattice[i]) {
  97. delete m_ppWordLattice[i];
  98. }
  99. }
  100. m_ppWordLattice = NULL;
  101. m_dwLatticeLength = 0;
  102. }
  103. }
  104. BOOL CCHTWordBreaker::InitData(
  105. HINSTANCE hInstance)
  106. {
  107. BOOL fRet = FALSE;
  108. m_pcLexicon = new CCHTLexicon;
  109. if (!m_pcLexicon) { goto _exit; }
  110. fRet = m_pcLexicon->InitData(hInstance);
  111. if (!fRet) { goto _exit; }
  112. m_pcRuleLex = new CRuleLexicon;
  113. if (!m_pcRuleLex) { goto _exit; }
  114. m_psBreakResult = new SBreakResult;
  115. if (!m_psBreakResult) { goto _exit; }
  116. FillMemory(m_psBreakResult, sizeof(SBreakResult), 0);
  117. m_psBreakResult->puWordLen = new UINT[LATTICE_LENGHT];
  118. m_psBreakResult->pbTerminalCode = new BYTE[LATTICE_LENGHT];
  119. m_psBreakResult->puWordAttrib = new UINT[LATTICE_LENGHT];
  120. if (!AllocLattice(LATTICE_LENGHT)) { goto _exit; }
  121. fRet = TRUE;
  122. _exit:
  123. if (!fRet) {
  124. if (m_pcLexicon) {
  125. delete m_pcLexicon;
  126. m_pcLexicon = NULL;
  127. }
  128. if (m_pcRuleLex) {
  129. delete m_pcRuleLex;
  130. m_pcRuleLex = NULL;
  131. }
  132. if (m_psBreakResult) {
  133. if (m_psBreakResult->puWordLen) {
  134. delete m_psBreakResult->puWordLen;
  135. }
  136. if (m_psBreakResult->pbTerminalCode) {
  137. delete m_psBreakResult->pbTerminalCode;
  138. }
  139. if (m_psBreakResult->puWordAttrib) {
  140. delete m_psBreakResult->puWordAttrib;
  141. }
  142. m_psBreakResult = NULL;
  143. }
  144. DestroyLattice();
  145. }
  146. return fRet;
  147. }
  148. BOOL CCHTWordBreaker::LatticeGrow(
  149. DWORD dwNewLength)
  150. {
  151. BOOL fRet = FALSE;
  152. if (dwNewLength <= m_dwLatticeLength) {
  153. fRet = TRUE;
  154. goto _exit;
  155. }
  156. DestroyLattice();
  157. if (AllocLattice(dwNewLength)) {
  158. fRet = TRUE;
  159. } else {
  160. AllocLattice(LATTICE_LENGHT);
  161. }
  162. if (m_psBreakResult) {
  163. if (m_psBreakResult->puWordLen) {
  164. delete m_psBreakResult->puWordLen;
  165. }
  166. if (m_psBreakResult->pbTerminalCode) {
  167. delete m_psBreakResult->pbTerminalCode;
  168. }
  169. if (m_psBreakResult->puWordAttrib) {
  170. delete m_psBreakResult->puWordAttrib;
  171. }
  172. m_psBreakResult->puWordLen = new UINT[dwNewLength];
  173. m_psBreakResult->pbTerminalCode = new BYTE[dwNewLength];
  174. m_psBreakResult->puWordAttrib = new UINT[dwNewLength];
  175. }
  176. _exit:
  177. return fRet;
  178. }
  179. DWORD CCHTWordBreaker::BreakText(
  180. LPCWSTR lpcwszText,
  181. INT nTextLen,
  182. CBaseLex* pcBaseLex,
  183. DWORD dwMaxWordLen,
  184. BOOL fBreakWithParser)
  185. {
  186. m_psBreakResult->dwWordNumber = 0;
  187. if (!LatticeGrow(nTextLen)) { goto _exit; }
  188. if (BuildLattice(lpcwszText, nTextLen, pcBaseLex, dwMaxWordLen)) {
  189. GetResult();
  190. // process Surrogate Char begin
  191. /*
  192. INT nCurrentIndex;
  193. DWORD dwSurIndex;
  194. nCurrentIndex = 0;
  195. for (dwSurIndex = 0; dwSurIndex < m_psBreakResult->dwWordNumber; ++dwSurIndex) {
  196. if (m_psBreakResult->puWordLen[dwSurIndex] == 1) { // High word of surrogate char should be breaked into signal char word
  197. if (lpcwszText[nCurrentIndex] >= 0xd800 && lpcwszText[nCurrentIndex] <= 0xdbff) { // High word is
  198. if (nCurrentIndex >= nTextLen - 1) { // Should be an error
  199. } else if (lpcwszText[nCurrentIndex + 1] >= 0xdc00 && lpcwszText[nCurrentIndex + 1] <= 0xdfff) { // Is surrogate char
  200. DWORD dwMoveDataNum;
  201. dwMoveDataNum = m_psBreakResult->dwWordNumber - (dwSurIndex + 1 + 1);
  202. m_psBreakResult->puWordLen[dwSurIndex] = 2;
  203. CopyMemory(&(m_psBreakResult->puWordLen[dwSurIndex + 1]), &(m_psBreakResult->puWordLen[dwSurIndex + 1 + 1]), dwMoveDataNum * sizeof(UINT));
  204. CopyMemory(&(m_psBreakResult->pbTerminalCode[dwSurIndex + 1]), &(m_psBreakResult->pbTerminalCode[dwSurIndex + 1 + 1]), dwMoveDataNum * sizeof(BYTE));
  205. CopyMemory(&(m_psBreakResult->puWordAttrib[dwSurIndex + 1]), &(m_psBreakResult->puWordAttrib[dwSurIndex + 1 + 1]), dwMoveDataNum * sizeof(UINT));
  206. m_psBreakResult->dwWordNumber -= 1;
  207. //nCurrentIndex -= 1;
  208. } else {// Should be an error
  209. }
  210. }
  211. }
  212. nCurrentIndex += m_psBreakResult->puWordLen[dwSurIndex];
  213. } */
  214. // process Surrogate Char end
  215. if (fBreakWithParser) {
  216. #ifdef PARSER
  217. DWORD i, dwBeginIndex, dwParseLen;
  218. PWORD pwTerminalCode;
  219. pwTerminalCode = NULL;
  220. dwParseLen = 0;
  221. pwTerminalCode = new WORD[m_psBreakResult->dwWordNumber];
  222. MultiByteToWideChar(950, MB_PRECOMPOSED, (const char *)m_psBreakResult->pbTerminalCode,
  223. m_psBreakResult->dwWordNumber, pwTerminalCode, m_psBreakResult->dwWordNumber);
  224. for (dwBeginIndex = 0; dwBeginIndex < m_psBreakResult->dwWordNumber; dwBeginIndex += 1) {
  225. if (m_psBreakResult->pbTerminalCode[dwBeginIndex] == ' ') { continue; }
  226. for (dwParseLen = 1; dwBeginIndex + dwParseLen < m_psBreakResult->dwWordNumber; ++dwParseLen) {
  227. if (m_psBreakResult->pbTerminalCode[dwBeginIndex + dwParseLen] == ' ') {
  228. break;
  229. }
  230. }
  231. for ( ; dwParseLen > 1; --dwParseLen) {
  232. if (m_pcRuleLex->IsAWord(&pwTerminalCode[dwBeginIndex], dwParseLen)) {
  233. break;
  234. }
  235. }
  236. if (dwParseLen > 1) { // adjust break result
  237. for (i = 1; i < dwParseLen; ++i) {
  238. m_psBreakResult->puWordLen[dwBeginIndex] += m_psBreakResult->puWordLen[dwBeginIndex + i];
  239. }
  240. m_psBreakResult->puWordAttrib[dwBeginIndex] = ATTR_RULE_WORD;
  241. DWORD dwMoveDataNum;
  242. dwMoveDataNum = m_psBreakResult->dwWordNumber - (dwBeginIndex + dwParseLen);
  243. CopyMemory(&(m_psBreakResult->puWordLen[dwBeginIndex + 1]),
  244. &(m_psBreakResult->puWordLen[dwBeginIndex + dwParseLen]),
  245. dwMoveDataNum * sizeof(UINT));
  246. CopyMemory(&(m_psBreakResult->pbTerminalCode[dwBeginIndex + 1]),
  247. &(m_psBreakResult->pbTerminalCode[dwBeginIndex + dwParseLen]),
  248. dwMoveDataNum * sizeof(BYTE));
  249. CopyMemory(&(m_psBreakResult->puWordAttrib[dwBeginIndex + 1]),
  250. &(m_psBreakResult->puWordAttrib[dwBeginIndex + dwParseLen]),
  251. dwMoveDataNum * sizeof(UINT));
  252. m_psBreakResult->dwWordNumber -= (dwParseLen - 1);
  253. }
  254. }
  255. if (pwTerminalCode) {
  256. delete [] pwTerminalCode;
  257. }
  258. #endif
  259. }// if support parser
  260. } // if build lattice success
  261. _exit:
  262. return m_psBreakResult->dwWordNumber;
  263. }
  264. DWORD CCHTWordBreaker::GetResult(void)
  265. {
  266. DWORD dwRet = 0;
  267. DWORD dwLen = 0;
  268. SLocalPath sLocalPath[2];
  269. UINT uBestIndex = 0, uCandIndex, uLocalPathIndex;
  270. DWORD dw2ndIndex, dw3rdIndex;
  271. DWORD i, j, k;
  272. m_psBreakResult->dwWordNumber = 0;
  273. uCandIndex = (uBestIndex + 1) % 2;
  274. while (dwLen < m_dwSentenceLength) {
  275. uLocalPathIndex = 0;
  276. if (m_pdwCandidateNumber[dwLen] == 1) {
  277. sLocalPath[uBestIndex].dwLength[0] = 1;
  278. sLocalPath[uBestIndex].bTerminalCode[0] = m_ppWordLattice[dwLen][0].bTerminalCode;
  279. sLocalPath[uBestIndex].wAttribute[0] = m_ppWordLattice[dwLen][0].wAttr;
  280. } else {
  281. FillMemory(&sLocalPath[uBestIndex], sizeof(SLocalPath), 0);
  282. for (i = 0; i < m_pdwCandidateNumber[dwLen]; ++i) {
  283. FillMemory(&sLocalPath[uCandIndex], sizeof(SLocalPath), 0);
  284. ++sLocalPath[uCandIndex].uStep;
  285. sLocalPath[uCandIndex].dwLength[uLocalPathIndex] = m_ppWordLattice[dwLen][i].uLen;
  286. sLocalPath[uCandIndex].wUnicount[uLocalPathIndex] = m_ppWordLattice[dwLen][i].wCount;
  287. sLocalPath[uCandIndex].wAttribute[uLocalPathIndex] = m_ppWordLattice[dwLen][i].wAttr;
  288. sLocalPath[uCandIndex].bTerminalCode[uLocalPathIndex++] = m_ppWordLattice[dwLen][i].bTerminalCode;
  289. dw2ndIndex = dwLen + m_ppWordLattice[dwLen][i].uLen;
  290. if (dw2ndIndex < m_dwSentenceLength) {
  291. for (j = 0; j < m_pdwCandidateNumber[dw2ndIndex]; ++j) {
  292. ++sLocalPath[uCandIndex].uStep;
  293. sLocalPath[uCandIndex].dwLength[uLocalPathIndex] = m_ppWordLattice[dw2ndIndex][j].uLen;
  294. sLocalPath[uCandIndex].wUnicount[uLocalPathIndex] = m_ppWordLattice[dw2ndIndex][j].wCount;
  295. sLocalPath[uCandIndex].wAttribute[uLocalPathIndex] = m_ppWordLattice[dw2ndIndex][j].wAttr;
  296. sLocalPath[uCandIndex].bTerminalCode[uLocalPathIndex++] = m_ppWordLattice[dw2ndIndex][j].bTerminalCode;
  297. dw3rdIndex = dw2ndIndex + m_ppWordLattice[dw2ndIndex][j].uLen;
  298. if (dw3rdIndex < m_dwSentenceLength) {
  299. for (k = 0; k < m_pdwCandidateNumber[dw3rdIndex]; ++k) {
  300. ++sLocalPath[uCandIndex].uStep;
  301. sLocalPath[uCandIndex].dwLength[uLocalPathIndex] = m_ppWordLattice[dw3rdIndex][k].uLen;
  302. sLocalPath[uCandIndex].wUnicount[uLocalPathIndex] = m_ppWordLattice[dw3rdIndex][k].wCount;
  303. sLocalPath[uCandIndex].wAttribute[uLocalPathIndex] = m_ppWordLattice[dw3rdIndex][k].wAttr;
  304. sLocalPath[uCandIndex].bTerminalCode[uLocalPathIndex++] = m_ppWordLattice[dw3rdIndex][k].bTerminalCode;
  305. GetScore(&(sLocalPath[uCandIndex]));
  306. if (CompareScore(&(sLocalPath[uCandIndex]), &(sLocalPath[uBestIndex])) > 0) {
  307. CopyMemory(&sLocalPath[uBestIndex], &sLocalPath[uCandIndex], sizeof(SLocalPath));
  308. }
  309. --uLocalPathIndex;
  310. --sLocalPath[uCandIndex].uStep;
  311. }
  312. } else {
  313. GetScore(&(sLocalPath[uCandIndex]));
  314. if (CompareScore(&(sLocalPath[uCandIndex]), &(sLocalPath[uBestIndex])) > 0) {
  315. CopyMemory(&sLocalPath[uBestIndex], &sLocalPath[uCandIndex], sizeof(SLocalPath));
  316. }
  317. }
  318. --uLocalPathIndex;
  319. --sLocalPath[uCandIndex].uStep;
  320. }
  321. } else {
  322. GetScore(&(sLocalPath[uCandIndex]));
  323. if (CompareScore(&(sLocalPath[uCandIndex]), &(sLocalPath[uBestIndex])) > 0) {
  324. CopyMemory(&sLocalPath[uBestIndex], &sLocalPath[uCandIndex], sizeof(SLocalPath));
  325. }
  326. }
  327. --uLocalPathIndex;
  328. --sLocalPath[uCandIndex].uStep;
  329. }
  330. }
  331. m_psBreakResult->puWordLen[m_psBreakResult->dwWordNumber] = sLocalPath[uBestIndex].dwLength[0];
  332. m_psBreakResult->pbTerminalCode[m_psBreakResult->dwWordNumber] = sLocalPath[uBestIndex].bTerminalCode[0];
  333. m_psBreakResult->puWordAttrib[m_psBreakResult->dwWordNumber] = sLocalPath[uBestIndex].wAttribute[0];
  334. ++m_psBreakResult->dwWordNumber;
  335. dwLen += sLocalPath[uBestIndex].dwLength[0];
  336. }
  337. return m_psBreakResult->dwWordNumber;
  338. }
  339. INT CCHTWordBreaker::CompareScore(
  340. PSLocalPath psLocalPath1,
  341. PSLocalPath psLocalPath2)
  342. {
  343. if (psLocalPath1->uPathLength > psLocalPath2->uPathLength) {
  344. return 1;
  345. } else if (psLocalPath1->uPathLength < psLocalPath2->uPathLength) {
  346. return -1;
  347. } else if (psLocalPath1->uStep < psLocalPath2->uStep) {
  348. return 1;
  349. } else if (psLocalPath1->uStep > psLocalPath2->uStep) {
  350. return -1;
  351. } else {
  352. }
  353. if (psLocalPath1->fVariance > psLocalPath2->fVariance) {
  354. return -1;
  355. }
  356. if (psLocalPath1->fVariance < psLocalPath2->fVariance) {
  357. return 1;
  358. }
  359. if (psLocalPath1->uCompoundNum > psLocalPath2->uCompoundNum) {
  360. return -1;
  361. }
  362. if (psLocalPath1->uCompoundNum < psLocalPath2->uCompoundNum) {
  363. return 1;
  364. }
  365. if (psLocalPath1->uDMNum > psLocalPath2->uDMNum) {
  366. return -1;
  367. }
  368. if (psLocalPath1->uDMNum < psLocalPath2->uDMNum) {
  369. return 1;
  370. }
  371. if (psLocalPath1->wUniCountSum > psLocalPath2->wUniCountSum) {
  372. return 1;
  373. }
  374. if (psLocalPath1->wUniCountSum < psLocalPath2->wUniCountSum) {
  375. return -1;
  376. }
  377. return 0;
  378. }
  379. void CCHTWordBreaker::GetScore(
  380. PSLocalPath psLocalPath)
  381. {
  382. UINT i;
  383. double fAverageSum;
  384. psLocalPath->uCompoundNum = 0;
  385. psLocalPath->uDMNum = 0;
  386. psLocalPath->uPathLength = 0;
  387. psLocalPath->fVariance = 0;
  388. psLocalPath->wUniCountSum = 0;
  389. for (i = 0; i < psLocalPath->uStep; ++i) {
  390. if (psLocalPath->wAttribute[i] & ATTR_COMPOUND) {
  391. psLocalPath->uCompoundNum++;
  392. }
  393. if (psLocalPath->wAttribute[i] & ATTR_DM) {
  394. psLocalPath->uDMNum++;
  395. }
  396. psLocalPath->uPathLength += psLocalPath->dwLength[i];
  397. psLocalPath->wUniCountSum += psLocalPath->wUnicount[i];
  398. }
  399. fAverageSum = (double)psLocalPath->uPathLength / psLocalPath->uStep;
  400. for (i = 0; i < psLocalPath->uStep; ++i) {
  401. if (fAverageSum > psLocalPath->dwLength[i]) {
  402. psLocalPath->fVariance += (fAverageSum - psLocalPath->dwLength[i]);
  403. } else {
  404. psLocalPath->fVariance += (psLocalPath->dwLength[i] - fAverageSum);
  405. }
  406. }
  407. }
  408. BOOL CCHTWordBreaker::BuildLattice(
  409. LPCWSTR lpcwszText,
  410. DWORD dwTextLen,
  411. CBaseLex* pcBaseLex,
  412. DWORD dwMaxWordLen)
  413. {
  414. DWORD i, j;
  415. FillMemory(m_pdwCandidateNumber, sizeof(DWORD) * dwTextLen, 0);
  416. // we should use head link
  417. for (i = 0; i < dwTextLen; ++i) {
  418. m_pdwMaxWordLength[i] = 1;
  419. for (j = i; (j - i + 1) <= dwMaxWordLen && j < dwTextLen; ++j) {
  420. if (m_pcLexicon->GetWordInfo(&lpcwszText[i], (j - i + 1),
  421. &(m_ppWordLattice[i][m_pdwCandidateNumber[i]].wCount),
  422. &(m_ppWordLattice[i][m_pdwCandidateNumber[i]].wAttr),
  423. &(m_ppWordLattice[i][m_pdwCandidateNumber[i]].bTerminalCode))) {
  424. m_ppWordLattice[i][m_pdwCandidateNumber[i]++].uLen = (j - i + 1);
  425. if (j - i + 1 > m_pdwMaxWordLength[i]) {
  426. m_pdwMaxWordLength[i] = j - i + 1 ;
  427. }
  428. } else if (pcBaseLex && pcBaseLex->GetWordInfo(&lpcwszText[i], (j - i + 1),
  429. &(m_ppWordLattice[i][m_pdwCandidateNumber[i]].wAttr))) {
  430. m_ppWordLattice[i][m_pdwCandidateNumber[i]].wCount = APLEXICON_COUNT;
  431. m_ppWordLattice[i][m_pdwCandidateNumber[i]].bTerminalCode = ' ';
  432. m_ppWordLattice[i][m_pdwCandidateNumber[i]++].uLen = (j - i + 1);
  433. if (j - i + 1 > m_pdwMaxWordLength[i]) {
  434. m_pdwMaxWordLength[i] = j - i + 1 ;
  435. }
  436. } else {
  437. }
  438. }
  439. if (!m_pdwCandidateNumber[i]) {
  440. m_ppWordLattice[i][0].uLen = 1;
  441. m_ppWordLattice[i][0].wCount = 0;
  442. m_ppWordLattice[i][0].wAttr = 0;
  443. m_ppWordLattice[i][0].fVariance = 0;
  444. m_ppWordLattice[i][0].bTerminalCode = ' ';
  445. ++m_pdwCandidateNumber[i];
  446. }
  447. }
  448. m_dwSentenceLength = dwTextLen;
  449. return TRUE;
  450. }
  451. /*
  452. DWORD CCHTWordBreaker::LongestRuleWord(
  453. DWORD dwIndex)
  454. {
  455. DWORD dwRet = 0, i;
  456. for (i = 0; i < m_pdwCandidateNumber[dwIndex]; ++i) {
  457. if (m_ppWordLattice[dwIndex][i].bAttr & ATTR_RULE_WORD) {
  458. if (m_ppWordLattice[dwIndex][i].uLen > dwRet) {
  459. dwRet = m_ppWordLattice[dwIndex][i].uLen;
  460. }
  461. }
  462. }
  463. return dwRet;
  464. }
  465. */