OmniAICreator/Qiita-1.07M huggingface dataset:
インプレースアップグレードで Windows10 を修復

ある日 Windows10のアプリケーションが動かなくなったと連絡がありました。
Windwos10 が起動するところまではいくけれども、そこからアプリケーション諸々が起動しなくなったそうです。
「インターネットもつながらない」ということで、マシンを送ってもらって直接トラブルシュートすることにしました。


# 環境

- Windows10 Pro

![20220213-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139524/80a9ecfe-cc92-15c8-8db4-3dd945ac7080.png)
# 症状

- Windows10は起動する
- Windows10が起動した直後、アプリケーションは起動する
- 数分すると新たにアプリケーションが起動できなくなる
- その状態で既に起動したアプリケーションは動作している
- その状態で既に起動したアプリケーションから別プログラムを実行することはできない
- マウスは動作する

# イベントビューワ

起動した直後に「コンピュータの管理」を立ち上げ、イベントビューワを確認する。
システムのログで、「DCOMサーバーを起動できません」が大量に記録されている。

ログ中、"TextInputHost.exe" というファイルが示されている。

TextInputHost.exe で調べてみると、「Touch Keyboard and Handwriting Panel Service」のファイルらしい。

# サービス

TextInputService をサービスで確認すると、「手動」の設定で状態は「実行中」となっている。このサービスの停止ボタンがグレーアウトして押せない。

![20220215-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139524/b22b9c6d-f925-2907-6d76-2f7471d650fb.png)

# TextInputService

「スタートアップの種類」を「無効」にして再起動すると、


- 数分すると新たにアプリケーションが起動できなくなる

この症状は無くなった。しかしながらこれは日本語変換システムと関係しているらしく、日本語変換が有効にできなくなる。
なのでTextInputServiceの修復を行わないといけない。

# TextInputService が壊れた原因



システムログで見ると、昨晩にWindowsUpdateがかかっていて、4つのプログラムがインストールされている。

- KB5009467
- KB501042 ( 記録ミス？ 多分KB5010342 )
- Microsoft OneDrive 2.012.0117.0003
- Servicing Stack 10.0.19041.1525

このうち、1番目から3番目までをアンインストールしたが改善しなかった。
となると 4番目の Serivicing Stack が原因ぽい。これをアンインストールしようとしたが、Servicing Stack はアンインストールできない仕組みになっているようだ。


# 修復

アンインストールで修復できないとなると、システムの復元かな？ しかしながら、Windows10 ではシステムの復元はデフォルトで有効になっておらず、このマシンは有効の設定をしていなかった。


では修復セットアップが使えるかな？ Windowsのインストールディスクを使って行う修復セットアップ、今ではインプレースアップグレードと呼ぶらしい。

# インプレースアップグレード

以前インストール時に作成したWindows10インストールUSBメモリを使用。
このUSBのメモリは Windows7 Media Creation Tool を使って作成したもの。

まず、問題となるPCを TextInputService の「スタートアップの種類」を「無効」にして起動。「手動」に直してから以下の手順で進める。

USBメモリを刺して setup を実行。
![20220213-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139524/d534f0b7-14be-61dc-f99a-46fc2b2c4f4a.png)

セットアップ画面が出るので進めていく。
![20220213-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139524/22e4ca0f-e17a-460d-c2ae-08ab58b18177.png)

以下の設定でインストール。
![20220213-4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139524/60b9c5be-e76a-62c1-06a2-77d478763b06.png)

このようにして進めると、再起動などを経てインプレースアップグレードが完了します。
途中37%でずっと止まっていて心配しましたが、放置していると進みました。

完了後、問題は解消しているようです。
javascript66_prototype属性

私たちが作っている全ての関数に対して、パソコンが該当関数の中にprototype属性を入れます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1067004/65e2cec5-1bbd-dae0-a3f9-d1f7f9373da9.png)
関数ごとに原型対象は違います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1067004/af236435-f2a5-913e-a6a0-0f3ef361d0b8.png)

普通の関数がprototypeを呼び出しても意味ありません。
関数が構造関数としてprototypeを呼び出す時、構造関数(クラス）から生成された全てのインスタンスには一つの隠された属性はクラスのprototype対象を指しています。
その隠された属性については__proto__でアクセスすることができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1067004/e93b3c0d-0489-e572-94a1-82c7830cead5.png)
クラスとインスタンスの中に共通の属性があり、その属性は全部同一オブジェクト（0x123)を指しています。
同一オブジェクトは一つのパブリック領域に相当し、全てのクラスおよびクラスから生成されたインスタンスはこのオブジェクトをアクセスすることができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1067004/0d318d43-4716-a131-1e7a-ee4a5bf0c10d.png)
そのため、私たちは各オブジェクトの中に共有している内容を全部、共有のオブジェクト内に設置することができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1067004/b1502f27-bec1-954a-8fd3-742521369a9d.png)
構造図はこうなります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1067004/168142ad-74da-31ef-cd40-d55cd707d77e.png)

一つのオブジェクトの属性またはメソッドをアクセスする時に、
まずはオブジェクト自身で探します。あれば、直接使用します。
なければprototypeオブジェクトの中で探して呼び出しします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1067004/c43dcb8d-1784-cdba-0780-d34ac6a6e581.png)
JIS鋼材DBの開発記録(1) 

## 鋼材断面インターフェイス
- サマリ記述の追加
<div>鋼材便覧を基にしたデータベースに向けたインターフェイスの定義を以下のように改善した。</div>
<div>もっと良い二乗や三乗の記述方法があれば、より良く記述でき候、されど愚生は知らず候</div>

```C#:IStructureSteelCrossSection.cs
    public interface IStructureSteelCrossSection
    {
        /// <summary>
        /// 鋼材断面積(cm2)を計算し返す関数
        /// </summary>
        /// <returns>鋼材断面積(cm2)</returns>
        double crossSectionArea();
        /// <summary>
        /// 鋼材表面長(cm)を計算し返す関数
        /// </summary>
        /// <returns>鋼材周長(cm)</returns>
        double crossSectionRerimeter();
    }
```

このサマリを記述することでインテリセンスに記述可能になる。

- 既知の改善点

単純にdouble型を返すだけではなく、単位を含めて返すような構造体があればもっと良い。
https://github.com/angularsen/UnitsNet
アメリカのプロの人たちがもう既にオープンソースライブラリを開発済みでした。


## 棒状鋼材インターフェイスの定義
断面と長さを乗算すれば一般的な棒を定義することが可能である。
というわけで長さ、鋼材表面積、重量を返すインターフェイスを定義する。

```C#:IStructureSteelBeam.cs
    public interface IStructureSteelBeam
    {
        /// <summary>
        /// 鋼材長を記録する関数、mmで記録しておいた方がよさそうである。
        /// </summary>
        double length { get; set; }
        /// <summary>
        /// 鋼材表面積をcm2で計算し返す関数
        /// </summary>
        /// <returns>鋼材表面積(cm2)を返す</returns>
        double surfaceArea();
        /// <summary>
        /// 鋼材重量をkgで返す関数、基本はジェネリックで定義しておく
        /// </summary>
        /// <returns>鋼材重量(kg)を返す<</returns>
        double weight();
    }
```
【一般】Sharperlightのインストール

``本記事の画像はSharperlight 5.3 となっていますが、2024年4月30日現在はSharperlight 6.0が最新です。``
インストーラのダウンロードについては、[こちら。](https://qiita.com/Miki_Yokohata/items/4df399d558c5203d8461 "Sharperlight インストーラのダウンロード")

Sharperlightのインストーラ msi を使用してインストールを開始します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/a2dde04e-9cd1-cbc9-b719-d5623ccd3840.png)

ようこそSharperlight 6.0 64bit のセットアップへというダイアログが表示されます。
![2022-02-18 Sharperlight 5.3 64bit Setup.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/3155fdb9-590d-e9c7-276d-bfa851e23a29.png)
残念ながらSharperlightの母国は英語圏なので、インストーラは英語表示です。

Next(次へ)ボタンを押して、先に進んでください。
インストールの同意ダイアログが表示されるので、下部のチェックボックスをオンにし、Next(次へ)を押します。
![Qiita 2022-02-18 Sharperlight 5.3 64bit Setup Aggrement.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/b0071f6a-3038-524a-5556-137bb020fc0d.png)
いくつかのオプションが表示されます。通常は完全なセットアップが推奨されますので、ここではComplete(完全)を選択します。
![Qiita 2022-02-18 Sharperlight 5.3 64bit Setup Options.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/abfdfa6a-919e-3bc8-8cce-b4b9df4a93e5.png)
あとはインストーラの指示に従い、Install(インストール)やNext(次へ)を押していくとインストールが完了します。
最後は、Finish(終了)を押して完了です。
![Qiita 2022-02-18 Sharperlight 5.3 64bit Setup Finish.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/f4b6cac8-0e88-b012-e4d0-8012551a57f6.png)
次の記事["Sharperlightの使用準備をする"](https://qiita.com/Miki_Yokohata/items/97b2fe578903e59c9233)で、Sharperlightの基本動作ができるように設定する方法を見ていきます。

インストールに必要な環境は、以下の通りです。
Microsoft .NET Framework 4.8
Windows7とService Pack1およびWindowsServer 2008 R2 SP1以降
Microsoft SQL Server 2008 R2 あるいはそれ以降
クライアントPCへのインストールの際
Microsoft Excel 2007、2010、2013、2016、2019、2021およびOffice365のローカルインストールバージョン
Railsのcollection_check_boxesで特定データのみチェックボックスにしたい

#言語
HTML/CSS/JavaScript
Ruby 2.6.3

#フレームワーク
Vue 2.6.12
Rails 6.1.3.2

#やりかったこと
collection_check_boxesの仕組みで特定のカレンダーのみ、
新規登録画面に表示させたい。
![スクリーンショット 2022-02-18 12.14.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468139/53750bff-0f97-b7af-95bd-9e7677b63c4d.png)

#困った点
調べて下記の記述でチェックボックを表示させました。
ただ、作ったカレンダーがすべてチェックボックスになってしまいました。

~~~~~
<%= f.label :カレンダーに参加 %><br />
<div class="checkbox">
  <%= collection_check_boxes(:calendar, :calendar_id, Calendar.all, :id, :name) do |b| %>
    <%= b.label { b.check_box + b.text } %>
  <% end %>
</div>
~~~~~

#原因
Calendar.allでDBから全てのカレンダーを読み込んでいました。

![スクリーンショット 2022-02-18 16.12.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468139/bc661606-90c8-3c2e-d84a-1d587afebcd6.png)




#解決方法
idの１、２、３のカレンダーのみチェックボックスにしたかったので、Calendar.all から Calendar.where(id: [1,2,3]) に変更しました。
![スクリーンショット 2022-02-18 16.07.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468139/9062f7d2-d772-7a92-1c12-46c6ac4a6b21.png)



#備考
一例としてご参考までに。
外部サイトからTeamsの特定チャネルのタブにアクセスさせる（ディープリンク）

オンプレミスの SharePoint Server の社内ポータル等から、Teams の特定のチャネルに貼ってあるタブにアクセスしたい場合の設定方法の紹介になります。

まず、ターゲットのタブを作ります。この例だとSharePoint Online のWebサイトにしてありますが、 Power Apps アプリなどでも大丈夫です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2095776/884d7efb-1db6-5918-b427-2179ff35dba3.png)

このタブをクリックするとメニューが出てきますので、「タブへのリンクをコピー」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2095776/27d844b0-575d-71d1-940d-58d8a9fc5ee7.png)
すると、クリップボードにURLがコピーされますので、メモ帳などにペーストします。URLは結構長いので、右端で折り返すなどで表示してみてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2095776/83379742-228a-7744-d319-987e17dc8dc3.png)

これをディープリンクと呼びます。詳細な仕様については以下をご確認ください。
https://docs.microsoft.com/ja-jp/microsoftteams/platform/concepts/build-and-test/deep-links
さて、これを直接ブラウザに張り付けてみましょう。どうなるでしょうか。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2095776/e6c61107-bb74-8346-9dfb-7f10bf1a7826.png)
こんな画面になりますね（以下、起動確認画面と呼ぶことにします。）Teamsクライアントがすでに起動している場合、[開く]をクリックすれば、Teamsクライアントが前面に表示され（フォーカスされるといいます）、意中のチャネル内タブが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2095776/f432e790-a884-fd1b-a3ea-b9b148061a8a.png)

## 起動確認画面を省きたい

社内ポータルなどで上記のディープリンクを活用する場合、起動確認画面が表示されるので、一手損になってしまいます。これを省くためにはどうすればいいでしょうか。
https://～～～の部分を msteams:/ に変更します。

msteams:/l/entity/(以下同じ)

すると初回は以下のように聞かれますがチェックを入れて[開く]を押すと2回目以降はダイレクトにTeamsが呼び出されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2095776/525b0f10-59ae-d5ad-94e7-1d60ca7a6811.png)

これはOSのURL Scheme による挙動になります。msteams に対してTeamsが既定のアプリとして認識されるため、Teamsが起動するということになります。（もともとのリンクは https だったので、既定のブラウザが起動していたということになります）
なお、管理者が制御している場合などは上記の動作とならない可能性があります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2095776/c6afb61d-d574-a236-69ed-1186287b7ade.png)

## まとめ

ディープリンクは特定のチャネルタブだったり、特定のユーザに対するチャットだったりを直接指定するURLになります。https から msteams に変更すると直接Teamsに対してアクセスできるのでよりシームレスになると思います。
Public DNS64 一覧

## はじめに
最近、国内でもIPv6シングルスタックと共に、NAT64/DNS64が話題になっています。
国内の有名サービスだと、NTT docomo、楽天モバイル、BIGLOBEなどがNAT64/DNS64を提供しています。
今回は、誰でも使用可能な公共DNS64をまとめてみました。

## 注意
DNS64は、NAT64が実装されているネットワークにおいて初めて機能します。
それ以外のネットワークで後述のDNSキャッシュを設定すると、IPv4のみのサイトが表示出来なくなったり、読み込みに遅延が発生する可能性があります。DNS64の仕組みを理解した上での利用をお願いします。

## 一覧
|名称|プライマリ|セカンダリ|DoT|
|:-|:-|:-|:-|
|[Google Public DNS](https://developers.google.com/speed/public-dns/docs/dns64)|2001:4860:4860::6464|2001:4860:4860::64|dns64.dns.google|
|[1.1.1.1(Cloudflare/APNIC)](https://developers.cloudflare.com/1.1.1.1/ipv6-networks#configure-dns64)|2606:4700:4700::64|2606:4700:4700::6400|dns64.cloudflare-dns.com|
|[TREX(フィンランド)](https://www.trex.fi/2011/dns64.htm)|2001:67c:2b0::4|2001:67c:2b0::6|dns64.trex.fi|
|[IPng(アムステルダム)](https://ipng.ch/s/articles/2024/05/25/nat64-1.html)|2a02:898:146:64::64|||
|[NAT64.net(ヘルシンキ)](https://nat64.net/)|2a01:4f9:c010:3f02::1|||
|[NAT64.net(アムステルダム)](https://nat64.net/)|2a00:1098:2c::1|||
|[NAT64.net(ロンドン)](https://nat64.net/)|2a00:1098:2b::1|||

## digコマンドでテストしてみる
現状IPv6のためのAAAAレコードが登録されていないサービスに対して、CloudFlareのDNS64を使って問合せをしてみます。

```shell
% dig @2606:4700:4700::64 AAAA twitter.com                                                                                                          [~][php5]

; <<>> DiG 9.10.6 <<>> @2606:4700:4700::64 AAAA twitter.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 13807
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;twitter.com.			IN	AAAA

;; ANSWER SECTION:
twitter.com.		1330	IN	AAAA	64:ff9b::68f4:2ac1
twitter.com.		1330	IN	AAAA	64:ff9b::68f4:2a41

;; Query time: 7 msec
;; SERVER: 2606:4700:4700::64#53(2606:4700:4700::64)
;; WHEN: Fri Feb 18 12:00:57 JST 2022
;; MSG SIZE  rcvd: 96
```

ちゃんとNAT64用の`64:ff9b::/96`のプレフィックスのIPv6アドレスが返ってますね。

ちなみに、これらのIPv6アドレスをIPv4互換表記であらわすと次のようになります。

|通常の表記|IPv4互換表記|実際のIPv4アドレス|
|:-|:-|:-|
|64:ff9b::68f4:2ac1|64:ff9b::104.244.42.193|104.244.42.193|
|64:ff9b::68f4:2a41|64:ff9b::104.244.42.65|104.244.42.65|
IPv4アドレスが、NAT64用IPv6アドレスに置き換わっていることが分かりますね。

次にAAAAレコードが既に登録されているサービスとして、試しにqiita.comを問い合わせてみます。

```shell
% dig @2606:4700:4700::64 AAAA qiita.com                                                                                                            [~][php5]

; <<>> DiG 9.10.6 <<>> @2606:4700:4700::64 AAAA qiita.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 41656
;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;qiita.com.			IN	AAAA

;; ANSWER SECTION:
qiita.com.		53	IN	AAAA	2406:da14:add:902:d6f0:2ac2:96c:a547
qiita.com.		53	IN	AAAA	2406:da14:add:901:bfa0:2dbd:353e:ff6e
qiita.com.		53	IN	AAAA	2406:da14:add:900:cbb3:7389:31dd:1524

;; Query time: 9 msec
;; SERVER: 2606:4700:4700::64#53(2606:4700:4700::64)
;; WHEN: Fri Feb 18 12:45:03 JST 2022
;; MSG SIZE  rcvd: 122
```

ちゃんとQiitaのサーバーのIPv6アドレスが返されました。
Index signature（インデックス型）~あのObjectの型を使いたい~

# Index signature（インデックス型）とは
参考: https://typescript-jp.gitbook.io/deep-dive/type-system/index-signatures
> JavaScript(TypeScript)のObjectは、他のJavaScriptオブジェクトへの参照を保持し、文字列でアクセスできます。

コードで実際に見てみましょう

#### 通常
```ts
export default class Test {
  public static id: number = 1
}

const id: number = Test.id
```

#### index signatureを使う場合
```ts
export default class Test {
  public static id: number = 1
}

const id: Test['id'] = Test.id
```

このように`object[property名]`でそのオブジェクトのプロパティーの型を使うことができます。

# Object.propertyを通した値のみしか入れられないようにすることは可能か？

#### 理想
```ts
class a {
  public static a: number = 1
}
const b: a['number'] = 1 // error
const c: a['number'] = a.a // ok
```

#### 現実
```ts
class a {
  public static id: number = 1
}
const b: a['id'] = 1 // ok
const c: a['id'] = a.a // ok
```

結論: できない

# Object.propertyを積極的に使うべきか？
使える場面もあるけど、私は基本使わない
DDDの概念の値Objectで別途型を構成した方がいい気がする

```ts
type AId = number
class Klass {
  public static id: AId = 1
}
const b: AId = 1 // ok
const c: AId = Klass.id // ok
```
Voicy.jpの再生スピードを、任意のショートカットキー押下でUP・DOWNできる様にするスクリプト

```javascript
/*
  2022/02/18
  Voicy.jpの再生スピードを、任意のショートカットキー押下でUP・DOWNできる様にするスクリプト。
  by rtyaoke

  chrome拡張の「ScriptAutoRunner」等での使用がおすすめ。
  https://chrome.google.com/webstore/detail/scriptautorunner/gpgjofmpmjjopcogjgdldidobhmjmdbm?hl=ja-jp
*/
window.onload=function(){

  // shortcut.jsのインポート用に、DOM要素を作成
  const script = document.createElement('script');
  script.src = 'https://www.openjs.com/scripts/events/keyboard_shortcuts/shortcut.js';

  // shortcut.jsが読み込み完了してからショートカット登録
  script.onload = function() {
    const op = {
      'type':'keydown',
      'propagate':false,
      'disable_in_input':true,
      'target':document
    }
    // 再生スピードUP用。↓の"E"を任意のショートカットキーに変更する。
    shortcut.add("E",function() {
      const speed_btn = document.querySelector("button.speed");
      speed_btn.click();
    },op);
    // 再生スピードDOWN用。↓の"Q"を任意のショートカットキーに変更する。
    shortcut.add("Q",function() {
      const speed_btn = document.querySelector("button.speed");
      speed_btn.click();
      speed_btn.click();
      speed_btn.click();
    },op);
  }

  // shortcut.jsのインポート用のDOM要素を<head>内に追加
  document.head.appendChild(script);

}
```
100日でSQLの達人になる@LeetCode! Day18 <条件に合うものをカウントするCOUNTIF !?, AVGは遅い。>

## 1173. Immediate Food Delivery I （問題レベル: Easy）要課金
今日の問題はこれ。
発注日と配達日が一致している注文が全体の何％かを計算するという問題。

最初は、`COUNT(条件式)`が使えるかと思ったが、SQLでは駄目で、`SUM(CASE WHEN)`や、`SUM(IIF())`を使う必要があった。

今日のポイントは条件に合うもののカウントは、`SUM(CASE WHEN)`や、`SUM(IIF())`を使う必要がある！

細かいところでは、
```
SELECT ROUND(100.00 * AVG(order_date = customer_pref_delivery_date),　2) AS immediate_percentage
FROM deliver
```
こちらは構文は正しいはずなのに、時間切れで何度やってもアクセプトされませんでした。
`AVG句`は処理が遅くなりやすいようです。

```
SELECT ROUND(100.00 * SUM(CASE WHEN order_date = customer_pref_delivery_date THEN 1 ELSE 0 END) / COUNT(*),2) AS immediate_percentage
FROM delivery
```
こっちの方が一見複雑なクエリですがアクセプトされました。

```
SELECT ROUND(100.00 * AVG(IIF(order_date = customer_pref_delivery_date, 1.00, 0.00)), 2) AS immediate_percentage
FROM delivery
```
`AVG句`を使ったクエリでは、これならばOKでした。

一番早かったのはこちら。
```
SELECT ROUND(100.00 * SUM(IIF(order_date = customer_pref_delivery_date,1,0)) / COUNT(*), 2) AS immediate_percentage
FROM delivery
```

また`MS SQL Server`での小数点の扱いがいまいち、ちゃんとわかっていません。途中で1.00などを掛けると良さそうなことは分かりました。
追記：Day22で少し小数点問題については理解が深まりました。

* [LeetCode](https://leetcode.com/)の問題は、`MS SQL Server`で解いています。

この記事は下記を参考にしました。
[POTEPAN STYLE SQLで高速化を考えた時にできるテクニック・コツをまとめて紹介！](https://style.potepan.com/articles/26070.html)
やっぱり、予約語は大文字を使用しないとだめなのか。
JavaScriptの繰り返し処理（for,map）

## JavaScriptの繰り替え処理について

###　for文とは
for文によるループは指定された条件がfalseと評価されるまで繰り返されます。JavaScriptのforループはJavaやC言語のforループと同じです。

```js

const arr = ["日本", "米国", "英国"];
for (let i = 0; i < arr.length; i++) {
  console.log(arr[i]);
}
//=> "日本"
//=> "米国"
//=> "英国"
```

forループが実行されるときに次の処理が行われます。

```
for([初期化式];[条件式];[加算式])
```
- 初期化式が実行されます。この式は通常、一個またはそれ以上のループカウンターを初期化しますが、この構文ではいかなるレベルの複雑な式を入れることができます。初期化式で変数を宣言することができます。
- 条件式が評価されます。条件式の値がtrueの場合、ループ文が実行されます。条件式の値がfalseの場合、for ループは終了します。条件式がすべて省略されている場合、条件式は真であると仮定されます。
- 文が実行されます。複数の文を実行するには、それらの文をグループ化するためにブロック文 ({ ... }) を使用します。
- もしあれば、加算式が実行されます。


## for-in
次はfor-inで、これはあまり使ったことがないのですがオブジェクトの中身などを取り出したいときに使用されるみたいです。


```js
const obj = {
  name: "Luffy",
  age: 19
}

for(let key in obj) {
  console.log(key + ":" + obj[key]);
}
//=> "name:Luffy"
//=> "age:19"
```

for (変数 in オブジェクト)とすれば
オブジェクトのプロパティの数だけ繰り返してくれます。


## for-of
for...of 文は、反復可能オブジェクト、たとえば組込みの String, Array, 配列状オブジェクト (例えば arguments や NodeList), TypedArray, Map, Set, およびユーザー定義の反復可能オブジェクトなどに対して、反復的な処理をするループを作成します。これはオブジェクトのそれぞれの識別可能なプロパティの値に対して、実行される文を表す独自の反復フックを呼び出します。


```js
const numbers = [1,2,3,4,5,6];

for (let number of numbers) {
  console.log(number);
}

//=> 1
//=> 2
//=> 3
//=> 4
//=> 5
//=> 6
```

## forEach
最後はforeachですがこれは配列に対してつかえます。


```js

const obj = [
  {
    id: 1,
    name: "zoro"
  },
  {
    id: 2,
    name: "sanzi"
  }
];
 
obj.forEach( function( value ) {
    console.log( value.name );
});

//=> zoro
//=> sanzi
```

## map
決まった繰り返し処理はforですが、JavaScriptのES6以降ではmap関数を使用することでコードの短縮ができます。
map()メソッドは、与えられた関数を配列の全ての要素に対して呼び出し、その結果からなる新しい配列を生成します。

mapは、与えられたcallback関数（注意として、コールバックは 非同期 命令が完了した後に続いてコードが実行されます）を配列の順番通りに、各要素に対して一度ずつ呼び出し、その結果から新しい配列を生成します。callbackは、値が代入されている配列の要素に対してのみ呼び出されます。
mapは新しい配列を生成するので、返された配列を使わない場合、mapを使うのはパターンに合いません。代わりに、forEachまたは、for-ofを使用してください。

```js
const array = [1, 4, 9, 16].map(x => x * 2);

console.log(array); 

//=> [2, 8, 18, 32]
```


##　まとめ
今日は繰り返し処理の一部について書かせて頂きました。明日はFindについて書こうと思います。
Google Drive上の公開されたファイルをダウンロードする

## 2024年1月21日更新

2024年1月の段階で仕様が変更されたとのご連絡をいただきましたのでこちらも更新させていただきます。元ネタは[こちら](https://gist.github.com/tanaikech/f0f2d122e05bf5f971611258c22c110f)です。

現在は、下記のURLからGoogle Drive上のパブリックで共有されたファイルをダウンロードできます。この場合、一度のリクエストで大きなサイズのファイルもダウンロードが可能のようです。

```
https://drive.usercontent.google.com/download?id={fileId}&confirm=xxx
```

今のところ`confirm=xxx`の`xxx`は、`x`でも`y`でも`z`でも動作するようです。

curlコマンドは下記のとおりです。

```bash
curl "https://drive.usercontent.google.com/download?id={fileId}&confirm=xxx" -o filename
```

または

```bash
curl -L "https://drive.usercontent.google.com/download?id={fileId}&confirm=xxx" -o filename
```

今回は、[昨年末から年始にかけて2度の変更がありました](https://workspaceupdates.googleblog.com/2023/10/upcoming-changes-to-third-party-cookies-in-google-drive.html)ので、また変更がありましたら更新させていただきます。



## 概要
この投稿では、Google Drive上の公開されたファイルをダウンロードする方法について紹介させていただきます。

## 内容
最近、Google Drive上の公開されたファイルをダウンロードする仕様が変更されまして、それに関する問い合わせが届いていますのでので、こちらでも仕様変更後のダウンロード方法について紹介させていただきます。

Google Drive上で公開されたファイルをダウンロードする際、ファイルサイズが小さい場合（40MB以下程度と思われます）は、これまで通り、下記のような`webContentLink`を使ってダウンロードすることができます。ここではサンプルとしてcurlコマンドを使用します。

```bash
$ curl -L "https://drive.google.com/uc?export=download&id=### fileId ###" -o sampleFilename
```

ところが、ファイルサイズが大きくなると、ファイルのウィルススキャン不可を承認するプロセスを通過する必要があるためにダウンロードを開始するまでに２段階のプロセスが必要になり、上記のコマンドだけではダウンロードすることができません。これまでは、ダウンロードするためのIDを最初に`webContentLink`へリクエストした際に得られるCookieから取得していました。最近この部分が変更され、下記のようにダウンロードするためのIDが返されるHTMLデータに含まれるようになっています。

```
<form id="downloadForm" action="https://drive.google.com/uc?export=download&amp;id={fileId}&amp;confirm={value for downloading}" method="post">
```

これをcurlを使ったbashスクリプトで表現すると次のようになります。

```bash
fileid="### file id ###"
filename="sampleFilename"
html=`curl -c ./cookie -s -L "https://drive.google.com/uc?export=download&id=${fileid}"`
curl -Lb ./cookie "https://drive.google.com/uc?export=download&`echo ${html}|grep -Po '(confirm=[a-zA-Z0-9\-_]+)'`&id=${fileid}" -o ${filename}
```

２回目のcurlコマンドではCookieを使用しなくてもダウンロードできますが、テストを行っていたところ、Cookieを使用する方がダウンロード速度が速いとの報告を受け、このようにしました。今の時点では上記の方法でGoogle Drive上で公開されたサイズの大きなファイルをダウンロードすることができます。これもまた今後の仕様変更でアップデートする必要があるかもしれません。
git-ftpでリモートの変更ファイルをローカルにダウンロードする方法

```bash
git-ftp download
```

これで、リモートの変更ファイルをローカルにダウンロードできます。

# 上手くできない時は

以下のようなエラーが出るときがあります。

```bash
cd: 致命的エラー: Certificate verification: unable to get local issuer certifica:
```

その際は、

ターミナルをロゼッタを使って開く

https://www.kzyrepository.com/2020/12/%E8%A7%A3%E6%B1%BA%E6%B3%95cannot-install-in-homebrew-on-arm-processor%E3%82%A8%E3%83%A9%E3%83%BC%E3%81%AE%E8%A7%A3%E6%B1%BA%E6%B3%95m1%E3%83%81%E3%83%83%E3%83%97/

lftpをインストール


```
brew install lftp 
```

```
mkdir ~/.lftp
echo "set ssl:verify-certificate no" >> ~/.lftp/rc
```

これで、`git-ftp download`が使えるようになるはずです。

# 参考

https://obel.hatenablog.jp/entry/20170903/1504442470

# Wordpressやエンジニアの便利な情報を発信しています

Wordpressのブログにて記事を最新情報を更新中です！
エンジニアリングに関する情報を随時更新中ですので、ぜひご覧ください。

https://blog.takechan-smile.jp/
CSVのヘッダを付け替えるPowerShell

7.2で動作確認済み

Copy-Item -Path ＜付け替え先ヘッダファイル＞ -Destination ＜結果ファイル＞ -Force
Get-Content -Encoding utf8NoBOM ＜元ファイル＞ | Select-Object -Skip 1 | Add-Content -Encoding utf8NoBOM ＜結果ファイル＞
【Ruby】each_with_objectを使うと便利だよって話

each_with_objectを使う方と使わない方で同じ結果になる。
each_with_objectを使う方が1行コードを減らせるし、すっきりして見やすいのでeach_with_objectを積極的に使っていきたい。

##each_with_objectを使わない①


```ruby
users = User.all
posts = []
users.each do |user|
  posts << user.posts
end
```

##each_with_objectを使わない②

```ruby
users = User.all
posts = users.map(&:posts)
```

```ruby
#> posts
#=> [[#<Post:0x00007f01540e45a0
   id: 303,
   content: "内容内容内容内容内容内容",
   created_at: Fri, 18 Feb 2022 11:41:52.844699000 JST +09:00,
   updated_at: Fri, 18 Feb 2022 11:41:52.844699000 JST +09:00>],
 [#<Post:0x00007f0154482540
   id: 304,
   content: "内容内容内容内容内容内容",
   created_at: Fri, 18 Feb 2022 11:41:53.312786000 JST +09:00,
   updated_at: Fri, 18 Feb 2022 11:41:53.312786000 JST +09:00>]]
```

##each_with_objectを使う

```ruby
users = User.all
posts = users.each_with_object([]) do |user, array|
  array << user.posts
end

#> posts
#=> [[#<Post:0x00007f01540e45a0
   id: 303,
   content: "内容内容内容内容内容内容",
   created_at: Fri, 18 Feb 2022 11:41:52.844699000 JST +09:00,
   updated_at: Fri, 18 Feb 2022 11:41:52.844699000 JST +09:00>],
 [#<Post:0x00007f0154482540
   id: 304,
   content: "内容内容内容内容内容内容",
   created_at: Fri, 18 Feb 2022 11:41:53.312786000 JST +09:00,
   updated_at: Fri, 18 Feb 2022 11:41:53.312786000 JST +09:00>]]
```

##参考

https://docs.ruby-lang.org/ja/latest/method/Enumerable/i/each_with_object.html
MacOSでtorchvisionのimportエラー

# 現象

torchvisionを読み込むと、libpngとか、libjpegのエラーが出る人向け。

```
import torchvision
/Users/<USER NAME>/.pyenv/versions/py38-test2/lib/python3.8/site-packages/torchvision/io/image.py:11: UserWarning: Failed to load image Python extension: dlopen(/Users/<USER NAME>/.pyenv/versions/3.8.12/envs/py38-test2/lib/python3.8/site-packages/torchvision/image.so, 0x0006): Library not loaded: @rpath/libpng16.16.dylib
  Referenced from: /Users/<USER NAME>/.pyenv/versions/3.8.12/envs/py38-test2/lib/python3.8/site-packages/torchvision/image.so
  Reason: tried: '/usr/local/lib/libpng16.16.dylib' (no such file), '/usr/lib/libpng16.16.dylib' (no such file)
  warn(f"Failed to load image Python extension: {e}")
```

# 対策
/usr/local/lib/の下にシンボリックリンクを作成する。

```bash
sudo ln -s /opt/homebrew/Cellar/libpng/1.6.37/lib/libpng16.16.dylib /usr/local/lib/libpng16.16.dylib
sudo ln -s /opt/homebrew/Cellar/jpeg/9e/lib/libjpeg.9.dylib /usr/local/lib/libjpeg.9.dylib
```
androidアプリのアイコン変更がうまくいかなかった件

Duplicate resources
この文が出て、アプリアイコンが変更されない時は、

.Webpと.pngファイルが両方存在していると思います。

片方だけ残して、もう片方だけは削除しましょう。

そうしたら自分はいけました。
Python - pandasライブラリを使ってCSVファイルを読み込む - 初級編

Pythonには超絶便利なライブラリがたくさん。

今回は、Pandasライブラリを使ったCSV読み込み手順をご説明。
infoは除いたけど、とりあえず最低限これが使えれば、
ファイル読み込み処理の初歩はできますね👌

# 1. ライブラリのインストール（入っていなければ）
以下を実行。

```python
#x.x.xはインストールするバージョン
mamba install pandas==x.x.x -y 
#x.x.xはインストールするバージョン
mamba install numpy=x.x.x -y 
```

# 2. Import宣言

```python
import pandas as pds
import numpy as np
```

# 3. ファイルの読み込み (read_csv) ※ロードしたに過ぎないので、この後データの読み上げが必要。
csvファイルを読み込むには、read_csvを使います。  
pandas.read_csv(ファイルパス, option)で呼び出します。  

```python
import pandas as pds

file_path = "https://people.sc.fsu.edu/~jburkardt/data/csv/cities.csv"
d_frame = pds.read_csv(file_path, header=None)
```

# 4. 先頭から n行のデータを読み込み(head)

```python
d_frame.head(5) #先頭からヘッダを除いて5行を取得できる
```

# 5. 末尾から、n行のデータを読み込み(tail)

```python
d_frame.tail(5) #末尾から5行を取得できる
```

# 6. DataFrameにヘッダを追加する(CSVにあるときは、read_csvのOptionを変える)

```python
headers = ["LatD", "LatM", "LatS", "NS", "LonD", "LonM", "LonS", "EW", "City", "State"]
d_frame.columns = headers #先頭にヘッダが出力される
```

# 7.欠落値を落とす

```python
d_frame2 = d_frame.replace('?', np.NaN) #"?"の値は、NaNに置き換える
d_frame = d_frame2.dropna(subset["price"], axis=0) #Price=NaNのデータを除外する
```

# 8.列名を書き出す

```
d_frame.columns
```

# 9.読み込んだDataFrameをCSVに書き出す

```
d_frame.to_csv("xxx.csv", index=False)
```

# 10.データタイプを調べる(DataFrameに含まれる列および、列の方一覧が表示される)

```
d_frame.dtypes
```

# 11. 数値型列の統計サマリ情報（データ数や、平均値などの統計サマリ）を見る

```
d_frame.describe()
```

# 12. 数値以外のフィールドも（できるだけ）統計サマリ情報を見る

```
d_frame.describe(include = "all")
```

# APPENDIX - csv以外の読み書き

```
# json
pds.read_json()
pds.to_json()

# excel
pds.read_excel()
pds.to_excel()

#hdf
pds.read_hdf()
pds.to_hdf()

#sql
pds.read_sql()
pds.to_sql()
```
git-ftpで特定のブランチの変更をアップロードする方法

git-ftpを使っていて、特定のブランチの変更を一時的にアップロードしてサーバで確認したい時、
以下のコマンドを打てば、ブランチの差分だけアップロードできます。

```bash
git-ftp push -b ブランチ名
```

メインブランチに戻したい時は、以下のコマンドでメインブランチの状態に戻せます。

```bash
git-ftp push -b main
```

一時的にブランチの内容をアップしたい際や、
メインブランチにすぐ戻したい時に便利ですね✨

# Wordpressやエンジニアの便利な情報を発信しています

Wordpressのブログにて記事を最新情報を更新中です！
エンジニアリングに関する情報を随時更新中ですので、ぜひご覧ください。

https://blog.takechan-smile.jp/
[py2rb] 多重継承の４

# はじめに
移植やってます。
( from python 3.7 to ruby 2.7 )
# 多重継承 (Python)
https://qiita.com/superrino130/items/89b58bfb4e89500a8e7c

前回の記事で、多重継承は表現できたものの、継承の継承は表現できていなかったので、やってみた。
# delegate (Ruby)
```ruby
require 'delegate'

class Fi
  def original_method(*args, **kwargs)
    'original'
  end
end

class Se1
  def method_missing(method, ...)
    'dummy'
  end
end

class Se2
  def initialize(...)
    @c = SimpleDelegator.new(Fi).new(...)
    @k =[@c]
  end

  def method_missing(method, ...)
    @k.each do |x|
      if x.respond_to?(method)
        return x.method(method).call(...)
      else
        if x.method_missing(method, ...) != 'dummy'
          return x.method_missing(method, ...)
        end
      end
    end
  end
end


class Th
  def initialize(...)
    @c1 = SimpleDelegator.new(Se1).new(...)
    @c2 = SimpleDelegator.new(Se2).new(...)
    @k =  [@c1, @c2]
  end

  def method_missing(method, ...)
    @k.each do |x|
      if x.respond_to?(method)
        return x.method(method).call(...)
      else
        if x.method_missing(method, ...) != 'dummy'
          return x.method_missing(method, ...)
        end
      end
    end
  end
end

third = Th.new
puts third.original_method
puts third.dummy_method

# output
original
`block in method_missing': private method `method_missing' called
```
![20220218.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538241/bb69f053-9082-8d8f-af91-5c2dda3b838d.png)

サーチ順として、`Th`→`Se1`→`Se2`→`Fi`といって`original`を出力しています。

https://docs.ruby-lang.org/ja/latest/class/Delegator.html

`dummy_method`でエラーになるので、大丈夫と思われ。
# メモ
+ Python の 多重継承の４ を学習した
+ 百里を行く者は九十里を半ばとす
ActiveModelSerializerで発生したN+1問題を解決



##前提

UserとPostは1対Nの関係

```ruby
class Post < ApplicationRecord
 belongs_to :user
end
```

```ruby
class User < ApplicationRecord
 has_many :posts, dependent: :destroy
end
```

##解消前

テストログを見るとserializerが走ったときにN+1問題が発生していた。

```ruby
def index
  posts = current_user.posts
  render json: posts
end
```

##解消後

```ruby
def index
  posts = current_user.posts.preload(:user)
  render json: posts
end
```


scopeに切ると他でも使えてベター

```model.rb
scope :preload_user, -> { preload(:user) }
```


```ruby
def index
  posts = current_user.posts.preload_user
  render json: posts
end
```

##参考

preloadとeager_loadの使い分けは下記の記事を参照

https://qiita.com/ryosuketter/items/097556841ec8e1b2940f
CSVをダブルクォート囲み＆LF改行に変換するPowerShell

7.2で動作確認済み

Import-Csv ＜元ファイル＞ -Encoding utf8NoBOM | ConvertTo-Csv -NoTypeInformation | Foreach-Object { $_ + "`n" } | Set-Content ＜結果ファイル＞ -Encoding utf8NoBOM -NoNewline
Goの環境変数読み出しについて

環境変数をセットする際に、いくつかパターンがあるなと思ったのでまとめます。

# 基本

```:basicEnv.go
package main

import (
    "os"
    "fmt"
)

func main() {
    os.Setenv("ENV", "debug")
    env := os.Getenv("ENV")
    fmt.Println(env)
}
```

# `joho/godotenv`
```:.env
ENV=debug
```

```:dotEnv.go
package main

import (
    "os"
    "fmt"
    "log"
    dotenv "github.com/joho/godotenv"
)

func main() {
    if err := dotenv.Load(); err != nil {
        log.Fatalln(err)
    }
    env := os.Getenv("ENV")
    fmt.Println(env)
} 
```

# `ini`
```:config.ini
[dev]
ENV=debug
```

```:ini.go
package main

import (
        "fmt"
        "log"
        "gopkg.in/ini.v1"
)

// ConfigList is ...
type ConfigList struct {
        ENV        string
}

func main() {
        config, err := ini.Load("config.ini")
        if err != nil {
                log.Fatalf("config.iniの読み込み失敗：%v", err)
        }
        cfg = ConfigList{
                ENV:        config.Section("dev").Key("ENV").MustString(""),
        }
        fmt.Println(cfg.ENV)
}

```


# `caarlos0/env`
今のお気に入り:tea:
```:bash
$ echo $ENV
debug
```

```
package main

import (
    "log"
    "fmt"
	"github.com/caarlos0/env"
)

type ServerConfig struct {
	Env     string `env:"ENV,required"`
}

func main() {
	var config ServerConfig
	if err := env.Parse(&config); err != nil {
		log.Fatalln(err)
	}
    fmt.Println(config.Env)
}
```

## おまけ
環境変数読み出しの失敗テストの時に、ポインタで格納している。
コンストラクタを用意していないので、前の同じ値が参照される。
アドレスの削除が必要になった。

という状況で、`os.Clearenv()`を使うことで打開した。

```:config.go
package config

import (
	"github.com/caarlos0/env"
)

type ServerConfig struct {
	Env     string `env:"ENV,required"`
	GinMode string `env:"GIN_MODE,required"`
	Port    string `env:"PORT,required"`
}

// Load 環境変数を読み出す
func Load() (*ServerConfig, error) {
	var config ServerConfig
	if err := env.Parse(&config); err != nil {
		return nil, err
	}

	return &config, nil
}
```

```:config_test.go
// TestLoad_Failed 失敗: 環境変数読み出しテスト
func TestLoad_Failed(t *testing.T) {
	var err error
	cases := []struct {
		env  string
		mode string
		port string
	}{
		{mode: "debug_failed", port: "4000"},
		{env: "YOUR_ENV_failed", port: "5000"},
		{mode: "debug_failed_2"},
	}
	for _, c := range cases {
		// テスト前に、環境変数を削除しておく
		os.Clearenv()

		if c.env != "" {
			if err = os.Setenv("ENV", c.env); err != nil {
				t.Fatalf("環境変数のセットに失敗しました。\n詳細: %v", err)
			}
		}
		if c.mode != "" {
			if err = os.Setenv("GIN_MODE", c.mode); err != nil {
				t.Fatalf("環境変数のセットに失敗しました。\n詳細: %v", err)
			}
		}
		if c.port != "" {
			if err = os.Setenv("PORT", c.port); err != nil {
				t.Fatalf("環境変数のセットに失敗しました。\n詳細: %v", err)
			}
		}
		scfg, err := config.Load()
		if scfg != nil || err == nil {
			t.Fatalf("環境変数の読み出しに失敗しました。\n詳細: %v", err)
		}
	}
}

```
TypeScriptめも


コンストラクタについて
```ts:test.ts
//コンストラクタの引数を使ってプロパティを宣言することができる
//アクセス制御の装飾子をつけると、それがそのままプロパティになる。 

class Test{
  constructor(public prop: string) {
  }
}
const test = new Test("testprop");
console.log(test.prop); //-> "testprop"
```
【JS】よく使うデザインパターン【先輩わんわんエンジニアより】

先輩わんわんエンジニア（現役）の方が
「よく使うよ～」っていうデザインパターンを
教えてもらったので、詳細をググって勉強📝

※畑はベンチャーです🐾

※Zenが一番詳しい…いい時代になった
#シングルトン

https://zenn.dev/morinokami/books/learning-patterns-1/viewer/singleton-pattern

> メリデメ：
インスタンス化を一度に限定することで、使用されるメモリ容量を大幅に削減できる可能性
 JavaScript ではオブジェクトを直接作成できるため、通常のオブジェクトを使用するだけでまったく同じ結果を得ることができます。

> シングルトンは実際には<b>アンチパターン</b>と考えられており、JavaScript ではこれを避けることができます


#オブザーバー

https://zenn.dev/morinokami/books/learning-patterns-1/viewer/observer-pattern


https://note.com/erukiti/n/n1d6e0fcfaefc
PHPでMDB2の接続確認をする

MDB2の接続周りを確認した時のメモ。

```php:動作確認用のコード
<?php
// MDB2の読み込み
require_once 'MDB2.php';

// DNSを定義
$dns = 'mysql://ユーザー:パスワード@ホスト:ポート/DB?charset=utf8';

// DBへ接続
$db = MDB2::connect($dns);

// 接続結果の確認
if(MDB2::isError($db)) {
    echo "NG:" . $db->getMessage() . "\n";
    exit;
} else {
    echo "OK\n";
}

// SQLを実行
$query = 'select * from hoge limit 5';
$q = $db->query($query);

// 実行結果を出力
while($row = $q->fetchRow()) {
  var_dump($row);
}
```
PythonのEnumの比較結果がなぜかFalseになる

## はじめに

Pythonのunittestを書いていて、同じはずのEnumオブジェクトの比較結果がなぜかFalseになってしまう事象で少し詰まった。結果的に凡ミスだったが、今後ハマる人のために共有しておく。

PythonのEnumやunittestについては詳しく触れないので、気になる人は以下の公式資料を参照すると良い。

https://docs.python.org/3.9/library/unittest.html

https://docs.python.org/ja/3.9/library/enum.html

## TL;DR

importのパッケージ名を揃えれば解決する。

## 問題の再現

以下のようなディレクトリ構成を考える。

```
enumbug
├── b.py
├── enums.py
└── test.py
```

各ファイルの中身は以下の通り。

```enums.py
from enum import Enum


class Color(Enum):
    RED = 1
    BLUE = 2
    GREEN = 3
```

```b.py
from enums import Color


class B(object):
    def __init__(self):
        self.color = Color.RED
```

```test.py
import unittest

from enumbug.b import B
from enumbug.enums import Color


class Test(unittest.TestCase):
    def test(self):
        b = B()
        self.assertEqual(b.color, Color.RED)
```

ここでunittestを実行すると以下のようになり、`b.color`と`Color.RED`の比較がFalseになる。両方ともColor.REDなのに。

```bash
$ PYTHONPATH=<enumbugの親ディレクトリのフルパス> python -m unittest test 2>&1|pbcopy

F
======================================================================
FAIL: test (test.Test)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "<enumbugの親ディレクトリのフルパス>/enumbug/test.py", line 10, in test
    self.assertEqual(b.color, Color.RED)
AssertionError: <Color.RED: 1> != <Color.RED: 1>

----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (failures=1)
```

`<Color.RED: 1> != <Color.RED: 1>`ってなんやねん、同じやろって10分くらいキレてた。

## 原因
いろいろ調べた結果以下の記事を見つけ、Colorをインポートしているパッケージが異なることが原因だと分かった。

https://bugs.python.org/issue30545

上記の`b.py`では`from enums import Color`としているのに対し、`test.py`では`from enumbug.enums import Color`としてしまっており、比較している2つのEnumが別のパッケージのものになってしまっていたためにEnumの比較がFalseになってしまっていた。

パッケージのimportをIDEに頼って自動化してしまっていたためになかなかこれに気が付けなかったのが悔やまれる。ただこれ分かっていないと原因探るの難しい気はした。エラーメッセージでわかるようにしてほしい。
HP miniへのUbuntu最新版のインストール

随分前に購入したHP miniにUbuntu14.04を入れて使っていたのですが流石に古くなってきたので最新版にアップグレードすることにしました。

しかし、古すぎて順次バージョンアップしていく必要があり、まずは16.04に上げた時点でGUIとインターネット接続に問題が発生。

ってことで仕方なく、最新の20.04を初期インストールすることに。
MacでUSBにインストールイメージを作成してインストールするには少々コマンドで作業が必要だったのでメモしておきます。

# 手順
1. Ubuntuのisoイメージをダウンロード
1. UbuntuのディスクイメージをUSBに展開
1. 対象パソコンでUSBブートしてインストール
1. wifiを有効化してインターネットにつながるようにする
1. 日本語辞書を使えるようにする

# Ubuntuのisoイメージをダウンロード
日本語remixはこちらからダウンロードできます。
http://www.ubuntulinux.jp/products/JA-Localized/download

しかし、macのsafariからだとセキュリティエラーになったので、本家の方からダウンロードしました。
https://jp.ubuntu.com/

# UbuntuのディスクイメージをUSBに展開
以下のサイトを参考にUSBにディスクイメージを作成しました。
私の場合1〜2時間かかりました。
https://www.gworks.jp/2018/09/mac-linux-ubuntu-usb-installer/

ただし、最後のddコマンド実行時に権限エラーが発生します。
Macのセキュリティのためなので、以下のサイトを参考にターミナルにディスクフルアクセスの権限を設定してください。
https://blog.ukixa.com/2019/10/mac-dd-peration-not-permitted.html

# 対象パソコンでUSBブートしてインストール
やっとディスクイメージを作成できたので対象となるパソコンでUSBからインストールです。
この時点でHP miniはi686CPUだったので18.04以上のUbuntuはインストールできないことに気づく😱
ってことで仕方なく16.04でUSBにイメージを作り直しして再度Ubuntuをインストール。

この時サードパーティー製のドライバを同時にインストールしないとwifiが使えなくなるので要注意。

# Wifiを有効にしてインターネットにつながるようにする
無線LANが有効になるので、Wifiの設定をしたのですがDNSエラーが発生。
以下を参考にDNSを再設定しました。

https://thr3a.hatenablog.com/entry/20180711/1531285176

しかしこれでもまだ接続が不安定😢
色々試した結果、以下の方法で解決しました。

以下を参考に「/etc/sysctl.conf」に以下の2行を追加してIPv6サポートを外す。

```:sysctl.conf
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1IPV6サポートを外す。
```

https://techblog.show-ya.blue/2016/05/25/ubuntu16-04%E3%81%A7dns%E3%82%A8%E3%83%A9%E3%83%BC%E3%81%8C%E3%82%88%E3%81%8F%E5%87%BA%E3%82%8B%E5%A0%B4%E5%90%88%E3%81%AE%E5%AF%BE%E5%87%A6/


DNSサーバの設定については、/etc/resolved.confにGoogleのDNSの設定を追記するようにあります。

```:resolved.conf
nameserver 8.8.8.8
```

しかし、Ubuntuでは/etc/resolved.confはPC再起動時に自動的に書き換えられる模様。
色々と調べましたが上書きしているプログラムの元ファイルがわからず、最終的に以下の方法で対応しました。
※resolved.confは/run/resolvconf/resolv.confのシンボリックリンクになっています。

```
$sudo rm /etc/resolved.conf
$sudo cp /run/resolvconf/resolv.conf /etc/
$sudo vi /etc/resolved.conf
　→nameserver 8.8.8.8を記述して保存
```
要はシンボリックリンクを外して、resolved.confをハードコピーして、そこにネームサーバ設定を記述した感じです。

その後Ubuntuを再起動することでやっとWifiが使えるようになりました。

# 日本語辞書を使えるようにする
Ubuntuの本家からインストールしたせいか、日本語辞書がうまく動かなかったので以下の対応を行いました。
ちなみに私の場合は、mozcはインストールしなくても入っていたので、GUIでのテキスト入力設定にmozcを追加しただけです。

https://symfoware.blog.fc2.com/blog-entry-1397.html

# まとめ
SDGsを考えた上でも、しっかり古いパソコンもできる限り使い潰したいのですがなかなか難儀ですね。。。
SQLで日付範囲指定 (SQL Server)

# SQLで日付範囲指定
__BETWEEN__ を使って、下記のようにする。

```
SELECT * FROM table1 WHERE date_col BETWEEN '2022-02-17 00:00:00' AND '2022-03-17 00:00:00';
```

# 日付の文字列の形式（スタイル）が違う場合
例えば、日付のスタイルが、`'2022-02-17 00:00:00'` ではなく、`'20220217'`　の場合（`yyyymmdd`の形式）に対応したいとき。

__CONVERT__を使う。

```
SELECT * FROM table1 WHERE CONVERT(VARCHAR(10),date_col,112) BETWEEN '20220217' AND '20220317';
```

CONVERTの3つ目の引数`112`は スタイルで、スタイルには、`112 = yyyymmdd` , `111 = yyyy/mm/dd` など、様々なものがあるようです。

# 参考
https://docs.microsoft.com/ja-jp/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver15
exifinterfaceを使った画像回転処理の実装

#初めに
以前、画像のUriからByteArrayに変換する処理を投稿させていただきましたが、それを利用して今度は画像が編集によって回転されている場合、逆方向に回転をさせてしっかりと縦画面表示になる様に実装していきたいと思います

#導入方法
```kotlin
implementation("androidx.exifinterface:exifinterface:1.3.3")
```
###実装例

```kotlin
val imageStream: InputStream? = contentResolver.openInputStream(imageUri)

            val bitmap = BitmapFactory.decodeStream(imageStream)
            val byteArrayOutputStream = ByteArrayOutputStream()
            val compressFormat = when (mimeType) {
                MediaType.Jpeg -> {
                    Bitmap.CompressFormat.JPEG
                }
                MediaType.Png -> {
                    Bitmap.CompressFormat.PNG
                }
                else -> {
                    throw IllegalStateException("media type not found")
                }
            }
            // ここで回転されてる角度を取得する
            val orientation = ExifInterface(
                contentResolver.openInputStream(imageUri)
                    ?: throw ImageDecodeException("stream can not open")
            ).getAttributeInt(
                ExifInterface.TAG_ORIENTATION,
                ExifInterface.ORIENTATION_NORMAL
            )

            // ここで回転されてる角度によって回転し直す角度を決めて変換メソッドを呼び出す
            val rotatedBitmap = when (orientation) {
                ExifInterface.ORIENTATION_ROTATE_90 -> rotateImage(bitmap = bitmap, angle = (90F))
                ExifInterface.ORIENTATION_ROTATE_180 -> rotateImage(bitmap = bitmap, angle = (180F))
                ExifInterface.ORIENTATION_ROTATE_270 -> rotateImage(bitmap = bitmap, angle = (270F))
                else -> bitmap
            }

            rotatedBitmap.compress(compressFormat, 100, byteArrayOutputStream)
            byteArrayOutputStream.toByteArray()
```

```kotlin
    /**
     * 回転した Bitmap を返す
     */
    private fun rotateImage(bitmap: Bitmap, angle: Float): Bitmap {
        return Bitmap.createBitmap(
            bitmap,
            0,
            0,
            bitmap.width,
            bitmap.height,
            Matrix().apply { setRotate(degree) },
            true
        )
    }
```
###最後に
以前の投稿でも書いた部分を書くかは悩みましたが、備忘録的な扱いが多いためわかりやすい様にコード全体を書きました。
何かの役に立てれば幸いです
MSニュースメモ:古い AzureAD ConnectSyncバージョンの廃止

Microsoft ニュースレターのうち、気なるものをメモ。

AzureAD Connect Sync は、Microsoft 社のサイト情報（[Azure AD Connect:バージョンのリリース履歴](https://docs.microsoft.com/ja-jp/azure/active-directory/hybrid/reference-connect-version-history)）にもあるように、windows 2016 以降は 2.x 系、Windows 2016 よりも前は 1.x 系となります。
この、1.x 系が、2022年8月31に廃止になるようです。Q&A に「しばらくは動くかも？」とありますが、早めに 2.x 系にアップグレードしておく方がよさそうです。

# ニュースメールの内容

原文: [旧版 AzureAD（Azure Active Directory）ConnectSyncバージョンの廃止](https://admin.microsoft.com/AdminPortal/home?ref=MessageCenter/:/messages/MC333227)

キーポイント

* タイミング：2023年3月中旬にこのポリシーが有効になり、最後にリリースされたバージョン（2022年3月中旬より前の最新バージョン）より前のすべてのバージョンが廃止されます。
    * このポリシーは、2022年8月31日のAzure AD ConnectSyncのすべての1.xバージョンの廃止を変更するものではありません。これは、SQL Server2012およびAzureAD認証ライブラリ（ADAL）コンポーネントの廃止によるものです。
* アクション：Azure AD Connect Syncの最新リリースバージョンをまだ使用していない場合は、最新バージョンに置き換えられた日から12か月が経過する前に、Azure AD ConnectSyncソフトウェアをアップグレードする必要があります。
* ロールアウト：テナントレベル

影響:

廃止されたバージョンのAzureAD Connectを実行すると、予期せず動作を停止する場合があります。また、最新のセキュリティ修正、パフォーマンスの改善、トラブルシューティングおよび診断ツール、およびサービスの強化が行われていない可能性があります。

よくある質問:

- Q:アップグレードしないと、サポートを受ける資格がなくなりますか？
    - A：常にサポートを提供しますが、廃止されたバージョンのAzure AD Connect Syncに依存している場合は、組織が必要とするサポートエクスペリエンスを提供できない可能性があります。したがって、最新の状態を維持することが重要です。
- Q：廃止されたバージョンのAzure AD Connect Syncは、2023年3月15日以降に機能しなくなりますか？
  - A：おそらくそうはなりません。廃止されたバージョンは、サービスやソフトウェアに依存している可能性がありますが、それらは利用できなくなったり、その日を過ぎると機能しなくなったりする可能性があります。したがって、現在と言うことが重要です。
- Q：使用中のバージョンと最新リリースバージョンの間に機能的な違いはありますか？
  - A：アップグレード元のバージョンによっては、セキュリティの改善、バグの修正、機能の強化が行われる場合があります。 Azure AD Connect：バージョンリリース履歴ページで機能ごとに変更を確認できます。

準備するために必要なこと：

Azure AD Connect Syncサーバーをアップグレードするには、次の手順に従います。AzureAD Connect：以前のバージョンからアップグレードします。

置き換えられたバージョンのAzureAD Connect Syncの廃止について懸念がある場合、または最新バージョンへのアップグレードについて質問がある場合は、サポートトピックとして「ディレクトリ同期-Azure ADConnect」を記載したサポートリクエストを開いてください。

より詳しい情報:

* [Azure AD Connect2.xバージョンの廃止](https://docs.microsoft.com/ja-jp/azure/active-directory/hybrid/reference-connect-version-history#retiring-azure-ad-connect-2x-versions)
情報セキュリティ

情報セキュリティの３要素：
機密性
完全性
可用性

攻撃の流れ：
１予備調査
使われいている言語は？Webシステムの構成は？本社所在地は？サーバの場所は？etc..

2 脆弱性調査
攻撃対象への全体調査を終えた後に、くさのリンの中で一番弱い箇所を探す

3 攻撃の試行
一番弱い箇所を攻撃する

4 権限の上昇
内部に侵入しただけでは、重要な情報をアクセスできないため、管理者権限を奪い取る。

5 バッグドアの設置
後から何度も侵入ために作る、秘密の入り口。

Webアプリケーションを攻撃する手法：
SQLインジェクション：
Webフォームなどの入力インタフェースなどを利用してデータベースに発行されるSQLを変更。
例：PWを'aaa' or '1'='1'

対策：
1 入力値のチェック（バリデーション）
　パスワード入力を半角英数字にして[']を防ぐ
　入力文字を厳格に指定する

2 プリペアードステートメント
　先に用意した変数に格納してチェックする（元々はDBスピードアップのため）

クロスサイトスクリプティング（XSS)
HTMLフォームの中に悪意のあるJavaScriptを埋め込む攻撃
→　レスポンスの中に埋め込み、Webブラウザを実行させる

クロスサイト：別のサイト
スクリプト：実行内容

対策：サニタイジング
HTMLに埋め込むと、構造を変更してしまう文字列を無害に
<script>alert('XSS)</script>
&lt;script&gt;alert(&apos;XSS&apos)&lt;/script&gt;
Python　time.sleepのsleepできるのは最小何秒？

# 結論

**0.001秒** 

いちおう、スリープ時間は最小になったが、希望通りのスリープはしてくれない

# 検証

0.000~0.009のスリープ時間の計測

```python
def minimam_sleep(milliseconds):
    seconds = 0.001 * milliseconds
    start_time = time.time()
    time.sleep(seconds)
    stop_time = time.time()
    time_difference = stop_time - start_time
    return time_difference 
    
for milliseconds in range(10):
    print(round(milliseconds*0.001,3), minimam_sleep(milliseconds))
```

```
0.0 0.0
0.001 0.005999088287353516
0.002 0.016000986099243164
0.003 0.01599907875061035
0.004 0.016000986099243164
0.005 0.015250682830810547
0.006 0.01600027084350586
0.007 0.015999794006347656
0.008 0.016026735305786133
0.009 0.015388250350952148
```

# 検証２

0.000 ~ 0.009
関数の呼び出し時間を0.01と仮定

```
def minimam_sleep(milliseconds):
    seconds = 0.001 * milliseconds
    start_time = time.time()
    time.sleep(seconds)
    stop_time = time.time()
    time_difference = stop_time - start_time
    print(time_difference-0.01)
    
for i in range(10):
    minimam_sleep(i)
```
```
指定時間  計測値
0.0 -0.01
0.001 -1.9168853759767707e-06
0.002 0.005001058578491211
0.003 0.005999317169189453
0.004 -0.00539947509765625
0.005 0.0056257152557373045
0.006 0.005166044235229492
0.007 0.007832279205322265
0.008 0.006112565994262695
0.009 0.004662504196166992
```

# 検証３

0.00 ~ 0.09
呼び出し時間 0.01と仮定

```
0.0 -0.01
0.01 0.010998954772949219
0.02 0.024704208374023436
0.03 0.021732797622680662
0.04 0.04448675155639648
0.05 0.0432989501953125
0.06 0.060935964584350584
0.07 0.0719084644317627
0.08 0.0872886085510254
0.09 0.09419654846191407
```

# 考察

やっぱり0.001秒は当てにならない、ミリ秒確かに最小スリープといえるがうんちです。
ExcelVBAベーシック合格を振り返って

#はじめに
この記事では、私がExcelVBAベーシック合格するまでに実施した勉強法を振り返ります。
主観的な話になるかとは思いますが、これからExcelVBAベーシック取得に向けて勉強する人に少しでも助けになればと思います。
ExcelVBAスタンダードも合格次第記事にしようかと思います。
###プロフィール
事前情報として私のプロフィールを載せます。
・社会人歴3年目。VBA,SQLserver,SAPを利用し開発を行っている。
・学生時代は情報科に所属し、C,Java等のプログラミングやIT知識を学んでいた。

VBAは現場で使用している言語だったため、現場配属されてから学びました。
#勉強時間及び内容
期間：2021/10/14～2022/02/06
時間：週に1回、1時間半(計20時間程)
内容：公式テキスト,問題集(VBAエキスパート試験対策)
#勉強方法
私が行っていた勉強方法を段階ごとに分けて説明します。
###①テキスト読込
プログラミングについては現場で学べていましたが、用語知識が足りないと感じていたため、以下の本を購入し勉強しました。
[VBAエキスパート公式テキスト Excel VBAベーシック](https://www.amazon.co.jp/VBA%E3%82%A8%E3%82%AD%E3%82%B9%E3%83%91%E3%83%BC%E3%83%88%E5%85%AC%E5%BC%8F%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88-Excel-VBA%E3%83%99%E3%83%BC%E3%82%B7%E3%83%83%E3%82%AF-%E6%A8%A1%E6%93%AC%E5%95%8F%E9%A1%8C%E4%BB%98%E3%81%8D-%E7%94%B0%E4%B8%AD/dp/4908327114)

プログラミングの仕方等はだいたいわかっているので、主に用語を中心に勉強しました。読み進める途中で知らないことがあった場合は、実際にExcelを起動して探したり、コードを書いて処理の流れをウォッチしてみたりしていました。

__振り返り__
VBAの実務経験の有無を問わずやるべきだと感じました。普段の業務で使用しない用語やコード等あるため、一通りは読込をすることをおすすめします。
###②問題集にて試験対策
[VBAエキスパート試験対策](https://www.exam-vba.com/)というサービスを利用しました。約200問の問題が用意されており、問題が分野別に分かれていたり、解答履歴から間違えた数が多い問題をピックアップしたりできます。この問題集を2巡程解きました。

__振り返り__
実際の試験で全く同じ問題が出ることもあり必ずやるべきです。ただ、選択形式の問題を取り扱っているため、記述問題には対応できないかもしれません。
VBA未経験の方は特にテキスト読込の段階で、実際にExcelからVBE(Alt+F11)を起動して、サンプルコードの処理の流れをイミディエイトウィンドウやウォッチウィンドウで確認しつつ、コードの書き方を覚えることが良いかと思います。

#試験
__試験概要__
ExcelVBAベーシックHPより抜粋しました。
詳細は[VBAベーシック試験概要](https://vbae.odyssey-com.co.jp/about/excel_basic.html)よりご覧ください。

問題数：40問前後
試験時間：50分
合格基準：650～800点(1000点満点中) 
　⇒問題の難易度によって上下します。私の受験時は700点が合格ラインでした。

この概要から私が伝えたいことは下記の通りです。

__試験時間__
上の内容だと1分15秒で1問解く必要がありますが、問題によっては一瞬で解くことができるものがあります。前述のとおり、VBAエキスパート試験対策をすれば類似問題は多数出てくるので実際はもっと余裕のある解答時間になるはずです。
フラグ機能があるので、悩んだ問題はフラグを立てて、後で見返すようにすると尚のこと良いかと思います。
#ポイント
この試験を受験する上で未経験の方ほど難しい問題は、__処理の流れを見て判断する問題__だと感じました。
特に、コードをみてエラーとなる個所を選択する問題は未経験の方は判断しづらいかと思いますので、__実際にコードを書いて処理を実行することが大事__になるかと思います。
#おわりに
ざっくりでしたが、自分の勉強法を振り返って記事にまとめました。今回私はまずは資格を取得することを目標にしていたので、テキスト読込<問題集を意識して勉強しました。未経験の方はテキスト読込を重点的に行い、サンプルコードなどをVBEに記載して処理を実行し、流れをみるほうが理解が深まるかと思います。

これから受験する人は頑張ってください！
Linux上にPython+Selenium+Chromeの動作環境を構築する

# はじめに
SeleniumとはWebブラウザの操作を自動化できるフレームワークのことです。
本記事ではSeleniumを使う環境の一つとして、Linux上にPython+Selenium+Chromeの環境を構築する手順を紹介します。
# 構成
今回使うLinux環境は、以下のようなインスタンスをAWSにて作成しました。

|項目|設定値|
|---|---|
|プラットフォーム|Amazon Linux|
|インスタンスタイプ|t2.micro|

# 環境構築
以降のコマンドはデフォルトのユーザ(ec2-user)を使った場合のものとなってます。
### Python3のインストール
まず、yumのアップデートを実行します。

```
$ sudo yum update
```
yumのアップデートが完了後、Python3のインストールを行います。
Python3をインストール時に合わせてpip3もインストールされます。

```
$ sudo yum install python3
```
なお、Amazon Linuxではデフォルトで`python`コマンドにはPython2の方が適用されています。(`python -V`で確認可能)
そのため、Python3で実行したい場合は`python3`コマンドを使う必要があります。

### Seleniumのインストール
`pip3`コマンドでSeleniumをインストールします。

```
$ pip3 install selenium
```

### Chromeのインストール
インストールのためにChromeのリポジトリを設定します。
viを用いてファイル[google-chrome.repo]を作成します。

```
$ sudo vi /etc/yum.repos.d/google-chrome.repo
```

```google-chrome.repo
[google-chrome]
name=google-chrome
baseurl=http://dl.google.com/linux/chrome/rpm/stable/$basearch
enabled=0
gpgcheck=1
gpgkey=https://dl-ssl.google.com/linux/linux_signing_key.pub
```

設定したリポジトリを使ってChromeをyumでインストールします。

```
$ sudo yum install --enablerepo=google-chrome google-chrome-stable
```

インストールしたChromeのバージョンを確認します。

```
$ google-chrome --version
```

### chromedriverのインストール
SeleniumをChromeで使用するために必要なchromedriverをwgetでインストールします。
なお、chromedriverは先ほど確認したChromeとバージョンを合わせる必要があります。
今回Chromeのバージョンが98だったため、バージョンが98のchromedriverをインストールする場合のコマンドを載せています。
他バージョンのchromedriverが必要な場合は[ダウンロードサイト](https://chromedriver.chromium.org/downloads)を参考に`wget`コマンドの後ろを書き換えて下さい。

```
$ wget https://chromedriver.storage.googleapis.com/98.0.4758.102/chromedriver_linux64.zip
```

インストールしたchromedriverのzipファイルを解凍します。

```
$ unzip chromedriver_linux64.zip
```

chromedriverをパスが通っている場所に移動させます。

```
$ sudo mv chromedriver /usr/local/bin/
```

chromedriverの権限を変更します。

```
$ chmod 755 /usr/local/bin/chromedriver
```

不要になったchromedriverのzipファイルは削除します。

```
$ rm chromedriver_linux64.zip
```

以上で環境構築完了です。

# サンプルコード
簡単なものですが、構築した環境で実行できるサンプルコードを載せておきます。
動作はgoogleのトップページから「chrome」と検索する、という内容となってます。

```python:sample.py
from selenium import webdriver
from time import sleep

driver = webdriver.Chrome()

driver.get("https://google.com")
sleep(3)
driver.find_element_by_name("q").send_keys("chrome")
sleep(3)
driver.find_element_by_name("q").submit()
sleep(3)
driver.quit()
```

# 最後に
Seleniumを用いた開発に伴い環境構築を行ったため、メモ程度に本記事にまとめてみました。
少しでも何かしらの参考になれば幸いです。

# 参考
https://blog.denet.co.jp/centos7-python-selenium/