勉強のため、いまさらながらダイクストラ法をJavaで書いてみた。

/** ノード間をつなぐ節 */
public class Edge {
	/** 距離 */
	private int distance;
	/** 接続するノード */
	private Node node;

	public Edge(Node node, int distance) {
		this.node = node;
		this.distance = distance;
	}

	public int getDistance() {
		return distance;
	}

	public Node getNode() {
		return node;
	}

}

/** ノード */
public class Node {
	private int id;
	/** 始点からの距離 */
	private int distance = Integer.MAX_VALUE;
	/** 最短距離となるルートの一つ前のノード */
	private Node shortDistanceNode;

	public Node(int id) {
		this.id = id;
	}

	private boolean done;
	private List edges = new ArrayList();

	public int getId() {
		return id;
	}
	public int getDistance() {
		return distance;
	}
	public void setDistance(int distance) {
		this.distance = distance;
	}
	public boolean isDone() {
		return done;
	}
	public void setDone(boolean done) {
		this.done = done;
	}
	public List getEdges() {
		return edges;
	}
	public void addEdge(Edge edge) {
		edges.add(edge);
	}
	public Node getShortDistanceNode() {
		return shortDistanceNode;
	}
	public void setShortDistanceNode(Node shortDistanceNode) {
		this.shortDistanceNode = shortDistanceNode;
	}
}

public class NodeComparator implements Comparator {
	@Override
	public int compare(Node arg0, Node arg1) {
		return arg0.getDistance() - arg1.getDistance();
	}
}

public class Main {

	public static void main(String[] args) {

		Node[] nodes = generateNodes();

		// 始点を設定
		nodes[0].setDistance(0);

		// 優先度付きキューにノードを追加する
		PriorityQueue nodeQueue = new PriorityQueue(100, new NodeComparator());
		for ( Node node : nodes ) {
			nodeQueue.add(node);
		}

		while ( !nodeQueue.isEmpty() ) {
			Node node = nodeQueue.remove();
			// 隣接ノードの距離を更新する
			for ( Edge edge : node.getEdges() ) {
				int newDistance = node.getDistance() + edge.getDistance();
				if ( newDistance < edge.getNode().getDistance() ) {
					Node forwardNode = edge.getNode();
					forwardNode.setDistance(newDistance);
					forwardNode.setShortDistanceNode(node);
					// 距離を更新した場合は正しい位置にノードを追加し直す
					nodeQueue.remove(forwardNode);
					nodeQueue.offer(forwardNode);
				}
			}
		}

		// 最短ルートのノードのリストを作る
		List route = new ArrayList();
		Node mostShortNode = nodes[nodes.length - 1];
		while ( mostShortNode != null ) {
			route.add(mostShortNode);
			mostShortNode = mostShortNode.getShortDistanceNode();
		}
		Collections.reverse(route);

		// ルートを表示
		for ( Node node : route ) {
			System.out.println(node.getId() + ":" + node.getDistance());
		}
	}

	private static Node[] generateNodes() {
		Node[] nodes = new Node[22];
		for ( int i = 0; i < nodes.length; i++ ) {
			nodes[i] = new Node(i);
		}
		for ( int i = 0; i < nodes.length - 9; i += 3 ) {
			for ( int j = 0; j < 3; j++ ) {
				nodes[i + j].addEdge(new Edge(nodes[i+j*3+1], 1 + (int)(Math.random() * 30)));
				nodes[i + j].addEdge(new Edge(nodes[i+j*3+2], 1 + (int)(Math.random() * 30)));
				nodes[i + j].addEdge(new Edge(nodes[i+j*3+3], 1 + (int)(Math.random() * 30)));
			}
		}
		return nodes;
	}
}

ソース

ここ1年でSilverlightとAdobe Flexをフロントに据えたプロジェクトを経験した。もちろん実業務で。

感想箇条書き
・Flexバグ多すぎ。Silverlightの品質が良いのではない。Flexの品質が圧倒的に悪い。
・FlexはUIコンポーネントだけでなく、コンパイラも誤ったエラー検出をしたりする。
・AS3(ActionScript3.0)よりはC#の方が使いやすかった。クロージャの記述やコレクションなどはC#の方が楽だった。
・XMLの扱いはe4xのあるAS3の方が楽。
・AdobeはFlex捨てた。(AdobeはHTML5に注力すると宣言したうえ、FlexはApacheに寄贈された)
・Silverlightもしばらくしたら消えるだろうけれど、開発スタイルはWinRTなどへ引き継がれるし移行もおそらく簡単。
・SilverlightのMVVMはなかなか良い。

まとめ: SilverlightかFlexかだったらSilverlight一択。Flexは既に死んでいる。

Alert.show()を使った場合、ダイアログの縦幅、横幅の最大サイズが設定されているおかげで想定外の表示を
してしまう場合がある。これを回避するために、Alertの拡張などを考えたのだけれど、肝心な部分がmx_internalだったり
privateだったりして、うまく拡張できなさそうだった。結局は一から作るハメになる。Flexではよくあること。
とりあえず MessageBox.mxml を作って以下を実現した。
・最大height,widthの拡張
・最大heightを越える場合はスクロールバーを表示。
ただのPanelを継承してるだけなので、あとは自由にカスタマイズできる。

AdvancedDataGridでセルの内容をコピーする方法は各所で紹介されているが
階層データやセル選択モード(MULTIPLE_CELLS)に対応しているものがない。
いろいろ試行錯誤した結果、AdvancedDataGridを継承して、下記のようなコードを書けば
選択中のセルの情報を取得できそうなことがわかった。
最初はlistItemsを使おうと思ったのだけれど、listItemsは基本的に表示中のアイテムしか
保持していないため、選択中のデータの取得には使えなかった。
それにしても、この程度のことが簡単にできないFlexにちょっと萎えた。

public var selectedCellDatas:Array = [];
override protected function addCellSelectionData(uid:String, columnIndex:int, selectionData:AdvancedDataGridBaseSelectionData):void {
	var column:AdvancedDataGridColumn = columns[selectionData.columnIndex] as AdvancedDataGridColumn;
	var label:String = column.itemToLabel(selectionData.data);
	selectedCellDatas.push({
		rowIndex: selectionData.rowIndex,
		columnIndex: selectionData.columnIndex,
		label: label
	});
	super.addCellSelectionData(uid, columnIndex, selectionData);
}
override protected function clearCellSelectionData():void {
	selectedCellDatas = [];
	super.clearCellSelectionData();
}

AdvancedDataGridにコンテキストメニューを開いた位置を取得したいときがあるのだが、なかなかうまい方法がなかった。
itemRollOverが発生した時に行と列を保存しておくという手法がちらほら書かれているが、試してみたところうまく機能しない場合があった。
稀にロールオーバーのイベントが飛んで来ずに、マウスのポイントしている位置とコンテキストメニューを開いた時に取得できる位置がずれてしまう。
そこで別の方法を探すことにしたのだが、なかなか良さそうな方法を見つけた。
AdavancedDataGridはデフォルトの状態で、マウスがポイントしている行やヘッダの背景色を変更する。
この背景色を変える処理を参考にして、ポイントしている位置を取得すればいい。
AdvancedDataGridはソースコードが公開されているので、mouseOverHandler()の実装を参考にして
うまくマウスのポイント位置から行と列を取得することができた。

この下記のサンプルだとコンテキストメニューのアイテムをクリックしたときに保存された行と列の情報にアクセスしているが
コンテキストメニューを開いた瞬間のマウスポイント位置のデータをうまく取得できる。
どうやら、コンテキストメニューを開いたあとはAdvancedDataGridのmouseOverHandler()が呼び出されないらしい。
もしメニューが開いたあともmouseOverHandler()が呼ばれる場合はContextMenuEvent.MENU_SELECTの発生時に
データを他に逃がす処理などを入れなければならなかったが、その必要はなくシンプルに実装できた。

<?xml version="1.0" encoding="utf-8"?>
<mx:AdvancedDataGrid xmlns:fx="http://ns.adobe.com/mxml/2009"
					 xmlns:s="library://ns.adobe.com/flex/spark"
					 xmlns:mx="library://ns.adobe.com/flex/mx"
					 creationComplete="advanceddatagrid1_creationCompleteHandler(event)">
	<fx:Declarations>
	</fx:Declarations>
	<fx:Script>
		<![CDATA[
			import mx.controls.Alert;
			import mx.controls.advancedDataGridClasses.AdvancedDataGridColumn;
			import mx.controls.listClasses.IDropInListItemRenderer;
			import mx.controls.listClasses.IListItemRenderer;
			import mx.events.FlexEvent;

			[Bindable]
			public var mouseOveredHeaderColumn:AdvancedDataGridColumn;
			[Bindable]
			public var mouseOveredColumn:AdvancedDataGridColumn;
			[Bindable]
			public var mouseOveredRow:int = -1;

			override protected function mouseOverHandler(event:MouseEvent):void {
				mouseOveredHeaderColumn = null;
				mouseOveredColumn = null;
				mouseOveredRow = -1;

				var item:IListItemRenderer = mouseEventToItemRenderer(event);
				var dropInItem:IDropInListItemRenderer;
				if (item) {
					dropInItem = item as IDropInListItemRenderer;
				}

				var colNum:int;
				if (dropInItem && dropInItem.listData) {
					colNum = dropInItem.listData.columnIndex;
				}
				if (dropInItem
					&& isHeaderItemRenderer(item)
					&& sortableColumns
					&& colNum != -1
					&& columns[colNum].sortable
					&& Object(item).hasOwnProperty("mouseEventToHeaderPart") )
				{
					var headerPart:String = Object(item).mouseEventToHeaderPart(event);
					mouseOveredHeaderColumn = item.data as AdvancedDataGridColumn;
				}
				else if ( item != null && !isHeaderItemRenderer(item) )
				{
					mouseOveredColumn = this.columns[colNum] as AdvancedDataGridColumn;
					var pt:Point = itemRendererToIndices(item);
					mouseOveredRow = pt.y;
				}
				super.mouseOverHandler(event);
			}

			protected function advanceddatagrid1_creationCompleteHandler(event:FlexEvent):void
			{
				var item:ContextMenuItem = new ContextMenuItem("くりっくみー");
				item.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, clickListener);
				var cmenu:ContextMenu = new ContextMenu();
				cmenu.customItems.push(item);
				this.contextMenu = cmenu;
			}

			protected function clickListener(event:ContextMenuEvent):void {
				var str:String = "";
				str += "row:" + mouseOveredRow + "  ";
				if ( mouseOveredColumn ) {
					str += "column:" + mouseOveredColumn.headerText;
				}
				if ( mouseOveredHeaderColumn ) {
					str += "header:" + mouseOveredHeaderColumn.headerText;
				}
				Alert.show(str);
			}

		]]>
	</fx:Script>
</mx:AdvancedDataGrid>