Frequently used words (click to add to your profile)

javac++androidlinuxc#windowsobjective-ccocoa誰得qtpythonphprubygameguibathyscaphec計画中(planning stage)翻訳omegatframeworktwitterdomtestvb.netdirectxゲームエンジンbtronarduinopreviewer

Recent Chamber Activity

2023-04-16
2023-03-24
2023-03-22
2023-01-25

Recent Wiki Changes

2023-04-16
2023-03-24
2023-01-25
2023-01-08
2023-01-07

Wiki Guide

Side Bar

オブジェクトリストでオブジェクトの名前が重複しないように連番を振る処理

はじめに

ドローソフトや、CAD、3Dレンダリングソフト等は、キャンバスや3D空間に複数のオブジェクトを配置していく作りになっていることが多いと思います。 また、ソフトによってはそれらのオブジェクトはリストで管理できて、リスト上でリネームができたり、外部ファイルからオブジェクトをインポートできたりするかもしれません。

  • 例:Inkscapeのオブジェクトリスト

example_of_object_list.png

ここで仮に、「オブジェクト名の重複禁止」の要件を含むソフトを作るケースを考えます。 その制約条件下では、リネームにより他のオブジェクトと名前が重複してしまう可能性がありますので、 リネーム処理の際に例えば

  • 名前が重複した場合はにエラーメッセージを出してリネームを強制キャンセルする
  • リネームした名前の末尾に連番を振る

といった処理を加えることになります。 外部ファイル等から複数のオブジェクトを一括でインポートしたいときにも同様の処置が必要になるはずです。 強制キャンセルであれば比較的実現しやすいですが、ユーザとしては入力のし直しが必要となるため、 連番を振る処理の方が使い勝手は良さそうです。 ただ、連番を振る案の場合は、単純に連番を振るだけだと、振った結果重複することも考えられるため、それを考慮して処理を作るとなると結構考えることが多くてなかなか実装しにくいと思います。

そこで、そういった状況で汎用的に使えるよう、「リネーム、一括インポート時にオブジェクト名が重複しないように連番を振る処理」を作ってみましたので紹介します。 名前本体と連番部分を区切る文字や、連番の桁数や表記等、好みや個々の事情によって変える必要があるかと思いますので、該当部分は適宜書き換えての使用、ということでお願いします。

処理例

本プログラムは、オブジェクト名重複チェックと自動リネームを一度に実施する関数 ResolveDuplicateName と、その動作を見るためのGUIサンプルで構成されています。 リネーム処理、一括インポート処理のそれぞれを模した例を示します。

  • リネーム処理

auto_numbering_on_rename.png

  • インポート処理

auto_numbering_on_import.png

ライセンス

  • Boost License v1.0

ソース

ResolveDuplicateName の内容

/// ==============
///    処理本体
/// ==============
/// <summary>
/// 名前リストについて、名前の重複を調べ、重複している名前については連番をふり直す。
/// </summary>
/// <param name="nameList">名前リスト</param>
/// <param name="renamableBegin">連番ふり直し対象の開始インデックス</param>
/// <param name="renamableEnd">連番ふり直し対象の終了インデックス</param>
/// <returns></returns>
static public int[] ResolveDuplicateName(string[] nameList, int renamableBegin, int renamableEnd) {
	int begin = 0, end = nameList.Length;

	// [処理1] 名前の重複数をカウントする
	// Key:末尾の連番が残ったままの名前、 Value:重複カウント数
	var orgNameDuplicateCount = new Dictionary<string, int>();
	for (int i = begin; i < end; ++i) {
		var name = nameList[i];
		if (string.IsNullOrEmpty(name)) continue;
		int cnt = 0;
		if (!orgNameDuplicateCount.TryGetValue(name, out cnt)) {
			orgNameDuplicateCount.Add(name, 1);
		} else {
			orgNameDuplicateCount[name] = cnt + 1;
		}
	}

	// [処理2] 名前から末尾の連番部分を外し、末尾の連番を外した名前毎に連番の最大値を取得する
	// [処理3] 末尾の連番部分を外した名前のリストを作成する
	// Key:末尾の連番を外した名前、 Value:ついている番号の最大値 
	var wnNameMaxNumber = new Dictionary<string, int>();
	var wnNames = new string[end - begin];
	for (int i = begin; i < end; ++i) {
		var nameWithoutNumber = "";
		var name = nameList[i];
		if (string.IsNullOrEmpty(name)) continue;
		int suffixNumber = -1;
		var lastPeriodIndex = name.LastIndexOf('.');
		if (lastPeriodIndex > 0) {
			nameWithoutNumber = name.Substring(0, lastPeriodIndex);
			var suffix = name.Substring(lastPeriodIndex + 1);
			int num;
			if (int.TryParse(suffix, out num) && num >= 0) {
				suffixNumber = num;
			}
		} else {
			nameWithoutNumber = name;
			suffixNumber = -1;
		}
		wnNames[i - begin] = nameWithoutNumber;
		int lastMaxNumber = -1;
		if (!wnNameMaxNumber.TryGetValue(nameWithoutNumber, out lastMaxNumber)) {
			wnNameMaxNumber.Add(nameWithoutNumber, suffixNumber);
		} else {
			if (lastMaxNumber == -1 && suffixNumber == -1) suffixNumber = 0;
			if (lastMaxNumber < suffixNumber) wnNameMaxNumber[nameWithoutNumber] = suffixNumber;
		}
	}

	// [処理4] 重複している名前に連番をふり直して重複解除する。
	// [処理5] 連番をふり直した要素インデックスを記録する。
	var resolved = new List<int>();
	if (renamableBegin < begin) renamableBegin = begin;
	if (renamableEnd > end) renamableEnd = end;
	for (int i = renamableBegin; i < renamableEnd; ++i) {
		var name = nameList[i];
		if (string.IsNullOrEmpty(name)) continue;
		if (name[name.Length - 1] == '.') name = name.Substring(0, name.Length - 1);
		bool nameNumberless = (name == wnNames[i - begin]);

		// 末尾に連番がついていない名前の場合は、連番がついているものと重複判定する。
		// 末尾に連番がついている名前の場合は、重複してなければ名前変更不要。
		if (!nameNumberless && orgNameDuplicateCount[name] == 1) continue;

		var nameWithoutNumber = wnNames[i - begin];
		int maxNumber;
		if (!wnNameMaxNumber.TryGetValue(nameWithoutNumber, out maxNumber)) continue;
		if (nameNumberless && maxNumber == -1) continue;
		nameList[i] = string.Format("{0}.{1}", nameWithoutNumber, maxNumber + 1);
		resolved.Add(i);
		wnNameMaxNumber[nameWithoutNumber] = maxNumber + 1;
	}
	return resolved.ToArray();
}