ソフトウェア-開発全般

ニコカラ動画のご紹介(750 突破記念)

Nkm768カラオケ字幕付き動画を手軽に作れるツール「ニコカラメーカー」を活用して作成された動画の数が、750 を超えました。

501~750 番目のニコカラの中から、いくつかピックアップしてご紹介させていただきます。

すべての一覧は、以下のリンクからご覧になれます。
ニコカラメーカーを使用して作成した動画をニコニコ動画にアップロードした場合、ニコカラメーカー 簡易説明書動画をコンテンツツリーの親にご登録お願いします。上記リンクに反映されます。

アリスのお茶会 ~デキる女と(ry(Raildex さん)

東方ニコカラです。

ニコカラメーカーのインライングラフィックス機能を活用し、パート分けをアイコンで分かりやすく行っています。

NeGa/PoSi*ラブ/コール(ディレイドさん)

東方ニコカラです。

ディレイドさんの投稿ペースは速く、次々とニコカラを投稿されています。魂音泉さんや凋叶棕さんのニコカラが多いようです。

Bi☣hazard(饒速水琥白主さん)

UTAU 音源「気球音アイコ」5 周年で作曲された UTAU オリジナル曲のニコカラです。

英語歌詞ですが、ルビが振られているので分かりやすいです。

ふたりの点滅(しゅれんつさん)

CeVIO 黄咲愛里オリジナル曲のニコカラです。

動画の雰囲気に合わせた字幕配色になっています。

いちにちっ!(ver.Xmas)(徒者さん)

メドレーカラオケです。

徒者さんはニコカラ初挑戦とのことです。新しくニコカラを作り始めて下さる方が増えるのは嬉しいですね。

マグネット・ジョーに気をつけろ(Ottomo さん)

星屑スキャットのボカロカバー版のニコカラです。

Ottomo さんもニコカラ初挑戦とのことです。丸くて優しいフォントをセレクトされています。


これからもどんどんニコカラが増えていくのを楽しみにしています。


ニコカラメーカーの今後の見通し

ニコカラメーカーカラオケ字幕付き動画を手軽に作成できる「ニコカラメーカー」の今後のバージョンアップについて、見通しを整理しておきます。現時点でのものですので、今後変更の可能性は多いにあります。

2016/04/14 現在、ニコカラメーカーに頂いている要望等は 61 件で、そのうち、37 件は既に対応済みです。

今後検討が必要な大規模改修は、以下となります。
  • 半透明字幕
  • 複数歌詞ファイルの読み込み(パート分け)
  • 歌詞のアニメーション
  • ルビ入力
  • 出力形式の多様化

半透明字幕

歌詞字幕や影などを透けるようにできる機能は、比較的近い将来に対応したいと考えています。

しかしながら、内部処理が複雑なので、実現できるかどうかはまだ不透明です。

複数歌詞ファイルの読み込み(パート分け)

デュエット曲などでは、歌詞ファイルをパートごとに分けて作成される方がいらっしゃいます。現時点では、歌詞ファイルを 1 つしか読み込めませんが、将来的には複数の歌詞ファイルを読み込めるようにしたいと考えています。

単に読み込めるというだけではなく、自動レイアウトもパートごとに分かれるようにする予定です。

歌詞のアニメーション

これについては、直近での対応予定はありません。一般的な商業カラオケではアニメーションしておらず、応用的な内容となるためです。

技術的にも困難が予想され、実現できるかどうか、かなり難しい状況です。

しかし、実現できれば楽しいニコカラが作れるようになりますので、将来的には検討したいです。

ルビ入力

現時点でのニコカラメーカーは歌詞ファイルに記載されているルビを読み込むのみですが、ニコカラメーカー上で直接ルビを編集できる機能について、比較的近い将来に対応したいと考えています。

出力形式の多様化

現時点でのニコカラメーカーは無圧縮 AVI のみ出力可能ですが、将来的には、多様なフォーマットで出力できるようにしたいと考えています。

しかし、技術的には困難が予想され、実現できるかどうかは現時点で不透明です。

ニコカラメーカーを開発中

ルートスフィアニコカラ(カラオケ字幕付き動画)を手軽に作れるフリーソフト「ニコカラメーカー」を開発している。

というのも、ニコカラを作ってみたいと思ってネットで調べてみたら、手順がものすごく難しそうで、それならツールを自作した方が楽なんじゃないか、と思ったからである。

現状、ニコカラは主に以下のような手順で作るらしい(サイトによって少しずつ違うのでイマイチわからないが)。[]内は各段階での成果物のファイル拡張子、【】内は代表的な使用ツール。
  1. 素材(動画、タイムタグ付き歌詞ファイル)を揃える。 [.mp4 .txt]【RhythmicaLyrics】
  2. タイムタグ付き歌詞ファイルを字幕に変換する。 [.ass]【txt2ass】
  3. ルビを振る。 [.ass]【txt2ass】
  4. 出来映えを確認する。 [.avi]【VirtualDubMod / Vobsub / TextSub】
  5. ツールを行ったり来たりしながら 2~4 を繰り返す。保存のやり方を間違えるとデータが消えるらしい。
  6. エンコードする。 [.mp4]【AviUtl】
特に、2~5 の繰り返しが鬼のように面倒くさそうな感じである。1 つのツールでできないので、注意しないと修正した時にデータの齟齬が発生したりするようだ。

また、どのサイトもエンコードに AviUtl を使うと書いてあったが、何か理由があるのだろうか。どうにも AviUtl は敷居が高くて踏み込めない。エンコードだけなら夏蓮根の方が親切丁寧で手っ取り早いと思うのだが。

そんなこんなで、開発中のニコカラメーカーは、2~5 の作業を簡単にできるようにする予定。

現在できているところまでで、サンプル的に動画を出力してみたのが、冒頭のスクリーンショット(クリックすると拡大する)。ニコカラっぽくなっていると思う。

ニコカラメーカーニコカラメーカーの動作の様子は、右の写真(まだ機能はほとんど実現できていないが……)。

今までのニコカラ作りのやり方だと、汎用ツールを使っているので、ニコカラには不要な機能まで山ほどあり、逆にそれが分かりづらさにつながっていると思う。ニコカラメーカーはシンプルにニコカラに特化したツールにしたい。エンコードも、たぶん夏蓮根でいけると思う。

ニコカラメーカーを使った新しいニコカラの作り方は、以下のような手順になるだろう。
  1. 素材(動画、タイムタグ付き歌詞ファイル)を揃える。 [.mp4 .txt]【RhythmicaLyrics】
  2. ルビを含めた字幕を、動作を確認しながら出力する。 [.avi]【ニコカラメーカー】
  3. エンコードする。 [.mp4]【夏蓮根】
【追記】

動画にまとめました


C# で SQLite を便利に使うサンプルコード(LINQ to SQLite)

.NET Framework C# で SQLite3 を使う方法について、すでにいくつか情報はあるものの、数が少なかったりするので、自分がハマった罠なども含めて、ここにまとめておく。合わせて、サンプルプログラムも公開する。

どの SQLite ライブラリを使うか?

C# で SQLite を使うための方法はいくつかあり、自分が把握している範囲では以下のようになる。
  • C 言語用 DLL を P/Invoke で使う……本家本元の DLL を使い、従来型の文字列によるクエリでプログラミング。最新のライブラリを使える(本稿執筆時点で 3.9.2)。しかし、C 言語レベルの生産性しかなく、労多くして功少なしなので、どうしても最新のバージョンを使いたい時以外はお薦めしない。
  • System.Data.SQLite……SQLite 本家による .NET 用ライブラリ。データベースを便利に使う ADO.NET に対応しており、生産性が向上。C 言語用 DLL よりは多少バージョンが古い(3.8.11.1 ベース)ものの、総合的に一番お薦め。
  • Mono.Data.SqliteClient…….NET のオープン・クロスプラットフォーム実装である Mono プロジェクトが作成したライブラリ。ADO.NET 対応。文字コードが Unicode(恐らく UTF-16)と記載があるのが気になる(本家は UTF-8)。別途本家 C 言語用 DLL が必要な模様。
  • csharp-sqlite……マネージドコードのみで作成されたライブラリ。ADO.NET 対応。2011 年で開発停止の模様。バージョンは 3.7.7.1。マネージドコードしか使えない制約がある場合に重宝しそう。
  • sqlite-net……コンパクトなライブラリ。ADO.NET 非対応。できる限り実行ファイルサイズを小さくしたい場合に活躍しそう。2012 年あたりで開発が停止しているようだ。
以降では、System.Data.SQLite を使う。

ADO.NET とは?

System.Data.SQLite は ADO.NET に対応しているが、そもそも ADO.NET とは何か。ADO.NET の詳しい解説は @IT の記事などに記載があるが、プログラマーから見たメリットを三行で書くとすれば、
  • エディタのコード補完で楽ちんかつ安全にクエリを書ける(LINQ)
  • 設計時からデータ構造をビジュアル化できる(Entity Framework)
  • 使うデータベース(SQLite/Oracle……)によらず同じコードを使い回せる
あたりになるのではと理解している。

これにより、C 言語で SQLite をいじるよりもはるかに効率的にデータベースプログラミングが行える。

なお、本稿では、LINQ は使うが Entity Framework は使わない。

System.Data.SQLite のインストール

NuGetインストールは非常に簡単である。ここでは、Visual Studio 2015 Community / .NET Framework 4.5 の環境で実行しているものとする。
  1. Visual Studio を起動し、SQLite を使いたいプロジェクトを開く
  2. メニューの[ツール|NuGet パッケージマネージャー|ソリューションの NuGet パッケージの管理]をクリック
  3. 検索窓に「SQLite」と入力して SQLite パッケージを検索
  4. 検索結果の System.Data.SQLite を選択して、インストールボタンをクリック。関連するライブラリも含めて自動でインストールが終わる
たったこれだけ。

以前は、NuGet をコマンドラインで使う必要があった時代もあったようだが、現在は GUI で使える。

NuGet の欠点は、プロジェクトごとにインストールの必要があるため(プロジェクトに最適なバージョンを選んでインストールしてくれる)、SQLite を使うプロジェクトが複数ある場合は、ディスクをどんどん消費していく。その場合は、ダミープロジェクトでインストールしたバイナリを使い回したり、手動でバイナリをダウンロードして共用するといいかもしれない。

System.Data.SQLite の基本

データベースファイル(*.db とか *.sqlite3 とか)を開くには、SQLiteConnection クラスを使う。

SQLiteConnection を new する際にパラメーターを文字列で渡すのだが、SQLiteConnectionStringBuilder というお助けクラスを使うと、パラメーター文字列をミスなく生成できる。

SQLiteConnectionStringBuilder aConnectionString = new SQLiteConnectionStringBuilder
{
    DataSource = @"R:\Test.db"
};
using (SQLiteConnection aConnection = new SQLiteConnection(aConnectionString.ToString()))
{
    aConnection.Open();

    // ここにデータベース処理コードを書く
}


従来型の文字列によるコマンドを発行したい場合は、SQLiteCommand クラスを使う。

using (SQLiteCommand aCmd = new SQLiteCommand(aConnection))
{
    aCmd.CommandText = "CREATE TABLE IF NOT EXISTS t_test (test_id INTEGER NOT NULL PRIMARY KEY, test_name NVARCHAR NOT NULL, test_height REAL);";
    aCmd.ExecuteNonQuery();
}


テーブル構造定義クラスの導入

データへのアクセスを簡単・分かりやすくするために、テーブル構造を定義するクラスを作成する。

簡易名簿テーブル「t_test」が以下のような構造になっているとする。
フィールド名NULL備考
test_idINTEGER不可連番
test_nameNVARCHAR不可氏名
test_heightREAL身長

この場合、テーブル構造定義クラスは以下のようになる。

[Table(Name = "t_test")]
public class TTestData
{
    // ID
    [Column(Name = "test_id", DbType = "INT", CanBeNull = false, IsPrimaryKey = true)]
    public Int32 Id { get; set; }

    // 氏名
    [Column(Name = "test_name", DbType = "NVARCHAR", CanBeNull = false, UpdateCheck = UpdateCheck.Never)]
    public String Name { get; set; }

    // 身長
    [Column(Name = "test_height", DbType = "REAL", CanBeNull = true)]
    public Double? Height { get; set; }
}


フィールドをプロパティーとして定義しているシンプルなクラスである。

CREATE TABLE で作成した際のテーブル情報に合わせて、クラスメンバーの属性を記述している。例えば ID フィールドであれば、Name = "test_id" でデータベース上のフィールド名、DbType = "INT" でデータ型、CanBeNull = false で NULL 不可であることを表している。

DbType の型について注意点が 2 つ。
  • 整数型は DbType = "INT" とする。INTEGER ではエラーになる。
  • 文字列型は DbType = "NVARCHAR" とする。TEXT ではエラーになる。
特に、文字列型を TEXT としてしまうと、原因が分かりづらいエラーが発生し、ハマることになる。

身長フィールドは、NULL を許可している。CanBeNull = true にすると共に、プロパティーの型を「Double?」というように「?」を付けて nullable にしている。

レコードの挿入

テーブル構造定義クラスを導入したことにより、データベースの取扱が非常に簡単になる。

レコードの挿入を行うコードは、以下のようになる。SQLiteCommand クラスを使わなくて済むので全然 SQL っぽくなく、普通のコードのように書ける。

using (DataContext aConText = new DataContext(aConnection))
{
    Table<TTestData> aTableTest = aConText.GetTable<TTestData>();
    aTableTest.InsertOnSubmit(new TTestData { Id = 1, Name = "Fukada Kyoko" });
    aTableTest.InsertOnSubmit(new TTestData { Id = 2, Name = "Eda Ha", Height = 180.0 });
    aTableTest.InsertOnSubmit(new TTestData { Id = 3, Name = "Dan Gerou", Height = 150.5 });
    aTableTest.InsertOnSubmit(new TTestData { Id = 4, Name = "Baba Takashi" });
    aTableTest.InsertOnSubmit(new TTestData { Id = 5, Name = "Aikawa Ai", Height = 145.6 });
    aConText.SubmitChanges();
}


ID 1 や 4 では身長を設定していないので、データベース上 NULL として格納される。

レコードの検索

レコードの検索は LINQ to SQLite と呼ばれる手法で行う。ちょっと変わった書き方だが、エディタのコード補完が効くのでミスが減る。

using (DataContext aConText = new DataContext(aConnection))
{

    Table<TTestData> aTableTest = aConText.GetTable<TTestData>();
    IQueryable<TTestData> aQueryResult =
            from x in aTableTest
            where x.Name == "Eda Ha" || x.Height < 150.0
            orderby x.Height
            select x;
    foreach(TTestData aData in aQueryResult)
    {
        Debug.WriteLine(aData.Name);
    }
}


from のところが SQL の発行に相当する部分。なんとなく SQL に似ているので、読めば理解はできると思う。

結果は foreach で回せるので、非常に簡単に扱える。

レコードの削除

レコードを削除する場合、削除したいレコードを検索し、それを削除する、という流れになる。

検索については前節と同じで、その結果を、DeleteAllOnSubmit() メソッドにぶち込むだけ、とこれまた簡単。

using (DataContext aConText = new DataContext(aConnection))
{
    Table<TTestData> aTableTest = aConText.GetTable<TTestData>();
    IQueryable<TTestData> aDelTargets =
            from x in aTableTest
            where 140 < x.Height && x.Height < 160
            select x;
    aTableTest.DeleteAllOnSubmit(aDelTargets);
    aConText.SubmitChanges();
}


サンプルコード

以上をまとめたものを、サンプルコードとしておいておく。

なお、CREATE TABLE や CREATE INDEX は少々コーディングが面倒くさいので、テーブル構造定義クラスの情報から自動的にテーブルを作成するための補助クラス(LinqUtils.cs)を入れてある。

Visual Studio 2015 Community で動作する。

参考資料











System.Data.Linq.dll で NotSupportedException(解決)

SQLite3 を C# で使いたくて、いろいろ試していたら、文字列の比較でエラーが発生。

やったことは、System.Data.SQLite(SQLite の公式 ADO.NET プロバイダー)をインストールし、LINQ to SQLite でデーターベースにアクセス。ソースコードはおよそ以下のような感じ。

    Table<TTestData> aTableTest = aConText.GetTable<TTestData>();
    IQueryable<TTestData> aQueryResult =
            from x in aTableTest
            where x.Name == "Eda Ha"
            select x;


ここで、aContext は DataContext 型のインスタンス。TTestData は以下の定義。

[Table(Name = "t_test")]
public class TTestData
{
    // ID
    [Column(Name = "test_id", DbType = "INT", CanBeNull = false, IsPrimaryKey = true)]
    public Int32 Id { get; set; }

    // 氏名
    [Column(Name = "test_name", DbType = "TEXT", CanBeNull = false, UpdateCheck = UpdateCheck.Never)]
    public String Name { get; set; }
}


NotSupportedExceptionこのようなコードを実行すると、IQueryable の結果を使う段階で、NotSupportedException が発生する。

    例外がスローされました: 'System.NotSupportedException' (System.Data.Linq.dll の中)
    追加情報:SQL Server では、NText、Text、Xml、または Image の各データ型の比較はハンドルされません。

where x.Name == "Eda Ha" の箇所が where x.ID == 1 のような数値型の比較だと例外は発生しない。

原因がわからずさんざん悩んだ。「SQL Server では~」のエラーメッセージで検索しても情報が見つからない。

結論としては、Name の DbType がまずかった。

DbType を NVARCHAR にして、CREATE TABLE する際の型も NVARCHAR にしたら、うまく動作するようになった。

どうやら、SQLite を .NET で使う場合は、文字列型は NVARCHAR にしないといけないようだ。

なお、エラーが発生した環境は、
  • Visual Studio Community 2015
  • .NET 4.5
  • System.Data.SQLite 1.0.98.0(sqlite-netFx45-setup-x86-2012-1.0.98.0.exe)
関連記事

Visual Studio の .lib を C++Builder にリンクするのは無理っぽい

Visual Studio (Visual C++) 2013 で作成したスタティックライブラリ(.lib)を、C++Builder XE3 のプロジェクトに組み込みたいのだが、無理のようだ。

VC は COFF というオブジェクトフォーマットを採用しており、一方で、C++Builder は OMF を採用している。このため、単純に、VC の .lib を C++Builder に組み込んでリンクしようとすると、

[ilink32 エラー] Error: '...\HOGE.LIB' には無効な OMF レコード (タイプ 0x21) が含まれています (COFF の可能性もあります)
というエラーとなってリンクできない。

形式が違うのなら変換すればいいじゃない、ということで、C++Builder に付属の coff2omf.exe を使おうとするものの、これは通常のスタティックライブラリを変換するためのツールではなく、インポートライブラリを変換するためのものなので、うまくいかない。

サードパーティーのツールはどうか。objconv を試してみた。

ojbconv -fomf in.lib out.lib
のようにして使ってみたものの、警告がたくさん表示された。結果の .lib を C++Builder に組み込むと、

[ilink32 エラー] Fatal: EXTDEF フィックスアップ インデックス (モジュール 'xxx_00.obj' 内) は無効です
というエラーになってうまくいかない。

DLL を使うしかないか……。




C++Builder XE3 で BMP/JPEG/PNG 画像を合成する

C++Builder は、様々なフォーマットの画像を扱うのも簡単だ。……が、まとまった情報が見つからなくて苦労したので、ここにまとめておく。動作確認は C++Builder XE3。

例として、JPEG 画像を背景画像として、その上に、BMP 画像と透過 PNG 画像を重ねる。合成結果を PNG 画像として保存する。

背景 JPEGBMP透過 PNG
TestBG Test1
※Web 用に PNG に変換してあるが、元は BMP
Test2


準備として、ビットマップ形式以外の画像ファイルを読み書きしたい場合は、対応するヘッダをインクルードしておく。

#include <Jpeg.hpp>
#include <PNGImage.hpp>

ヘッダをインクルードしてしまえば、読み込みに関しては、画像フォーマットを気にせずに読み込めるようになる。書き込みは、対応する画像形式を扱うクラスに任せる。コードは以下。

void __fastcall TFormTest01::Button1Click(TObject *Sender)
{
    unique_ptr<TImage>        aImageMerge(new TImage(this));
    unique_ptr<TImage>        aImageBG(new TImage(this));
    unique_ptr<TImage>        aImage1(new TImage(this));
    unique_ptr<TImage>        aImage2(new TImage(this));
    unique_ptr<TPngImage>    aPng(new TPngImage());

    // 画像ロード
    aImageBG->AutoSize = true;
    aImageBG->Picture->LoadFromFile(L"R:\\TestBG.jpg");    // JPEG
    aImage1->AutoSize = true;
    aImage1->Picture->LoadFromFile(L"R:\\Test1.bmp");    // BMP
    aImage2->AutoSize = true;
    aImage2->Picture->LoadFromFile(L"R:\\Test2.png");    // PNG

    // 合成画像のサイズを背景画像に合わせる
    aImageMerge->Width = aImageBG->Width;
    aImageMerge->Height = aImageBG->Height;

    // 合成
    aImageMerge->Canvas->Draw(0, 0, aImageBG->Picture->Graphic);
    aImageMerge->Canvas->Draw(100, 200, aImage1->Picture->Graphic);
    aImageMerge->Canvas->Draw(300, 200, aImage2->Picture->Graphic);

    // PNG 保存
    aPng->Assign(aImageMerge->Picture->Graphic);
    aPng->SaveToFile(L"R:\\Result.png");
}

Result結果は右の画像のようになる。

シンプルなコードだが、透過もきちんと自動処理されている。

ポイントは、TImage を使うこと(ここでは動的生成しているが、もちろんフォームに貼り付けても良い)。TImage のローダーは、画像形式に関わらず読み込める。TBitmap を使うと PNG を読み込めなかった。

古いバージョンの C++Builder/Delphi だと、PNG の読み込みには TPngImage で画像の読み込みを行わなければならなかったようだが、現在は、TImage を使えば良い。

逆に、書き込みは、明示的に TPngImage を使っている。

aImageBG->Picture->SaveToFile(L"R:\\Result.png");

のように拡張子で自動的に PNG に変換してくれるかなーと期待したが、そんなことは無かった。



CodeIQ の「グループを作ろう!」にチャレンジ

目次

現役のエンジニア・企業がプログラミングの問題を出してくれる CodeIQ というサイトがある。問題を解くと、正解不正解の判定や、フィードバックなどを行ってくれる。ジャンルや言語などはさまざまある。

グループを作ろう!

今回は結城浩さんの「グループを作ろう!」という問題に挑戦してみた。アルゴリズムの問題で、プログラムを使って解いても使わずに解いても構わない。プログラムを使う場合、言語は何でも良い。解答(とコメントなど)を送ると、フィードバックをもらえる。

問題の概要は以下。

Bill=Billy
Mick=Michael
Billy=William
というように、同じ人物を表すニックネームのペアが 444 個与えられる。同じ名前をすべて連結し、
Bill=Billy=Will=William=Willie=Willy
Michael=Mick=Micky=Mike=Mikey
というようにグルーピングする。ただし、各行の順番および、同一行内の名前は ASCII 順に並べなければならない。

自分の解答:STEP 1 グループ番号割り振り

C++ でプログラムを組む前提で。

どうやってグループを寄せ集めていくかと考えたとき、グループ毎の配列(vector)を用意してしまう、というのは最初に思いつくが、さすがに効率が悪い(名前を読み込む度に、すべてのグループ配列を見て、すでにグループ化されているか調べる必要がある)。

漠然と、木を使えばできるのではないかとイメージしたが、残念ながら、木を簡単に実装する方法を思いつかなかった。

CodeIQ_グループ割り振りそこで代わりに、グループ番号を割り当てていく方法を採った(右はイメージ図)。

ペアの両辺を読み込み、それぞれの名前に既にグループ番号が割り当てられているか調べる。両辺とも未割り当てであれば、新しいグループ番号を割り振る。いずれかが割り当て済みであれば、もう片方に同じグループ番号を割り当てる。

CodeIQ_グループ変更両方とも割り当て済みで、かつ、違うグループ番号であれば、今までは別のグループとして扱ってきたが、実は同じグループであることが分かったということなので、片方のグループ番号をすべて書き換える。

名前→グループ番号の対応を格納するのは、STL の map を用いた。

自分の解答:STEP 2 結合

すべてグループ分けできたら、同じグループ同士を連結していく。string 型の vector を用意しておき、1 行に 1 グループを突っ込んでいく。

CodeIQ_行番号割り振りmap を先頭から読み込んでいき、同じグループ番号の文字列が既に vector にあるか調べる。あれば、文字列の末尾に新しい名前を追加する。無ければ、新しい行の先頭に名前を追加する。

グループ番号と行番号の対応も、やはり map で覚えておく(……が、今から思えば、配列の方が高速だった)。

map を使うことにより。キーが自動的に ASCII 順になっているので、新しい行ができた時は、単純に配列の末尾に加えれば良く、ソートの必要は無い。また、1 つの行に名前を追加する際も、末尾に追加するだけで良く、ソートの必要は無い。

注意点

注意すべきはグループの統合で、最初はこれを見逃していた。できあがった解答を眺めていて偶然気づいた。グループ統合のコードを後から付け加えたので、これだけ異質な感じがする。

計算量は、入力のペア数を N として、グループ統合がなければ恐らく O(NlogN) だが、グループ統合があると最悪 O(N^2) になるのだろう。

ソースコードの抜粋

// グループ番号割り当て
while ( aInFile && getline(aInFile, aLine) ) {
    get_names(aLine, &aName1, &aName2);

    // 名前のペアをマップに登録していく
    p1 = aGroupMap.find(aName1);
    p2 = aGroupMap.find(aName2);
    if ( p1 == aGroupMap.end() && p2 == aGroupMap.end() ) {
        // 新規登録
        aGroupMap[aName1] = aGroupIndex;
        aGroupMap[aName2] = aGroupIndex;
        aGroupIndex++;
    } else if ( p1 != aGroupMap.end() && p2 == aGroupMap.end() ) {
        // aName1 が登録済み→aName2 を同じグループに登録
        aGroupMap[aName2] = p1->second;
    } else if ( p1 == aGroupMap.end() && p2 != aGroupMap.end() ) {
        // aName2 が登録済み→aName1 を同じグループに登録
        aGroupMap[aName1] = p2->second;
    } else {
        // 両方登録済み
        if ( p1->second != p2->second ) {
            // 違うグループとして登録されているのでグループを統合
            change_group(&aGroupMap, p1->second, p2->second);
        }
    }
}

// A=B=C=D というような結合行を作成していく
while ( aGItr != aGroupMap.end() ) {
    aRItr = aResultMap.find(aGItr->second);
    if ( aRItr == aResultMap.end() ) {
        // 新規に結果行に登録
        aResult[aResultIndex] = aGItr->first;
        aResultMap[aGItr->second] = aResultIndex;
        aResultIndex++;
    } else {
        // 既存の行に追加
        aResult[aRItr->second] += "="+aGItr->first;
    }
    aGItr++;
}


模範解答

CodeIQ_木模範解答では、やはりグルーピングに木を使っていた。

面白かったのは木の実装方法。木を map で実装していた(模範解答は perl と思われる言語で実装されていたので、実際には連想配列)。

map のキーに、葉っぱの名前を入れ、値に、根っこの名前を入れる。根っこの名前は、値を空文字列にでもしておく。

こうすることで、数回 map を探索すれば、一番根っこの名前にたどりつける。1 つの根っこから出てくる名前はみな同じグループで、グループの数だけ、根っこ(空文字列)があることになる。

木をこうやって実装するのは定石なのだろうか。シンプルでいいな。

木の良い点は、グループの結合が楽ちんなこと。2 つのグループの根っこのうち、片方の空文字列をやめて、もう片方の根っこの名前を入れるだけで良い。どんなにグループが巨大でも、1 つの書き換えで済む。

感想

単にグルーピングするだけというシンプルな問題なのに、やってみるといろいろ考えることがあって面白かった。Union Find アルゴリズムとか Union Find Tree データ構造などという問題らしい。

実装言語を問わないこともあり、いろんな解答があったと聞く。なかにはエクセルで解いた人もいるとか。

他の人がどんな解答をしているのか気になっているのだが、残念ながら、ググっても自分の解答を公開している人はほとんどいないようだ。

結城さんがせっかく公開 OK に設定してくれているのだから、自分の解答を公開する人が増えてくれればいいなと思う。


文字コードを指定してメモリから文字列を読み込む(C++Builder XE3/TMemoryStream)

C++Builder は、ファイルの読み込みがとても簡単で、TStringList::LoadFromFile() により、文字コードを適切に処理して読み込んでくれる。

LoadFromFile() は見ただけで使い方が分かる。しかし、その親戚である、メモリからの読み込みを行う LoadFromStream() については、まとまった説明が見当たらなかったので試行錯誤。

で、以下が結果のコード。メモリに格納されている文字列を、文字コードを指定して TStringList に読み込む。

void __fastcall TForm1::Button1Click(TObject *Sender)
{
    const char                  UTF8_STR[] = { 0xE6, 0x98, 0xA5 };
    unique_ptr<TMemoryStream>   aMemStream(new TMemoryStream());
    unique_ptr<TStringList>     aStrList(new TStringList());

    aMemStream->Write(UTF8_STR, 3);
    aMemStream->Seek(static_cast<long>(0), soBeginning);
    aStrList->LoadFromStream(aMemStream.get(), TEncoding::UTF8);
    ShowMessage(aStrList->Text);
}


UTF8_STR が文字列が格納されているメモリ領域。今回は定数にしてあるが、通常は、どこかから持ってきた内容を格納しているバッファになる。

TStringList::LoadFromStream() は、TStream 型(の派生クラス)からしかデータを読み出せない。メモリ領域をそのまま読むことはできないので、仲介役の TMemoryStream が必要になる。

TMemoryStream にメモリ領域の内容を書き込んでいるのが aMemStream->Write(UTF8_STR, 3); TMemoryStream にメモリ領域の内容を読み込むというイメージだったので、最初 Read() を使いそうになってしまったが、Write() なので注意。

Write() 後に、読み書き位置を先頭に戻しておく。

あとは、LoadFromStream() に TMemoryStream へのポインタを渡せば、文字列が読み込まれる。

文字コードの指定は、
  • UTF-8……TEncoding::UTF8
  • UNICODE(UTF-16)……TEncoding::Unicode
  • 指定無し……自動判別
など。TEncoding についてはここが詳しい。

以上、コードを実行すると、メモリ領域から 3 バイトを読み取って、「春」を取得できる。

LoadFromStream

Delphi 用 RS-232C コンポーネントを C++Builder XE で使う

Delphi(および C++Builder の旧バージョン)で手軽に RS-232C(COM ポート)を使うためのコンポーネントに ComPort Library(TComPort)がある。C++Builder XE でも何とか動作したので、やり方を整理しておく。

【実行時パッケージの作成】

  • すべてのプロジェクトを閉じた状態で、[ファイル→プロジェクトを開く]メニューにて、ComPort Library の Source フォルダにある CPortLibCB6.bpk(C++Builder 6 用)を開く。
  • 自動的にプロジェクトが C++Builder XE 用に変換される。
  • ビルド構成を Release にする(右側のプロジェクトマネージャーペインで選択)。
  • メイクすると、CPortLibCB6.bpl ができあがる。


【設計時パッケージの作成】

  • [ファイル→プロジェクトを開く]メニューにて、DsgnCPortCB6.bpk を開く。
  • 実行時パッケージと同様にメイクし、DsgnCPortCB6.bpl を得る。

【パッケージのインストール】

  • CPortLibCB6.bpl を Windows のシステムライブラリフォルダ(Windows 7 64bit なら C:\Windows\SysWOW64)にコピーする。
  • [コンポーネント→パッケージのインストール]メニューにて、追加ボタンをクリックして DsgnCPortCB6.bpl を追加する。デフォルトにチェックを入れると毎回設定しなくて良いだろう。

【ComPort Library を利用するアプリケーションのメイク】

  • ツールパレットペインに「CPortLib」タブが増えていて、その中の TComPort をフォームに貼り付ければ RS-232C にアクセスできるようになる。
  • [プロジェクト→オプション]メニューで、ディレクトリと条件定義→ライブラリパスに、CPortLibCB6.bpl/DsgnCPortCB6.bpl のパスを追加する。
  • [プロジェクト→オプション]メニューで、ディレクトリと条件定義→条件定義に DONT_USE_WINSPOOL_SETPORTA を追加する。
  • winspool.h(include\windows\sdk 等にある)を開き、SetPortA/SetPortW を宣言している箇所を、#ifndef DONT_USE_WINSPOOL_SETPORTA~#endif で挟む
  • CPort.hpp をインクルードする。
  • CPort.hpp を開き、inline じゃない方の EComPort() の宣言 2 つをコメントアウトする。

以上でビルドができる。



≪参考≫

C++Builder XE でウィンドウを透明・半透明にする

Normalウィンドウの透明化・半透明化あれこれ。

元々は右のような普通の(不透明な)ウィンドウの透過度を、いくつかの方法で変化させてみる。

動作は Windows 7 (64bit) / C++Builder XE で行っているが、別の環境では別の動作になるかも。

* クライアント領域のみ透明化

クライアント領域のみを透明化させるには、フォームの TransparentColor/TransparentColorValue プロパティを使う。
void	TFormTest01::Test01()
{
	TransparentColor = true;
	TransparentColorValue = TColor(0x000001);
	Canvas->Brush->Color = TColor(0x000001);
	Canvas->FillRect(TRect(0, 0, 300, 300));
	Panel2->Color = TColor(0x000001);
}
TransparentColorTransparentColor プロパティを true にすることで透過モードとなり、フォーム上の TransparentColorValue プロパティで指定した色(コンポーネント同士の重なりに関係なく、指定した色で塗られている部分はすべて)が完全透過となる。半透明にすることはできない。

Windows API で言えば、SetLayeredWindowAttributes() を LWA_COLORKEY でコールした状態だと思われる。ただし、Windows API の場合、透過部分ではマウスイベント等を受け取れない(透明と言うよりはウィンドウに穴が空いて、背後のデスクトップ等にイベントが行く)ようだが、C++Builder の場合はイベントを受け取れる。

* ウィンドウ全体を半透明化

ウィンドウ全体を半透明にするには、フォームの AlphaBlend/AlphaBlendValue プロパティを使う。
void	TFormTest01::Test02()
{
	AlphaBlend = true;
	AlphaBlendValue = 128;
}
AlphaBlendAlphaBlendValue = 0 で完全透明、AlphaBlendValue = 255 で完全不透明、その中間で半透明となる。

コンポーネントも含め、ウィンドウ内がすべて一律で透明になる。

Windows API で言えば、SetLayeredWindowAttributes() を LWA_ALPHA でコールした状態だと思われる。

* グラスフレーム(ガラス効果)の拡張

Windows Vista から導入された Aero において、グラスフレーム(タイトルバーのもやもやした半透明)をクライアント領域にまで広げるには、GlassFrame プロパティを使う。
void	TFormTest01::Test07()
{
	GlassFrame->Enabled = true;
	GlassFrame->SheetOfGlass = true;
}
GlassFrameGlassFrame->Enabled を true にするとグラスフレーム拡張モードとなり、GlassFrame->SheetOfGlass も true にすることで、クライアント領域全体でガラス効果を得られる。GlassFrame->SheetOfGlass を false にして、GlassFrame->Bottom などのプロパティで任意の範囲でガラス効果を得るようにすることもできる。

Windows API で言えば、Desktop Window Manager の DwmExtendFrameIntoClientArea() をコールした状態と思われる。
カウンター


カンパのお願い
Amazon でお買い物の際は、下記で検索して頂けたら幸いです。
記事検索
最新コメント
  • ライブドアブログ