演習 6 : 2 番目の Lookup クラス
チュートリアル本編の演習の説明に戻るには、ここをクリックします。
-
VS Code - ObjectScript を使用して、ObjectScript.Lookup1 の先頭行を ObjectScript.Lookup2 に変更し、ObjectScript.Lookup2 という名前で保存します。
-
CurrentCount() メソッドは、BitMap-ID インデックスを使用します。ここには個人データごとに 1 ビットが格納されているため、必要なのはチャンクのループと $BitCount だけです。
Class ObjectScript.Lookup2 { /// count the "1" bits from the chunks of the Bitmap-ID index ClassMethod CurrentCount() { set records = 0, chunk = "" for { // use the 3-argument $order to get the next chunk and the bits stored there set chunk = $order(^PersonI("Bitmap-ID", chunk), 1, bits) quit:(chunk = "") // add the "1" bits to the count set records = records + $bitcount(bits, 1) } write !, "There are ", records, " records in the database." } }
-
CurrentCount() を呼び出すように Main() を変更します。電話番号を検索する新しい ElseIf はどこに配置してもかまいません。以下の例では、誕生日を検索する ElseIf の上に配置しています。
Class ObjectScript.Lookup2 { /// main loop section, dispatch to different methods based on user input ClassMethod Main() { do ..CurrentCount() while ..GetInput(.type, .search) { if (type = "help") {do ..Help()} elseif (type = "phone") {do ..Phone(search)} elseif (type = "dob") {do ..DOB(search)} } } }
-
電話番号も処理するように GetInput() を変更します。ValidDOB() はエラー・メッセージを書き込むため、誕生日の検索は最後に処理する必要があります。電話番号を検索する新しい ElseIf を、誕生日を検索する ElseIf の上に配置するのには、このような理由があります。Else はエラー・メッセージの最後の部分を書き込みます (このハックについてはコメントで説明しています)。
Class ObjectScript.Lookup2 { /// prompt user for a lookup string, return search type and search string ClassMethod GetInput(output type as %String, output search as %String) as %Boolean { read !, "Lookup: ", lookup return:(lookup = "") 0 // user entered nothing so return FALSE if (lookup = "?") { set type = "help", search = "" } // the RegEx accepts ###- or ###-###-#### only elseif $match(lookup, "\d{3}-(\d{3}-\d{4})?") { set type = "phone", search = lookup } elseif (##class(ObjectScript.DataEntry4).ValidDOB(lookup, .convdate)) { set type = "dob", search = convdate } else { // this is a hack for invalid input // ValidDOB() writes an error message, and the text below gets added to that write ", name, or phone" set (type, search) = "" } return 1 } }
-
新しい Phone() メソッドは、まず完全に一致する値を処理してから、一致するエリア・コードを処理します。
Class ObjectScript.Lookup2 { /// lookup phone or area code ClassMethod Phone(phone as %String) { set count = 0 // handle exact match first set id = $get(^PersonI("Phone", phone)) if (id '= "") { set count = 1 write !, "1) " do ..DisplayLine(id) quit } // handle area code matches next elseif (phone?3n1"-") { // use 3-argument $order to get first matching phone number and its ID number set ph = $order(^PersonI("Phone", phone), 1, id) // loop through matching phones, and number them while ($extract(ph, 1, $length(phone)) = phone) { write:(count = 0) "...finding area code matches" set count = count + 1 write !, count, ") " do ..DisplayLine(id) // use 3-arg $order to get the next phone number and its ID number set ph = $order(^PersonI("Phone", ph), 1, id) } } if (count = 0) {write "...no matches"} } }
-
Help() を変更します。
Class ObjectScript.Lookup2 { /// display lookup options ClassMethod Help() { write !, "You can enter:", !?10, "* date of birth", !?10, "* full phone number or area code only ""617-""", ! } }
-
[保存] ボタン、[コンパイル] ボタンをクリックします。
-
ターミナルを開始し、do ##class(ObjectScript.Lookup2).Main() と入力してメソッドを実行し、Phone の新規検索をテストします。
-
もう一度 Main() を変更して、名前を検索する別の ElseIf を追加します。
do ..CurrentCount() while ..GetInput(.type, .search) { if (type = "help") {do ..Help()} elseif (type = "phone") {do ..Phone(search)} elseif (type = "name") {do ..Name(search)} elseif (type = "dob") {do ..DOB(search)} }
-
もう一度 GetInput() を変更して、名前を処理するようにします。ここでも、新しい ElseIf は、誕生日を検索する ElseIf の上に配置します。
if (lookup = "?") { set type = "help", search = "" } // the RegEx accepts ###- or ###-###-#### only elseif $match(lookup, "\d{3}-(\d{3}-\d{4})?") { set type = "phone", search = lookup } /* the $zconvert converts the last name and first name entered to Last,First format the pattern match accepts Lastname only, or Lastname,Firstname */ elseif ($zconvert(lookup, "W")?1U.L.1(1","1U.L)) { set type = "name", search = $zconvert(lookup, "W") } elseif (##class(ObjectScript.DataEntry4).ValidDOB(lookup, .convdate)) { set type = "dob", search = convdate } else { // this is a hack for invalid input // ValidDOB() writes an error message, and the text below gets added to that write ", name, or phone" set (type, search) = "" }
-
新しい Name() メソッドはこれまでで最も長いメソッドです。これは、該当する $Order ループを使用します。一致する姓それぞれについて、$Order ループは、すべての名前をチェックして該当する姓があるかどうかを確認するか (ユーザが姓だけを入力した場合)、最初に一致する名前を対象にしたループになります。姓と名前それぞれについて、最後の $Order ループはすべての ID 番号をチェックします。
Class ObjectScript.Lookup2 { /// lookup names in these forms: Smith; Smith,John; Smith,J; Sm,John; Sm,J ClassMethod Name(name as %String) { set count = 0 set last = $piece(name, ",", 1), first = $piece(name, ",", 2) // last may be an exact match, so find preceding last name set ln = $order(^PersonI("Name", last), -1) // loop through last names for { set ln = $order(^PersonI("Name", ln)) // quit as soon as last name doesn't match original quit:($extract(ln, 1, $length(last)) '= last) // first may be "". Otherwise, it may be an exact match, so find preceding first name if (first = "") {set fn = ""} else { set fn = $order(^PersonI("Name", ln, first), -1)} // loop through first names for { set fn = $order(^PersonI("Name", ln, fn)) // quit as soon as first name doesn't match original, or is "" quit:(($extract(fn, 1, $length(first)) '= first) || (fn = "")) set id = "" // loop through all IDs for { set id = $order(^PersonI("Name", ln, fn, id)) quit:(id = "") write:(count = 0) "...finding name matches" set count = count + 1 write !, count, ") " do ..DisplayLine(id) } } } if (count = 0) {write "...no matches"} } }
-
もう一度 Help() を変更します。
Class ObjectScript.Lookup2 { /// display lookup options ClassMethod Help() { write !, "You can enter:", !?10, "* date of birth", !?10, "* full phone number or area code only ""617-""", !?10, "* full name: Smith,John", !?10, "* last name: Smith", !?10, "* partial name: Sm,J or Smith,J or Sm,John", ! } }
-
[保存] ボタン、[コンパイル] ボタンをクリックします。
-
ターミナルを開始し、do ##class(ObjectScript.Lookup2).Main() と入力してメソッドを実行し、Name の新規検索をテストします。
-
matches 配列を使用して一致する値を記録し、ユーザが値を選択して表示できるようにします。この配列については何も宣言する必要はありません。そのまま使用できます。コードに 4 行を追加する必要があります。DOB() の Quit と Write の間に、行を 1 つ追加します。
quit:(id = "") set matches(count) = id // keep track of matches write !, count, ") "
Phone() の Set と Write の間に、"完全一致" の If の行を 1 つ追加します。
set count = 1 set matches(1) = id // keep track of exact match write !, "1) "
Phone() の Set と Write の間に、"エリア・コード一致" の ElseIf の行を 1 つ追加します。さらに、Name() の Set と Write の間に、最内部の入れ子の For ループ (ID 番号) の同じ行を追加します。
set count = count + 1 set matches(count) = id // keep track of matches write !, count, ") "
-
これでコードが matches 配列を構築するようになりました。続いて、この配列を引数として取り、ユーザが選択した ID 番号を返す Select() メソッドを記述する必要があります。このコードは、ユーザが整数を入力したかどうかも、整数が 1 ~ count の範囲内かどうかもチェックしないことに注意してください。これは単に、$Get を使用して、ユーザが選択した値が matches 配列内の既存の添え字かどうかを確認し、存在する場合は対応する ID 番号を返し、存在しない場合は空文字列を返します。
Class ObjectScript.Lookup2 { /// user makes a choice from the matches array, return the corresponding ID or "" ClassMethod Select(ByRef matches as %Integer, Output id as %Integer) { set id = "" for { read !!, "Choose by number: ", choice quit:(choice = "") set id = $get(matches(choice)) quit:(id '= "") // stop looping if user makes a valid choice write "...Invalid choice" } } }
-
続いて、DOB()、Phone()、および Name() からの Select() の呼び出しを追加する必要があります。DOB() で、これをメソッドの最後に追加します。
do ..Select(.matches, .id)
Phone() および Name() で、メソッドの最後にある、"一致が見つからない" 場合の If を以下のように更新します。
if (count = 0) {write "...no matches"} else {do ..Select(.matches, .id)}
DOB()、Phone()、および Name() のシグニチャを、選択された ID 番号を返すように変更します。
Class ObjectScript.Lookup2 { ClassMethod DOB(intdob as %Date, output id as %Integer) { } ClassMethod Phone(phone as %String, output id as %Integer) { } ClassMethod Name(name as %String, output id as %Integer) { } }
DOB()、Phone()、および Name() が常に、選択された ID 番号を返すようにするには、各メソッドの最初に id を初期化します。
set id = ""
もう一度 Main() を変更し、id を参照によって DOB()、Phone()、および Name() に渡すようにします。
do ..CurrentCount() while ..GetInput(.type, .search) { if (type = "help") {do ..Help()} elseif (type = "phone") {do ..Phone(search, .id)} elseif (type = "name") {do ..Name(search, .id)} elseif (type = "dob") {do ..DOB(search, .id)} }
-
TakeAction() は今のところ非常に単純で、単にレコードを取得して表示します。
Class ObjectScript.Lookup2 { /// display chosen record ClassMethod TakeAction(id as %Integer) { set record = ^PersonD(id) do ##class(ObjectScript.DataEntry4).Display(record) } }
入力が適切な場合に TakeAction() を呼び出すように Main() を変更します。さらに、Help() は他の 3 つのメソッドと同様に id を返さないため、set id = "" を If に追加します。
do ..CurrentCount() while ..GetInput(.type, .search) { if (type = "help") {do ..Help() set id = ""} elseif (type = "phone") {do ..Phone(search, .id)} elseif (type = "name") {do ..Name(search, .id)} elseif (type = "dob") {do ..DOB(search, .id)} if ((type '= "") && (id '= "")) {do ..TakeAction(id)} }
-
[保存] ボタン、[コンパイル] ボタンをクリックします。
-
ターミナルを開始し、do ##class(ObjectScript.Lookup2).Main() と入力してメソッドを実行し、最後の変更内容をテストします。