ラベル C# の投稿を表示しています。 すべての投稿を表示
ラベル C# の投稿を表示しています。 すべての投稿を表示

2016/06/28

[C#]要素をすべて掛ける(乗算する)

このエントリーをはてなブックマークに追加
要素をすべて乗算したかったんです。
すべて足し合わせるのは、Sumを使えばいいのですがLINQには乗算はありません。すべて掛けるなんて、あんまり使わないしね。
ExcelにはPRODUCT関数というのがあるらしく、値の積を計算するもののようです。このキーワードを知っていたら手早く探せたかも…。

Aggregateを使う

掛けるときはAggregateを使うとスマートでした。

int product = list.Aggregate((now, next) => now * next);

Aggregateコードは、LINQを使わずに展開すると下のコードと同じものです。

int next = 1;
foreach (int now in list) {
    next *= now;
}
int product = next;


めでたしめでたし。


サンプルソース


int[] list = { 1, 2, 3, 4 };

// すべて足す
int sum = list.Sum();
Console.WriteLine(sum); // 10

// すべて掛ける
int p = 1;
foreach (int val in list) {
    p *= val;
}
Console.WriteLine(p); // 24

// すべて掛ける
int product = list.Aggregate((now, next) => now * next);
Console.WriteLine(product); // 24

機会があったら覚えていくスタンス

さて、余談ですがAggregateはMSDNでは次のように説明されています。

Aggregate<TSource>(IEnumerable<TSource>, Func<TSource, TSource, TSource>)
シーケンスにアキュムレータ関数を適用します。

なんのこっちゃ、ぜんぜんわかりません。
LINQは使い方を知れば便利そうと解るけれど、インテリセンスやMSDNからの習得は難しいものもあるようです。
サンプルのコードがあって、こういう風に使うんだという一例があれば、なんとなくわかるし習得も容易ですね。

日頃よく使わないメソッドはやっぱりその挙動にも不安が残るものです。何度も検証しないと手放しで任せられないし、乗算のコードに展開したものを書いても、そんな目くじらを立てるほどのロスがあるわけでもありません。
意地でも新しいことを覚えないというスタンスはどうかと思いますが、片っ端から習得しようとしてもほとんどは出番がなく使わないままなのが現実です。
なので、機会があったら覚えていこうというくらいでいいかなと。

もちろんそのすべてを習得して自在に連携している人もいるでしょうし、それが理想モデルです。
けれど、自在に扱える人も右往左往しながら習得したはずですよ。

以上です。よきLINQライフを!

Calculate product with LINQ
http://stackoverflow.com/questions/252333/calculate-product-with-linq

2016/06/13

[C#]絵文字の対策

このエントリーをはてなブックマークに追加
MySQLにインサートしようと思ったら、
MySQL Error: Incorrect string value
というエラーで止まってしまいました。

どうやら対象の文字のなかに絵文字があって、そいつが引っかかっていたようです。
1文字2バイトで表現される普通の文字と違って、絵文字は1文字が4バイト構成で表現されていて、そいつらはサロゲートペア文字というようです。

対策を考える

対策として考えたのは2つ。
  • MySQLのDBを1文字4バイトに対応させる方法
  • 絵文字を除去してしまう方法
ううむ。これって、4バイトへの対策はMySQLに丸投げしてるだけだし、絵文字除去も臭いものにフタという感じで、見方によってはどちらも雑な気がします。

そもそも絵文字なんて気にもしていなかったわけだし、晴天の霹靂のようにエラーが発生してから、どうしようかなあなんて考えている時点で遅くて、とりあえず一時しのぎの対策を講じようというのは少し危険ですね。
まあ、そんなこと言っていても始まらないので対策に飛びつく前に、少しだけ落ち着いて全体を見渡しておきましょう。
抑え込むだけではなくて、それぞれの対策の要点を理解したうえでどちらか都合のいいほうを選ぶという分には、きっと少しはマシでしょう。

DBを1文字4バイトに対応させる

MySQLは5.5以降からUTF8MB4に対応しているようなので、設定してあげればよいようです。
現在のテーブルごとの文字コードを確認するのと、指定のテーブルのキャラクターセットをutf8mb4に変更するSQLです。

-- 現在の文字コードの確認
show table status from データベース名;

-- テーブルのキャラクターセットを変更
alter table テーブル名 default character set utf8mb4;

なお、今回は除去する方針をとったので絵文字対応後の行追加は未検証です。

絵文字を除去する

絵文字を判別して除外する方法です。もしくは、よくある「〓(下駄)」のような文字に置き換えておくというのも手かもしれません。
// 絵文字を除外する
text = new string(text.Where(n => !Char.IsSurrogate(n)).ToArray());

// 絵文字を〓に置換
text = new string(text.Select(n => Char.IsSurrogate(n) ? '〓' : n).ToArray());
どちらも、やっていることは、Char配列に分解してから、一文字づつサロゲートペア文字かどうか判別しているだけ。
そのあとくっつけなおして文字列型に戻しています。

まとめ

ということで勉強になりました。
ちなみに、自分は絵文字を排除するように対応しました。
DBの設定を変えられないわけではないけれど、要件上では「めちゃくちゃ頻度が少ない」のと「残しておいて有用なことはない」と判断できたからです。
強引に除去しただけなので、文章のつながりが解らなくなるのも出るかもしれないけれど、それはやむなしってことで。

iOSやAndroidもサポートしちゃっている絵文字なので、もっと自在に扱えるようにならないといけなさそうな時流ではありますね。
しかし、この話、ちゃんと理解しようとすると深そうです。なのでこの辺で〆ておきますです…。

2016/04/25

[C#]GraphicsPathから各点の情報を取得する

このエントリーをはてなブックマークに追加
GraphicsPathはおなじみのパスデータを扱うクラスです。
描画時には、GraphicsPathはそのまま、GraphicsのDrawPathメソッド/FillPathメソッドの引数として利用することができるので、中身を意識する必要はありませんね。

GraphicsPathの各点の情報は、PathDataプロパティで取得することができます。
後述しますが、GraphicsPath.PathPointsプロパティとGraphicsPath.PathTypesプロパティで点を拾っても同じなのですが、GraphicsPath.PathDataプロパティを使うほうが効率的です。

GraphicsPathのPathDataプロパティを使う

PathDataには、Points(PointFの配列)とTypes(byteの配列)を持ちます。PointsとTypesの配列数は必ず同じになります。
Pointsで座標点を表し、Typesでは点の種類を表します。Typeとして持つのは、直線であるか曲線であるかというような情報です。

0始点
1直線
3ベジェ曲線(3点セット)
128終点

0は始点。128は終点です。128はビットフラグですので、直線からの終点は129、曲線からの終点は131となります。
ベジェ曲線は、3つ続きで表現されます。直前の点から伸びるハンドル、次の点へ伸びるハンドル、次の点座標の3つが1つのセットとなります。

各点を描画するコード

理屈はさておき、各点を描画してみます。

ソースコード
文字のアウトラインパスに、イラストレータ風にいうところのパスとハンドルを描画してみます。
Form上にPictureBoxを置いて、Buttonをトリガーにしています。

// using System.Drawing;
private void button1_Click(object sender, EventArgs e) {
    string str = "永";

    Graphics g = this.pictureBox1.CreateGraphics();
    g.SmoothingMode = SmoothingMode.HighQuality;
    

    // 1.文字のアウトラインの描画
    float fontSize = 200f;
    FontFamily fontFamily = new FontFamily("メイリオ");

    GraphicsPath path = new GraphicsPath();
    StringFormat format = new StringFormat();
    path.AddString(str, fontFamily, (int)FontStyle.Bold, fontSize, Point.Empty, format);

    g.FillPath(Brushes.LightGray, path);


    // 2.パスデータをとりだす
    PathData pathData = path.PathData;

    PointF[] points = pathData.Points;
    byte[] types = pathData.Types;
    int pointCount = points.Length;
    
    // 3.パスとハンドルを描画する
    PointF prev = PointF.Empty;
    for (int i = 0; i < pointCount; i++) {
        PointF p = points[i];
        byte t = types[i];

        if ((t & 3) == 3) { // 曲線
            this.DrawHandle(g, prev, p);

            i++;
            p = points[i];
            this.DrawHandle(g, points[i + 1], p);

            i++;
            p = points[i];
            this.DrawPathPoint(g, p);
        } else if ((t & 1) == 1 || t == 0) { // 直線
            this.DrawPathPoint(g, p);
        }

        prev = p;
    }

    g.Dispose();
}

// ポイントのサイズ
private const float POINT_SIZE = 5.0f;

// パスを描画する
private void DrawPathPoint(Graphics g ,PointF p) {
    g.FillRectangle(Brushes.Red, p.X - POINT_SIZE * 0.5f, p.Y - POINT_SIZE * 0.5f, POINT_SIZE, POINT_SIZE);
}

// ハンドルを描画する
private void DrawHandle(Graphics g, PointF p1, PointF p2) {
    g.DrawLine(Pens.Blue, p1,p2);
    g.FillEllipse(Brushes.Blue, p2.X - POINT_SIZE * 0.5f, p2.Y - POINT_SIZE * 0.5f, POINT_SIZE, POINT_SIZE);
}

このコードを実行するとこのようになります。
いい感じですね。

コードの解説
上のコードは、1.文字のアウトラインの描画、2.パスデータをとりだし、3.パスとハンドルの描画という3つの工程から出来ています。

1.文字のアウトラインの描画
「永」という文字を描画するためのコードです。
GraphicsPathのAddStringメソッドで、座標の取得対象になるパス情報を生成しています。
生成したGraphicsPathは、GraphicsのFillPathメソッドで描画しています。

2.パスデータをとりだし
PathDataプロパティにより、パス情報を取得します。
パス情報は、座標点のPointsプロパティと、点の種類を示すTypesプロパティに分かれます。

3.パスとハンドルの描画
PathData.Typesにより点の種類を判別しながら、パスとハンドルを描画しています。
パスは四角、ハンドルは丸と線を描画しています。
曲線の座標点は、一つ前の点から伸びるハンドル、次の点から伸びるハンドル、次の点座標の3つが1セットとして記録されています。

点の種類を判別するために古典的なビット判定を使っています。

(t & 1) == 1 →直線
(t & 3) == 3 →曲線

注意点:GraphicsPath.PathData.PointsプロパティとGraphicsPath.PathPointsプロパティ

GraphicsPathにはPathDataプロパティの他に、GraphicsPath.PathPointsプロパティとGraphicsPath.PathTypesプロパティがあります。
GraphicsPath.PathData.Pointsプロパティも、GraphicsPath.PathPointsプロパティも、まったく同じなのですが、ここで注意してもらいたいことがあります。

プロパティというものの性質から、単に保持している値を拾ってくるだけという印象がありますが、GraphicsPathの内部では、動的に持っている値を必要に応じて座標点の配列として出力するようにできているようです。そのため、参照するだけでもものすごく重たいのです。

例えば、ループ内で次のようなコードを書いてしまうと、参照のたびに処理が入るので結構な非効率なのです。

for (int i = 0; i < pointCount; i++) {
    PointF p = path.PathPoints[i];
    byte t = path.PathTypes[i];
  
    // ... 処理
}

GraphicsPathのPathDataプロパティを拾っておいて、そこからPointsとTypesを参照するのがよいでしょう。

PathData pathData = path.PathData;
PointF[] points = pathData.Points;
byte[] types = pathData.Types;
int pointCount = points.Length;

for (int i = 0; i < pointCount; i++) {
    PointF p = points[i];
    byte t = types[i];
  
    // ... 処理
}

以上です。

2016/04/18

[C#]BinaryReaderのReadInt32が遅いとき

このエントリーをはてなブックマークに追加

ファイルの読み書きを高速化する

テキストファイルを読み書きするにはStreamReader/StreamWriterを使えばいいのでお手軽です。
テキストファイル以外のバイナリファイルだったり、すべてを読み込むわけにはいかない大きなファイルを扱うときには、BinaryReaderを使うことになるでしょう。

また、繰り返しReadInt32を繰り返し呼んでいるところは、その度にじりじりとファイルアクセスしています。これが首を絞めるということはあまりないかもしれませんが、ファイルアクセスはメモリアクセスと比較すると当然ながら低速です。
速いに越したことはないはずなので、高速化していきましょう。
改善案としては、ReadInt32メソッドを繰り返し呼ぶところを、ReadBytesメソッドで一気読みしてからBitConverter.ToInt32で分離させます。

BitConverterとは
BitConverterはバイナリ配列(byte[])の必要なところを、型変換してくれるクラスです。
BitConverter.ToInt32メソッドは、バイナリ配列と、開始位置を指定してあげれば、その部分のint値が取り出せます。

ReadInt32だけでなく、ほかにもReadDoubleメソッドやReadSingleメソッドも用意されているので、同様の改善ができそうです。
ただし、ReadStringメソッドは、最初からデータサイズ込みの可変長なので、もう一つ奥のほうに手を伸ばさないと難しそうですね。

部分的なまとめ読みができる
データファイルが大きくて、そして全部の情報はいらないというときに、部分的にまとめ読みできるというワザができるわけです。
読み込みだけでなく、書き込みについても同様です。
当方、あんまりファイルアクセスに関わるコードを書かないので勉強になりました。

大きさが定まっていないデータの構造は、必要なところだけを読み込み/書き込みできるように工夫しておくほうが効率的ということですね。

具体例

具体的なサンプルとして、GraphicsPathのパスデータ(PathData)を書き出したバイナリファイルを読み込む処理を考えることにします。
パスデータ(PathData)の座標(Point)の配列をたっぷり持っています。

呼び出し側のコード
using System.Drawing;
using System.IO;

// 呼び出し側

fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
binaryReader = new BinaryReader(fileStream,Encoding.UTF8);

// 他の処理

int pointCount = binaryReader.ReadInt32(); // パス数
Point[] points = this.ReadPathPoints(binaryReader, pointCount);

1.BinaryReader.ReadInt32で読み込むコード
// ReadPathPointsメソッド
private Point[] ReadPathPoints(BinaryReader binaryReader, int pointCount) {
    Point[] points = new Point[pointCount];
    for (int i = 0; i < pointCount; i++) {
        int x = binaryReader.ReadInt32();
        int y = binaryReader.ReadInt32();

        points[i] = new Point(x,y);
    }
    return points;
}

2.ReadBytesとBitConverter.ToInt32で読み込むコード
// ReadPathPointsメソッド
private Point[] ReadPathPoints(BinaryReader binaryReader, int pointCount) {
    byte[] b = binaryReader.ReadBytes(pointCount << 3);
  
    Point[] points = new Point[pointCount];
    for (int i = 0; i < pointCount; i++) {
        int x = BitConverter.ToInt32(b, i << 3);
        int y = BitConverter.ToInt32(b, (i << 3) | 4);
    
        points[i] = new Point(x,y);
    }
    return points;
}


ビット演算を使っていますが、ビット演算による高速化の恩恵は誤差のレベルです。

速度評価とまとめ

ざっくり処理速度の評価です。
改良ポイントの評価としてはややアバウトですので、参考程度に思ってください。

ReadInt32呼び出しのときは、2388msでした。
改良して669msになりました。処理時間が1/4になりました。

やったね。

ということで、やや限定的ではありますが、左から右に負荷軽減できるテクニックとして勉強になりました。
おしまいです。

バイナリ・ファイルを読み書きするには?[C#、VB]
http://www.atmarkit.co.jp/fdotnet/dotnettips/669bincopy/bincopy.html

2016/04/13

[C#]DataTableからDictionaryを作る

このエントリーをはてなブックマークに追加
DataTableからDictionaryに変換する方法です。

DataTableからDictionaryに変換

Dictionaryは連想配列と呼ばれるように、キーを指定すると、対応する値を返してくれるデータ構造です。
配列を一つ一つ照合するのではなく、ハッシュテーブルという構造によって、効率よく値を引っ張ってきてくれます。
データサイズが大きく、繰り返し利用するものほど、この恩恵によるメリットが大きくなるでしょう。

また、DataTableは、DBとの親和性も高いので、大きいサイズを取り扱うことが多くなります。
任意の行を拾うのに、DataViewのRowFilterメソッドを使う方法もありますが、やや複雑ですし、ものによっては大げさになりがちです。

さくっと、値だけを拾ってきたいというときに、Dictionaryに変換しておく方法を知っておくと何かと便利です。

ソースコード
// ダミーのDataTableを作成します
DataTable table = new DataTable();
table.Columns.Add("name");
table.Columns.Add("mail");
table.Columns.Add("sex");
table.Columns.Add("age");

table.Rows.Add("杉浦 光", "sugiura_hikaru@example.com", "女", "42");
table.Rows.Add("戸塚 人志", "toduka_hitoshi@example.com", "男", "72");
table.Rows.Add("熊井 知史", "kumai_tomofumi@example.com", "男", "24");
table.Rows.Add("高木 朝陽", "takagi_asahi@example.com", "女", "44");
table.Rows.Add("平田 ちえみ", "hirata_chiemi@example.com", "女", "39");
table.Rows.Add("溝口 隆博", "mizoguchi_takahiro@example.com", "男", "50");
table.Rows.Add("椎名 長利", "shiina_nagatoshi@example.com", "男", "30");
table.Rows.Add("谷 フミヤ", "tani_fumiya@example.com", "男", "27");
table.Rows.Add("黒崎 勝久", "kurosaki_katsuhisa@example.com", "男", "69");
table.Rows.Add("浦野 真一", "urano_shinichi@example.com", "男", "54");

// DataTableをDictionaryに変換します
var dictionary = table.AsEnumerable()
    .ToDictionary(
        row => Convert.ToString(row["name"]), 
        row => Convert.ToString(row["mail"])
    );

Console.WriteLine(dictionary["溝口 隆博"]);
// ●出力結果
// mizoguchi_takahiro@example.com
長いですが、ほとんどはサンプルのためのTableを生成しているだけで、変換してるところは一行です。
var dictionary = table.AsEnumerable()
    .ToDictionary(
        row => Convert.ToString(row["name"]), 
        row => Convert.ToString(row["mail"])
    );
  • TableクラスのAsEnumerableメソッドでLinQを使えるようにします。
  • ToDictionaryで、Dictionary化させます。
  • キーと対応する値を指定します。ここではnameをキーに、mailを値に設定しています。

抽出して変換

AsEnumerableメソッド以降はLINQが使えますので、必要な条件を満たすアイテムのみを抽出することもできます。
ソースコード
// 条件を指定して変換(女性のみ抽出)
var dictionaryWomanOnly = table.AsEnumerable()
    .Where(row => Convert.ToString(row["sex"]) == "女")
    .ToDictionary(
        row => Convert.ToString(row["name"]),
        row => Convert.ToString(row["mail"])
    );

Console.WriteLine(dictionaryWomanOnly.Count());
// ●出力結果
// 3

Transform a DataTable into Dictionary C#
http://stackoverflow.com/questions/19712958/transform-a-datatable-into-dictionary-c-sharp


2016/04/05

[C#]Graphics.DrawStringとGraphicsPath.AddStringのサイズを合わせる

このエントリーをはてなブックマークに追加
さて。
Graphics.DrawStringメソッドとGraphicsPath.AddStringメソッドはどちらも文字の描画を扱うときに使うメソッドです。
困りごととして、フォントサイズの指定の仕様がそれぞれで異なっていることです。

この記事は、以前に書いた[C#]文字のアウトラインを描くへの補足です。

Graphics.DrawStringメソッドの引数は、Graphics.DrawString(String, Font, Brush, PointF, StringFormat)という指定に対して、GraphicsPath.AddStringメソッドは、GraphicsPath.AddString(String, FontFamily, Int32, Single, Point, StringFormat)です。
Graphics.DrawStringメソッドの呼び出しのFontオブジェクトのフォントサイズをそのまま、GraphicsPath.AddStringメソッドの4つ目の引数に指定すると、AddStringのほうが一回り小さく描画されます。

どちらのメソッドにも72fを指定したときの描画は下のようになります。青がGraphics.DrawString、オレンジがGraphicsPath.AddStringです。

Graphics.DrawStringメソッドとGraphicsPath.AddStringメソッドのフォントサイズの指定には、単位に違いがあり、そのため同じ数値を指定しても出力が異なってしまうようです。

Graphics.DrawStringメソッドに指定するFontは通常Point単位に対して、GraphicsPath.AddStringメソッドに指定するemSizeはPixel単位なのでした。
ということで、単位を合わせるために、変換してあげればよいわけです。

調べたうちでは、3つの方法がありました。
  1. font.Heightプロパティからemサイズを計算する
  2. font.SizeInPointをPixel単位に変換する
  3. FontオブジェクトをPixel単位で生成する
ただし、シビアな精度ではいずれも万能ではありません。
数値計算の誤差だと思いますがサイズによって、またフォントによっても、わずかながらズレが出ますのでご注意ください。

1.font.Heightプロパティからemサイズを計算する

ほしいのはpixel単位のemサイズです。

方法 : フォント メトリックを取得する‎
https://social.msdn.microsoft.com/Search/ja-JP?query=%E3%83%95%E3%82%A9%E3%83%B3%E3%83%88%20%E3%83%A1%E3%83%88%E3%83%AA%E3%83%83%E3%82%AF%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B%E2%80%8E%20&pgArea=header&emptyWatermark=true&ac=4

emサイズとは、文字がすっぽりおさまる正方形のサイズです。上の図でいうと、アセントとディセントに少しのスキマを足した大きさになります。
Fontオブジェクトのfont.Heightプロパティは、文字の高さ(一行分の高さ)をPixel単位で取れます。図では、行間に相当します。
これをもとに、Pixcel単位のフォントのemサイズを計算してあげます。

font.GetEmHeightメソッドは、フォントのデザイン単位がとれます。デザイン単位とは、emサイズを2048としたとき(フォントによりますが)の、メトリックのサイズ情報です。同じくデザイン単位の行間はfont.GetLineSpacingメソッドを使って取得できます。

float emSize = (float)font.Height * font.FontFamily.GetEmHeight(font.Style) / font.FontFamily.GetLineSpacing(font.Style);

これでPixel単位のemサイズが取れました。

ソースコード
// using System.Drawing;
string str = "Hello World!";

Graphics g = this.pictureBox1.CreateGraphics();
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
g.SmoothingMode = SmoothingMode.HighQuality;

PointF point = new PointF(5.0f, 15.0f); // 文字を描画する原点(左上)

float fontSize = 72f;
FontFamily fontFamily = new FontFamily("メイリオ");

// Graphics.DrawStringによる描画
Font font = new Font(fontFamily, fontSize); // →単位がPoint
g.DrawString(str, font, Brushes.LightBlue, point);

// GraphicsPath.AddStringによる描画
float emSize = (float)font.Height * font.FontFamily.GetEmHeight(font.Style) / font.FontFamily.GetLineSpacing(font.Style);

GraphicsPath path = new GraphicsPath();
StringFormat format = new StringFormat();
path.AddString(str, font.FontFamily, (int)font.Style, emSize, point, format);
g.DrawPath(Pens.OrangeRed, path);

g.Dispose();

実行結果
これを実行させると、このようになります。

ばっちり、ぴったりです。
青く塗られているほうがGraphics.DrawString、オレンジのフチがGraphicsPath.AddStringによる描画です。

2.font.SizeInPointをPixel単位に変換する

PointをPixelに換算する方法です。
1inch=72pointです。一度、inchに置き換えてから、pixelに変換する方法で計算できます。

float emSize = (float)g.DpiY * font.SizeInPoints / 72.0f;

3.FontオブジェクトをPixel単位で生成する

Fontオブジェクトの生成時に、単位を指定することができます。
通常、単位を省略すると単位はpointになるのですが、GraphicsUnit.Pixelを指定することで、最初からpixcel単位のフォントサイズにしておくことができます。

ソースコード
// using System.Drawing;
string str = "Hello World!";

Graphics g = this.pictureBox1.CreateGraphics();
g.SmoothingMode = SmoothingMode.HighQuality;

PointF point = new PointF(5.0f, 15.0f); // 文字を描画する原点(左上)

float fontSize = 96f;
FontFamily fontFamily = new FontFamily("メイリオ");

// Graphics.DrawStringによる描画
Font font = new Font(fontFamily, fontSize, GraphicsUnit.Pixel); // →単位がPixel
g.DrawString(str, font, Brushes.LightBlue, point);

// GraphicsPath.AddStringによる描画
GraphicsPath path = new GraphicsPath();
StringFormat format = new StringFormat();
path.AddString(str, font.FontFamily, (int)font.Style, font.Size, point, format);
g.DrawPath(Pens.OrangeRed, path);

g.Dispose();

72pointの指定は、Pixel換算だと96pxなので、fontSizeは96にしています。

わずかなズレが出ます

フォントによって、まちまちでズレが出ました。
Georgiaフォントを使用して、「1.font.Heightプロパティからemサイズを計算する」で描画。

Georgiaフォントを使用して、「2.font.SizeInPointをPixel単位に変換する」で描画。
算出方法によって、こちらはぴったり。

また大きいサイズだとぴったりなのですが、小さいサイズを指定するとぴったりにならずにズレが出ることがあります。

フォントサイズ 72ポイント、「2.font.SizeInPointをPixel単位に変換する」で描画。

フォントサイズ 36ポイント、「2.font.SizeInPointをPixel単位に変換する」で描画。

内部の計算誤差なのかなあ。フォントによるズレも、フォントサイズの指定を大きくすることで軽減されるようです。
誤差としてはたぶんだいたい1pixel以内に収まるものなのかなあと思います。
というわけで、いずれも精度的には万能ではありませんので、シビアな見当が必要な方はご注意ください…。

はい、以上です。
文字の描画サイズについてでした!

Font in 'GraphicsPath.AddString' is smaller than usual font
http://stackoverflow.com/questions/2292812/font-in-graphicspath-addstring-is-smaller-than-usual-font

2016/02/09

[C#]2つの集合の差分を調べる

このエントリーをはてなブックマークに追加
集合が2つあって、片方にはなくて、もう片方には存在する要素の差分を調べます。
例として、常用漢字にある漢字で、JIS第1水準にはない漢字を調べるコードを書きます。

常用漢字とJIS第1水準とはなにかをざっくり


常用漢字
https://ja.wikipedia.org/wiki/%E5%B8%B8%E7%94%A8%E6%BC%A2%E5%AD%97

常用漢字とは「現代の国語を書き表す場合の漢字使用の目安」として、国が選定した漢字です。2136字あります。

JIS漢字コード
https://ja.wikipedia.org/wiki/JIS%E6%BC%A2%E5%AD%97%E3%82%B3%E3%83%BC%E3%83%89

JIS漢字コードの第1水準漢字とは、日本工業規格において定められた漢字コードのうち使用頻度が高いとされている漢字です。2965字です。

常用漢字はよく使われる目安として出版物や教育に関わるようです。方や、JIS漢字コードはコンピュータ上の日本語表示の文字コードとして作られていたもの。第4水準という一度も見たことのない漢字まで一通り決められています。ざっくり理解ですが、作られた目的は全然違うようです。
しかし、どちらもよく使われる漢字同士なので似通った集合なわけですね。

常用漢字は2136字で、JIS漢字コードの第1水準漢字は2965字です。もちろん、最初から人力で調べる気はありませんので、プログラムに頼りましょう。

コード


常用漢字とJIS漢字コードの第1水準漢字のリストは、こちらのサイトを参考にさせていただきました。

漢字辞典オンライン
http://kanji.jitenon.jp/

漢字のリストは、2つともテキストファイルにしてます。
ここに置いておきます。

// using System.IO;

private void button1_Click(object sender, EventArgs e) {
        
    string joyo = this.ReadFile("常用漢字(2136字).txt");
    string jis = this.ReadFile("JIS第1水準(2965字).txt");

        // 差分を拾う
    string sabun = new string(joyo.Where(n => !jis.Contains(n)).ToArray());

    Console.WriteLine(sabun);
}

private string ReadFile(string file) {
    StreamReader reader = new StreamReader(file);
    string text = reader.ReadToEnd();
    reader.Close();

    // 改行を省いておきます
    text = text.Replace(System.Environment.NewLine,"");

    return text;
}

実行結果

上のコードを実行した結果です。

丼刹拉訃恣哺惧羞貪喩彙楷毀嗅傲慄箋瘍辣憬摯踪嘲諧錮緻曖璧籠鬱

はい。
30文字ヒットしました。

解説

解説と言うほどのことはしていませんが、コードはサンプルとなる文字列を拾ってくる部分がほとんどです。
「常用漢字(2136字).txt」、「JIS第1水準(2965字).txt」のファイルは実行ファイルの直下に必要です。
ファイルの存在確認すらしてないコードなのでご注意ください。

なんだかんだで、主要な処理は下の一行です。
string sabun = new string(joyo.Where(n => !jis.Contains(n)).ToArray());

joyoにある文字を走査して、jisに存在していない漢字を文字列として再構築しています。
  • stringはcharの配列として利用できますので、そのままLinqのWhereメソッドを利用します。
  • jis.Contains(n)は、joyo内の1文字(n)がjis内に含まれていたらtrueを返します。
  • joyo.Whereメソッドは、否定演算子(!)があるので含まれなかったものだけを戻していきます。
  • 最後に、charの配列から文字列stringを生成してます。


途中にある否定演算子(!)をなくせば、「どちらにも存在する要素を調べる」コードに早変わりします。
string sabun = new string(joyo.Where(n => jis.Contains(n)).ToArray());

こちらのコードでは、常用漢字とJIS第1水準のどちらにも該当する漢字が抽出できます。

おまけに


サンプルとして使用した常用漢字と第1水準漢字のことばかり喋っていますが…。
(知らなかった分、気づきが多かったです)

JIS第1水準は3000字近くあるので、常用漢字を包含しているかと思いきや、そういうわけでもないんですね。
というのも、常用漢字は最近(2010年)に改定されていて、差分文字の多くはそのときに追加されたもののようです。
  • 「丼」とか。たしかに近年になって頻度が増えたのでしょう。
  • 「嵐」とか「韓」とか。流行りとかではないのかもしれないけれど、文化ってそういうものですものね。
  • 「鬱(うつ)」が常用に、というのはいい気はしません。

もちろんJISの漢字コードを改定するわけにいかないですからね。


はい、以上です。
お粗末さまでした。

2015/08/11

MeCabにWikipediaの単語を追加する

このエントリーをはてなブックマークに追加
ニュースコンテンツに含まれる用語・単語を抽出するような、いわゆるキーワード抽出がやりたいなあと思ったので、その過程で試せるMeCabとWikipediaの単語登録をやってみました。
達成目標はあんまり高くなくていい。それっぽいことができたらいいかなくらいで、さくっといろいろ動かしてみるというところを目標にしています。
参考にしているサイトや記事が2011年なので、なんとも今更って技術なのかもしれません。MeCabもWikipediaもすごいってことですね。
Windows向け、使用言語はC#です。

ざっと紹介する内容は下の通りです。
  1. MeCabをインストールする
  2. Wikipediaのページタイトルを辞書登録する(ダウンロード→CSVに変換→辞書ファイルに変換→辞書ファイルを登録する→まずは動かしてみる)
  3. Wikipediaから除外したほうがいい単語を考える

MeCabを導入してみる

MeCabをインストールします。

MeCab - Wikipedia
https://ja.wikipedia.org/wiki/MeCab

MeCabとは
文章を解析して、名詞や動詞、形容詞などの文節に区切ってくれるソフトです。
形態素解析と呼ばれます。

こういう感じです。
すもももももももものうち
すもも 名詞,一般,*,*,*,*,すもも,スモモ,スモモ
も 助詞,係助詞,*,*,*,*,も,モ,モ
もも 名詞,一般,*,*,*,*,もも,モモ,モモ
も 助詞,係助詞,*,*,*,*,も,モ,モ
もも 名詞,一般,*,*,*,*,もも,モモ,モモ
の 助詞,連体化,*,*,*,*,の,ノ,ノ
うち 名詞,非自立,副詞可能,*,*,*,うち,ウチ,ウチ
EOS

MeCabのWindows版をインストールします

以下のサイトからWindows版のexeファイル「mecab-0.98.exe」をダウンロードする。
http://sourceforge.net/projects/mecab/files/

「mecab-win32」→「0.98」→「mecab-0.98.exe」をダウンロードします。
インストール時に聞いてくる文字コードは、UTF-8を選択しました。

MeCabのWindows版をインストールしてみる : 走りながら考えるエンジニア
http://handsrecs2nd.seesaa.net/article/140090025.html

MeCabを使ってみる

とりあえず実行してみる。
MeCabを実行するためにバッチファイルを作ります。

"C:\Program Files (x86)\MeCab\bin\mecab.exe" in.txt -o out.txt

in.txtは、解析した文章を入れて、テキストファイルにしたものです。
バッチファイルで上を実行すると解析結果がout.txtに出力されます。

入力テキストはニュースから拝借しました。他意はありません。

人工知能 創造的な仕事奪う?(2015年8月9日(日)掲載) - Yahoo!ニュース
http://news.yahoo.co.jp/pickup/6170034

実行結果はこのようになります。
入力テキスト
人工知能研究に、新たにディープラーニングというアイデアが出現。『ターミネーター』や『攻殻機動隊』など、空想の世界が現実味を帯びてきた。
出力結果
人工 名詞,一般,*,*,*,*,人工,ジンコウ,ジンコー
知能 名詞,一般,*,*,*,*,知能,チノウ,チノー
研究 名詞,サ変接続,*,*,*,*,研究,ケンキュウ,ケンキュー
に 助詞,格助詞,一般,*,*,*,に,ニ,ニ
、 記号,読点,*,*,*,*,、,、,、
新た 名詞,形容動詞語幹,*,*,*,*,新た,アラタ,アラタ
に 助詞,副詞化,*,*,*,*,に,ニ,ニ
ディープラーニング 名詞,固有名詞,組織,*,*,*,*
という 助詞,格助詞,連語,*,*,*,という,トイウ,トユウ
アイデア 名詞,一般,*,*,*,*,アイデア,アイデア,アイデア
が 助詞,格助詞,一般,*,*,*,が,ガ,ガ
出現 名詞,サ変接続,*,*,*,*,出現,シュツゲン,シュツゲン
。 記号,句点,*,*,*,*,。,。,。
『 記号,括弧開,*,*,*,*,『,『,『
ターミネーター 名詞,固有名詞,一般,*,*,*,ターミネーター,ターミネーター,ターミネーター
』 記号,括弧閉,*,*,*,*,』,』,』
や 助詞,並立助詞,*,*,*,*,や,ヤ,ヤ
『 記号,括弧開,*,*,*,*,『,『,『
攻 名詞,固有名詞,人名,名,*,*,攻,オサム,オサム
殻 名詞,一般,*,*,*,*,殻,カラ,カラ
機動 名詞,一般,*,*,*,*,機動,キドウ,キドー
隊 名詞,接尾,一般,*,*,*,隊,タイ,タイ
』 記号,括弧閉,*,*,*,*,』,』,』
など 助詞,副助詞,*,*,*,*,など,ナド,ナド
、 記号,読点,*,*,*,*,、,、,、
空想 名詞,サ変接続,*,*,*,*,空想,クウソウ,クーソー
の 助詞,連体化,*,*,*,*,の,ノ,ノ
世界 名詞,一般,*,*,*,*,世界,セカイ,セカイ
が 助詞,格助詞,一般,*,*,*,が,ガ,ガ
現実味 名詞,一般,*,*,*,*,現実味,ゲンジツミ,ゲンジツミ
を 助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
帯び 動詞,自立,*,*,一段,連用形,帯びる,オビ,オビ
て 助詞,接続助詞,*,*,*,*,て,テ,テ
き 動詞,非自立,*,*,カ変・クル,連用形,くる,キ,キ
た 助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
。 記号,句点,*,*,*,*,。,。,。
EOS


一応さくっと動きました。
これでも画期的で楽しいですが、分断され過ぎて残念。
人工知能、攻殻機動隊が残念ですね。
辞書として単語を追加してあげることで、この辺は回避できます。
しかし、単語一個一個を手作業で辞書を作るのは大変なので、Wikipediaの単語リストを拝借して登録してみます。

Wikipediaのタイトルリストから辞書を作る


Wikipediaのタイトルリストのダウンロード
Wikipediaから見出しリストをダウンロードします。
こちらからダウンロードします。
jawiki-latest-all-titles-in-ns0.gz
http://download.wikimedia.org/jawiki/latest/

wikipediaのタイトルリスト(jawiki-latest-all-titles-in-ns0.gz)をこちら(http://download.wikimedia.org/jawiki/latest/)からダウンロード。適当なディレクトリ(home/foo/bar)に解凍します。

fukushimuのメモ帳 [mecab] mecab辞書にwikipediaのタイトルリストを追加 [wikipedia][はてなキーワード]
http://fukushimu.blog.shinobi.jp/mecab/-mecab-%20mecab%E8%BE%9E%E6%9B%B8%E3%81%ABwikipedia%E3%81%AE%E3%82%BF%E3%82%A4%E3%83%88%E3%83%AB%E3%83%AA%E3%82%B9%E3%83%88%E3%82%92%E8%BF%BD%E5%8A%A0%20-wikipedia--%E3%81%AF%E3%81%A6%E3%81%AA%E3%82%AD%E3%83%BC%E3%83%AF%E3%83%BC%E3%83%89-

なお、Windowsでgzファイルを解凍するには、それなりの解凍ソフトが必要です。ご注意ください。
自分は、Lhaplusで解凍しました。
HoeHoe.com - Lhaplus
http://www7a.biglobe.ne.jp/~schezo/

Wikipedia見出しリストからCSVを作成
見出しリストは、155万行くらいの単語リストです。
こんなんです。

辞書ファイルに変換する前に、ちょっとだけ内容のケアをしてあげます。
CSVから辞書ファイル(.dic)を作成するのに、カンマが単語内に含まれているものは、別の文字に置き換えるなどの対応が必要なのです。
また、後述しますが、キーワードとしてあってもどうしようもない単語も含まれていますので、これを除外するにも、そのまま使うのではなくて、なんらかの置き換えが必要になってくるでしょう。

コンソールアプリとして、さくっとC#で書きました。

using System.IO;
using System.Text.RegularExpressions;

static void Main(string[] args) {
    // 引数の1個目をWikipediaのオリジナルデータとする
    
    if(args.Length == 0 || !File.Exists(args[0])){
        return; // 指定なしかファイルなし
    }

    string wikipedia = args[0];
    string csv = "wikipedia.csv";

    StreamReader reader = new StreamReader(wikipedia, Encoding.UTF8);
    StreamWriter writer = new StreamWriter(csv, false, Encoding.UTF8);
    try{
        while (!reader.EndOfStream) {
            string line = reader.ReadLine();
            int length = line.Length;

            double max = Math.Max(-36000, -400 * Math.Pow(length, 1.5));
            if (length > 1){
                line = line.Replace(',', '_'); // カンマを置換

                writer.WriteLine("{0},0,0,{1},名詞,固有名詞,*,*,*,*,{0},*,*,wikipedia_word,", line, max);
            }

        }
    }catch(Exception ex){
        Console.WriteLine(ex.Message);
    }finally{
        reader.Close();
        writer.Close();
    }

    Console.WriteLine("おわり!");
    Console.ReadKey();
}

カンマ区切りなので、カンマがあるワードだけ置換しておきます。

はい、これで、実行。
100万行以上あるので、計測用にタイマー仕込もうかなとも思ったけれど、動かしてみたら10秒くらいで終わりました。このままでいいことにします。
ただし、出来上がったwikipedia.csvファイルは170MBくらいある。なかなかでかいですね。

CSV(wikipedia.csv)を辞書(.dic)に変換
辞書ファイルをつくるには、MeCabをインストールしたところにある、mecab-dict-index.exeを使うらしい。

"C:\Program Files (x86)\MeCab\bin\mecab-dict-index.exe" -d "C:\Program Files (x86)\MeCab\dic\ipadic" -u wikipedia.dic -f utf8 -t utf8 wikipedia.csv

上を実行。
できた。
wikipedia.dicファイル、223MBでした。(サイズは目安です)

作った辞書をMeCabに登録する
eCabインストール先のetcフォルダにあるmecabrcファイルを書き換えます。

自分のところでは、C:\Program Files (x86)\MeCab\etc\mecabrcでした。
mecabrcは何の拡張子とかついてませんが、メモ帳かなんかで編集すればいいかと。

userdic = "(dicのあるディレクトリ)\wikipedia.dic"
を追加して上書きする。

MeCabを走らせてみる(検証その1)
さあ、実行してみましょう!
ぶっこんでみます。
使う文章は、先ほどと同じテキストです。

入力テキスト
人工知能研究に、新たにディープラーニングというアイデアが出現。『ターミネーター』や『攻殻機動隊』など、空想の世界が現実味を帯びてきた。
Wikipediaを辞書に登録した出力結果
人工知能 名詞,固有名詞,*,*,*,*,人工知能,*,*,wikipedia_word,
研究 名詞,サ変接続,*,*,*,*,研究,ケンキュウ,ケンキュー
に 助詞,格助詞,一般,*,*,*,に,ニ,ニ
、 記号,読点,*,*,*,*,、,、,、
新 名詞,一般,*,*,*,*,新,シン,シン
たに 名詞,固有名詞,*,*,*,*,たに,*,*,wikipedia_word,
ディープラーニング 名詞,固有名詞,*,*,*,*,ディープラーニング,*,*,wikipedia_word,
とい 名詞,固有名詞,*,*,*,*,とい,*,*,wikipedia_word,
う 感動詞,*,*,*,*,*,う,ウ,ウ
アイデア 名詞,固有名詞,*,*,*,*,アイデア,*,*,wikipedia_word,
が 接続詞,*,*,*,*,*,が,ガ,ガ
出現 名詞,サ変接続,*,*,*,*,出現,シュツゲン,シュツゲン
。 記号,句点,*,*,*,*,。,。,。
『 記号,括弧開,*,*,*,*,『,『,『
ターミネーター 名詞,固有名詞,*,*,*,*,ターミネーター,*,*,wikipedia_word,
』 記号,括弧閉,*,*,*,*,』,』,』
や 助詞,並立助詞,*,*,*,*,や,ヤ,ヤ
『 記号,括弧開,*,*,*,*,『,『,『
攻殻機動隊 名詞,固有名詞,*,*,*,*,攻殻機動隊,*,*,wikipedia_word,
』 記号,括弧閉,*,*,*,*,』,』,』
など 助詞,副助詞,*,*,*,*,など,ナド,ナド
、 記号,読点,*,*,*,*,、,、,、
空想 名詞,固有名詞,*,*,*,*,空想,*,*,wikipedia_word,
の 助詞,連体化,*,*,*,*,の,ノ,ノ
世界 名詞,固有名詞,*,*,*,*,世界,*,*,wikipedia_word,
が 接続詞,*,*,*,*,*,が,ガ,ガ
現実 名詞,固有名詞,*,*,*,*,現実,*,*,wikipedia_word,
味 名詞,一般,*,*,*,*,味,アジ,アジ
を 助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
帯び 動詞,自立,*,*,一段,連用形,帯びる,オビ,オビ
て 助詞,接続助詞,*,*,*,*,て,テ,テ
き 動詞,非自立,*,*,カ変・クル,連用形,くる,キ,キ
た 助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
。 記号,句点,*,*,*,*,。,。,。
EOS

いい感じ。
攻殻機動隊はいけました。だけど、wikipedia_wordが邪魔してますね。

たに 名詞,固有名詞,*,*,*,*,たに,*,*,wikipedia_word,
とい 名詞,固有名詞,*,*,*,*,とい,*,*,wikipedia_word,
こういうのが引っかかってしまうのとややこしい。
固有名詞じゃないのに固有名詞扱いされている語句がヒットしてますね。
こういうのは、あらかじめ除外しておくすることにしましょう。

Wikipediaの見出しリストの除外条件を考える


省ける語句がものすごいありそうなので、除外条件を考えます。

どっちかというと、わりときつめに条件設定しておくほうがいい気がします。
Wikipediaの追加単語に求めているのは、長い単語を優先的にくっつけてほしいというものだからですね。
短すぎる単語とか、マイナーすぎるのはわりとどうでもいい。
条件によって省かれてしまうものは、あんまり優先的な用語ではなかったと諦めてしまってもいい。

記号から始まっている
記号から始まる単語は除外。 ただし、拡張子の登録はあってもいいので.だけ許す。 [!"#$%&'()\*\+\-\.,\/:;<=>?@\[\\\]^_`{|}~]
数字は除外
ただの数字が単語としてあるので除外。 ただし0から始まるものだけ残す。007とかあるから。
_アンダーバー+(曖昧さ回避)は除外
曖昧さ回避のためのページ用のタイトルです。 いらないので除外。
_アンダーバー+([カテゴリ])は除外



括弧内でカテゴリが指定されているようです。
これで単語を登録していても無駄なので除外。

_ アンダーバー入りは除外



Wikipediaのタイトルルールなのでしょう。アンダーバーでサブタイトルを指定していうようです。
このページタイトルで単語がヒットすることは絶対にないと言えるので、アンダーバー入りは除外としてしまいます。
(曖昧さ回避)、([カテゴリ])も含めて除外対象にしてしまいます。

数字+年、数字+年代は除外しない



1030年とかが固有名詞として登録されるのはどうかと思うけれど、キーワードとして考えるなら一単語として抽出できるほうが有用な気がするので残す。

数字+年+出来事は除外



「1935年全豪テニス選手権」というタイトルがあるようです。
こういうのは除外することにします。単語として絶対ヒットしないから。

数字+月+数字+日(日付)は除外しない



固有名詞としてはもちろん不適切。違和感ありますが、西暦と同じで、キーワードとして考えるなら抽出できるほうがいいので残す。

ひらがな2文字を省く



ひらがな2文字で、Wikipediaにしかない言葉というのをあんまり期待しなくてよさそう。
もともとある辞書で網羅できているものとする。
上の出力結果でも出ているけれど、「たに」「とい」を辞書から除外する。

条件のとこだけだと、ソースはこんな感じですかね。

if (length <= 1){

} else if (line.IndexOf("_") >= 0) {
    // アンダーバー入りは除外
} else if (Regex.IsMatch(line, "^[1-9][0-9]*$")) {
    // 数字
    // 0から始まる数字は残す
} else if (Regex.IsMatch(line, "^[0-9]+[年][代]?$")) {
    // 数字+年
    writer.WriteLine("{0},0,0,{1},名詞,固有名詞,*,*,*,*,{0},*,*,wikipedia_year,", line, max);
} else if (Regex.IsMatch(line, "^[0-9]+[年][^代]+$")) {
    // 数字+年+文字
} else if (Regex.IsMatch(line, "^[0-9]+[月][0-9]+[日]$")) {
    writer.WriteLine("{0},0,0,{1},名詞,固有名詞,*,*,*,*,{0},*,*,wikipedia_date,", line, max);
    // 日付
} else if (Regex.IsMatch(line, "^[!\"#$%&'()\\*\\+\\-\\,\\/:;<=>?@\\[\\\\\\]^_`{|}~]")) {
    // 記号から始まっている(.「ドット」以外)
} else if (Regex.IsMatch(line, "^[ぁ-ん]{2}$")) {
    // ひらがな2文字
} else { 
    writer.WriteLine("{0},0,0,{1},名詞,固有名詞,*,*,*,*,{0},*,*,wikipedia_word,", line, max);
}

除外条件は、年や日付を単語に入れるかどうかなど好みで決めればいいかと。条件はあくまでご参考に。

コストについて

double max = Math.Max(-36000, -400 * Math.Pow(length, 1.5));

この謎の式は参照元のサイトにならって、そのまま使っています。どうやらコストと呼ばれるものらしいです。
値が小さいほど、出現率が高い。
つまりこの式は、長い単語ほど重宝しようという意味のようです。なんか解らないでもない。
しかし、数式自体はよく解らないのでそのまま触らないことにします。
http://mecab.googlecode.com/svn/trunk/mecab/doc/dic.html
コストは,その単語がどれだけ出現しやすいかを示しています. 小さいほど, 出現しやすいという意味になります. 似たような単語と 同じスコアを割り振り, その単位で切り出せない場合は, 徐々に小さくしていけばいいと思います。


はい、実行。
まあまあ。
20秒くらい掛かりました。
できたファイルは130MBくらいあるなあ。気にしてても仕方ないからいいや。
同じように、これで辞書ファイルをつくります。

MeCabを走らせてみる(検証その2)
そして、さっきのテキストもう一回実行してみる。

入力テキスト
人工知能研究に、新たにディープラーニングというアイデアが出現。『ターミネーター』や『攻殻機動隊』など、空想の世界が現実味を帯びてきた。
Wikipedia辞書を使った出力結果
人工知能 名詞,固有名詞,*,*,*,*,人工知能,*,*,wikipedia_word,
研究 名詞,サ変接続,*,*,*,*,研究,ケンキュウ,ケンキュー
に 助詞,格助詞,一般,*,*,*,に,ニ,ニ
、 記号,読点,*,*,*,*,、,、,、
新た 名詞,形容動詞語幹,*,*,*,*,新た,アラタ,アラタ
に 助詞,副詞化,*,*,*,*,に,ニ,ニ
ディープラーニング 名詞,固有名詞,*,*,*,*,ディープラーニング,*,*,wikipedia_word,
という 助詞,格助詞,連語,*,*,*,という,トイウ,トユウ
アイデア 名詞,固有名詞,*,*,*,*,アイデア,*,*,wikipedia_word,
が 接続詞,*,*,*,*,*,が,ガ,ガ
出現 名詞,サ変接続,*,*,*,*,出現,シュツゲン,シュツゲン
。 記号,句点,*,*,*,*,。,。,。
『 記号,括弧開,*,*,*,*,『,『,『
ターミネーター 名詞,固有名詞,*,*,*,*,ターミネーター,*,*,wikipedia_word,
』 記号,括弧閉,*,*,*,*,』,』,』
や 助詞,並立助詞,*,*,*,*,や,ヤ,ヤ
『 記号,括弧開,*,*,*,*,『,『,『
攻殻機動隊 名詞,固有名詞,*,*,*,*,攻殻機動隊,*,*,wikipedia_word,
』 記号,括弧閉,*,*,*,*,』,』,』
など 助詞,副助詞,*,*,*,*,など,ナド,ナド
、 記号,読点,*,*,*,*,、,、,、
空想 名詞,固有名詞,*,*,*,*,空想,*,*,wikipedia_word,
の 助詞,連体化,*,*,*,*,の,ノ,ノ
世界 名詞,固有名詞,*,*,*,*,世界,*,*,wikipedia_word,
が 接続詞,*,*,*,*,*,が,ガ,ガ
現実 名詞,固有名詞,*,*,*,*,現実,*,*,wikipedia_word,
味 名詞,一般,*,*,*,*,味,アジ,アジ
を 助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
帯び 動詞,自立,*,*,一段,連用形,帯びる,オビ,オビ
て 助詞,接続助詞,*,*,*,*,て,テ,テ
き 動詞,非自立,*,*,カ変・クル,連用形,くる,キ,キ
た 助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
。 記号,句点,*,*,*,*,。,。,。
EOS
ほぼほぼいい感じです!
いやー、おもしろーい!

単語の除外条件やら、キーワード抽出するためにもっと都合のいいやりかたがあると思うので、さらにここからの調整は必要かもしれません。
しかし、まずはさくっと動かすことができたのでよしとします。

以上です。ではでは。

2015/05/17

[C#]DataTableをさくっとつくるには

このエントリーをはてなブックマークに追加
何かと便利なDataTableです。
DataTableを扱って何かするときに、慣れれば難しいところはありませんが、とにかく冗長的な印象があります。
まずダミーデータの準備に労力が費やされてしまうことがあって、結構面倒なのです。
そういうDataTableについて、ちょこっと記法に気を付けることで、記述内容を大幅に削減してしまおうというのがこのエントリのテーマです。

DataTableを作成するためのコード

DataTableの作成は①DataColumnを追加して列(スキーマ)を準備、そのあと②データを格納させるDataRowを追加する必要があります。

DataColumnを追加して列(スキーマ)を準備
まず、DataTableのインスタンスをつくる。これはそのまま。
DataTable table = new DataTable();

列となるDataColumnを追加します。
個人情報のようなものが格納されるとして、普通は、こんな感じになりますね。
table.Columns.Add(new DataColumn("name"));
table.Columns.Add(new DataColumn("ruby"));
table.Columns.Add(new DataColumn("sex"));
table.Columns.Add(new DataColumn("age"));
table.Columns.Add(new DataColumn("birthday"));
table.Columns.Add(new DataColumn("married"));
table.Columns.Add(new DataColumn("prefecture"));

はい、これ。
工夫すると、こう書けます。
string[] columnList = { "name", "ruby", "sex", "age", "birthday", "married", "prefecture"};
table.Columns.AddRange(columnList.Select(n => new DataColumn(n)).ToArray());
文字列の配列をもとに、DataColumnの配列を作って一気に追加しています。
たった2行に収まりました。もちろん強引に1行にもできます。

データを格納させるDataRowを追加する
次に、行となるDataRowを追加します。
DataTableに準備した列(スキーマ)に合わせた行を作成するので、DataTableのNewRowメソッドを呼び出して、新しい行になるDataRowを拾ってあげます。
DataRowに設定する値を入れて、改めて、DataTableのRowsプロパティに追加する。
(覚えたてのとき、自分にはこの操作が意味不明でした)
コードにすると、こんな感じです。
DataRow row = table.NewRow();
row["name"] = "三村 瑠璃亜";
row["ruby"] = "みむら るりあ";
row["sex"] = "女";
row["age"] = "32";
row["birthday"] = "1982/7/16";
row["married"] = "既婚";
row["prefecture"] = "茨城県";
table.Rows.Add(row);
これで1個のデータです。結構なカタマリになります。

DataTableのRowsプロパティのAddメソッドには、オーバーロードとしてobject[]を指定することができます。
なので、上のカタマリは、下記のように書いてもいいのです。
table.Rows.Add("三村 瑠璃亜", "みむら るりあ", "女", "32", "1982/7/16", "既婚", "茨城県");
はい、1行です。
行に追加した順番に気を付ける必要がありますが、テストパターンとして内容に変化を付けるときなどは、こちらのほうが見やすいようにも思います。
もちろん、テストデータらしく、hogehogeとかaaaとかだったら、どの列のデータなのかは見失いやすいので注意が必要ですが…。

サンプルコード
ということでざっとDataTableのダミーデータを作成するためのサンプルコードを載せておきます。

// DataTableのダミーデータを作成する

// DataColumnを追加して列(スキーマ)を準備
DataTable table = new DataTable();
string[] columnList = { "name", "ruby", "sex", "age", "birthday", "married", "prefecture"};
table.Columns.AddRange(columnList.Select(n => new DataColumn(n)).ToArray());

// データを格納させるDataRowを追加する
table.Rows.Add("三村 瑠璃亜", "みむら るりあ", "女", "32", "1982/7/16", "既婚", "茨城県");
table.Rows.Add("山崎 隆", "やまざき たかし", "男", "32", "1982/10/3", "未婚", "東京都");
table.Rows.Add("滝 慢太郎", "たき まんたろう", "男", "31", "1983/6/30", "未婚", "福岡県");
table.Rows.Add("石塚 浩正", "いしづか ひろま", "男", "29", "1985/3/1", "既婚", "熊本県");
table.Rows.Add("中西 光", "なかにし ひかる", "男", "55", "1959/2/22", "既婚", "福島県");
コピペコピペでNewRowメソッド呼び出しによる長く平坦な記述よりも、よっぽど表データっていう感じがしますね。

2015/05/09

[C#]クエリ文字列を操作する

このエントリーをはてなブックマークに追加
ご存じ、URLの後ろにくっついてるパラメータをクエリ文字列と言います。

クエリ文字列を扱うときにDictionaryに変換する自作関数をセコセコ作ったり、またくっつけるための関数を作ったり、そういう操作をしていたのですが、HttpUtility.ParseQueryStringメソッドでまるっと操作できるようです。

HttpUtility.ParseQueryStringメソッドを使う

HttpUtility.ParseQueryStringメソッドはクエリ文字列を引数に、NameValueCollectionオブジェクトを返します。
NameValueCollectionは、Dictionary型のようにキーより値が参照できます。
HttpUtility.ParseQueryStringメソッドが返すNameValueCollectionは、ToStringメソッドで、&と=でつなげたクエリ文字列がそのまま返ります。部分的に値を変更して改めてクエリ文字をつくるような操作もお手の物です。

サンプルコード
// using System.Web;
// using System.Collections.Specialized
// ダミーのURLです
string url = "http://www.hogehoge.co.jp/index.htm?var1=%u3042%u3044%u3046%u3048%u304a&var2=abc&var3=12345";
Uri uri = new Uri(url);
string queryString = uri.Query;

Console.WriteLine(queryString);
// ?var1=%25u3042%25u3044%25u3046%25u3048%25u304a&var2=abc&var3=12345

// ①クエリ文字列からNameValueCollectionを生成
NameValueCollection query = HttpUtility.ParseQueryString(queryString,Encoding.UTF8);

// ②クエリの値を確認
foreach(string key in query.Keys){
    Console.WriteLine(key + "=" + HttpUtility.UrlDecode(query[key]));
}
// var1=あいうえお
// var2=abc
// var3=12345

// ③クエリの値を変更
query["var1"] = "かきくけこ";

// ④クエリ文字列を再生成
Console.WriteLine(query.ToString());
// var1=%u304b%u304d%u304f%u3051%u3053&var2=abc&var3=12345

コードの解説
①クエリ文字列からNameValueCollectionを生成
ダミーURLをもとに、HttpUtility.ParseQueryStringメソッドを使用してクエリ文字列からNameValueCollectionを生成します。

②クエリの値を確認
NameValueCollectionオブジェクトに値がどのように格納されているかクエリの値を確認します。

③クエリの値を変更
キー(var1)に対応するクエリの値を変更します。

④クエリ文字を再生成
NameValueCollectionオブジェクトのToStringメソッドを呼び出すと、=と&で結合されたクエリ文字列が生成されます。


NameValueCollectionはSystem.Collections.Specializedというあまり聞きなれない名前空間にあります。
HttpUtility.ParseQueryStringメソッドのために、using System.Web;が必要です。(要System.Web.dll参照)

NameValueCollectionをDictionaryに変換する

NameValueCollectionはDictionaryに似ているのですが、Linqが使えません。
何かとDictionary形式のほうが使い勝手がいいとお考えの人は、変換してあげてください。

Dictionary<string,string> dic = query.Keys.Cast<string>().ToDictionary(n => n, m => query[m]);

それではよきクエリ文字ライフを!

2015/04/03

[C#]全角数字/アルファベットを半角に変換する

このエントリーをはてなブックマークに追加
全角の数字、全角のアルファベットを半角に変換したいなー、と思った。
全角英数はもっさりしてて読みにくい。
なんか、いかにも一発でできるメソッドが用意されてそうなのだけどないらしい。
なので自力でコードを書きました。

全角数字、全角アルファベットを半角にする


変換テーブルを作って自作することにします。
がっつり組んで、がりがり動かしているだけなので、種も仕掛けもありません。

書いたのは、見本となるテキストの文字を走査して、変換テーブルと照らし合わせて置換していくというコードです。
もしくは、変換テーブルのほうを走査してReplaceしていくようなコードも書けそうです。今回は1文字づつの走査でいいので前者のほうが効率よさそうですね。

使う文章
※たまたま拾ってきたニュース記事です。他意はありません。
一方、日本で「VAIO Phone」が正式に発表された際、パナソニックのスマホ「ELUGA U2」と「ロゴが違うだけではないか」、「ELUGA U2のコピーに近いにもかかわらず、価格はELUGA U2より高い」などと批判の声があがったことを伝えた。
「VAIO Phone」の販売出だしつまづく・・・「惨敗としか表現できない」=中国
http://headlines.yahoo.co.jp/hl?a=20150402-00000179-scn-sci

ソースコード
string text =
    "一方、日本で「VAIO Phone」が正式に発表された際、" +
    "パナソニックのスマホ「ELUGA U2」と「ロゴが違うだけ" +
    "ではないか」、「ELUGA U2のコピーに近いにもかかわら" +
    "ず、価格はELUGA U2より高い」などと批判の声があがっ" +
    "たことを伝えた。";

// 変換テーブルをつくる
Dictionary conv = new Dictionary() {
    {'1','1'},{'2','2'},{'3','3'},{'4','4'},{'5','5'},
    {'6','6'},{'7','7'},{'8','8'},{'9','9'},{'0','0'},
    {'A','A'},{'B','B'},{'C','C'},{'D','D'},{'E','E'},
    {'F','F'},{'G','G'},{'H','H'},{'I','I'},{'J','J'},
    {'K','K'},{'L','L'},{'M','M'},{'N','N'},{'O','O'},
    {'P','P'},{'Q','Q'},{'R','R'},{'S','S'},{'T','T'},
    {'U','U'},{'V','V'},{'W','W'},{'X','X'},{'Y','Y'},
    {'Z','Z'},
    {'a','a'},{'b','b'},{'c','c'},{'d','d'},{'e','e'},
    {'f','f'},{'g','g'},{'h','h'},{'i','i'},{'j','j'},
    {'k','k'},{'l','l'},{'m','m'},{'n','n'},{'o','o'},
    {'p','p'},{'q','q'},{'r','r'},{'s','s'},{'t','t'},
    {'u','u'},{'v','v'},{'w','w'},{'x','x'},{'y','y'},
    {'z','z'},
    {' ',' '},
};

string s = new string( text.Select( n => (conv.ContainsKey(n) ? conv[n] : n )).ToArray() );

Console.WriteLine(s); // 出力結果を表示

出力結果
一方、日本で「VAIO Phone」が正式に発表された際、パナソニックのスマホ「ELUGA U2」と「ロゴが違うだけではないか」、「ELUGA U2のコピーに近いにもかかわらず、価格はELUGA U2より高い」などと批判の声があがったことを伝えた。

いいじゃん! そんでVAIO Phoneがなんですって?
ニュースのネタの内容についてはあんまりわかりませんが、ずいぶんと読みすくなったね。

string s = new string( text.Select( n => (conv.ContainsKey(n) ? conv[n] : n )).ToArray() );
結局、主要コードはこの1行なので、ざっと。
  1. Selectでtextを一文字づつ走査。
  2. conv.ContainsKey(n) ? conv[n] : n で変換テーブルに該当したのを返す。
  3. ToArray()でchar[]にして、new string(char[])でstringが返ります。

置換対象は全角数字と全角アルファベット(大文字/小文字)、あと全角スペースの計63文字(10+26+26+1)としています。
全角スペースについては、先頭に来るやつは新しい段落としての意味があるので単純置換はまずいですね。

ConvStrを使って変換するやりかた


文字列を全角/半角に変換するには?(VB.NET関数活用)
http://www.atmarkit.co.jp/fdotnet/dotnettips/265vbhanzen/vbhanzen.html

Strings.StrConvでは、全角カタカナも半角カタカナになってしまうようで、なんていうか、やりすぎなのです。
半角カタカナなんて使う場面はもうないのです。

Strings.StrConvを使うには、Microsoft.VisualBasicの参照が必要です。

ソースコード
// using Microsoft.VisualBasic;

string text =
    "一方、日本で「VAIO Phone」が正式に発表された際、" +
    "パナソニックのスマホ「ELUGA U2」と「ロゴが違うだけ" +
    "ではないか」、「ELUGA U2のコピーに近いにもかかわら" +
    "ず、価格はELUGA U2より高い」などと批判の声があがっ" +
    "たことを伝えた。";

string s = Strings.StrConv(text, VbStrConv.Narrow, 0);

Console.WriteLine(s);

出力結果
一方、日本で「VAIO Phone」が正式に発表された際、パナソニックのスマホ「ELUGA U2」と「ロゴが違うだけではないか」、「ELUGA U2のコピーに近いにもかかわらず、価格はELUGA U2より高い」などと批判の声があがったことを伝えた。

パナソニックのスマホて。なんか、すごいおちょぼ口な感じ。

正規表現を使ったやりかた


正規表現を使ったやりかたもあります。
すべてにStrings.StrConvするのではなくて、該当文字だけがStrings.StrConvの恩恵を受ければいいじゃないかという考え方ですね。

全角英数字のみを半角に変換するには?[C#、VB]
http://www.atmarkit.co.jp/fdotnet/dotnettips/1051anhankaku/anhankaku.html

おしまいですー。

2015/02/25

[C#]パスワードのためのアルファベットと数字混在のランダムな文字列の生成する

このエントリーをはてなブックマークに追加
パスワード生成とかに使われそうな感じのやつです。
IWg7G9mN←こういうのが出力できるとOK。

最初はランダムに数字を取得して文字コードから文字列を取得したらいいかなと考えましたが、単に候補文字をざっと準備しておいてランダムに選ぶほうが明瞭ですね。
アルファベット、数字だけじゃなくて、ひらがなカタカナ、漢字混在で作るにしても候補文字を準備しとくほうが簡単そうです。

文字の配列要素を用意しとく

候補文字を並べて準備しておいて、単純にランダムに選ぶというコードです。
非効率ではあるけれど、あきらかにメンテナンスもしやすいし汎用性もありそう。

private static string STR_GROUP_NUMBER = "0123456789";
private static string STR_GROUP_ALPHABET_LARGE = "ABCDEFGHIJKLNMOPQRSTUVWXYZ";
private static string STR_GROUP_ALPHABET_SMALL = "abcdefghijklnmopqrstuvwxyz";

private static Random _r = null;

// 引数(count)は出力文字の桁数です
public static string RandomString(int count) {
    if (_r == null) {
        _r = new Random();
    }
    string group = STR_GROUP_NUMBER 
        + STR_GROUP_ALPHABET_LARGE 
        + STR_GROUP_ALPHABET_SMALL;
        
    char[] list = new char[count];
    for (int i = 0; i < count; i++) {
        int num = _r.Next(0, group.Length); // ランダムに0~nの数値を返す
        list[i] = group[num];
    }
    return new string(list);
}
数字を出さないようにするためにはSTR_GROUP_NUMBERを除外すればいいだけ。
あるいは記号を追加するのも、文字列を足せばいいだけですね。

Randomクラスはインスタンス生成時の時刻から任意の乱数が選ばれます。
インスタンスを毎時生成すると、間髪入れず連続で呼び出したときは乱数が被ってしまうので、それを回避するために一回生成したものを使いまわすようにしています。

出力結果

桁数8桁のランダム文字を出力してみます。

for (int i = 0; i < 10;i++) {
    Console.WriteLine(RandomString(8));
}

IWg7G9mN
WxHPMkPP
qTu9oblq
WDKokf1J
19mw9TSh
MQlw0cIZ
IjttEtHh
kpa2ozbq
wyMExSGN
1DlMppqu

ばっちりです。

2015/02/22

[C#]ダミーデータをDataTableにして使う

このエントリーをはてなブックマークに追加
デバックやるにしても、何か学習するにも、hogehogeとかaaaとかダミーとして使ってしまうことがあります。aaaとかbbbとか。面倒くさいものなので動けばいいからと思って進めてると、ログ系とか、DB系でけっこうはまるのです。
そこでダミーデータをさくっと展開するためのサンプルコードを置いておきます。(解説っていうよりサンプルコードです)
テストとか、とりあえずなんか動かしてみるのにそれっぽいデータを用意するときに使えると便利です。

ダミーデータを準備する

今回使用するダミーデータは、なんちゃって個人情報さんから引っ張ってきます。
なんちゃって個人情報は、テスト用の個人情報をダミーで生成してくれるというオンラインサービスです。(つい最近知りました)
なんちゃって個人情報
http://kazina.com/dummy/

XMLにして出力件数を指定。今回の出力件数は200くらいにしました。

なんちゃって生成をクリック。
はい、XMLができました。

ファイルの名前を付けて保存からXMLを保存します。

※ちなみに、GoogleChromeでやるとXML作成まではやってくれましたが、そのままファイル保存すると失敗してしまいました。HTMLのなれの果てのようなファイルしかできないときは、IEでやってみてください。
↑失敗です

ダミーデータを展開する

生成されるXMLはrecordの繰り返しで構成されています。
1つのrecordは下のようになっています。構成は生成時のオプションによって変わりますので注意。

<record>
    <name>矢田 桃子</name>
    <ruby>やだ ももこ</ruby>
    <mail>yada_momoko@example.com</mail>
    <sex>女</sex>
    <age>68</age>
    <birthday>1947/1/16</birthday>
    <married>既婚</married>
    <prefecture>大阪府</prefecture>
    <keitai>080-1669- 474</keitai>
    <carrier>ドコモ</carrier>
    <curry>左ルー・別口派</curry>
</record>

DataSetのReadXmlメソッドつかって一発で読み込んでもいいんですが、XMLを展開して読み込むことにします。
/*
using System.Xml.Linq;
using System.Xml.XPath;
using System.Data;
using System.IO;
*/
// なんちゃって個情報をDataTableに展開する
private DataTable LoadDummyData(string dummyXml) {
  DataTable table = new DataTable();
  string[] columns = new string[] { 
  "name", "ruby", "mail", "sex", "age", "birthday", 
  "married","prefecture","keitai", "carrier","curry" };

  // ①DataTableのカラムをつくる
  table.Columns.AddRange(columns.Select(n => new DataColumn(n)).ToArray());

  // ②Xml読み込み準備
  StreamReader reader = new StreamReader(dummyXml, System.Text.Encoding.GetEncoding("shift-jis"));
  XDocument xDoc = XDocument.Load(reader);

  // ③recordエレメントを抽出
  IEnumerable<XElement> records = xDoc.XPathSelectElements("//record");
    
  foreach (XElement record in records) {
    DataRow row = table.NewRow();
                
    // ④DataRowに値をセットしていく
    columns.ToList().ForEach(n => row[n] = record.XPathSelectElement("./" + n + "").Value);
    table.Rows.Add(row);
  }
        
  return table;
}

// DataSetから読み込む方法
private DataTable LoadDummy(string dummyXml) {
  StreamReader reader = new StreamReader(dummyXml, System.Text.Encoding.GetEncoding("shift-jis"));

  DataSet ds = new DataSet();
  ds.ReadXml(reader);

  return ds.Tables[0];
}


ダミーデータを使ってみる

使ってみましょう。
GridViewのDataSourceにセットして、DataBindすれば。
string dummyXml = Path.Combine(this.Server.MapPath("dummy_cgi.xml"));

GridView1.DataSource = this.LoadDummyData(dummyXml);
GridView1.DataBind();
はい、このとおり。

以上です。

2015/02/20

テンキーを回転させる計算式

このエントリーをはてなブックマークに追加
なんていうか練習問題です。頭の体操と思っていただければいいです。
テンキーというのは、1-9までの数字が、ちょうど5を中心に八方を示す配置ですね。下から123と三つずつ、三列並んでいるやつです。
これをぐるっと90度づつ回転させることを考えてみましょう。
if文を9つ(この場合はswitch文だけど)書いてもいいけど。任意のキーnを回転させたとして、計算式で求めてみよう。

右に90度回転させる

右に90度がっと回したとき。
こういうふうにしたい。

計算式
計算式は3掛けて、10でわったあまり。
n=(n * 3) % 10;

180度回転させる

180度回転。反転です。
という感じ。

計算式
計算式は、10引くだけ。
n=10-n;

左に90度回転させる

っていう。

計算式
右に90度回転させて、それを180度回転させると、左に90度回転させたことになる。
n=10-(n*3)%10;

左に90度回転させる(別解)

もしくは、右に回転するときは3掛けたので、その逆算、3でわってあげればいい。
ただし十のくらいが面倒で、3でわれない数字をわれるように十のくらいを補間する必要があります。2桁の数字は、桁のそれぞれの数字を足して3でわれるなら、元の数字も3でわれるという性質があります。

計算式
ごちゃごちゃしますが、下の計算でOKです。(上のほうが簡単ですね)
n = ((2 - (n - 1) % 3) * 10 + n) / 3;

まとめ

ずいぶん前にこういう感じのプログラムを書いたのを、昨日ふと思い出したので書きました。
ものすごく狭いノウハウなので実践で使うことはまずないだろうけれど、頭の体操としての練習課題としては楽しい問題です。
計算式が、こんなんでいいの?っていうくらい簡潔なのがいい。

2015/02/17

[C#]昇順の配列を作る

このエントリーをはてなブックマークに追加
単純に整数の{1,2,3,4,5,6,7...n}までの配列を作るやりかた。
Enumerable.Rangeが便利です。

自力で配列をつくるやりかた

配列の初期化のときに指定しちゃってもいいんだろうけれど。
int[] list = new int[]{1,2,3,4,5,6,7,8,9,10};

自力でやるときは下のようなコードですね。
int[] list = new int[10];
for(int i=0;i<10;i++){
     list[i] = i;
}
別にいいんだけど、なんかなー。ってことで。

Enumerable.Rangeが便利!

Enumerable.Rangeメソッドというのが使えます。
このメソッドはEnumerable.Range(1,10)で戻り値が1~10までを順番に返すIEnumerableができます。
単なるint配列じゃなくて、IEnumerableなので、何やら加工するのにも有用です。

・2倍すると偶数配列になります
Enumerable.Range(1,10).Select(n=> n*2);

2015/02/06

[C#]try-catch-finallyの通る順番

このエントリーをはてなブックマークに追加

try-catch-finallyの通る順番

古典的ですが、try-catchの通る順番について検証をしてみました。
catch内でthrowしてもfinallyにいくのか?とか。知ってるけど、言われるとちょっと不安になってしまった。やってみりゃいいじゃん、ということで、やってみた。
もちろん結果は、想定どおり。不信がる必要はありませんでした。
try中にExceptionが発生すると、catch行って、finally実装されてたらそこにいくわけです。そりゃそれ以外の結果だと、納得いかないでしょうよ。
ソースコード
// 二重のtry-catchにて、奥のcatch内にてthrowしてもfinallyを通る順番に変化はないのかの検証
Console.WriteLine("0");
try {
    Console.WriteLine("1");

    try {
        Console.WriteLine("2");
        throw new Exception(""); // ここでthrow入ります
    } catch (Exception ex) {
        Console.WriteLine("3");
    throw ex;
    } finally {
        Console.WriteLine("4");
    }

} catch (Exception ex) {
    Console.WriteLine("5");
} finally {
    Console.WriteLine("6");
}
Console.WriteLine("7");
出力結果









はい、安心です。
出力結果は、予想通り。ちゃんと動いてくれてます。

yield returnと組み合わせたときのfinallyの呼び出し

おまけにyield returnと組み合わせたときのfinallyの呼び出しってどうなんだろうと。yield returnは呼び出し時に実行される遅延実行なので、打ち切られたときとかもちゃんと呼ばれるのかと。
これもめんどくさそうですが気にしたほうがよさそうです。

ソースコード
private void button1_Click(object sender, EventArgs e) {
    Console.WriteLine("0");

    IEnumerable<int> en = this.Hoge();
    Console.WriteLine("1");

    foreach (int i in en) {
        if (i == 0) {
            Console.WriteLine("3");
        } else if (i == 1) {
            Console.WriteLine("5");
        }
    }
    Console.WriteLine("7");
}

private IEnumerable<int> Hoge() {
    try {
        Console.WriteLine("2");
        yield return 0;

        Console.WriteLine("4");
        yield return 1;

    }finally{
        Console.WriteLine("6 呼ばれた!"); // これがいつ呼ばれるのか?
    }
}

実行結果






6 呼ばれた!


はい、こんな感じですね。3のところでreturnしちゃっても、そのあとちゃんと6が呼ばれます。

2014/12/19

[C#]ConfigurationSettingsは古いからConfigurationManagerを使えという警告

このエントリーをはてなブックマークに追加
いまさらのやつですが。

ConfigurationSettingsに出る警告

App.ConfigのappSettings利用時、ConfigurationSettingsに出る警告です。
'System.Configuration.ConfigurationSettings.AppSettings'は古い形式です:
"This method is obsolete, it has been replaced by System.Configuration!System.Configuration.ConfigurationManager.AppSettings"
ConfigurationSettingsは古いから、ConfigurationManagerを使えとのことですね。
だけどインテリセンスにConfigurationManagerというのが見当たらない。ConfigurationManagerと打ち込んでも、コンパイルは通らない。
(→ConfigurationManagerは名前空間にない、アセンブリ参照が不足してるとなります)

別に動くしいいかな、なんてあきらめてしまって、古いままでもそのまま放置してあったりします。
警告が出てる以上、そのままでいいということはないと思うので何とかしときましょう。

解決方法
参照設定に、System.Configration.dllを追加させると解決します。

毎回プロジェクト作成のたびにやる必要もないのだけど、解ってるだけにわざわざって感じですね。
あらかじめ参照追加されるような設定はないかな?

2014/09/08

[C#]LINQで複数条件のファイルを抽出する

このエントリーをはてなブックマークに追加
Directory.GetFilesの引数ではpatternがワイルドカード(*と?)しか使えないので、複数の拡張子を条件に抽出するようなとき、たとえば*.jpg|*.pdf|*.epsというような書き方ができません。

複数条件のファイルの抽出

LINQを使うことで簡潔でした。LINQで書く必要はあんまりないかもしれませんが、まあ練習用です。
二つのデータの集合を照合させるという意味としても直接的な気がしてます。
string path = @"C:\Windows\Temp";
string[] patterns = {".jpg",".pdf",".eps"};
string[] files = Directory.GetFiles(path, "*.*");

var ret = files.Where(file => patterns.Any(pattern => file.ToLower().EndsWith(pattern)));
一文で書けました。

使ってるLINQメソッド
Where条件に当てはまるものを抽出
Anyいずれかが条件に当てはまるとtrueを返す

ファイルが条件に適合するかどうかの判定は、下の文です。
file.ToLower().EndsWith(pattern)
この部分を変更することで、正規表現に対応したりいろいろできそうです。

なお、patternsは適合率の高い拡張子を左に置くほうが、先に判定されるので処理効率がいいはずです。

サブフォルダ内のファイルも対象にする

おまけです。サブフォルダ内のファイルも対象にするには、DirectoryのGetFilesメソッドの引数にSearchOption.AllDirectoriesを指定してあげます。
string[] files = Directory.GetFiles(path, "*.*",SearchOption.AllDirectories);

2014/09/06

[C#]LINQを使った二重ループ(ネスト)の書き方

このエントリーをはてなブックマークに追加
ループ処理のなかでさらにループするような処理です。

LINQで二重ループ

LINQで多重ループするコードの書き方です。
例として、座標系に均等な点(Point)を作るコードを書きます。

// 二重ループの書き方
int[] list = { 10,20,30,40,50 };

// ①foreachを使う場合
List<Point> points1 = new List<Point>();
foreach(int x in list){
    foreach(int y in list){
        points1.Add(new Point(x, y));
    }
}

// ②クエリで書くとき
IEnumerable<Point> points2 =
    from x in list
    from y in list
    select new Point(x, y);

// ③メソッドで書くとき
IEnumerable<Point> points3 = list.SelectMany(x => list.Select(y => new Point(x,y)));
// SelectManyを使わないとIEnumerableが入れ子になる。IEnumerable<IEnumerable<Point>>

メソッドで書くときにしんどい

foreachと、クエリ式がものすごく簡潔に見えます。

メソッドで書いてると、あれ?どう書くんだ?と詰まってしまう。
list.Select(x => list.Select(y => new Point(x,y)));
とやると、当然ながら戻り値がIEnumerable<IEnumerable<Point>>という入れ子状態になるのです。
回避するため、SelectManyメソッドというがわざわざ用意されているようです。

見易さ、完結さとしては、ちょっと厳しいものがあるかな。
無理矢理一行にしている感じがあります。

三重ループだともう、書く気がしないですね。
そもそも、多重ループになるコードは見直したほうがいいっていう通説のほうが正しいのでしょうけれど。

2014/09/01

[C#]LINQを使用するメリット

このエントリーをはてなブックマークに追加
LINQを使用するメリットが解らなかったのです。
前の記事ではLINQについておさらいしました。
[C#]LINQを覚えた(ラムダ式と型推論も)

LINQを使用するメリットが解らなかった

さて、どうしてもLINQを使わないとできないことなんてないということが、わりとLINQを学習するきっかけを失わせる壁でした。今まで書いてたコードがLINQを使うとこうなるというのは解るのだけど、それの何がいいのかが全然納得いかなかったりするのです。LINQを使うと、foreachを書かなくて済むなんていうのだけれど、それのどこがメリットなのか解らない。
つまり、foreach書かないといけないときは、結局foreachで書くわけだから、最初からforeachで書けばいいじゃんと思ってしまう。foreachを書かなくていいことに何のメリットがあるんだろう? 書き方が完結になるとかいうのは、わざわざ特殊な省略してるってことじゃないのかと、特殊な省略なんて理解が中途半端だとしょうもないトラブルのもとにもなる、そういう懸念がずっとあった。
特殊な記述が増えてしまうのは紛らわしいだけで、今まで書いたコードを全部書き換えて、新しい記法の学習と、振る舞いの検証を強制されるだけじゃないかと思ってました。

たとえば、処理効率が全く必要ないところであっても、ループの中で変数宣言してるような書き方に生理的な嫌悪を抱いてしまう人は少なくないのではないかなと思います。近しいものがあって、きっとわざわざ学習しようなどと考えることがなかったのです。
よくありそうな誤解が、ループの効率と遅延実行についての仕組みです。これについては改めてまた書くかもしれません。

LINQを使用すれば意識が変わる

LINQ記法はデータの、集合に対するアプローチなのだということが理解できた。(なんかWikipediaにもそういう風にも書いていたかもしれない)

自分はまだ十分に書き慣れているとは言い難いし、めちゃくちゃ便利で人に薦めたいと思っているわけでもないのだけど、なんとなく自分の中でブレイクスルーがあったので、それについて書いておきます。的外れなことを言ってしまっていたらごめんなさい。

どういうことかというと、たとえばcolorsという集合から黄色のものだけを抽出して、newColorsという集合をつくるコードを例にします。

IEnumerable<Color> colors = CreateColors();
List<Color> newColors = new List<Color>();
foreach(Color color in colors ){
    if(color.isYellow()){
        newColors.Add(color);
    }
}
foreach文を使って書いているものは、細かな部品を意識的に組み合わせて意図する処理を表現しているというのが、普通なはずです。
LINQを使うと、このようになります。
IEnumerable<Color> colors = CreateColors();
IEnumerable<Color> newColors = colors.Where(c => c.isYellow());
書くときに意識することが全然違う(はず)。
ある集合を抽出する条件を設定するというコードなので、意味合いが直接的なのです。
foreachは当然だけど、データ集合に対するアプローチという意味合いはどこにもなくて、配列を順番に繰り返す構文でしかない。集合を抽出する条件でふるいに掛ける、という目的に合わせてforeachに分解したコードとして翻訳して書いているということなのです。

きっとLINQを書き慣れるほど、その書き方にどっぷり慣れるのだろうと予想していて、なるほど、慣れてしまった人は他の言語でループに翻訳展開する過程がものすごく億劫になるのかもしれません。
コードとしても、データの集合に対するアプローチが直接的にコードとして展開できなら、細かなところに目がいかなくていいので可読性も増すのかなと。

記法がシンプルだったら、利用頻度もあがるだろうし、コード展開もそれに合わせて設計するようになるというスパイラルがあるわけですね。

設計をLINQに合わせて行うようになって、さくさく書ける代わりに、大きな認識違いをしてしまって非効率極まりないコードを走らせてしまうという可能性も十分にあるわけだけど、考え方をシンプルにするということは突っ走るためには何よりいいことなので、失うものを気にするよりも享受するほうがよさそうです。

軽々に流行りの情報に引っ掻き回されるのは何より疲弊してしまう困ったことなのだけど、警戒して高い壁を作りすぎて新しいことの受け入れを拒絶しすぎるのも困りごとですね。あんまりコード書く機会もなかったから仕方ないのだけど、価値を見出してしまった以上、これからがんがん使っていきたいと思います。