久しぶりの加法定理
5月の連休中に長男(高2)の数学で、久しぶりに加法定理と再会した。
これを用いると、↓のようなお絵かきマクロに必要な値を求めることができる。
点Pを中心に点Q1を 回転させたときの点Q2の座標。
まず、基本に立ち返ってみる。下図においては、定義により次が成立する。
両辺にを掛けて、
では、この場合はどうだろう。
Q1がx軸からα回転したものであるとき、Q2はx軸からα+β回転するわけだから、
先程の結果から考えると以下が成立する。
ここで加法定理を用いると、次のように変形できる。
角度αを求めなくとも、
であるから、加法定理の中身からαに関する部分が消えて、そこそこシンプルに
なってくれるわけで。
ということで、例えば ↓ こんな具体例で、Q2の座標を求めてみよう。
結果がこちら。多分、合ってると思う。
参考まで。
組合せ表を作る 2.重複を認めない
先日は、部品1~部品4が各々AからCの何れかの値を取り得るとき、
その全ての組合せ表を作ることに挑戦した。
infoment.hatenablog.com
今回は、A~CではなくA~Dの何れかの値を取るものとして、
それらが重複しない場合のみの組合せ表を作成してみよう。
重複しないのだから、例えば
・AAAA
・AABC
のように、同じ文字が二つ以上含まれるものは除外される。
・ABCD
・BCAD
のように、全ての文字が含まれる場合のみ残るわけだ。
重複を許さないということで、お馴染みの辞書(連想配列)を用いて
マクロで作成するなら、例えばこんな感じだろうか。
Sub Sample() Dim i As Long Dim j As Long Dim k As Long Dim l As Long Dim arr(0 To 4 ^ 4, 1 To 5) Dim counter As Long Dim Dict As Scripting.Dictionary Set Dict = New Dictionary ' ラベル行データ作成。 arr(0, 1) = "No." arr(0, 2) = "部品1" arr(0, 3) = "部品2" arr(0, 4) = "部品3" arr(0, 5) = "部品4" ' ボディ部のデータ作成。 ' "A"が、Chr関数でChr(65)であることを利用している。 For i = 1 To 4 For j = 1 To 4 For k = 1 To 4 For l = 1 To 4 ' i~lを辞書に登録し、重複がない場合のみ登録する。 Dict(i) = i Dict(j) = j Dict(k) = k Dict(l) = l If UBound(Dict.Keys) = 3 Then counter = counter + 1 arr(counter, 1) = counter arr(counter, 2) = Chr(i + 64) arr(counter, 3) = Chr(j + 64) arr(counter, 4) = Chr(k + 64) arr(counter, 5) = Chr(l + 64) End If Dict.RemoveAll Next l, k, j, i ' 値貼り付け。 Range("A1").Resize(counter + 1, 5) = arr End Sub
今回は重複チェックで辞書を使っているので、前回よりさらに手間が増えている。
※きっと、もっとスマートな方法があるに違いない。
そこで前回同様、関数で対応することを考えてみた。
※しつこいようだが恐らく、これも既に広く知られている手法と思う。
まず前回同様、関数で組合せ表を作成する。
ここにもう一列追加して、部品1~部品4までの文字種類数を数える。
なお、SUMPRODUCT関数とCOUNTIF関数を組み合わせたこの方法は、
様々なサイトで紹介されているので、そちらを参照してほしい。
あとは、文字種類数が4のものでフィルタをかければよい。
前回に増して万人受けしないものが出来てしまったが、やはり個人的に
嫌いではない。もし採用され場合は、時と場合と各位のお好みで。
参考まで。
組合せ表を作る 1.すべての組合せを考える
詳細は割愛するが、仕事で3つの装置の部品を組み合わせて何某か評価することとなった。評価するための組合せ表が必要となったので、作ってみた。
エッセンスのみ、一部だけ表すと、こんな感じだ。
これをマクロで作成するなら、例えばこんな感じだろうか。
Sub Sample() Dim i As Long Dim j As Long Dim k As Long Dim l As Long Dim arr(0 To 3 ^ 4, 1 To 5) Dim counter As Long ' ラベル行データ作成。 arr(0, 1) = "No." arr(0, 2) = "部品1" arr(0, 3) = "部品2" arr(0, 4) = "部品3" arr(0, 5) = "部品4" ' ボディ部のデータ作成。 ' "A"が、Chr関数でChr(65)であることを利用している。 For i = 1 To 3 For j = 1 To 3 For k = 1 To 3 For l = 1 To 3 counter = counter + 1 arr(counter, 1) = counter arr(counter, 2) = Chr(i + 64) arr(counter, 3) = Chr(j + 64) arr(counter, 4) = Chr(k + 64) arr(counter, 5) = Chr(l + 64) Next l, k, j, i ' 値貼り付け。 Range("A1").Resize(3 ^ 4 + 1, 5) = arr End Sub
今回の例でいえば、部品数が4つなので、4重ループとなっている。
しかしこの数がもっと増えた場合は、マクロを組むのも面倒くさい。
そこで、関数で対応することを考えてみた。
※言うまでもないことだが恐らく、既に広く知られている手法と思う。
まず表に一列追加して、通し番号を三進数に変換する(BASE関数を利用)。
部品数が4つあるので、4桁にしたい。そこでさらに、数字に変えて4桁表示にしよう。
次に、部品のセルに、三進数の各桁の数字を表示してみる。2列目が1文字目、
3列目が2文字目・・・という規則性があるので、A~Cを表示するセルの式は
全て共通で対応できる。
こうすると、0~2が順番に登場する組合せ表が出来上がるわけだ。数字は
- 0 ⇒ A
- 1 ⇒ B
- 2 ⇒ C
に対応している。Char関数で変換しよう。
結果として無事、組合せ表を作成することができた。
登場した数式は、作業用に三進数で表した列と、A~Cを表示する列の2つのみ。式の数という意味では、かなりシンプルになったと思う。
ただし万人受けするかといえば、疑問符が付く(個人的には結構好きかも)。
ということで、もし採用される場合は、時と場合と各位のお好みで。
参考まで。
1から30までを12列で並べる
前回は、名簿を段組みにする100本ノックに挑戦した。
infoment.hatenablog.com
今回は、段組みについてのお話。
例えば、1から30までの数字を、12列で折り返して並べたいとする。
こんな感じだ。
このとき、30番目の数が何行何列目に来るか知るには、どのような方法が
あるだろうか。やってみた。
1. IF関数を駆使する。
- 行・・・数が1なら1行目。列数が1なら、前の列数に1を足す。
それ以外は、一つ前と同じ。 - 列・・・数が1なら1列目。前の列数が12なら、1に戻る。
それ以外は、一つ前に1を足す。
実際、式をセットするとこうなる。
テーブルに式をセットしたので、だいぶん複雑になってしまった。理屈としては
一番簡単だが、式の可読性という点では今一つか。
2. 余りを利用する。
12で割ったときの余りで、列数を求める。ただし、12列目は余りが0になって
しまい都合が悪いので、1引いた値を12で割った余りをもとめ、それに1を
足し直すことで列数を求める。
行数は、12で割った商を小数点第一位で切り上げることで求める。
実際、式をセットするとこうなる。
式としてはかなりシンプルになった。ただし、
「1引いた値を12で割って、余りに1を足す」
の部分が、ちょっと難しいかもしれない。
3. 12進数にする。
1から12が繰り返し現れるわけだから、12進数の一桁目が列数で、
二桁目以上が行数になる。11の一桁目がAに、12の一桁目がBに
なるので、結構ややこしい。なお、各変換には以下の関数を用いる。
- 10進数 ⇒ 12進数 BASE関数
- 12進数 ⇒ 10進数 DECIMAL関数
実際、式をセットするとこうなる。
今回の中でもっとも複雑な式となっていて、しかも一番難解なものに
なってしまった。
でも、嫌いじゃないかも。実際の場面でどの方式を採用するかは、
時と場合と、各位のお好みで。
参考まで。
VBA100本ノック 30本目:名札作成(段組み)
こちらで公開されている、100本ノックに挑戦。
www.excel-ubara.com
素晴らしい教材を公開いただき、ありがとうございます。
上記リンク先から、問題文を転載。
名札が二列になっているので、改行することに2進法を使えないか?と
ちょっと考えてみた。今回は、考えてみただけで実装はしていない。
Sub VBA_100Knock_030() Dim Ws(ShName.en名簿 To ShName.en名札) As Worksheet Set Ws(ShName.en名簿) = Worksheets("名簿") Set Ws(ShName.en名札) = Worksheets("名札") ' 名簿データを一旦配列に格納し、名札用に並び替える。 Dim SrcArray As Variant SrcArray = Range(Ws(ShName.en名簿).Range("B2"), _ Ws(ShName.en名簿).Range("B2").CurrentRegion. _ SpecialCells(xlCellTypeLastCell)) Dim DstArray() As Variant ReDim DstArray(1 To 2 * WorksheetFunction.RoundUp(UBound(SrcArray) / 2, 0), _ 1 To 2) Dim i As Long Dim RowIndex As Long Dim ColumnIndex As Long For i = 1 To UBound(SrcArray) RowIndex = 2 * WorksheetFunction.RoundUp(i / 2, 0) - 1 ' データが偶数行か奇数行かで、一列目と二列目を判別する。 If WorksheetFunction.IsOdd(i) Then ColumnIndex = 1 Else ColumnIndex = 2 End If DstArray(RowIndex, ColumnIndex) = SrcArray(i, 1) DstArray(RowIndex + 1, ColumnIndex) = SrcArray(i, 2) Next Application.ScreenUpdating = False Ws(ShName.en名札).Range("A1").Resize(UBound(DstArray), 2) = DstArray ' 名札の書式コピー&ペースト。 Ws(ShName.en名札).Rows("1:2").Copy Ws(ShName.en名札).Rows("1:" & UBound(DstArray)).PasteSpecial Paste:=xlPasteFormats Ws(ShName.en名札).Range("A1").Select Application.CutCopyMode = False Application.ScreenUpdating = True End Sub
今回も毎度のように、汎用性を持たせようとして、冗長な作りになって
しまった。実行した結果がこちら。
※冒頭リンク先の解答例および解説も、ぜひご一読ください。
参考まで。
VBA100本ノック 29本目:画像の挿入
こちらで公開されている、100本ノックに挑戦。
www.excel-ubara.com
素晴らしい教材を公開いただき、ありがとうございます。
上記リンク先から、問題文を転載。
選択する方法は、先日も使用した「Application.FileDialog」を採用。
「選択できる拡張子は適当に」とのことだったので、今回は「*.jpg」とした。
Sub VBA_100Knock_029() ' 画像を挿入するセル。 Dim TargetCell As Range Set TargetCell = ActiveCell ' 挿入する画像のファイルパス。 Dim FilePath As String With Application.FileDialog(msoFileDialogFilePicker) ' ファイル選択ダイアログ表示。 .Show ' ファイルの複数選択不可。 .AllowMultiSelect = False ' ダイアログタイトル。 .Title = "画像データ選択" ' 選択フィルタ。 .Filters.Clear .Filters.Add "画像データ", "*.jpg" FilePath = .SelectedItems(1) End With ' 挿入する画像。 Dim Drawing As Picture Set Drawing = ActiveSheet.Pictures.Insert(FilePath) ' 画像の縦横比を固定。 Drawing.ShapeRange.LockAspectRatio = msoTrue ' 画像とセル、各々の縦横比を取得。 ' どちらが「より縦長か?」を比較して、挿入する画像サイズをセルの ' 幅に合わせるか高さに合わせるか決定する。 Dim AspectRatio(1) As Double AspectRatio(0) = TargetCell.Height / TargetCell.Width AspectRatio(1) = Drawing.Height / Drawing.Width ' セルの方が縦長なら、画像はセルの横幅に合わせる。 If AspectRatio(0) >= AspectRatio(1) Then Drawing.ShapeRange.ScaleWidth TargetCell.Width / Drawing.Width, msoTrue ' 高さ方向の位置修正(セルの中央に配置)。 ' 幅方向は、セルと同サイズのため調整不要。 Drawing.Top = TargetCell.Top + (TargetCell.Height - Drawing.Height) / 2 Else Drawing.ShapeRange.ScaleHeight TargetCell.Height / Drawing.Height, msoTrue ' 幅方向の位置修正(セルの中央に配置)。 ' 鷹さ方向は、セルと同サイズのため調整不要。 Drawing.Left = TargetCell.Left + (TargetCell.Width - Drawing.Width) / 2 End If End Sub
何を基準に、どのように拡大または縮小すればよいか、少し悩んでしまった。
実行した結果がこちら。
ちなみにこの画像は、数年前に家族で訪れた「眼目山立山寺」で撮った写真。
樹齢400年の栂並木が素晴らしく、機会があればもう一度訪れてみたい。
youtu.be
※冒頭リンク先の解答例および解説も、ぜひご一読ください。
参考まで。
VBA100本ノック 28本目:シートをブックに分割
こちらで公開されている、100本ノックに挑戦。
www.excel-ubara.com
素晴らしい教材を公開いただき、ありがとうございます。
上記リンク先から、問題文を転載。
実際の業務でも、似たようなケースがあると思う。フォルダの存在確認は、
個人的にはFileSystemObjectのFolderExistsが直感的に理解しやすいので多用
している。読んで字のごとく、そのまんまだし。
ということで、作成したのがこちら。
Sub VBA_100Knock_028() ' Microsoft Scripting Runtime参照済み。 Dim FSO As Scripting.FileSystemObject Set FSO = New Scripting.FileSystemObject ' ワークシートのループ用変数。 Dim Ws As Worksheet ' 部署名。 Dim PartName As String Dim FileName As String Dim FolderName As String Dim FolderPath As String Dim FilePath As String Dim Wb As Workbook Application.ScreenUpdating = False For Each Ws In Worksheets ' 「部署名_人の名前」のシート名でないと成立しない。 PartName = Split(Ws.Name, "_")(0) FolderPath = ThisWorkbook.Path & "\" & PartName If Not FSO.FolderExists(FolderPath) Then MkDir FolderPath End If On Error Resume Next FileName = Split(Ws.Name, "_")(1) If Err.Number = 0 Then FilePath = FolderPath & "\" & FileName Ws.Copy Set Wb = ActiveWorkbook Wb.SaveAs FilePath, xlOpenXMLWorkbook Wb.Close False End If On Error GoTo 0 Next Application.ScreenUpdating = True End Sub
ファイル名やパスをいちいち変数に入れており、これを冗長と感じる
人も居るかもしれない。個人的に、誰かに引き継ぐことを前提に作る
場合は、多少くどいと思われても、上記のような作り方にすることが
多いかも。
実行した結果がこちら。
※冒頭リンク先の解答例および解説も、ぜひご一読ください。
参考まで。