SSブログ
プログラミング ブログトップ
前の10件 | -

Golangでファイルポインタを関数の引数で渡せなかった。(解決した) [プログラミング]

何か腑に落ちないGolangの仕様みたいなんだけど、ファイルを開いてからその先の別の関数の中で書いたり読んだりできないみたいなのだ。そもそもファイルポインタが引数として渡せない仕様になっていて、コンパイルが通らなかったり実行ができない。もしかしたらすごい初歩的なところでつまづいているのかもしれないですが、できないものはできないので書いておく。

実際のソースを見ていこうと思う。まずは動くソースを示します。何か別の問題があると話が止まっちゃうしね。とりあえずos.Create()で開いて、WriteString()で書いてみます。

package main

import "os"

func main() {
	fp, err := os.Create("hoge.txt")
	if err != nil {
		panic(err)
	}
	fp.WriteString("てすとだよ?ん\n")
}


ふむ、普通にファイルができて書き込めている。これを関数に書き出して、ファイルポインタを渡してみようと思う。

これだとエラーが出て実行できない。
package main

import "os"

func main() {
	fp, err := os.Create("hoge.txt")
	if err != nil {
		panic(err)
	}
	foo(fp)
}

func foo(fp *File) {
	fp.WriteString("てすとだよ?ん\n")
}


エラー表示は以下で、
$ go run sample.go 
# command-line-arguments
./sample.go:13:13: undefined: File


Create()で*File型のファイルポインタができているはずなんだけど、引数に指定できない。型は間違っていないはず。
https://pkg.go.dev/os#Create
でもFile型が定義されていないと出る。意味不明。
*をつけたりつけなかったりしてもダメでした。


今度はbufioを使ってみる。
package main

import (
	"bufio"
	"os"
)

func main() {
	fp, err := os.Create("hoge.txt")
	if err != nil {
		panic(err)
	}
	writer := bufio.NewWriter(fp)
	_, err = writer.WriteString("てすとだす\n")
	if err != nil {
		panic(err)
	}
	writer.Flush()
}

普通にファイルに書き出される。でも、ファイルポインタと同様に、writerを渡して関数に外出しにしようとすると同様に実行できなかったり、コンパイラに嫌われる。

package main

import (
	"bufio"
	"os"
)

func main() {
	fp, err := os.Create("hoge.txt")
	if err != nil {
		panic(err)
	}
	writer := bufio.NewWriter(fp)
	foo(writer)
}

func foo(writer *Writer) {
	_, err := writer.WriteString("てすとだす\n")
	if err != nil {
		panic(err)
	}
	writer.Flush()
}

型もあっているはずなのに、
https://pkg.go.dev/bufio#NewWriter
./sample.go:17:18: undefined: Writer

とFileと同様にダメ扱いされてしまう。どういうことだ。

とりあえずos.Create()などで作った場所で書き込みましょうということらしい。C言語でこういうようなことあったっけ?

とりあえず、関数に引数で渡せないのでどうしようもない。どういう理屈で出来ないのかがわからないのだけれど、大人しくファイルを開いたところで書き込んだりしとけということなんだろうな。何か、腑に落ちない感じ。理由があれば全然いいんだけど、言語的に出来なくしている訳が知りたい。

書き込むデータは別の関数とかで作って、ファイル開いたところに戻せよってことなんだろうけど、やっぱちょっと気持ち悪い。



《後記》

nobonoboさんに指摘していただき、型にパッケージ名が足りていないとわかりました。
具体的には*Fileを*os.Fileとし、*Writerを*bufio.Writerにするというものでした。確かにスコープ的にパッケージ名がないとどこのFileだかWriterだかわかりませんものね。やっぱり初歩的なつまづきでした。サンプルファイルを上げておきます。

package main

import "os"

func main() {
	fp, err := os.Create("hoge.txt")
	if err != nil {
		panic(err)
	}
	foo(fp)
}

func foo(fp *os.File) {
	fp.WriteString("てすとだよ?ん\n")
}


package main

import (
	"bufio"
	"os"
)

func main() {
	fp, err := os.Create("hoge.txt")
	if err != nil {
		panic(err)
	}
	writer := bufio.NewWriter(fp)
	foo(writer)
}

func foo(writer *bufio.Writer) {
	_, err := writer.WriteString("てすとだす\n")
	if err != nil {
		panic(err)
	}
	writer.Flush()
}


タグ:Golang
コメント(0) 
共通テーマ:パソコン・インターネット

Golangでmapをソートする。 [プログラミング]

タイトル通りのことはできません。結局、mapを構造体に入れ込んでそれでソートして使わないといけないみたい。そもそもmapを順に出力しようとすると、ランダムで出てきてしまうらしい。基本的にmapをそのまま使っていたらソートできないようだ。なので構造体に入れずともスライスに入れてソートしないことにはどうにもならない。

どうしてそんな仕様になっているのかというと、固定してしまうと環境によって実行順序が違ってしまい、移植性が悪くなるからわざわざランダムにしているらしい。ランダムにしているからrangeでループさせる時に定まっていない。なんかめんどくさぁ。

https://zenn.dev/sinozu/articles/c02cecbab039795d071a

ここに書かれているように、keyでソートするならそれほど面倒でもないんだけど、valueでソートするとなると少し面倒。というか、value側でソートする方が多いと思うんだがどうだろうか。

Golanger(一般的にGo使いのことをなんていうんだ?)にとっては、mapがそのままではソートできないランダム出力仕様になっていることは当たり前なのか、mapをソートするところにはそのことが書いてあることが少ないみたいだ。なんでそのことを説明しないのかわからないが、ソースが得られればそれでいい人が大多数なんだろうな。


これ↓でいいんだろうけど、いまいち分かりづらいし、位置関係を維持している状態がいまいち掴みづらい。

https://code-maven.com/slides/golang/sort-map-by-value

やっていることはおんなじなんだけど、構造体で順序が維持されるので、こっちの方がいいかなと思う。ただ構造体に入れ直さないといけないのでちょっと面倒。まぁrangeで回して入れるだけなので、インプリ的にも処理的にもそこまで重くはないとは思うのだが。

https://qiita.com/Sekky0905/items/2d5ccd6d076106e9d21c

さらに面倒なことをしようとすると、Len(), Swap(), Less()を実装するとできるらしい。Golangに用意されているお作法的にはそういうふうにした方がいいんだろうけど、正直内部で何やっているのかが分かりづらい。

https://ashitani.jp/golangtips/tips_map.html#map_Sort

普通にググったらここが一番最初に出てきて、どっちのパターンも出てくるんだよね。英語なのと会社でstackoverflow遮断されているので見ていなかった。

https://stackoverflow.com/questions/18695346/how-can-i-sort-a-mapstringint-by-its-values


よく考えたら
sort.Slice(スライス, func(i, j int) bool {

の時に無名関数でLess()関数を定義しているのでやっていることは同じなのかもしれない。

書き方は分かるんだけども、やっぱり中で何やってんだかわからない。ソースを読むべきかなぁ。型に属する関数の書き方とかよく分かってないだけなんだろうけど、訳わかってないけどそう書くと動くみたいなのはなるべく無くしたい気はする。

Less関数で条件を指定している。それは分かる。内部でどう組み込んでいるのかが直接見ていないのでよくわかっていない。ソースを見れば分かるのだろうか。そのうち詳しく見ていくかもしれない。

タグ:Golang
コメント(0) 
共通テーマ:パソコン・インターネット

golangからGmailにメールできなかった。(→やっぱできた) [プログラミング]

GolangはGoogleが作った言語なんだけど、smtpパッケージで簡単なメールを送れるようになっている。送れるんだけどもGmail側で、しょぼいクライアントは拒否してしまうようになっているようだ。

まず特殊な環境で直接GmailのSMTPサーバじゃなくてリレーサーバを指定しないといけないところに無理があったのかもしれない。こんな風にsmtp.SendMail()で送れると思うでしょ?

https://blog.smtps.jp/entry/2019/07/10/163813

その環境はユーザー名とパスワードが要らないので、smtp.PlainAuth()で作る変数はnilに変えればよかった。適当なポートを指定して送れはしたんだけど、いつに経っても届きやしない。エラーは出ていないし、ポート番号を間違うとエラーになるので、手続き的に間違っているわけでもない。


ググって探し回っていると答えがあった。

https://blog.web-apps.tech/go-smtp-sendmail-without-mailserver/

上に書いてあるようにtelnetでつないで、エラーも出ず送れてはいるんだけど、実際には届いていない。何が悪いのかわからんかったけど、しょぼいクライアントからメールを出していると拒否されてしまうようだ。

https://qiita.com/Esfahan/items/6fa613069b13eb7daa17
googleが信頼性の低いアプリからのメールを受け付けない

自分はこれが問題であるようだった。だけど管理者ではないので自分ではその設定は変えられない。それまでLinuxのSendMailコマンドを使って送れていたので、それをGolang側からコマンドを呼び出すぐらいしか考えられない。すべてGolangでやろうとすると厳しそうだ。というか君、Google謹製言語のGolangだよね? なんでメールくらい送れないの? というか標準パッケージの操作を行ってもダメって厳しいよねw。


普通にGoogleのSMTPサーバを使えば自分の設定にできると思うので問題ないのだろうが、そういう環境ばかりではないと思うので、一応他のページで書いてあるように送れはするのだろうけど、とりあえず下のリンクでしょぼいクライアントでも受付するようにできるみたい。

https://myaccount.google.com/lesssecureapps

自分は環境により設定できませんでした。まぁ仕事だとできないこともある。


TLS関連でできないのかなと思ってもいたけれど、このソースではうまくいかず。つなぐところからこけていた気がした。
https://qiita.com/tamago0224/items/bdbc41432c43f0b569d3




と思っていたんだけど、あるサンプルコードを動かしたらGmailでメールを送れた。「googleが信頼性の低いアプリからのメールを受け付けない」というのは、この場合間違いであった。すいません m(_ _)m

https://gist.github.com/jim3ma/b5c9edeac77ac92157f8f8affa290f45

このソースを自分の状況に合わせて動かしたらすんなりメールが送れた。内容的な違いといえば、smtp.SendMail()を使わずに、プロトコルを一段一段実行しているところなんだけど、SendMail()に何か問題があるのだろうか?

ここにSendMail()内でやっている事のログを出せるみたい。結局パッケージ中で起こっていることはわからないから無理やり引っ張り出すしかないよな。

https://qiita.com/Neetless/items/6c802dddc52fa09aaede

なんにしても手続き的に順次やったらできて、一つの関数でできないということは、まとめ方が悪いとしか言いようがない。とりあえず、Gmailに送れてしまったので、SendMail()がやっている事を確かめるのは時間ができてからにしたい。どっちにせよ、どこかで失敗しているんだろうな。とりあえず考えられるのは、StartTLS()を使っているけど、エラーでプログラムを落とさずそのまま突き進んでいることだ。log.fatal()とかlog.panic()するとそこで終わっちゃうしね。その他のソースだとエラーを見て落とすようになっている。これではTLSを使っていない場合は続かなくはなる。

気になっているのは、StartTLSで落ちているんじゃないかということと、Mail()、Rcpt()ではname@hoge.comみたいな普通のメールアドレスを指定してあげるんだけど、Data()では<name@hoge.com>みたいに大なり小なりで括っているんだよね、たぶん。それがきちんとSendMail()で行っているのかというのが気がかり。

今は書かないけど、原因を突き止めて書きたいな。たぶんやったらここに続けて書くだろう。SendMail()で送れるよという記事も多いんだけれども、依然として一つ一つ手続き的にプロトコルを実行しているソースも多いので、たぶんSendMail()が完全ではないのかもしれない。





《後記》
SendMail()の詳細ログを出してみました。
https://qiita.com/Neetless/items/6c802dddc52fa09aaede
ここのまんまの事をやりました。実はこれをするのにかなり苦労して、自前のパッケージを入れるのがかなり苦労をしました。やたらgithubから取ってくるやり方を書いているんだけど、それはやったことあるので自分が書くパッケージを取り入れたいんじゃとイラつく。

GOPATHのsrcのところにおけばよさそうな感じなんだけど、今はそうなっていないようで、go mod initをやらないといけないらしい。上の$GOPATH/src/sandspace/smtp_test/smtp/ で行くとsandspaceディレクトリでgo mod init sandspaceを打って、go.modができた。そこからgo mod tidyを打てと言われたのでやったけど、
package sandspace/smtp_test/smtp is not in GOROOT(Goの標準パッケージのフォルダ)

みたいに出てうまく行かなかった。全然$GOPATHの中のディレクトリを見てくれない。

なんかGOPATHのディレクトリの中でやらないとできないのかなとか思ったけど、結局下のサイトのようにやったらできた。

https://qiita.com/tkj06/items/a5f79417935100045650

実際はコンパイルしたいファイルがあるディレクトリにsmtpフォルダだけを置いて、そのディレクトリのままで
go mod init test
go mod tidy

をして、
import "test/smtp"

すればよかった。なんかGOPATH全然関係ないじゃん。確かにGitHubから落としてくるときには、GOPATHの中に入っていたと思ったんだけどな。でも結局、できる事しかできると言えないよな。

たぶん昔はGOPATHの中に入れとけば使えたのかもしれないけど、今はできていないみたい。でも、そのままできるみたいなサイトが垢のように残ってしまっているのが、Google検索の敗北ですね。特に技術関係はすぐに使えなくなる確率が高いから、昔からのリンクが検索を邪魔をするってことが多いよな。

コンピュータ言語関係は基本的な文法とかは変わらないことが多いんだけれど、それ以外の環境関係が全然変わってしまうことも多いから、情報がすぐにゴミレベルのものになってしまう。動かない誤った情報は、障害にしかならんしね。そういうところはGoogle検索はうまく機能していない。文系的な固定的な情報ならいいのかもしれないけど、知見がころころ変わる分野には適していないんだよね。

とにかく、オレオレsmtpパッケージを使うことができたんだけど、普通に通信して普通に終わっていた。処理的にもsmtpパッケージの単体的な手続きと同じ事をしているっぽい。ログをここに出したい気もするのだが、出したところで誰かに助言してくれるでもないので、情報は出さないことにします。環境的に色々隠さないといけないし、それによって問題が隠れてしまうかもしれないし。


とりあえず、smtp.SendMail()が正常に動いているように見えて、実際にはメールが着信しないところまでは確かめた。smtpパッケージを段階的に個別にプロトコルを動かしているのにしたって、大して細かい事をしているわけじゃないんだよね。でも、SendMail()では送れないことは確かなのだ。

こうなったらステップ実行でもしたいところだけど、Golangでのデバッガってどうなっているんだろう。VSCodeでステップ実行とかできそうな気もするけど、正直VSCodeはオレオレツールが多すぎて確かな品質のものが少なかったりする。どうしようかな。もうメールは遅れているから、SendMail()に固執する必要はないんだけど。

コメント(0) 
共通テーマ:パソコン・インターネット

golangのmapのvalueにスライスを使えた。 [プログラミング]

いわゆる辞書型とかハッシュとか言われるものが、Golangではmap(連想配列)なわけだが、mapをスライスにするとかいう話しかあまり出てこなくて、key, valueのvalueの方を配列やスライスにするやり方とかがすぐに出てこなかった。

結局、自分で試してみてmapのvalue側を多値にできる事がわかったんだけど、一般的には最初に考えていたようにkey(配列とスライス以外)とvalue(スライス)を構造体に入れて、構造体をスライスにしてそれぞれにアクセスという事をしているのかなと思ったりした。内部的にはそれと同じ事をしているのかもしれないけど…。


普通のmapは
m := map[string]string{"key1": "value1", "key2": "value2", "key3": "value3"}

って感じで「一対一」なんだけど、自分は「一対多」のmapが欲しかった。それもvalueの方の多が変動してもいいようになってほしかった。できないかなと思っていたけど、普通にできた。言語的な限界とか変な意地悪とかはなくてよかった。

んで実際にどうするのか、というところだけどポインタとかで持たせることなく、普通にスライスにするだけで使える。
m := map[string] []string{}
m["hoge"] = []string{"value1", "value2", "value3"}
m["foo"] = []string{"value4"}
m["bar"] = []string{"value5", "value6"}

って入れられるので、1対多でも数が揃っていなくてOK。これをforループを二つ使えば、keyごとのvalueの同様の処理がザラっとできる。

何が便利かというとkeyでまとめられた処理を行なって、その後にvalueで個別に処理できる、というところだ。数があってなくても統一された処理を行えるので、個数が可変の処理が可能となる。ひとまとまりのグループ同士が数が揃ってないけど、処理は統一してやっちゃいたいという場合は重宝する。先のソースを続けていくと、

package main

import "fmt"

func main() {
	m := map[string][]string{}
	m["hoge"] = []string{"value1", "value2", "value3"}
	m["foo"] = []string{"value4"}
	m["bar"] = []string{"value5", "value6"}

	for key, value := range m {
		fmt.Println("key: " + key)
		for i, v := range value {
			fmt.Println(i, v)
		}
		fmt.Println()
	}
}


goのファイル名が適当ですまないけど、実行結果は下記のように出る。

$ go run package_main.go 
key: hoge
0 value1
1 value2
2 value3

key: foo
0 value4

key: bar
0 value5
1 value6

データの数がバラバラでも簡単な処理で画一に処理することができる。スライスを持つmapは一般的ではないのかな? 探し方が甘かったのかな? でも自分で色々やって解決するってプログラミングしている感じがして楽しい。

タグ:Golang
コメント(0) 
共通テーマ:パソコン・インターネット

シェルスクリプトの変数と文字列でハマった話。 [プログラミング]

業務アプリというか簡単なシステムをシェルスクリプトで組んでしんどかった時のお話。
基本的なところはワンライナーでできていたんだけど、シェルスクリプトで出力を変数で持ったりして、実際の最終的な出力を加工して出していたりしていた。

だけど、シェルスクリプトでの複数行の文字列の扱い方が気持ち悪くて往生した。まず、普通に複数行で書けなかった。

https://takuya-1st.hatenablog.jp/entry/2017/03/22/144700

複数行にべったり書くと何か引っかかっていて、ヒアドキュメントだとなんとなく上手くいっていた(気がした)。最終的には変数の書き方がダメだったのですが、それは後で書くことにします。出力の時になぜか改行が省略される。なぜだ。

https://jehupc.exblog.jp/15728862/

改行ありの出力は変数を""で囲わないといけないとか、記法的に直観性がない。${VAR}を"${VAR}"にしないといけないとか、やったことがないと思いつかない。とにかく、改行が付かなくてダラダラ一行になってしまうのキツイ。改行を省略してしまう理由ってなんなんだろう。


あと改行文字を入れるのも厳しくて、\nと書いても気にしてくれずにそのまま出る。基本的にエスケープ処理はしてくれないらしい。

https://qiita.com/kkdd/items/6b0534b3b64639ac3e25

改行文字を入れるだけなのに、こんなにも七面倒くさい。まぁお作法的に入れればいいだけなんだけど、何度も別のところで使うとなると、その一手間が煩わしい。


空白行を入れるのも面倒くさいかな。echoで出せばいいと書いているところもあるんだけど、変数を""で囲っても変数の末尾の空行は出力の時に省略されてしまったような気がした。これもやり方に寄るんだろうけど、その時はなんで省略してしまうんだと憤慨していた。

https://www.mussyu1204.com/wordpress/it/?p=308

結局、空行や改行を簡潔に書くには上のようにechoで出すのがわかりやすいのだろうか。というか、普通のプログラミング言語に比べて、変数の中の改行文字の扱いが面倒すぎる気がする。まぁもっとシェルスクリプトが書きやすかったら、スクリプト言語なんてあまり出てこなかったんだろうけどね。




あとこれが一番書きたかったことなんだけど、意味の分からない所で
+=: command not found

とか出ていて、普通に変数に足して代入しているだけなのになと思っていたら、変数を先頭にする場合は、$が必要ないのを初めて知った(たぶん)。今まで変数宣言以外は$を付けないといけないと思っていたけど、行の先頭だけは要らないというシェルスクリプトの仕様を知った。というか行中に書く時は$を付けないといけないと書いた方がいいのかな?

PHPとかはずっと付けないといけないし、他の言語の変数はプリフィックスがない方が普通だ。どちらにしても気持ち悪い。そもそも変数と=の間に空白入れちゃいけないっぽいのも気に食わない。これに関しては空白を許可しない意味がある(ワンライナー的に)ので仕方のないことかもしれないが、普通に読みにくいでしょ。


なので
$VAR+="文字列とか"

はダメで
VAR+="文字列とか"

としないと意味の分からないcommand not foundになってしまう。もちろん実際にコマンドがあるなしにかかわらずコケるので、わけのわからないコマンドがないエラーメッセージが出たら、ノリで$が付いていないか調べるといいと思います。


なんにしても普通のコンピュータ言語のように、すんなり文字列を扱えないというのは確かな話で、ちょっと試行錯誤は必要かなと。とりあえずはこのハマりは抜けたので良しとしよう。というか、普通の人は簡単に気付いているのかな? というか、旧コンピュータ的なエラーメッセージはどうにかならないかと思うのだが。そういう意味ではRustのコンパイルエラーはわりあいと親切なのがわかる。

コメント(0) 
共通テーマ:パソコン・インターネット

LZ圧縮をRustでやってみようとした。 [プログラミング]

前回、ランレングスをRustでやってみたんだけど、圧縮率としても微妙だし、本気でやりだすとファイルフォーマットとか色々考えないといけなくなるので、エンコードするところで止めました。元々ランレングスを使っているファイルフォーマットは、BMPの圧縮ぐらいにしか使われていないので、実際のデータとして検証するのが難しいところもある。そのため、わりに実装しやすいけど、実用的ではないということでやめました。

ファイル圧縮というと他にLZ法があるなとは思っていましたが、そこまで真剣に調べたことがありませんでした。仮に使うとしてもすでにライブラリがあったりで、中身を云々することなくファイルを指定してあげる程度で使えていたはずです。とりあえず、今回LZ法の色々とその方法を調べておくことにしました。そこそこボリュームがありそうなので、実際に実装するのはだいぶ後になりそう。

LZ法全体を舐めた後でZIPとか実装して、その後Linuxとかで使われるアーカイバをソースから探っていくことにしましょうかね。そこまで続けてできるかどうかわからんけど、C言語を置換していく一つのケースワークとしてやっていこうと思っています。まぁ途中で投げ出す可能性が高いですけどね。ランレングスも当初考えていたよりも、きっちりと決まっているものではなかったから、途中でやめてしまったし。




ともあれ、LZ法は圧縮方法としてはメジャーだと思うので、実装しておいてもいいかなと思ったりしている。現在のLZ法は既に確立したファイルフォーマットとして確立しているので、生の原理を知るのはいいかなと。

まずLZ法にはLZ77とLZ78があるらしい。これはググった説明ページのどこにも書かれていた。77の方がSliding Window、78の方が辞書の方法で圧縮をかけているようだ。今のところ、何のことやらわからない。

細かい圧縮法の違いは以下のサイトに出ている。LZ法と書かれているところだけど、こういう図はとてもありがたい。ついでに#でジャンプできるようにしておいてほしいw。

https://service.plan-b.co.jp/blog/tech/10282/

LZ77系にはDeflateとLZSSがあり
LZ78系にはLZWがある

形式名を言われたところでなんのこっちゃだけど、使われているファイル形式を見るとそのまんまなので直接的にわかりやすい。今の使われている感じとしてはZIPなどのDeflateが総本山な気がするけれども、なんとなく昔GIFで問題になったLZWを最初にやりたい気がしている。

若い人にはGIFの問題と言われてわからない人もいるかと思いますが、GIFの圧縮方法の特許で一悶着あったのです。ユニシスが他の買収した会社の特許で、フリーに使えていたGIFの使用料を取り始めたので、インターネット全体から大ブーイングになったという話です。

https://www.tohoho-web.com/wwwxx051.htm
https://www.gnu.org/philosophy/gif.ja.html

元々GIFは特にお金を払わずとも使えていたのですが、それを作るソフトに特許料を支払えと言い出したために、新しくPNGというフォーマットが生まれたと認識しています。なおPNGはDeflateを使っているのでLZWの特許には触れないわけですね。ともあれLZWの特許はとっくに切れているので、心置きなく使えるようにはなっています。ファイルフォーマットも一般的なので検証とかには比較的楽に使えそうな気はします。




とりあえず道筋はできました。今回もソースなどの提示はしません、すいません。そのうち書きます。LZの基礎をやって、LZWとDeflateをやってみる、でいきたいと思います。なかなか遠い道のりだとは思いますが、GitHubにソースを置いてやってみたいと思います。さてサンプルコードはないかな、と。Deflateのzlibとかは普通にそのままあるんだろうけど、LZWとかはきつそうな気がする。毎回最初はRustどうでもいい話になっちゃってすいません。

タグ:RUST
コメント(0) 
共通テーマ:パソコン・インターネット

Rustでランレングスから始めよう。 [プログラミング]

前回Rustをランレングスを使ってみようとして、ランレングスってなんぞやというところで終わってしまった。Rubyのソースを丸のままコピーして手直しして動くようにした。

https://mi-chan-nel.com/run-length-encoding/

案外、Rubyと変わらないので、endとか添字アクセスとかを変えればある程度は動いた。

run_length.rs
fn main() {
    let str = "AAAAAAEEEEFFFFFFDGGGGGGGSSSSSSAAAE"; 	
    println!("{}",str);
         
    let mut count = 0;
    for i in 1..(str.len()) {
      if str.as_bytes()[i-1] == str.as_bytes()[i] {
        count = count + 1
      }else{
        print!("{}{}",str.chars().nth(i-1).unwrap(),count+1);
        count = 0;
      }
    }         
   println!();
}


ちょっと驚いたことに、&str型の文字列に添字アクセスできないということ。&strはプリミティブ型と言いながらも、文字列の何番目かにアクセスするためにメソッドを使う。それって本当にプリミティブ型と言えるのかよと思いながらググってみると、String型の参照と出ていた。そのものはプリミティブ型なのかもしれないが、実態はオブジェクトっぽい。なんか気持ち悪いな、おい。何にしても一般的に言われるようなプリミティブ的には扱えない。

https://qiita.com/aflc/items/f2be832f9612064b12c6

あとstr.chars().nth(i-1).unwrap() のところとか全然わかって使っていない。Rustはオブジェクト指向じゃないと思っていたが、がっつりオブジェクト指向ぽい。というか、ググってすぐに解決法は出てくるのはいいけど、本当はきちんと調べたほうがいいんだろうなぁ。まだどこにきちんとしたリファレンスがあるとか全然把握していない。


あとfor文の条件式には( )がいらないのな。コンパイラに
warning: unnecessary parentheses around `if` condition
って言われた。入れない言語もあったかと思うが、それもちょっと気持ち悪い。

ただ全般的にコンパイラのエラーがイミフである事が少ない。訳のわからないエラーメッセージはほとんどない。解決策すら示してくれる。きちんと英語が読めれば大体大丈夫だ。英語出てもすぐに読まないけどなw。


実行する。
AAAAAAEEEEFFFFFFDGGGGGGGSSSSSSAAAE
A6E4F6D1G7S6A3


でも、これでは最後のEがカウントされない。なぜだ?
結局、問題となるのが文字列の最後が\nなどで終端処理されていないっぽい。
そのため、最後の処理は文字列の長さで検知しないといけないか、明示的に文字列に\nを入れないといけない。

《後記》終端文字は\0でした。でも\nでも動くのでこのままにしておきます。こういう凡ミス結構するんだよなぁ…

let str = "AAAAAAEEEEFFFFFFDGGGGGGGSSSSSSAAAE\n";


最初の文に\nを加えるのでもいいんだけど、\nがない前提でソースを書き換えてみる。

fn main() {
    let str = "AAAAAAEEEEFFFFFFDGGGGGGGSSSSSSAAAE"; 	
    println!("{}",str);
         
    let mut count = 0;
    for i in 1..(str.len()) {
      if str.as_bytes()[i-1] == str.as_bytes()[i] {
        count = count + 1;
      }else{
        print!("{}{}",str.chars().nth(i-1).unwrap(),count+1);
        count = 0;
      }
      if i == str.len() -1 {
        print!("{}{}",str.chars().nth(i).unwrap(),count+1);
      }
    }
   println!();
}

ちょっとハマった。書き方が悪いのかもしれないけど、\nがないだけで簡潔に書けなくなった。思ったより\nで終端処理する意味って案外大きいのかもしれない。

あと、for文のカウンタはmutで宣言しなくていいのねとか。そもそもletすらないか…。

というか、コード構築力がなくて、Rubyのソースがなかったらここまですぐには書けなかった。というか、元々for文の添字とか細々と考えるの嫌いなんだけど、C言語みたいにめんどくさくなくなってfor inでかけて良いなと思った。でも、根本的なところでやっぱ面倒なんだよね、添字。




ランレングス自体の問題は、デコードする時にこの形式だと9以上連続すると2文字以上になって処理が面倒になってくるというところだ。連続する文字の後を8bitの値で保存するということも考えたが、それでも255バイトまでの連続しか表せないよなぁとか思ったり。

そもそもバイト列が連続するかどうか、というバイナリファイルでは特殊な状態のものが一般的ではない、ということでデコードは書かず、これ以上はランレングスは止めにしておく。FAXのG3の規格とかもやっぱり特殊と言わざるを得ないしね。データの圧縮としては、膨張する可能性も大きいということで、とりあえずやったというところで止めておく。


実際にパソコンで使われているランレングスと言えば、BMP形式のファイルでRLEってのがあったなぁと思い出した。

https://ruche-home.net/program/bmp/rle

でも、今更BMPもないだろうと思ったし、そもそもmac上でやっているのでBMPは一般的ではないと思った。BMPは基本的にWindowsのフォーマットだと思うし。macはpictだっけ? 今となっては圧縮なしの生データみたいなフォーマットはないんだろうな。昔と違って圧縮伸長処理が瞬間でできるだろうし。

そんなわけでランレングスはこれ以上はあまり益がないのでやりません。とりあえず概念だけでもいいかなと。




今、エディタはVSCodeでRustコードを書いているのだけれども、デバッグ機能とかを入れようとするとHomebrewのRustを使えないっぽいので、結局動作とかをターミナルから実行しています。それでも普通なんだろうけど、VSCodeからできるならやりたいじゃないですか。でもできないんだよな。

PHPでもすごく苦労したけど、設定が明確ではないのがキツすぎる。状況によって違うんだよな。大体、本家が作っているわけでもないので、細かいところが行き届いていない感じ。VSCodeはMSとしてはそこそこ良いプロダクトだと思うけど、あまりにOSS的に分散しすぎている感があって、うまく動かないケースが多すぎる気がする。ただ単に日本語対応エディタとして、Linux上のファイルをいじるとかだと問題ないし便利なんだけどね。デバッガは外付けでやろうとするとかなり厳しいのかも。

タグ:RUST
コメント(0) 
共通テーマ:パソコン・インターネット

Rustでランレングスから始めよう。とした [プログラミング]

自分がモデムで電話回線を使ってインターネットを始めた頃には、いろいろ圧縮方法があって今みたいにOSに付いているzipでいいやというわけにはいかなかった。回線が細い頃には、少しでも圧縮率が高い方式が尊ばれて、実際ダウンロード時間を削ってくれた良いことであったし、解凍できない時にはダウンロードデータが間違っていることも分かったりしていた。

圧縮方式としてランレングスという方法が一般的だということは知っていた。でも、実際に使ったことはなかった。というか、大体の技術は使っていても間接的であるということなのだが、暗号化にしてもAPIにしたがっていればそれなりの事ができて用は足りていたわけだ。そんなわけで詳しくは知らない。多分続いているデータを束ねて圧縮するものっぽいことぐらいしか分かっていない。

ここに各種圧縮法とランレングスの実際が書かれている。わかりやすい。

http://www.snap-tck.com/room03/c02/comp/comp01.html

ただランレングスの実際としては、データが日本語であるという事は限定的すぎる。説明のためにわかりやすくという事なんだろうけど、なんのデータに対しても使えるやり方にしたい。

話はちょっと違うけど、パソコン通信の時代にはアスキー文字に符号化して圧縮する方式があったとか。どういう名前かは忘れた。そもそもメールもアスキー文字しか使っちゃいけなかったんだよね。Base64とかQuoted-Printableとかで送るのが基本なんだけど、一時期バイナリメールとかもあったね。初期の頃の通信はアスキー文字を使うという基本があったのかもね、よく知らんけど。


ランレングスと言えばFAXだろうと思ってググってみた。

http://www.infonet.co.jp/ueyama/ip/binary/run_len.html

ここで疑問に思ったのが、ランレングス法と書いてあるけど、やっている事はハフマン符号化じゃないかという事だった。ランレングスは連続したビットを数えてまとめることをやっているわけだけど、Modified Huffman codingと書いてある通り、ハフマン符号化じゃないかと。

思っていたランレングスは連続したビットの数を交互に示すものだった。下のような感じ。

https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q12174226402

分かりやすいけど、連続する数を示すときのビットの切れ目とかどうするんだと思ってしまう。
先のFAXのランレングスを再度見ると、連続したビットを示していることに変わりはないみたい。だからランレングスであり符号化でもあるという事なのかな?

http://www.infonet.co.jp/ueyama/ip/binary/mh_coding.html

それとG3 FAXという事で、別の方式の圧縮方法もありそうな気がする。理解のためには圧縮率が低くてもアルゴリズムが単純な方がいい。

https://ja.wikipedia.org/wiki/%E3%83%95%E3%82%A1%E3%82%AF%E3%82%B7%E3%83%9F%E3%83%AA

むー、G1で無圧縮で6分機とか言っているから、間の単純なランレングスとかはなさそうだな。FAXから離れて純粋にランレングスを改めて調べましょう。実際にあるものをやった方がいいと思ったけど、汎用技術ではなくて一つの規格になっているので、細かく見ていくとしんどくなりそうだし。


元々FAXなどで使われていて、二値データを圧縮するために使われていたわけだけど、プログラミングのサンプルとしてはバイト単位で続いているデータを圧縮するというのが多くて、デジタルデータ全般への汎用性はかなり落ちる。

C#でちょっと長たらしい。
https://algoful.com/Archive/Algorithm/RLE

Rubyがわかれば端的で良い。
https://mi-chan-nel.com/run-length-encoding/

今回は長くなったのでソースは書かないが、次回はRubyのソースを元にRustのソースを提示しようと思う。でも、ソースが短いので関数にわけないので、Rustで面倒な引数の渡し方を示す事がしにくいかもしれない。逆にRustどうでもいいソースになりそうだけど、はじめなので簡単なものにします。

タグ:RUST
コメント(0) 
共通テーマ:パソコン・インターネット

パソコンでプログラミングできていない [プログラミング]

パソコンでプログラミングできていないので、今はプログラマじゃない。仕事でもプログラミングのところからは外されているので、お家で自主的にプログラミングしないとプログラミングはしていないことになる。いろいろ力が落ちてきているので、それはそれでいいのかもしれないが。

やりたいことはrustとかgolangとかiOSのアプリ作りとか、そこそこあることはある。でも、すぐにでもやりたいという気持ちにはなっていない。あとラズパイとかちょっとやりたいかな。IoTも言われて久しいが、あんまり身近になっている感じはしないし。

とりあえず、MacのHomeBrewをアップデートしておこうかと思ったのだが、相当時間がかかってやっと最新になった。でもgolangもrustもこれで問題なくできる。Pythonも入れようかと思ったが、手を広げすぎな感じもしたのでやめた。というか、macには元々Python入ってるんだよな…。機械学習もあんまり興味がないのでPython新しくやる意味もないんだけどな。

一回、勉強のためにGolangでbusyboxを作ろうとしたけど、やっている人がいそうだしファイルサイズが小さくならないことを考えるとBusyboxはあまり嬉しくないんじゃ?と思ってやめた。RustならBusyboxでもいいと思ったんだけど、Rust自体うまくメモリ操作が出来なかったもんだから、途中でやめてしまった。あまりにコンパイラがうるさすぎて、どう回避していいかわからなかった。昔に始めたC言語以上に難解というか。たぶん仕組みがわかればそうでもないんだろうけど、怒られる理由がいまいちわからなかったりした。

Rustの面倒くさいところは、Software Designを読んでちょっとわかったつもりになっていたんだけど、実際に手を動かさないとわからないところもあるだろう。もう一回読み直してハンズオンしてみようかな。


Software Design (ソフトウェアデザイン) 2021年9月号 [雑誌]

Software Design (ソフトウェアデザイン) 2021年9月号 [雑誌]

  • 出版社/メーカー: 技術評論社
  • 発売日: 2021/08/18
  • メディア: Kindle版



データ圧縮とかを再度勉強するのもいいのかなと思ったりしている。それこそランレングスみたいな基礎的なところから、実際使われているアーカイバに使われるところまで。速度も出そうなので、コードの良し悪しも出るだろうし、ソースは別言語でそこそこ転がっていそうだしね。

Rustで怒られなくなる講座とかここで書きたい気はする。他の言語と同じように書こうとすると基本怒られてコンパイルが通らないので、そこを突破するための方法論。それがわかればRustも他の言語とそう変わらないと思うし。


昔はGUIアプリケーションとか作っているだけでも楽しかったけど、今ではもっぱらCUIのデータを処理するものがほとんどになっちゃってる。iOSのプログラミングだけは別だけど、GUIのプログラミングってプラットフォームに依存性が高いから、その専門的な知識ばかり増えてしまうし、今作りたいGUIアプリケーションってあまりない、というか思い浮かばない。


コメント(0) 
共通テーマ:パソコン・インターネット

RLoginの要望が通った話。 [プログラミング]

先にRLoginでスクリプトにコメント使えるのにマニュアルに書いていなかったり、スクリプトファイルを開いているとそのスクリプトを実行できない問題は、サイトに投稿したらすぐ対応してくれて、さすが有名どころのソフトの作者は違うなと思った。

https://miff.blog.ss-blog.jp/2021-10-19-1

というか、ユーザーのクレームというか要望への対応には、商用やOSSに限らず対応が早ければ使う人も嬉しいわけで、それだけムーブメントとしては盛り上がるわけで。盛り上がるまでが大変なんだけど、それを継続させるのもしんどいかなという気はしている。

スクリプトファイルの開いていると実行できない問題は、そこそこクリティカルな問題だと思われたので進言せずにはいられなかった。プログラミングをしているとエディタでファイルを開いたまま実行とか普通にするので、そういう状況は一般的なプログラムとしては妥当ではなかったのだ。

そうして開いていると実行できませんよと言ってみたら、なんだかプログラミングの中身のよくわからない事を言われてしまい、Windowsプログラミングそんなに知らねーよとなってしまったんだけど、やっぱりソースをエディタで開いていただけで実行できないのはおかしいと少し例をあげてできないかどうか聞いてみた。

そうしたら案外すんなり、やっぱできました、みたいな回答をもらえ次のバージョンで実装できるようになったのでした。やはりコンピュータを使っていることのコモンセンスはわりと重要で、他でできることは大体自分のところでもできるのが普通なんですよね。どんな幅広い知識を持ち合わせていても知らないところは知らないわけで、そこを現状を見て突っぱねてしまうのはもったいない気はします。

今までは
「スクリプト開く→書く→閉じる→実行する→失敗する→開く→書く→閉じる→実行する」
という繰り返しだったのが
「スクリプト開く→書く→実行する→失敗する→書く→実行する」
とスクリプトを閉じたり開いたりする手間がなくなって非常にデバッグが楽になるだろう。

まぁ開発環境としては普通にできることなんだが。それができなかったらブレークポイントとか張って変数の中身とか見れないしね。RLoginではそもそも変数とかを見る仕組みはないけど、ガリガリ試行錯誤するのに障壁となることは取り除いた方がいいと思ったし。というか、ろくに自分の書いたソースも見ないで実行してみて検証するなんていうこと普通にあるし、コンパイラは論理的な自動推敲ツールだと思っているし。Syntaxエラーはいちいち目で追っているのもしんどい時もある。それにRLoginのは実行するしかなくてエラーも吐かないみたいなので、何度も繰り返すのは目に見えているわけで。

大したコードを生産できないけど、もうちっと社会に貢献できるものを作ってみたいなと思ったりもする。どこかに組み込んで動かしてもらうようなライブラリとかをね。ただOSSも会社で普通に出す時代になっているので、職業的にできている人に比べて厳しいというのはあるよね。それにOSSで会社に雇われている人はそこそこレベル高い人多いだろうし。なんにしても、こういうのあるのになんでないんだよ、しょうがないから俺作ろってのが王道かもしれんな。

コメント(0) 
共通テーマ:パソコン・インターネット
前の10件 | - プログラミング ブログトップ