go to post Vitaliy Serdtsev · Mar 6, 2023 52 ClassMethod Check(w As %String) As %Boolean { a s w=$zu(28,w,5),l=$l(w,$e(w,$i(i))) g:l=2 a q 'l } 48 ClassMethod Check(w As %String) As %Boolean { f i=1:1:90{ret:$l($zu(28,w,5),$c(i))>2 0} q 1 }
go to post Vitaliy Serdtsev · Mar 6, 2023 For an empty string (a=""), the <UNDEFINED> error occurs because the variable r is not defined.
go to post Vitaliy Serdtsev · Feb 23, 2023 See String-to-Number Conversion and further. w ("45" < "46") === 45 < 46 w ("45" > "46") === 45 > 46 w ("V45" < "V46") === 0 < 0 w ("V45" > "V46") === 0 > 0 w ("V45" <= "V46") === 0 <= 0 w ("V45" >= "V46") === 0 >= 0 w +"45" -> 45 w +"V45" -> 0
go to post Vitaliy Serdtsev · Feb 20, 2023 #include %occInclude n alg,algInfo f alg="RSA-OAEP","A256KW","A256CBC-HS512","A256GCM" { s algInfo=##class(%Net.JSON.JWA).GetAlgInfo(alg) w alg," = ",$s(algInfo'="":algInfo.%ToJSON(),1:$system.Status.GetErrorText($$$ERROR($$$UnsupportedJOSEAlg,alg))),! }Output: RSA-OAEP = {"alg":"RSA-OAEP","kty":"RSA","use":"enc","key_ops":["wrapKey","unwrapKey"],"keysize":2048,"hashsize":0} A256KW = {"alg":"A256KW","kty":"oct","use":"enc","key_ops":["wrapKey","unwrapKey"],"keysize":256,"hashsize":0} A256CBC-HS512 = {"alg":"A256CBC-HS512","kty":"oct","use":"enc","key_ops":["encrypt","decrypt"],"keysize":256,"hashsize":512} A256GCM = Error #9723: Unsupported JOSE algorithm: A256GCMSince this algorithm is not supported, it remains either to try to modify the GetAlgInfo method or to work directly with the OpenSSL library or similar.
go to post Vitaliy Serdtsev · Feb 17, 2023 Try working directly with the private key file, for example: #include %msql s f=##class(%Stream.FileBinary).%New() s f.Filename="С:\your_private_key.pem" s privateKey=f.Read($$$MaxLocalLength) s myString = "text to sign" s signedTxt = ##class(%SYSTEM.Encryption).RSASHASign(256, $zcvt(myString,"O","UTF8"), privateKey) zw signedTxtThis code works for me.
go to post Vitaliy Serdtsev · Feb 13, 2023 There are a couple of points: could you please publish the source code of the class, and not the SQL script? The fact is that the command CREATE INDEX BITMAP_INDEX_ST_SEARCH_VISIT_BLOOD_TYPE_CODE ON SQLUser.ST_SEARCH_VISIT (BLOOD_TYPE_CODE) creates a regular index, not a bitmap index as expected. I'm afraid my code may not match yours. I created a test class with test data on Caché 2018.x and IRIS 2023.1FT and I have your query executed very quickly. Details below. User.STSEARCHVISIT.cls /// Class User.STSEARCHVISIT Extends %Persistent [ ClassType = persistent, DdlAllowed, Final, ProcedureBlock, SqlRowIdPrivate, SqlTableName = ST_SEARCH_VISIT ] { Property HQORGCODE As %Library.String(MAXLEN = 32) [ SqlColumnNumber = 2, SqlFieldName = HQ_ORG_CODE ]; Property HQORGNAME As %Library.String(MAXLEN = 32) [ SqlColumnNumber = 3, SqlFieldName = HQ_ORG_NAME ]; Property TENANTID As %Library.String(MAXLEN = 32) [ SqlColumnNumber = 4, SqlFieldName = TENANT_ID ]; Property GROUPPATIENTSN As %Library.String(MAXLEN = 32) [ SqlColumnNumber = 5, SqlFieldName = GROUP_PATIENT_SN ]; Property ORGPATIENTSN As %Library.String(MAXLEN = 32) [ SqlColumnNumber = 6, SqlFieldName = ORG_PATIENT_SN ]; Property NAME As %Library.String(MAXLEN = 64) [ SqlColumnNumber = 7 ]; Property SEXCODE As %Library.String(MAXLEN = 2) [ SqlColumnNumber = 8, SqlFieldName = SEX_CODE ]; Property SEXNAME As %Library.String(MAXLEN = 16) [ SqlColumnNumber = 9, SqlFieldName = SEX_NAME ]; Property BIRTHDATE As %Library.Date [ SqlColumnNumber = 10, SqlFieldName = BIRTH_DATE ]; Property MARRYCODE As %Library.String(MAXLEN = 2) [ SqlColumnNumber = 11, SqlFieldName = MARRY_CODE ]; Property MARRYNAME As %Library.String(MAXLEN = 16) [ SqlColumnNumber = 12, SqlFieldName = MARRY_NAME ]; Property IDENTIFYTYPECODE As %Library.String(MAXLEN = 5) [ SqlColumnNumber = 13, SqlFieldName = IDENTIFY_TYPE_CODE ]; Property IDENTIFYTYPENAME As %Library.String(MAXLEN = 32) [ SqlColumnNumber = 14, SqlFieldName = IDENTIFY_TYPE_NAME ]; Property IDENTIFYNUMBER As %Library.String(MAXLEN = 50) [ SqlColumnNumber = 15, SqlFieldName = IDENTIFY_NUMBER ]; Property BLOODTYPECODE As %Library.String(MAXLEN = 2) [ SqlColumnNumber = 16, SqlFieldName = BLOOD_TYPE_CODE ]; Property BLOODTYPENAME As %Library.String(MAXLEN = 12) [ SqlColumnNumber = 17, SqlFieldName = BLOOD_TYPE_NAME ]; Property MOBILE As %Library.String(MAXLEN = 50) [ SqlColumnNumber = 18 ]; Property MAILINGADDRESS As %Library.String(MAXLEN = 127) [ SqlColumnNumber = 19, SqlFieldName = MAILING_ADDRESS ]; Property VISITSERIALNO As %Library.String(MAXLEN = 64) [ SqlColumnNumber = 20, SqlFieldName = VISIT_SERIAL_NO ]; Property TABLEFLAG As %Library.String(MAXLEN = 1) [ SqlColumnNumber = 21, SqlFieldName = TABLE_FLAG ]; Property VISITTYPECODE As %Library.String(MAXLEN = 24) [ SqlColumnNumber = 22, SqlFieldName = VISIT_TYPE_CODE ]; Property VISITTYPENAME As %Library.String(MAXLEN = 64) [ SqlColumnNumber = 23, SqlFieldName = VISIT_TYPE_NAME ]; Property VISITDEPTCODE As %Library.String(MAXLEN = 32) [ SqlColumnNumber = 24, SqlFieldName = VISIT_DEPT_CODE ]; Property VISITDEPTNAME As %Library.String(MAXLEN = 64) [ SqlColumnNumber = 25, SqlFieldName = VISIT_DEPT_NAME ]; Property INOUTVISITNO As %Library.String(MAXLEN = 32) [ SqlColumnNumber = 26, SqlFieldName = IN_OUT_VISIT_NO ]; Property VISITTIME As %Library.PosixTime [ SqlColumnNumber = 27, SqlFieldName = VISIT_TIME ]; Property DISCHARGETIME As %Library.PosixTime [ SqlColumnNumber = 28, SqlFieldName = DISCHARGE_TIME ]; Property INHOSPITALTIME As %Library.PosixTime [ SqlColumnNumber = 29, SqlFieldName = IN_HOSPITAL_TIME ]; Property ICDCODE As %Library.String(MAXLEN = 20) [ SqlColumnNumber = 30, SqlFieldName = ICD_CODE ]; Property ICDNAME As %Library.String(MAXLEN = 64) [ SqlColumnNumber = 31, SqlFieldName = ICD_NAME ]; Property HEALTHCARDNO As %Library.String(MAXLEN = 32) [ SqlColumnNumber = 32, SqlFieldName = HEALTH_CARD_NO ]; Property HEALTHCARDTYPE As %Library.String(MAXLEN = 64) [ SqlColumnNumber = 33, SqlFieldName = HEALTH_CARD_TYPE ]; Property SURGCODE As %Library.String(MAXLEN = 20) [ SqlColumnNumber = 34, SqlFieldName = SURG_CODE ]; Property SURGNAME As %Library.String(MAXLEN = 64) [ SqlColumnNumber = 35, SqlFieldName = SURG_NAME ]; Property AGE As %Library.String(MAXLEN = 10) [ SqlColumnNumber = 36 ]; Property DISEASECODE As %Library.Integer(MAXVAL = 2147483647, MINVAL = -2147483648) [ SqlColumnNumber = 37, SqlFieldName = DISEASE_CODE ]; Property DISEASENAME As %Library.String(MAXLEN = 64) [ SqlColumnNumber = 38, SqlFieldName = DISEASE_NAME ]; Property LastChangedTime As %Library.PosixTime [ SqlColumnNumber = 39 ]; Property LastCreateTime As %Library.PosixTime [ SqlColumnNumber = 40 ]; Property GROUPORG As %Library.String(MAXLEN = 50) [ SqlColumnNumber = 41 ]; Parameter USEEXTENTSET = 0; /// Bitmap Extent Index auto-generated by DDL CREATE TABLE statement. Do not edit the SqlName of this index. Index DDLBEIndex [ Extent, SqlName = "%%DDLBEIndex", Type = bitmap ]; Index BITMAPINDEXSTSEARCHVISITBLOODTYPECODE On BLOODTYPECODE [ SqlName = BITMAP_INDEX_ST_SEARCH_VISIT_BLOOD_TYPE_CODE, Type = bitmap ]; Index BITMAPINDEXSTSEARCHVISITHQORGCODE On HQORGCODE [ SqlName = BITMAP_INDEX_ST_SEARCH_VISIT_HQ_ORG_CODE, Type = bitmap ]; Index BITMAPINDEXSTSEARCHVISITIDENTIFYTYPECODE On IDENTIFYTYPECODE [ SqlName = BITMAP_INDEX_ST_SEARCH_VISIT_IDENTIFY_TYPE_CODE, Type = bitmap ]; Index BITMAPINDEXSTSEARCHVISITMARRYCODE On MARRYCODE [ SqlName = BITMAP_INDEX_ST_SEARCH_VISIT_MARRY_CODE, Type = bitmap ]; Index BITMAPINDEXSTSEARCHVISITSEXCODE On SEXCODE [ SqlName = BITMAP_INDEX_ST_SEARCH_VISIT_SEX_CODE, Type = bitmap ]; Index BITMAPINDEXSTSEARCHVISITTABLEFLAG On TABLEFLAG [ SqlName = BITMAP_INDEX_ST_SEARCH_VISIT_TABLE_FLAG, Type = bitmap ]; Index BITMAPINDEXSTSEARCHVISITVISITDEPTCODE On VISITDEPTCODE [ SqlName = BITMAP_INDEX_ST_SEARCH_VISIT_VISIT_DEPT_CODE, Type = bitmap ]; Index BITMAPINDEXSTSEARCHVISITVISITTYPECODE On VISITTYPECODE [ SqlName = BITMAP_INDEX_ST_SEARCH_VISIT_VISIT_TYPE_CODE, Type = bitmap ]; Index INDEXSTSEARCHVISITBIRTHDATE On BIRTHDATE [ SqlName = INDEX_ST_SEARCH_VISIT_BIRTH_DATE, Type = index ]; Index INDEXSTSEARCHVISITDISCHARGETIME On DISCHARGETIME [ SqlName = INDEX_ST_SEARCH_VISIT_DISCHARGE_TIME, Type = index ]; Index INDEXSTSEARCHVISITGROUPPATIENTSN On GROUPPATIENTSN [ SqlName = INDEX_ST_SEARCH_VISIT_GROUP_PATIENT_SN, Type = index ]; Index INDEXSTSEARCHVISITIDENTIFYNUMBER On IDENTIFYNUMBER [ SqlName = INDEX_ST_SEARCH_VISIT_IDENTIFY_NUMBER, Type = index ]; Index INDEXSTSEARCHVISITINHOSPITALTIME On INHOSPITALTIME [ SqlName = INDEX_ST_SEARCH_VISIT_IN_HOSPITAL_TIME, Type = index ]; Index INDEXSTSEARCHVISITINOUTVISITNO On INOUTVISITNO [ SqlName = INDEX_ST_SEARCH_VISIT_IN_OUT_VISIT_NO, Type = index ]; Index INDEXSTSEARCHVISITMOBILE On MOBILE [ SqlName = INDEX_ST_SEARCH_VISIT_MOBILE, Type = index ]; Index INDEXSTSEARCHVISITNAME On NAME [ SqlName = INDEX_ST_SEARCH_VISIT_NAME, Type = index ]; Index INDEXSTSEARCHVISITORGPATIENTSN On ORGPATIENTSN [ SqlName = INDEX_ST_SEARCH_VISIT_ORG_PATIENT_SN, Type = index ]; Index INDEXSTSEARCHVISITVISITSERIALNO On VISITSERIALNO [ SqlName = INDEX_ST_SEARCH_VISIT_VISIT_SERIAL_NO, Type = index ]; Index INDEXSTSEARCHVISITVISITTIME On VISITTIME [ SqlName = INDEX_ST_SEARCH_VISIT_VISIT_TIME, Type = index ]; Index IdxMapOrg On HQORGCODE [ SqlName = IdxMapOrg, Type = index ]; Index LastChangedTimeIndex On LastChangedTime [ SqlName = LastChangedTimeIndex, Type = index ]; Index LastCreateTimeIndex On LastCreateTime [ SqlName = LastCreateTimeIndex, Type = index ]; Index idxGO On GROUPORG [ SqlName = idxGO, Type = index ]; Index INDEXPIPATIENTINFOHQORGCODE On (HQORGCODE, GROUPPATIENTSN) [ SqlName = INDEX_PI_PATIENT_INFO_HQ_ORG_CODE, Type = index ]; Index IdxGpsnorg On (GROUPPATIENTSN, HQORGCODE) [ SqlName = IdxGpsnorg, Type = index ]; Index IdxVisitorg On (VISITSERIALNO, HQORGCODE) [ SqlName = IdxVisitorg, Type = index ]; ClassMethod Fill(N = 50) { ; bulk insert via SQL. This is convenient if USEEXTENTSET = 1 /* d ..%KillExtent() f i=1:1:N &sql(insert %NOLOCK %NOCHECK %NOINDEX %NOTRIGGER %NOJOURN into ST_SEARCH_VISIT(HQ_ORG_CODE,VISIT_SERIAL_NO)values(:i#4,:i#5)) */ ; bulk insert via globals d DISABLE^%NOJRN k ^User.STSEARCHVISITD,^User.STSEARCHVISITI f i=1:1:N { s $li(^User.STSEARCHVISITD(i),1)=i#4, ;HQ_ORG_CODE $li(^User.STSEARCHVISITD(i),19)=i#5 ;VISIT_SERIAL_NO } s ^User.STSEARCHVISITD=N d ENABLE^%NOJRN w "insert - OK!",! d ..%BuildIndices(,,,$$$NO) w "BuildIndices - OK!",! d $system.SQL.TuneTable("ST_SEARCH_VISIT",$$$YES) d $system.OBJ.Compile($classname(),"cu-d") w "OK",! } } Generating 6000000 records USER>d ##class(User.STSEARCHVISIT).Fill(6e6) OK explain select count(distinct by(VISIT_SERIAL_NO,HQ_ORG_CODE) 1) from ST_SEARCH_VISIT <plans> <plan> <sql> select count(distinct by(VISIT_SERIAL_NO,HQ_ORG_CODE) 1) from ST_SEARCH_VISIT /*#OPTIONS {"DynamicSQL":1} */ </sql> <cost value="29794316"/> Call module B. Output the row. <module name="B" top="1"> Read index map ST_SEARCH_VISIT.IdxVisitorg, looping on %SQLUPPER(VISIT_SERIAL_NO), %SQLUPPER(HQ_ORG_CODE), and ID. For each row: Check distinct values for %SQLUPPER(HQ_ORG_CODE) and %SQLUPPER(VISIT_SERIAL_NO) using a temp-file. For each distinct row: Accumulate the count([value]). </module> </plan> </plans> select count(distinct by(VISIT_SERIAL_NO,HQ_ORG_CODE) 1) from ST_SEARCH_VISIT Row count: 1 Performance: 2.8704 seconds 6003078 global references 36016391 lines executed explain select count(1) from (select distinct by (VISIT_SERIAL_NO,HQ_ORG_CODE) VISIT_SERIAL_NO,HQ_ORG_CODE from ST_SEARCH_VISIT) <plans> <plan> <sql> select count(1) from (select distinct by (VISIT_SERIAL_NO,HQ_ORG_CODE) VISIT_SERIAL_NO,HQ_ORG_CODE from ST_SEARCH_VISIT) /*#OPTIONS {"DynamicSQL":1} */ </sql> <cost value="21000"/> Call module B. Output the row. <module name="B" top="1"> Read index map ST_SEARCH_VISIT.IdxVisitorg, looping on %SQLUPPER(VISIT_SERIAL_NO) and %SQLUPPER(HQ_ORG_CODE). For each row: Call module I. Accumulate the count([value]). </module> <module name="I" top="1"> Read index map ST_SEARCH_VISIT.IdxVisitorg, using the given %SQLUPPER(VISIT_SERIAL_NO) and %SQLUPPER(HQ_ORG_CODE), and looping on ID. For each row: Accumulate the [value]. </module> </plan> </plans> select count(1) from (select distinct by (VISIT_SERIAL_NO,HQ_ORG_CODE) VISIT_SERIAL_NO,HQ_ORG_CODE from ST_SEARCH_VISIT) Row count: 1 Performance: 0.0269 seconds 3079 global references 17711 lines executed
go to post Vitaliy Serdtsev · Feb 7, 2023 See for Caché 2018.1: FROM Subqueries and %VID View ID: %VID PS: It is a pity that again no one pays attention to the version of the product.
go to post Vitaliy Serdtsev · Feb 1, 2023 For Caché: $$GroupJob^%SYS.WorkQueueMgr($system.Context.WorkMgr().MasterGroup)
go to post Vitaliy Serdtsev · Jan 31, 2023 Is there any harm in putting in a massive number into the len parameter of the Read() method? You cannot read more than 3641144 characters from a stream to a string, as this is the limit for long strings: String Length Limit From the documentation for the Read() method: If no len is passed in, ie. 'Read()' then it is up to the Read implementation as to how much data to return. Some stream classes use this to optimize the amount of data returned to align this with the underlying storage of the stream. So you can write like this: Set inMsg = %request.Content.Read($$$MaxLocalLength) Macros are defined in %msql.inc: #define MaxStringLength 3641144 #define MaxLocalLength $zutil(96,39)
go to post Vitaliy Serdtsev · Jan 31, 2023 Instead of $ZPARENT, try using $system.Context.WorkMgr().MasterJob See your local documentation: MasterJob2022.1 ParentJob2022.2 PS: This is done differently for Caché.
go to post Vitaliy Serdtsev · Jan 31, 2023 See the answers here. In addition there are a few comments: you can use extrinsic function: ExtrinsicFunctions An extrinsic Caché ObjectScript function call operating on a database column you can use stored function: Defining and Using Stored Procedures you can encrypt the entire database, not just individual fields: Using Encrypted Databases you can restrict access to encrypted/sensitive fields to privileged users only: GRANT column-privilege
go to post Vitaliy Serdtsev · Jan 31, 2023 there are a few comments: you can write shorter: Property CarNumber As %String(TRUNCATE = 1) [ SqlComputeCode = {s {*}=$s(Clear:$$Decrypt({CAR_Number}),1:{CAR_Number})}, SqlComputed, SqlFieldName = CAR_Number ]; you can make a separate calculated field for the decrypted value, for example: Property CarNumber As %String(TRUNCATE = 1) [ SqlFieldName = CAR_Number ]; Property CarNumberDecr As %String(TRUNCATE = 1) [ Calculated, SqlComputeCode = {s {*}=$s(Clear:$$Decrypt({CAR_Number}),1:{CAR_Number})}, SqlComputed, SqlFieldName = CAR_NumberDecr ]; where do you get the value of the Clear variable (see above)? you can create your own data type for encrypted strings and override the corresponding methods there: How Data Type Classes Work In this case, you will not have to clutter up your queries with unnecessary encryption/decryption operations.
go to post Vitaliy Serdtsev · Jan 25, 2023 See Deploying code to production site especially the section Limitations for DeployToFile() and InstallFromFile()
go to post Vitaliy Serdtsev · Jan 25, 2023 There are a couple of points: what's wrong with users, roles, privileges, applications, etc.? %SYS>do ^SECURITY 1) User setup 2) Role setup 3) Service setup 4) Resource setup 5) Application setup 6) Auditing setup 8) SSL configuration setup 9) Mobile phone service provider setup 10) OpenAM Identity Services setup 11) Encryption key setup 12) System parameter setup 13) X509 User setup 14) KMIP server setup 15) Exit Option? 12 1) Edit system options 2) Edit authentication options 3) Edit LDAP options 4) Display system options 5) Export All Security settings 6) Import All Security settings 7) Exit Option? 5 Export ALL security records? Yes => Yes Warning: Before importing SSL configurations into a different configuration the same certificate directories and certificate files must exist in the new configuration, otherwise the import will fail. Export to file name SecurityExport.xml => Parameters? "WNS" => Confirm export of selected security records to SecurityExport.xml? No => yes Exported x Application security records Exported x DocDB security records Exported x Event security records Exported x KMIPServer security records Exported x LDAPConfig security records Exported x OpenAMIdentityService security records Exported x PhoneProvider security records Exported x Resource security records Exported x Role security records Exported x SQLPrivileges security records Exported x SSLConfig security records Exported x Service security records Exported x System security records Exported x User security records Exported x X509Credential security records Exported x X509Users security records Export complete 1) Edit system options 2) Edit authentication options 3) Edit LDAP options 4) Display system options 5) Export All Security settings 6) Import All Security settings 7) Exit Option? to transfer JDBC/ODBC settings, you can still use external tools, for example DbVisualizer or SQL Data Lens, where you can choose the data format (CSV, XML, JSON, Excel, etc.), the necessary fields and much more.
go to post Vitaliy Serdtsev · Jan 25, 2023 Did you copy/paste the query correctly here? Select Books.nam,Books.print,Books.Relativename,Books.Firstpage,Books.Lastpage,Books.trn,Books.lastissue, Books.firstissue,Books.person,Books.author,Books.price,Books.cd,Books.ab,Books.pf,Books.ju,Books.er, Books.qw,Books.qt,Books.mn, Records.qw,Records.er,Records.ty,Records.ui,Records.op,Records."as", OrderBooks.mn,OrderBooks.bv, OrderRecords.sd,OrderRecords.fg,OrderRecords.hj, Orders.lastdate From SQLUser.Books Books INNER JOIN SQLUser.Records Records ON Books.id=Recordsid INNER JOIN SQLUser.OrderedBooks OrderBooks ON Books.id=OrderBooks.id INNER JOIN SQLUser.OrderedRecords OrderRecords ON Books.id=OrderRecords INNER JOIN SQLUser.Orders Orders ON Books.id=Orders.id where ((Records.qw=OrderBooks.qw) and (Records.er is null))In addition to tuning tables, I would first look at the query plan and, accordingly, the indexes involved in it.
go to post Vitaliy Serdtsev · Jan 24, 2023 If you select the text with the mouse, you will see spaces on the right in the second "Output". In general, I think that such details should be enclosed in quotation marks.
go to post Vitaliy Serdtsev · Jan 24, 2023 Ok. If the nuances of formatting spaces and line feed are unimportant, then can yet shorten Robert's code: size = 40 ClassMethod Build(f As %Integer) { s a="#" f i=f:-1:1 w !?i,a s a=a_"##" }
go to post Vitaliy Serdtsev · Jan 23, 2023 It is necessary to take into account the following points: according to the documentation should be // add the columns to export Do mgr.ColumnNames.Insert("Closed") Do mgr.ColumnTypes.Insert("N") Do mgr.ColumnNames.Insert("DocumentType") Do mgr.ColumnTypes.Insert("S") Do mgr.ColumnNames.Insert("StatusCode") Do mgr.ColumnTypes.Insert("N") Do mgr.ColumnNames.Insert("StatusLastUpdated") Do mgr.ColumnTypes.Insert("TS") StringQuote only affects when escaping the corresponding characters, for example: Set mgr.StringQuote = $c(34) // double quotes a"b -> "a""b" Set mgr.StringQuote = $c(39) // ' a'b -> 'a''b' DateFormat and TimeFormat are applicable only for fields of type D and T, respectively, but you have a field of type TS TimeStampFormat is applicable only for import, but not for export
go to post Vitaliy Serdtsev · Jan 23, 2023 I hope that Eduard will be formulate the conditions of the task more carefully in the future. PS: Robert, you shouldn't have rushed to remove your solution. In any case, your solution was the best.