【Golang】QuickSort、訂正

wrongwrong163377.hatenablog.com
wrongwrong163377.hatenablog.com
以前の2記事のQuickSortに非効率な間違いがあったので訂正します。

間違っていたところ

再帰の門番ではLengthが2以下、つまり3未満で判定する必要がありましたが、2未満で判定してました。釣られて長さ1以下の判定を長さ1未満でやってました。
以下がついでに気に入らないところも直した修正版の門番です。

//門番
if len(arr) < 3{
	if !(len(arr) < 2 || arr[0] < arr[1]) {
		arr[0], arr[1] = arr[1], arr[0]
	}
	return arr
}

計測結果について

ロジックの間違いのせいで無駄にネストが深くなっていたため、今回の門番の方が効率は良いはずです。
という訳で図り直したりなんだりしていましたが、どうもCPUの調子が悪いのか結果が安定せず……。
並列化で実行時間は大体80%位になるという結論は変わらなかったので記事はそのままにします。

【Golang】簡単なQuickSortを並列化

wrongwrong163377.hatenablog.com
この記事のQuickSortのコードの門番には間違いがあるので訂正しました。

Golangの並列処理

以下の記事を参考にしました。
qiita.com
ざっくり読んだ感じだとgo func(){}()使えってことですかね。なんか.Netのtaskっぽく感じましたが勉強不足でお尻についてる()の意味とか分かってないです。
taskと比較すると、ラムダ式っぽい書き方しなくていいのが嬉しいかなと思いました。ラムダ式はパっと見書き方がぜんぜん違ったり、インデントがおかしくなりがちだったり、個人的には嫌悪感が先に来るんですよね。

ソースコード

ソースコードは以下の通りです。前回のコードから再帰呼出しをgoランタイムの中に入れて並列処理するように変更しました。

func qsort_go(arr []int) []int{
	//門番
	if len(arr) < 2{
		if len(arr) <= 1 || arr[0] <= arr[1]{
			return arr
		}else {
			return []int {arr[1], arr[0]}
		}
	}

	var piv int = arr[0]
	var small []int
	var big []int

	for i:=1;i<len(arr);i++{
		if piv > arr[i] {
			small = append(small, arr[i])
		}else {
			big = append(big, arr[i])
		}
	}

	finished := make(chan bool)
	go func(){
		small = qsort(small)
		finished <- true
	}()
	go func(){
		big = qsort(big)
		finished <- true
	}()

	// 終わるまで待つ
	<-finished
	<-finished

	return append(append(small, piv), big...)
}

比較

長さ50,000で値の範囲[0, 99]の乱数列に対して前回のqsortと今回のqsort_goの平均実行時間を簡単に比較してみました。
オプションやらは何も設定していないゆるゆる測定です。。。というか計測中CPU使用率が他プロセス含めても70%にすら達しなかったけどこれでいいんだろうか?
実行時間計測部は以下の通りです。

for i := 0; i < 100; i++{
	arr = RandomArr()

	start1 := time.Now()
	arrtemp = qsort(arr)
	end1 := time.Now()
	time1 += end1.Sub(start1).Seconds()

	start2 := time.Now()
	arrtemp = qsort_go(arr)
	end2 := time.Now()
	time2 += end2.Sub(start2).Seconds()
}

fmt.Println(time1 / 100)
fmt.Println(time2 / 100)
比較結果

平均すると並列化した方の実行時間は並列化前に比べ80%強となりました。

0.21223236900000003
0.17805255800000006

【Golang】簡単なQuickSortを書いてみた

wrongwrong163377.hatenablog.com
この記事のQuickSortのコードの門番には間違いがあるので訂正しました。

背景

何かを作るようなやる気は起きないけどプログラムは組んでたい気分だったので、前々から触ってみたかったGolangでQuickSortを書いてみました。

何でQuickSort?

知っているアルゴリズムの中では見栄えが良く、再帰で書けてコード量が少なくて済むかなーと思ったからです。

ソースコード

Quickソート本体は以下の通りです。
ピボットより小さいかそうでないかで分けて再帰、配列の長さが一定以下になれば並べなおしてリターンする感じです。

func qsort(arr []int) []int{
	//門番
	if len(arr) < 2{
		if len(arr) <= 1 || arr[0] <= arr[1]{
			return arr
		}else {
			return []int {arr[1], arr[0]}
		}
	}

	var piv int = arr[0]
	var small []int
	var big []int

	for i:=1;i<len(arr);i++{
		if piv > arr[i] {
			small = append(small, arr[i])
		}else {
			big = append(big, arr[i])
		}
	}
	return append(append(qsort(small), piv), qsort(big)...)
}
実行結果

0から100までの32個の乱数に対して実行した結果が以下です。

[28 73 50 20 89 55 83 0 64 47 61 34 23 31 42 7 99 80 80 4 2 87 99 59 33 4 86 47 70 88 0 16]
[0 0 2 4 4 7 16 20 23 28 31 33 34 42 47 47 50 55 59 61 64 70 73 80 80 83 86 87 88 89 99 99]

感想

最近VBを触りまくっているせいでセミコロンが自然に出てこない感じになってます。そんな状態でGolangを始めましたが、セミコロンが無いのはやりやすかったですね。
途中「え?」ってなるような部分も結構有りましたが、まあそれはそれとして……。
後で並列処理周りにも手を出してみたいと思います。

【VB】Taskを用いて非同期ファイル操作の続き【.Net】

2019/10/6追記

書き直しました。
qiita.com

改良点

前回の記事では色々とまとめた結果粒度が小さくなってしまっていたので、粒度を上げてみました。

ソースコード

outside6.wp.xdomain.jp
公開後に上記記事を見つけたので修正を行いました。修正内容は、Task.Resultで待機するのではなく、Awaitで待機することでメインスレッドが止まらないようにしたことです。

CopyFileでファイルコピーを開始し、Awaitで処理を待機しています。
今回はCopyFileでExceptionを握りつぶすような形としています。戻り値を構造体やクラス、Tupleなどにして沢山情報を返すこともできるでしょう。

Private Function CopyFile(ByVal FilePath As String, ByVal CopyTo As String) As Task(Of Boolean)
    Return Task.Run(
        Function() As Boolean
            Try
                IO.File.Copy(FilePath, CopyTo & IO.Path.GetFileName(FileName))
            Catch ex As Exception '何かミスった
                Return MsgBox(ex.Message, MsgBoxStyle.OkOnly) <> MsgBoxResult.Ok 'メッセージが閉じられたときfalseを返す
            End Try
            Return True
        End Function)
End Function

Private Async Sub TestMethod(ByVal FilePaths() As String, ByVal CopyTo As String)
    Dim taskList As New List(Of Task(Of Boolean))
    For Each FilePath As String In FilePaths 'ファイルコピー開始
        taskList.Add(CopyFile(FilePath, CopyTo))
    Next

    For Each t As Task(Of Boolean) In taskList
        If Await t Then
            ' 結果を見て何かしらの処理
        End If
    Next
End Sub

【VB】表示中の画像ファイルが削除できない問題の解決法【.Net】

表示中の画像ファイルが削除できない問題の解決法 - .NET Tips (VB.NET,C#...)
上記記事において「MemoryStream使った方がいい」という旨のコメントが有り、実際ボタンに設定した画像を削除しようとしてうまく行かなかったので(検証してないけどもしかして何かミスった?)、MemoryStreamを使ってやってみました。

コード

以下の通りです。ファイル内容をバイト列に読み込み、メモリーストリームにしてから画像を生成しています。
これで取得した画像を利用することで表示中の画像ファイルが削除できない問題が解決しました。

Public Function MakeNoLockImage(ByVal imagePath As String) As Image
    Dim fs = New IO.FileStream(imagePath, IO.FileMode.Open)
    Dim bs(fs.Length - 1) As Byte
    fs.Read(bs, 0, bs.Length)
    Dim thumbnail = Image.FromStream(New IO.MemoryStream(bs))
    fs.Dispose()
    Return thumbnail
End Function

【日記】壮絶なクソコードをリライトした話

wrongwrong163377.hatenablog.com

続きというか、以下の記事を紹介して頂いたのでそのアンサー的な記事として書きます。最近VBの記事が増えた原因がこれです。

hiroronn.hatenablog.jp

背景

5年程度担当者を代えながら煮込まれた結果できてしまったクソコードのリライトをしました。

リライトに踏み切った理由は、以下の3点からリファクタリングは不可能でリライトしたほうが早いと進言し、それが容れられたためです。

  • 自分がそのプロジェクトに振られた目的は新機能の実装であったが、コードは修正不能な量のバグと不合理を含んだ状態であり、新機能の実装が非常に困難な状態であった
  • コメントアウトされたプログラム行があまりにも多く、「何故この処理をする必要があるか」というコメントも無く、ドキュメントも無く、共通した処理が多くのファイルに分散して配置されており、見通しを立ててコードを管理できる状況に無かった
  • 流用できるコードもあるだろうという期待があった

記事を紹介していただいたのはリライトの終了後でしたが、正直もっと早く知ることができていたらという気分と、それでもリライトを選んだのは間違いではなかったかなという気分とがあります。

実際にやった結果

紹介した記事のレガシーの改善項ではリライトを選択する害として以下の4点が指摘されていましたので、各項目について結果を書きます。

  • 利用者にほとんどメリットが無い
  • もともとがバグだらけ
  • たいてい時間を超過する
  • アーキテクチャ的に不可能となるかもしれない 
利用者にほとんどメリットが無い

自分がリライトしていたのは簡易的な動画の編集・再生アプリケーションでしたが、微細なメモリリークや無駄な処理に起因する再生中の動画のフレーム飛びなどがありました。リライトによってこれらの問題を解消し、もっさりすることがあった動作の改善や何点か機能の追加も行えたため、利用者のメリットという点ではリライトは悪くない選択であったと思います。

そもそも新機能を実装できない程度には設計レベルで整理されておらず、使用しているライブラリの機能や仕組みへの理解もされているとは言い難いコードだったため、仮に旧コードに対して新機能の実装を行ったとしてもそのコードにバグが無いことを担保できる状況にありませんでした。よって、あの場面ではリライトするしか無かったと個人的には思っています。

ただし、書き直している間はアプリがそもそも利用できず、外面が同じものを作るだけでは動いているものを利用している側にとって全くメリットが無い訳で、新しい機能を追加するだとか、どうしても解消しなければならないバグが有るだとか、そういった状況がなければどれだけ魅力的に見えてもリライトするメリットは小さいとも感じました。

もともとがバグだらけ

これに関してはどうしようもない程に実感させられました。リライトを選択した時点ではもっと流用できるコードが有ることを期待しておりましたが、残念ながら使いまわせたのはUIだけで、そのUIも大きめに作り直しが入りました。

流用できる程度に作り込まれたコードは皆無でしたし、使えると思って移植したコードもバグや根本から仕様に合致していない処理を発見して結局全部作り直すなんてこともありました。

リライトに当たって流用できるコードは皆無だという前提で居たほうが良いという点は強く同意します。

大抵時間を超過する

超過しました。

計画を立てる段階での見通しの甘さもそうですが、作り直すからぶち当たる問題というものが非常に多く有り、仕様書のように絶対的な基準となるドキュメントも存在せず、当初は2週間程度で終わると思っていたリライトに5週間を要しました。

他人のせいにするような物言いになってしまいましたが、この点は見通しの甘さと自分の能力の不足を痛感した部分でもあります。アプリケーションをリライトするとなると広範な知識が必要となり、詰まった部分は単純な機能に対する勉強不足に起因していることがほとんどでした。また必要が無い状況で細かい部分を弄るなどして結果的に無駄とした時間があったのも反省点です。

アーキテクチャ的に不可能となるかもしれない 

これに関しては本を読んでいないため真意を理解できていませんが、実際OSやライブラリのバージョンの違いから実装不可能かもしれないという危機に陥る場面も有りました。

結果的には回避できましたが、リライトがそもそも完全に無駄になるような事態も覚悟するべきだという点に関しては痛感しました。

それでもやり切れたと思う面

今回のリライトでは苦労も多く有りましたが、最終的には何とかやり切ることができ、非常に良い経験もできました。

Git/Githubを用いた開発の経験

個人レベルですがGit/Githubを用いた開発を経験できました。

最初は完全に個人レベルでの利用に留めるつもりでしたが、バイトの方の中にはGit/Githubを使える方もおり、チーム開発というものを多少は経験できたかなと思います。Git/Githubへの理解も深まりました。

また、Gitだけでも使う効果は絶大だと感じました。旧コードではコードにコメント化を重ねた結果メインウィンドウだけで5000行を超えており(動いているコードは半分以下)非常に可読性の低い状況となっていましたが、リライトに当たってはコメント化されたコードによって可読性を損なうような事態を避けることができました。引き継ぐ際もGitを導入できたらいいなあ……。

コミットやブランチ作成のお作法などはまだまだ勉強中という感じですが、一歩目としては良い機会だったと思います。

開発をやり遂げた自信

リライトにあたっては内部的な設計を全て自分で考え、ほぼ全てのコードを独力で書き上げることができました。今までは「自分がどれ位できるのか」という点で全くと言っていいほど自信がありませんでしたが、今回の件で自分が思っているよりもできるんだということが確認できたと思います。

コーディング面でも、1日100コミット以上を何度も記録することができました。これまでは最大でも1日50コミット程度しかしたことがありませんでしたが、状況によってはそれだけ集中してコーディングに取り組むことができるということを知ることができて非常にいい機会となりました。

リライト中には細かいものも含め仕様変更が20回は入ったと思いますが、そんな中でも破綻させずにコードを書き上げることができたのは素直に嬉しかったです。

最後に、自分のリポジトリへのコントリビュートです。

2,102 commits  60,493 ++  46,161 --

補足

この件について愚痴を聞いて頂いた際に「クソコードとは言うけれど、書きたくてそうなった訳じゃないかもしれないんだからあまり口に出し過ぎないようにね」という旨の忠告を頂きました。

「コードはコード、個人の人格を攻撃している訳じゃないんだからいいじゃないか」とその場では思いましたが、過去記事を読み返すとしっかり人格攻撃に走っていましたね……(というかあの記事からもう1ヶ月経っていたことに驚きました)。

口は禍の元、自分は口が悪い方なので自重を心がけようと思います。

【日記】天才とはどのような人間か

yuiga-k.hatenablog.com

この記事に触発されて、自分が天才について思っていたこと・考えたことを書きます。

予防線として、この記事に書く内容は完全に自分が勝手に考えただけのもので何ら裏付けはありません。

天才の能力

自分は天才とは以下の2点を満たす人間だと思っています。

  1. その場では思考・説明より先に結論にがある
  2. 結論やその導出過程を見ても天才そのものを説明できない
1.その場では思考・説明より先に結論にがある

天才にとって結論は状況に対してしばしば反射的に導かれるものだと思います。それは決して裏付けの無いものではなく、意識・無意識問わず「その場では既に考え終わっていて後は引き出すだけ」の状況ができているからこそ反射的に導くことができます。

天才の結論と過程を再現することはできるかもしれませんが、完全な模倣はできないでしょう。天才は1分1秒の間に感性によって得たものを積み重ねているためにある日気づくことができ、既に気づいているからこそ反射的に引き出すことができる。これを真似するためにはまず感性を模倣し、そこから得られるものを積み重ねていく必要があります。

また、そのような感性と行動に共感することも難しいはずです。パッと見ただけでは何も見えない所からいきなり結論が出てきてしかも正しいというのでは、共感以前に「なんで?」という恐怖・畏れが先に来るのではないでしょうか。

勿論、天才であってもその場では結論を出せないような場面は多く存在するでしょうが、その場面への取り組み方としてもやはり天才はその積み重ねた結論の量から圧倒的な能力を発揮します。

2.結論やその導出過程を見ても天才そのものを説明できない

自分は天才の思考や行動に対する分析の結論は、微分方程式の一般解、特殊解、特異解のような関係で説明できると考えています。

つまり、天才の具体的な行動(≒特殊解)から、その目的や思考に用いる道具(≒一般解)を求めたり予想したりすることはできるが、それらをどのようにこねくり回しても天才そのもの(≒特異解)を求めることはできないということです。

 例えば天才の行動からある一つの原則を導いたとしても、「それをどのような場合に用いるべきで、用いるべきではない場面はどこか」という判断をすることはできないでしょう。このように、個別の特殊解や一般解、それら全てに共通している原則としての特異解は説明できていません。

確かに天才の行動には一定の裏付けが有り、順を追って説明することは不可能ではありませんが、その行動から天才の全てを説明することは不可能なのです。

天才の人間性

このようにある分野・ある場面に対して天才は超人的な能力を発揮しますが、天才も人間であり、人間性というものが確かに有ると自分は思います。

天才はなぜ凡人に期待するのか

天才が凡人に期待するのは、天才が自身の行動を凡人でも再現し模倣できると考えるからではないでしょうか。これは、天才自身が自分の行動・思考に裏付けを持ち、それに説明をつけることができるため生まれる感情だと自分は思います。

実際には先程挙げた微分方程式の例のように、個々の行動・思考に説明をつけることができるからといって天才を模倣できる訳ではなく、歴史を鑑みてもその期待はほぼ勘違いだと言い切ることができます。

しかし理性とは別に天才は、「理解できないと言われて説明を加えたのに何故理解してもらえないんだ」という感情を抱えているように見えます。この不満は天才にとって矛盾無く説明できる説明が存在しているにも関わらず、それを理解して貰えないことで発生する不合理を見せつけられる状態を解決してほしいという不満の表れでもあるのではないでしょうか。

逆にそこをある程度理解でき、実践できるからこそ、天才は秀才に対して興味を持たないのではないかとも感じます。

天才は何故排斥されるのか

これは分からないものは排斥するという「有機的な冷たさ」によるものでしょう。よく差別やいじめを引き起こしたり、無理解から的を外した言葉をかけるようなアレです。

人は物理的・心理的・能力的、どのような軸であれ距離が開くほどそれを理解できないことから排斥したがるか、排斥するまでは行かなくとも積極的にお近づきになろうとは思わない生き物です。天才も天才で、同じ軸で会話できない人間と話しても楽しさは少なく、気味が悪いと扱われることも多いため、理解できない人間を遠ざけるのが自然です。

また天才の少なさもこの傾向に拍車を掛けます。環境としてどのように天才と付き合うのが良いかを学習する機会が少ない以上、天才を排除しないように行動する人間もまた少なくなるでしょう。

天才とはどのような人間か

纏めると、自分は天才を特定分野に超人的な能力を発揮する一方で基本的には普通の人間と変わらないと考えています。目に見える部分が幾ら超人的であっても、誰かに認められた方が嬉しい、嫌なことが起きていたり続いたりしているのが嫌いという点は変わらないのでしょう。

書くだけ書いて投げっぱなしになりますが、自分の天才観でした。