我輩はブロガーではない。ネタもまだない

SASとかDelphiあたりの人様の役に立たないネタを提供します

HUAWEI Scale 3買いました。

先日、HUAWEI Scale 3 を購入しました。
随分前(多分2-3年前)に、ファーウェイの体重計が非常に優れている、使い勝って等が日本のプロダクトにはない視点で素晴らしい、という記事を見てからずっと気になっていたのですがようやく。

実はHUAWEI Scale 3 proがリリースされるタイミングだったのですが、新しい方は両手を使った計測も必要ですし、値段も高いしそこまで必要ないな、と思い、安くなっていたHUAWEI Scale 3 にしました。
何度も言います。安いです(4000円前後)。


続きを読む

RAD Studio 11で追加されたTWinControlのLockDrawingメソッド(実行イメージ、コードあり)

Embarcaderoさんのこちらのブログに記載された内容が分かりづらかったのでフルコード作成しました。
blogs.embarcadero.com


上記のブログでは画像しかないから効果が全くわかりませんね。
gifくらい貼ってくれてもいいのに…。

というわけで、コードとgifを貼っつけておきます。
なお、使用したのは Delphi 11.0 です。

コード:https://bitbucket.org/t_kawakami/lockdrawing/src/main/

続きを読む

mouse E10を買いました

mouse E10を買いました。
前回の楽天スーパーセールで購入しようと思ったのですが、購入期間が過ぎてしまったので公式サイトから購入。


Amazonだとスタイラスペンなしで、10日前くらいは12,661円で最安でしたが、今は16,000になっています。

さて、どうしてこれを買ったかというと、組み込みでディスプレイ付きのシステムを作ってほしいという依頼があったのですが、ラズパイ+液晶でやると、システムの基幹部分のコーディングをやり直さないといけないからです。
Windowsなら今手元にあるコードがほぼそのまま使えます(というヘボい理由)。

つまり、組み込みを一から作成するのではなく、タブレットPC+USBで組込基盤接続でどうですか?という話です。


というわけで、Windowsタブレット、できるだけ小さいもの、できれば安いもの、可能であれば防水あたり…
という条件で探していたところ、10インチと少し予定より大きめ(できれば7インチくらいが良かった)ですが、mouseコンピュータのE10を見つけたというわけです。

本当だったらPanasonicのTOUGHPADとかがいいんでしょうけど、値段がね…


というわけで、使ってみた感想を。

・本体容量について
セットアップ完了時点で空き領域は27.3GB/56.4GB およそ半分です。
本体になにかデータを保存するような使い方は推奨できませんね。
あっという間にいっぱいいっぱいになって、動作も緩慢になりそうです。

・性能について
現状使っている分には問題ありません。
付属のスタイラスペンもまぁ文字を書く分には全く支障ありません。
Apple Pencil等はつかったことないです)

・デザイン
持ちやすくて結構好きです。

キーパッド
磁石が弱いので、本体を持つと落ちるのがマイナスポイント。
打感はSurfaceのものよりたわみが少なく、いいと思います。


高性能なスペックを要求しないデスクトップアプリの検証には結構手軽でいいかも。
もう1台追加で買うかもしれません。

CDATA SAS Data Sets FireDAC Components の更新履歴

さて、なんだかバタバタしていて全く更新出来ませんでした。ネタも少しはあるんですが、うまく記事にまとめられず。

とりあえず、手元の開発で使用しているCDATAのSAS Data Sets FireDAC Components の更新履歴でも貼っつけておきます。
これは公式なものではなく、あくまでもリリース後(β版は除く)に私が報告した問題からまとめたものです。
公式できちんと出してくれればいいんですけどね。
今使ってるから、これから使われる方の参考になれば。(ダウンロードできるものが最新とも限らないので)

21.0.8109 : マニュアルの修正

  • update 等、対応していないコマンドの記述が存在する
  • VC++ (x86/x64) redistributable package が必要な旨がマニュアルにない

実はupdate, deleteには対応していません。SAS xptでも同様。未だにトップページでは使えるって言ってるの、結構問題だと思うんですけど。
(私は結局、テーブルをdropしてcreate & Insertで実装しました。)
また、V21U6から(21.0.6xxx?)VC++ (x86/x64) redistributable package が同梱されなくなったため、別途インストールが必要なのですが、この記載も追記されました。

21.0.8115 : テーブルの識別に関する修正

  • CREATE TABLE IF NOT EXISTS文がすでにテーブルが存在する場合にエラーを返す
  • CREATE TABLE直後に作成されたテーブルが識別されない

回避策として、Delphiでテーブルの存在チェックをする、一度FDConnectionを切断して再接続するという方法もあります。

21.0.8145 : 欠損値に関する修正

  • Nの変数に欠損値(NULL)が格納できない

結構致命的ですね。SASSASたらしめる「.」が格納できません。
このバージョンで以下のような感じで使えるように修正されました。

  create table if not exists nullcheck (id integer, text varchar(10));
  insert into nullcheck (id,text) values(null,'YYY'); 

21.0.8149 : 欠損値に関する修正

  • 指定しないNのカラムに欠損値が格納されず、0が格納されてしまう

以下のコードだと、idカラムに欠損値でなく、0が格納されてしまうという問題です。

  create table if not exists nullcheck (id integer, text varchar(10));
  insert into nullcheck (text) values('YYY'); 

21.0.8160 : 特定のデータセットの読み取りに関する修正

  • FDQueryでsortseq=linguistic()を指定したデータセットの1行目に空行が表示されてしまう

proc sortでsortseqなんてめったに使わないと思うんですが、何故か指定していたコードがあり、再現。
これは回避不可でした。

21.0.8163 : 特定のデータセットの読み取りに関する修正

  • FDQueryでsortseq=linguistic(case_first=lower NUMERIC_COLLATION=ON )を指定したデータセットの1行目に空行が表示されてしまう

sortseq=linguistic()の問題は前のビルドで対応出来ていましたが、さらにlinguisticにオプションを追加しても発生することが発覚。
もともとはsortseq=linguistic(case_first=lower NUMERIC_COLLATION=ON )で作成したデータセットで確認していたのですが、最小の再現コードがsortseq=linguistic()だったのです。

21.0.8165 : 小数の扱いに関する修正

  • Bulk Insertにおいて、カラム属性をAsFloatと指定した場合でにキャストエラーとなり、小数がデータセットに格納できない
  FDQuery1.SQL.Text:='create table if not exists floatcheck(id decimal(10,2),text varchar(10));';
  FDQuery1.ExecSQL;
  FDQuery1.Close;
  FDConnection1.ResourceOptions.ServerOutput := True;
  FDQuery1.SQL.Text := 'insert into floatcheck (id, text) values (:id, :text);';
  FDQuery1.Params.ArraySize:= 1;
  FDQuery1.ParamByName('id').DataType   := ftFloat;
  FDQuery1.ParamByName('id').AsFloats[i] := 12.3;
  FDQuery1.ParamByName('text').AsStrings[i] := 'AAAA';
  FDQuery1.Execute(FDQuery1.Params.ArraySize,0);
  FDConnection1.ResourceOptions.ServerOutput := False;
  FDQuery1.Close;

普通にInsert文ならOKでしたが、一気に挿入したかったので。

21.0.8179 : データセットに関する修正

  • 0obsのデータセットにInsertしてもSASで認識できない点を修正

Firedac側では正常にコマンドが終了し、select文でも行を正しく取得できるので問題を認識しづらかったと思います。
SAS側でデータを確認しようとすると、0オブザベーションですと表示されてしまいます。
欠損値のinsertチェックのときに発見しましたが、当時は状況が分からず後回しに。

以上です。

SASでWORDLE(その3:SAS/AF)

さてさて、SASでWORDLEというネタを引っ張ってきましたが、最後、SAS/AFでやるとどうなるでしょうか。
SAS/AFの場合、いわゆるアプリケーションとしてのFrameと、ちと古臭いProgram Screenがありますが、せっかくなのでProgram Screenでやってみたいと思います。

ソース付きカタログを以下に置いておくので、試したい方はどうぞ。SAS/AFのライセンスがなくても実行はできます。
https://bitbucket.org/t_kawakami/wordle_sas/src/master/


まず、任意のライブラリにカタログを作成し、Program Screenを新規作成します。
f:id:japelin:20220317004819p:plain


画面の構成はこんな感じです
f:id:japelin:20220317004926p:plain

白だと見づらいので、背景を黒くしています。
コマンドバーから

color background black

と実行します。

また、入力欄はわかりやすくするために、白の反転としています。
ここは、フィールド「&KWRD」を選択して同じ用にコマンドを

color mtext white r

と実行します。

また、文字は、選択後に

color mtext white

のコマンドで白くしています。

さて、次は一般属性です。

f:id:japelin:20220317005456p:plain
ツール>一般属性から以下のように指定します。

f:id:japelin:20220317005622p:plain
少なくとも、行数と列数を指定しないと、全画面になってしまいます。

一般属性を閉じたらツール>フィールド属性を開きます。

フィールド属性を下にスクロールすると、タイプ:ACTIONのフィールドが出てきます。
f:id:japelin:20220317005855p:plain
これは「&」のフィールドです。


入力した結果を表示したいので、保護=YES、オプションのCAPSをON、エイリアスは、配列で扱いたいので「RES+行+列」という名前に変更しておきます。
f:id:japelin:20220317010008p:plain

最後、ツール>ソースウィンドウでsclコードを書きます。
(ここでは、WORK.LISTの4obs目を使用するようになっています)

キモは、

  • 画面に入力した文字を表示するために配列として処理
  • fieldコマンドで処理したいので配列の内部変数名(resXX)を別の変数名(fieldname)にフィールド名として格納して処理
  • fieldコマンドで反転色を設定

あたりでしょうか。

max6回なので、6回以上できないように、また、成功したら入力欄を保護する、という処理も入れています。
ま、アプリケーションですから最低限の処理ということで。

length answer $5                                                              
       fieldname $5                                                           
;                                                                             
                                                                              
init:                                                                         
  control always label;                                                       
  rc=rc;                                                                      
  execcnt=0;                                                                  
  success=0;                                                                  
  resobs=4;                                                                   
  array res{30} $1 res11-res15                                                
                   res21-res25                                                
                   res31-res35                                                
                   res41-res45                                                
                   res51-res55                                                
                   res61-res65;                                               
                                                                              
  lid_answer=makelist(5);                                                     
  dsid=open('WORK.LIST','I');                                                 
  if dsid>0 then do;                                                          
    rc=fetchobs(dsid,resobs);                                                 
    if rc=0 then do;                                                          
      answer=getvarc(dsid,1);                                                 
      /*リストに保存*/                                                        
      do i=1 to 5;                                                            
        lid_answer=setitemc(lid_answer,substr(answer,i,1),i);                 
      end;                                                                    
    end;                                                                      
    rc=close(dsid);                                                           
  end;                                                                        
return;                                                                       
                                                                              
main:                                                                         
  if length(KWRD)=5 and execcnt<6 and success=0 then do;                      
    execcnt+1;                                                                
    success=1;                                                                
    do i=1 to 5;                                                              
      idx=SEARCHC(lid_answer,substr(upcase(KWRD),i,1));                       
      fieldname=cats('res',execcnt,i);                                        
      res{5*(execcnt-1)+i}=substr(upcase(KWRD),i,1);                          
      if idx=0 then do;                                                       
        rc=field('color grey r',fieldname);                                   
        success=0;                                                            
      end; else                                                               
      if idx=i then do;                                                       
        rc=field('color green r',fieldname);                                  
      end; else                                                               
      do;                                                                     
        rc=field('color yellow r',fieldname);                                 
        success=0;                                                            
      end;                                                                    
    end;                                                                      
    KWRD='';                                                                  
    if success then do;                                                       
      rc=field('protect','KWRD');                                             
    end;                                                                      
  end;                                                                        
return;                                                                       
                                                                              
KWRD:return;                                                                  
term:return;


最後に、実行>コンパイルしてエラーがないことを確認したら、実行します。
右クリックでもいいですし、以下のプログラムを実行してもOKです

proc display c=temp.wordle.wordle.program;
run;

こんなアプリケーションウィンドウが立ち上がりました。
f:id:japelin:20220317011654p:plain

入力するとこんな感じです。
f:id:japelin:20220317011713p:plain

SASでWORDLE(その2:UI追加)

SASでWORDLE - 我輩はブロガーではない。ネタもまだない
この追記です。

ベタですけど、%windowを使ってUIを作りました。

リストの作成のところは同じ

%let keychars=5;
%let resobs=4;
data list;
  length wordle $&keychars.;
  input wordle;
datalines;
PIZZA
QUICK
QUAKE
CRAZY
CHICK
WALTZ
;
run;


で、本題はここから。
%windowで入力欄を呼び出し、入力された内容をマクロ変数として、処理用のマクロに引き継いでいます(パラメータではないですけど)

%window wordleWindow
  color=gray
  icolumn=40
  irow=10
  rows=40
  columns=60
#1 @1 "Welcome to WORDLE in SAS"
#4 @8 "Enter 5 charactors and press Enter"
#6 @8 input 5 attr=underline REQUIRED=yes
;

%Macro Wordle;

  %display wordleWindow;  

  options nomprint nomlogic nonotes nosource;
  title ;
  %if %length(&input)^=&keychars %then %do;
    %put not &keychars charactors!;
  %end;%else
  %do;
    data res;
      %let success=1;
      set list(firstobs=&resobs obs=&resobs);
      %do i=1 %to &keychars;
        %let kw&i.=%substr(%upcase(&input),&i.,1);
        res&i=cats("&&kw&i",index(wordle,"&&kw&i"));
        keep res:;
      %end;
    run;
  
    ods html;
    proc report data=res nowd noheader
      style(column)={FONT_SIZE=12pt WIDTH=20 TEXTALIGN=center FONT_WEIGHT=bold COLOR=white};
      column res: ;
      %do i=1 %to &keychars;
        compute res&i;
               if substr(res&i,2)="0"  then call define (_COL_,'style','style={background=gray}');
          else if substr(res&i,2)="&i" then call define (_COL_,'style','style={background=green}'); 
          else                              call define (_COL_,'style','style={background=D1C513}'); 
          res&i=substr(res&i,1,1);
        endcomp;
      %end;
    run;
  %end;
  %let input=;

%Mend;  

これで、

%Wordle

を実行するだけで以下のウィンドウが立ち上がります。

f:id:japelin:20220316151610p:plain


f:id:japelin:20220316151701p:plain
で、このように入力してenterを押せば…

結果が出力されます
f:id:japelin:20220316151707p:plain


後は6回の制限だったり、正解したら処理できなくしたり、といったエラー処理は…やめときましょう。

なお、残念ながらSAS Studioでは%Windowが動かないので使えません。

SASでWORDLE

TwitterのTLに流れてきたこのツイートで、おお、SASでWordleか、ということでやってみました。

(UIに関して追記:https://japelin.hatenablog.com/entry/2022/03/16/154301


WORDLEについてはググって試して見るのが一番かと思いますので、とりあえずコードを。


まずは、解答用のデータセットを作成しています。
と同時に、文字数を指定するkeycharと、resobsでどのobsを解答として採用するかを指定しています。
(実際にはresobsはランダムに割り当てればいいと思いますがとりあえず。)

%let keychars=5;
%let resobs=4;
data list;
  length wordle $&keychars.;
  input wordle;
datalines;
PIZZA
QUICK
QUAKE
CRAZY
CHICK
WALTZ
;
run;

続いてマクロです。
ちょっと力技ですが、文字+結果 という変数を作って、その結果からセルの色を変更し、結果だけを出力対象としています。

%Macro Wordle(input);
  options nomprint nomlogic nonotes;
  title ;
  %if %length(&input)^=&keychars %then %do;
    %put not &keychars charactors!;
  %end;%else
  %do;
    data res;
      set list(firstobs=&resobs obs=&resobs);
      %do i=1 %to &keychars;
        %let kw&i.=%substr(%upcase(&input),&i.,1);
        res&i=cats("&&kw&i",index(wordle,"&&kw&i"));
        keep res:;
      %end;
    run;
  
    ods html;
    proc report data=res nowd noheader
      style(column)={FONT_SIZE=12pt WIDTH=20 TEXTALIGN=center FONT_WEIGHT=bold COLOR=white};
      column res: ;
      %do i=1 %to &keychars;
        compute res&i;
               if substr(res&i,2)="0"  then call define (_COL_,'style','style={background=gray}');
          else if substr(res&i,2)="&i" then call define (_COL_,'style','style={background=green}'); 
          else                              call define (_COL_,'style','style={background=D1C513}'); 
          res&i=substr(res&i,1,1);
        endcomp;
      %end;
    run;
  %end;
%Mend Wordle;

で、例えば、

%Wordle(ABCDZ)

とすると、こんな結果。
f:id:japelin:20220316131533p:plain

%Wordle(CRZDA)

f:id:japelin:20220316131625p:plain

で、最後は

%Wordle(CRAZY)

f:id:japelin:20220316131657p:plain

こんな感じです。
宣言はしていないですが、ほぼarrayの書き方ですね。


普段ods全く使わないので、結果の区切り文字が消えればもっときれいだと思うんですが。



ただ、これだとインターフェースがないのがイマイチですかね。
%window使おうとしたら、マクロコンパイルの関係でうまく動かなかったので、他の方法で何かしらUI用意したいところです。