派遣事務員の迷走

派遣事務員コロ子。会社の犬。顔出しNG。常に迷走している。

コロ子勘違いしていた インスタンスの生成

こんにちは。
派犬事務員のコロ子です。

クラスのインスタンスの生成でちょっと気になる事がある。
ループをしながらDictionaryオブジェクト(Collection オブジェクトでも)のアイテムにクラス型のオブジェクトを追加する場合、ループの中でインスタンスを生成しているコードをよく見かける。

【例】
クラスモジュール:Class1

Public data As Long


'標準モジュール

Sub Test1()

Dim dic As Dictionary
Set dic = New Dictionary

Dim i As Long

For i = 1 To 3

    Dim dicitem As Class1
    Set dicitem = New Class1
    
    Dim dickey As String
    dickey = "キー" & i
    
    dicitem.data = i * 2
    
    dic.Add dickey, dicitem

Next i


Dim j As Variant
For Each j In dic.Keys

    Debug.Print dic.Item(j).data

Next j

End Sub


イミディエイトウインドウ

2
4
6

結果は正しいけど、なんか納得できない。
なんでループの中でインスタンスを生成しているのだろう?

次のコードTest2、Test3を見ると

Sub Test2()

Dim dic As Dictionary
Set dic = New Dictionary

Dim dicitem As Long

Dim i As Long

For i = 1 To 3

    Dim dickey As String
    dickey = "キー" & i

    dicitem = i * 2
    
    dic.Add dickey, dicitem

Next i


Dim j As Variant
For Each j In dic.Keys

    Debug.Print dic.Item(j)

Next j

End Sub

イミディエイトウインドウ

2
4
6

test2で、DictionaryオブジェクトのアイテムにLong型の変数を入れると値渡ししているように思える。

Sub Test3()

Dim dic As Dictionary
Set dic = New Dictionary

'先にインスタンスを生成する
Dim dicitem As Class1
Set dicitem = New Class1

Dim i As Long

For i = 1 To 3

    Dim dickey As String
    dickey = "キー" & i
    
    dicitem.data = i * 2
     
    dic.Add dickey, dicitem

Next i


Dim j As Variant
For Each j In dic.Keys

    Debug.Print dic.Item(j).data

Next j

End Sub

イミディエイトウインドウ

6
6
6


でもtest3では参照渡ししているように思える。
ということはtest1では同じ変数名で何度もインスタンスを生成しているということなの??
混乱。

仕方ないのでノンプロ研で質問してみたところ、コロ子、インスタンスの生成について分かっていなかったことが判明。

【分かった事】
①オブジェクト型の変数には参照先が格納される。
なのでTest3ではDictionaryオブジェクトのキー1~3のアイテムには同じインスタンスが格納されている。

New クラス名でいくつでもインスタンスを生成できる。
Dim 変数名 As クラス名
Set 変数名 = New クラス名

はセットで1変数1インスタンスだと思っていた。
New クラス名インスタンスを生成できる」は耳にタコができるほど聞いていたのに・・・。
Dim 変数名 As クラス名 でクラス型の変数を宣言
Set 変数名 = New クラス名 で生成したインスタンスを変数に格納するという意味だった!

Dim A As Class1
Set A = New Class1
Set A = New Class1

と書いたら、2行目のAと3行目のAには別のインスタンスが格納されている。

変数宣言はループ中に書いたら1度しか処理されないのと同じで、
Set 変数名 = New クラス名も1度しか処理されないのかと思っていた!

ちなみに次のように書くとクラス型変数の宣言が不要。

'--省略--
Dim dicKey As String
For i = 1 To 3
    dicKey = "キー" & i
    dic.Add dicKey, New Class1
    dic.Item(dicKey).data = i * 2
Next i
'--省略--

そーゆーことなのね!!


ということは、前回の記事で書いたコードはクラス型のオブジェクトを配列にしたりして、普通に分かっている人からしたら意味不明なんだろうな・・・。

クラスモジュール、シートモジュールは前回のまま
標準モジュールのみ書き直し。

Public Sub Aggregate()

Dim list As ListObject
Set list = Sheet1.ListObjects(1)

Dim ran As ListRow

Dim dic As Dictionary
Set dic = New Dictionary

Dim i As Long

'データ読み込み
For Each ran In list.ListRows

    Dim dickye As String
    dickye = Sheet1.Key(ran)
    
    If dic.Exists(dickye) Then
        'すでにkeyが存在している時は、
        Call dic.Item(dickye).Add(ran)
        
    Else
    
        'keyが存在しない(初めて)時は、
        'Dicにクラス(RawData)型のオブジェクトを格納
        
        dic.Add dickye, New RawData
        Call dic.Item(dickye).Init(ran)
        
        i = i + 1
        
    End If

Next ran

Call Sheet2.Del


'データ書き出し
i = 2
Dim j As Variant
For Each j In dic.Keys

    Sheet2.Cells(i, cnNo).Value = dic.Item(j).No
    Sheet2.Cells(i, cnLot).Value = dic.Item(j).Lot
    Sheet2.Cells(i, cnData1).Value = dic.Item(j).data1
    Sheet2.Cells(i, cnData2).Value = dic.Item(j).data2
    Sheet2.Cells(i, cnData3).Value = dic.Item(j).Data3
    
    i = i + 1
    
Next j

Call Sheet2.MakeStyle
Sheet2.Activate

End Sub

インスタンスの生成、完全に理解した!(←こーゆーのが一番怪しい)

久々のVBA ノンプロ研中級講座の内容を活かす?

こんにちは。
派犬事務員のコロ子です。

実は去年12月にノンプロ研中級講座が終わってから、全くVBAを書いていなかった。
4月から部署を異動するので、ルーチン業務から外れ毎日雑用の日々。
最近はPython(GASにも)に気を取られてVBAはもうしばらくいいかなー、気分になっていた。

そんなダラダラした気分のところに上司から「マクロ作って」の依頼がきた。
VBAもういいや、な気分だったのに、話を聞いたらノンプロ研中級講座の内容がめちゃめちゃ使えそうだったから、少し気分が乗ってきた。

しかし、3ヶ月VBAをやってなかっただけなのに、関数の外にコード書いたり、変数を宣言するのを忘れたり、もうVBA忘れちゃったのかものひどい有り様だった。

ゆっくり良く考えてかっこいいコードが書きたかったけど、「急ぎ」だったので、とりあえず急いで作った。もっといい方法がないか今日考えたけど、時間があるからといっていいアイデア浮かぶわけでもない・・・。
でもノンプロ研中級講座の内容を盛り込めたと思う。

【内容】
下記のようなデータを番号、Lotが同じもので集計する。
データ1、データ2、データ3は各々最大値と最小値を除いて平均する。
f:id:SNegishi:20200320175627p:plain

表をテーブルにする。

表の中にカーソルを置いて「Ctrl+T」を押すとテーブルになる!
f:id:SNegishi:20200320181016p:plain

色が付いてオシャレになっただけじゃない。
テーブルにすると表をオブジェクトとして扱えるから便利。

考え方

①表の「番号」と「Lot」を繋げた文字列をキーにする。
【例】
2行目の場合は「BBBabc」がキー

②表の1行分のデータを格納するクラスを用意する。

③表を1行づつ読み込んで、Dictionaryオブジェクトに格納する
Key:①で作成したキー
Item:②のクラスを配列にして、配列のインデックス番号

Dictionaryオブジェクトに格納する際、Keyがすでに存在していたら、そのインデックス番号のクラスのデータを集計する。

コード

クラスモジュール RawDataクラス

Private no_ As String
Private lot_ As String
Private data1_ As Long
Private data2_ As Long
Private data3_ As Long
Private max1_ As Long
Private min1_ As Long
Private max2_ As Long
Private min2_ As Long
Private max3_ As Long
Private min3_ As Long
Private cnt_ As Long

'*初回の値をセット
Public Sub Init(ByVal list As ListRow)

    no_ = list.Range(cnNo)
    lot_ = list.Range(cnLot)
    
    data1_ = list.Range(cnData1)
    max1_ = data1_
    min1_ = data1_
    
    data2_ = list.Range(cnData2)
    max2_ = data2_
    min2_ = data2_
    
    data3_ = list.Range(cnData3)
    max3_ = data3_
    min3_ = data3_
    
    cnt_ = 1

End Sub


'*加算
Public Sub Add(ByVal list As ListRow)

    data1_ = data1_ + list.Range(cnData1)
    Call MaxMin(max1_, min1_, list.Range(cnData1))
    
    data2_ = data2_ + list.Range(cnData2)
    Call MaxMin(max2_, min2_, list.Range(cnData2))
    
    data3_ = data3_ + list.Range(cnData3)
    Call MaxMin(max3_, min3_, list.Range(cnData3))
    
    cnt_ = cnt_ + 1

End Sub

'最大値と最小値を探す
'(max, minはByRefにして変数の中身を置き換える)
Public Sub MaxMin(ByRef max As Long, ByRef min As Long, ByVal Data As Long)

    If Data > max Then
        max = Data
    ElseIf Data < min Then
        min = Data
    End If

End Sub


'番号
Public Property Get No() As String

    No = no_

End Property

'ロット
Public Property Get Lot() As String

    Lot = lot_

End Property

'データ1
Public Property Get Data1() As Long

    Data1 = WorksheetFunction.Round((data1_ - max1_ - min1_) / (cnt_ - 2), 0)

End Property

'データ2
Public Property Get Data2() As Long

    Data2 = WorksheetFunction.Round((data2_ - max2_ - min2_) / (cnt_ - 2), 0)

End Property

'データ3
Public Property Get Data3() As Long

    Data3 = WorksheetFunction.Round((data3_ - max3_ - min3_) / (cnt_ - 2), 0)

End Property

Sheet1モジュール

Public Property Get Key(list As ListRow) As String
    
    Key = list.Range(cnNo) & list.Range(cnLot)

End Property

Sheet2モジュール

Public Sub Del()

    Me.UsedRange.Offset(1, 0).Delete

End Sub

Public Sub MakeStyle()

'罫線
Me.Range("A2").CurrentRegion.Borders.LineStyle = xlContinuous
'ソート
Me.Range("A2").CurrentRegion.Sort Key1:=Range("A1"), _
                                  Order1:=xlAscending, _
                                  Key2:=Range("B1"), _
                                  Order2:=xlAscending, _
                                  Header:=xlYes


End Sub

標準モジュール

参照設定:Microsoft Scripting Runtimeを忘れずに。

Public Enum ColumnName

    cnNo = 1
    cnLot
    cnData1
    cnData2
    cnData3

End Enum

Public Sub Aggregate()

Dim list As ListObject
Set list = Sheet1.ListObjects(1)

Dim ran As ListRow

Dim Dic As Dictionary
Set Dic = New Dictionary

'RawDataクラスは動的配列にする
Dim Data() As RawData
Dim i As Long

'データ読み込み
For Each ran In list.ListRows

        Dim kye As String
        kye = Sheet1.Key(ran)
        
        If Dic.Exists(kye) Then
            'すでにkeyが存在している時は、配列のインデックス番号のデータを加算
            Call Data(Dic.Item(kye)).Add(ran)
            
        Else
        
            'keyが存在しない(初めて)時は、Dicにデータを格納
            ReDim Preserve Data(i)
            Set Data(i) = New RawData
            
            Dic.Add kye, i
            Call Data(i).Init(ran)
            i = i + 1
        End If

Next ran

Call Sheet2.Del

'データ書き出し
For i = 2 To UBound(Data) + 2

    Sheet2.Cells(i, cnNo).Value = Data(i - 2).No
    Sheet2.Cells(i, cnLot).Value = Data(i - 2).Lot
    Sheet2.Cells(i, cnData1).Value = Data(i - 2).Data1
    Sheet2.Cells(i, cnData2).Value = Data(i - 2).Data2
    Sheet2.Cells(i, cnData3).Value = Data(i - 2).Data3

Next i

Call Sheet2.MakeStyle
Sheet2.Activate

End Sub

集計結果

f:id:SNegishi:20200320190343p:plain

こんな感じで集計。

①シートモジュールを使う
②配列
③クラス
④テーブル
などなど、中級講座の内容。
今だから正直に言うけど、テーブルについては知らなかったし、クラスもちょっと自信なかった。TAじゃなくて受講するレベルだった疑惑があるけど、テーブルもクラスも使って作ったから許して~。

職場でプログラミング部を作成

こんにちは。
派犬事務員のコロ子です。

最近Googleの凄さがなんとなく分かってきた。(今更)
無料のGoogleアカウントを作成すれば、Excelみたいのとか、Wordみたいのとかその他いろいろタダで使えるし、ネット上のエリアに15GBまで無料で保存できる。
しかも、他のPCとかスマホとかからもアクセスできて、他の人とも共有できたり、凄く便利じゃない!?(今更)
パッケージ版Microsoft office派の会社で働いていると、なかなかこの感覚が分からない。

また、ノンプロ研でチャットツールのSlackを使っていて、これもすごく便利。会社で使っているところもけっこうある。コロ子が会社の人に、こういうツールあるんだよ、と口頭で説明しても、みんな???ってなるし、「うちの会社、そういうのダメだから」と一蹴されてしまう。

そうはいっても世の中便利なもの沢山ある。知らないのはもったいない。
会社はクラウド不可、アプリのダウンロード不可だけど、プライベートで遊びでやる分には構わないよね?
部活みたいな感じにして、いろいろ便利なITツールを使ったり、プログラミングをしたりしてみんなで一緒に勉強していくのはどうだろうか?

これから会社も変わっていかなければならないだろうから、若い人たちが(若くなくても)こういうのに慣れて、便利だから導入しましょう、みたいな働きかけができればいいし、「自分で仕事に最適な便利ツールを探して、使いこなす力」というもの必要なのではないかと思う。
アプリなどのダウンロードも、ちゃんと申請して情シスの許可が降りればできるらしい。どのような物をどのように使って、どのような効果があるかをちゃんと説明できる力も必要。

そんな訳でプログラミング部を作ってみた。

IT部にしようか迷ったけど
コロ子=VBA
みたいなイメージはできていると思うので、プログラミング部の方が馴染むかな。
それにIT語れるほど詳しくないし・・・。
業務関係なく「ゆる~くプログラミングして遊ぼう」を全面に押し出し、やってみよう。

部員を募集

部員募集の張り紙をしてみたところ、8人集まった。
すごい!優秀な人ばっかり!!
Slackでのオンライン部活。
嬉しいことにSlack経験者ゼロ。それだけでも部活作った甲斐ある。
仕事は関係ないので、自宅のパソコンで、オンラインでの部活動。
自宅のパソコンだとExcel(Office)が入ってない人もいるから、VBAよりGASやPythonがメインかな。

部活を作って早速だけど、コロ子休みを取ってちょっと出かけてくる。
コロ子が帰ってくるまでに課題を出しておいたけど、みんなやってくれるかな。
(今回はパソコンを持って行かないので、ちょっと心配)
提出期限は来週だけど、まだ誰も出してくれてない。
でも、誰もやってくれなくても大丈夫。
ノンプロ研で学んだスーパードライがある。
スーパードライ:周りとの温度差があっても気にしないでクールに乗り切る術。ダメだったらやり方を変えればいいだけ。)

koroko-resort.hatenablog.com

Pythonでゲームを写経

こんにちは。
派犬事務員のコロ子です。

Pythonでゲーム

そろそろ何かそれっぽい物を作ってみたい。
基礎編、難しいところはすっ飛ばしてゲームコードを写経してみた。

洞窟の中をロケットが進むゲーム。
洞窟は色の付いた画面に、黒い長方形をループしながら位置と形を変えて追加していく。新しい長方形を追加したら最初の長方形を消す。
そうすることによって画面が動いているように見える。
全然分かんないけど、早くゲームで遊びたいから、取り急ぎ写経。

よし!できた!

ゲームスタート!

え!!!マジ!絶対ムリ!!


f:id:SNegishi:20200128220931g:plain


コードをよーく見てみると、間違ってる。
偶然できたバグが無理ゲー過ぎてビックリ!

実際にはこんな感じでどんどん洞窟が狭くなる。まっすぐ飛ぶのが結構難しい。

f:id:SNegishi:20200128224014g:plain
写経成功


今回覚えた事

Pythonの配列?(リストとかタプルとか)はインデックス番号を-1とすると、最後の要素が取れる。

test = [1,2,3,4,5]
print(test[1])
print(test[-1])

 ↓

2
5

便利!
なんかいっぱいコード書いたけど、はっきり理解できたのはこれくらい。
バグの原因はループのインデントが間違っていた。
ループを抜けるにはインデントを戻さないといけないけど、本だとインデントの位置が分かりにくくて、まだちょっと慣れない。

【Python】 for文に変数が複数!!

こんにちは。
派犬事務員のコロ子です。

先日、ノンプロ研のVBA中級講座とPython初級講座合同の卒業LT大会に行ってきた。「こんな事できるようになたよ!」と講座で学んだ事を発表する会。
どうやらPython初級講座を受けるとWebサイトのスクレイピングとかできるようになるらしい。
「初級講座なのにそんな事できるようになっちゃうの!?コロ子もやりたーい!!」
とLTを聞きながらテンション上がって、次回講座の仮予約を申し込んでみた。
VBA, GAS, Pythonで最少催行人数に最も早く達したコースのみ開講なので、みんな急いてPythonに申し込みするのだ!
    ↓
docs.google.com


でも一つ注意は、Python初級講座に全くの初心者はいないらしいから講座開設までに少しでも先に進めなければ・・・。
お正月休みから全く進んでない・・・。

今日の驚き

そんな訳で細々とPythonをやっていたら「for文に変数が複数」をいうコードに遭遇。

①for文に変数が2つ

for i, j in [(1, 2), (3, 4), (5, 6)]:
    print(i, j)

えっ!!!こんなのアリ!?
変数2個、どうやってループするんだろう?

実行結果

1 2
3 4
5 6

変数「i」と「j」に要素が一度に代入される。
f:id:SNegishi:20200113001155p:plain

凄い!驚き!VBAしか知らなかったから、最初なかなか受け入れられなかった。

ちなみにfor文に変数が一つ、これはなんとなく馴染みがあるので受け入れ可能。

for i in [(1, 2), (3, 4), (5, 6)]:
    print(i)

(実行結果)

(1, 2)
(3, 4)
(5, 6)

VBAでいうところのFor each文に似ている。
ちょっと違うけどVBAならこんな感じ。

Dim i As Variant
Dim Arr() As Variant

Arr = Array(1, 2, 3)

For Each i In Arr
    Debug.Print i
Next i

(実行結果)

1
2
3

配列から1つづつ要素を取り出してるところが似ている。

VBAは1つづつしか要素を取り出せないけど、Pythonは一度に複数の要素を取り出せるので便利!

ママ犬も迷走

こんにちは。
派犬事務員のコロ子です。

f:id:SNegishi:20200105203502p:plain
西の神社で勾玉をもらい、東の神社で袋をもらう


今日、ママ犬がコロ子の犬小屋に来た。
年末に「年が明けたら、アンタの小屋に行くから」と予告され「えーー!!来なくていいよ、何しにくるの??」と恐る恐る待つところにママ犬が来た。
(昔からママ犬は超怖い)

ママ犬「あんた、仕事してるの?」
コロ子「してます・・・」
ママ犬「何?バイト?パート?」
コロ子「派犬・・・」
ママ犬「それはいくつまで働けるの?」
コロ子「無期雇用契約しなかったから、いくつまでとかは特にない・・・」
ママ犬「そう。私もう年だし、そんなに長く生きられないと思うのよ」
いやー、それはない!自分勝手で気ままな、こういうタイプが一番長生きする。ババ犬もジジ犬も長生きだったし、結構な健康オタクだ。
ジジ犬なんて「オレはもう年だからそろそろ死ぬ」とかいいつつ、それから20年以上生きた。

ママ犬「それで死ぬ前に持っているお金全部使おうと思っているの」
終活ってやつ?お金余ってるの?
イヤイヤ、それはない。パパ犬がパチンコで全部使ってる。
昔からそれでさんざん喧嘩してた。
ママ犬「あんた、この先生きて行くのに、何か勉強して資格とか取った方がいいんじゃない?もし勉強するなら援助しようと思っているの」
えっ!マジで!!この年から親に援助してもらって取る資格って何?
コロ子「えっ!?アメリカに留学してMBAを取るとかそういうヤツ?」
ママ犬「そんなにはないわよ!!!」
ほら!やっぱりないじゃん。そもそもお金の問題以前に無理だし。
コロ子「じゃ、普通に大学とか大学院とか?でももう受験勉強とか嫌だし、卒業しても新卒枠で就職できるとも思えないんだけど」
ママ犬「そんなにもないわよ!」
コロ子「じゃぁ何?いくら?」
別に普通に現金くれてもいいんだけどーーー。
でも、ママ犬にお金貰ったらいろいろ面倒臭そう。
子供の頃はお小遣いの内訳を全部言わされていたし。
何億とかなら話は別だけど、それほどの額でもないのにこの先も絶対的な従属関係を取られるのはウンザリだ。
ママ犬「とにかく、何か勉強するなら考えてもいいと思っただけよ。じゃぁ。」
と1円も置かずに帰って行った。

ノンプロ研で勉強しているので大丈夫です。

Python始めました

明けましておめでとうございます。
派犬事務員のコロ子です。
本年もよろしくお願いいたします。

f:id:SNegishi:20200101174633p:plain
朝から飲んでます

今年の目標

去年のノンプロ研BT大会で「来年は他の言語に挑戦する」とか言ってしまったので、取り急ぎPythonに挑戦。
どこにも行かなくてヒマだからPythonじゃなくて、Pythonの為にどこにも行かないんだから!(強がってみる)

以前もちょこっとPythonに手を出して途中で挫折したので、今回は「ゲームを作りながら楽しく学べるPythonプログラミング」というので再挑戦。ゲームの方が楽しそう、という安易な考えでチョイス。

今日もハマった

pygameモジュールをインポートしてRectクラスとやらを使うところで今日はハマった。

import pygame

r= Rect(10,20,30,40)

エラーになるーーー。
この書き方ではダメらしい。

import pygame

r= pygame.Rect(10,20,30,40)

とするか、

from pygame.locals import Rect

r= Rect(10,20,30,40)

にすればOKだった。

import モジュール名
import pygameとするとpygameに定義されている全てのクラスや関数が使えるらしい。余計なものまでインポートされても、全部あるから用は足りる。でも使用する場合は必ず
pygame.Rect(10,20,30,40)のように、頭にモジュール名を付けなければいけない。

from モジュール名 import 関数/クラス/定数
from pygame.locals import Rect
だと、Rectクラスだけインポートされて
Rect(10,20,30,40)と書けばOK。

お正月早々、一日これにハマった。