探しても探してもわいてくるバグ。今回のバグはかなり難しい!と自負できます。
これはもう、設計段階でどれだけ例外的な場合を考えることができるかにかかっています。
完璧なアルゴリズムに則ってさっさと組んでしまいたいです。
さて、そんなわけでこのブログの読者様にもぜひ挑戦してみて欲しいです。
ちなみに本来のコードを改造して、ちょっとだけ解きやすくなっています。
クラス名が適当なのは無視ということで...。
class Entry {
private String id;
private double value;
public static Comparator<Entry> ID_ASCEND_COMP;
// getter, setter は定義済みとします。
}
class Application {
// 問題のメソッド
public List<Entry> merge(List<Entry> list1, List<Entry> list2 ){
Collections.sort(list1, Entry.ID_ASCEND_COMP);
Collections.sort(list2, Entry.ID_ASCEND_COMP);
Iterator<Entry> it1 = list1.iterator();
Iterator<Entry> it2 = list2.iterator();
Entry e1 = null;
Entry e2 = null;
List<Entry> merged = new ArrayList<Entry>();
while(it1.hasNext() && it2.hasNext()){
if(e1==null) e1 = it1.next();
if(e2==null) e2 = it2.next();
int key = Entry.ID_ASCEND_COMP.compare(e1,e2);
if(key<0){
merged.add(e1);
e1 = null;
} else if(key==0) {
e1.setValue(e1.getValue()+e2.getValue());
merge.add(e1);
e1 = null;
e2 = null;
} else {
merged.add(e2);
e2 = null
}
}
if(e1!=null) merged.add(e1);
if(e2!=null) merged.add(e2);
while(it1.hasNext()) merged.add(it1.next());
while(it2.hasNext()) merged.add(it2.next());
return merged;
}
}
で、このメソッドが何をしているかというと、引数に与えられた2つのコレクションを1つにまとめるということをしています。ただし、IDが重複しているインスタンスがあれば、片方のインスタンスに value を加算してそれを追加するというメソッドです。ちなみに、list1とlist2がnullの場合は考えなくても構いません。(NullPointerExceptionが発生して終了するだけなんで。)また、ID_ASCEND_COMPは、Entryのidをソーとキーとして昇順に並び替えるComparatorです。*2
続きを読む
大学での研究を行うために、Cygwin上でPerlのスクリプトを実行することにしました。*3
これまではLinux上で動かすだけだったので、PerlからJavaプログラムを呼び出しても
全然問題なかったんですが、Cygwinになるとちょっと厄介な問題が発生します。
その問題というのは、PerlとJavaでパスの指定の仕方が異なるという点です。
Perlでは、よくCygwinでも使われている /cygdrive/c/(以下略) という形式で書くことができます。
また、スクリプトが存在する位置から見た相対パスでももちろん指定することができます。
しかし、このPerlスクリプトからJavaプログラムを呼び出した場合は、Windows形式で
直接指定しないといけません。(例えば、c:/Documents\ and\ Settings/hogehoge/ とか。)
なので、Perlスクリプト用のパスとJavaプログラム用のパスを別々に考えないといけないわけです。
ウェブ上を探してみると、Javaプログラムのためにパスを変換してくれるラッパー
アプリケーションがあるようなんですが、まだ試していません。
これがうまく動作するなら、とても便利そうなんですけどね。
なんとかならないんでしょうか、このCygwinの仕様。
Javaで行列式を計算させるのに一番手っ取り早いのはフリーのライブラリを利用することでしょう。ですが、そのようなことができない場合*4 、自分でソースコードを書く必要があります。そのことについて、ちょっと前に話に挙がっていたので作ってみました。
public class Matrix {
// 行列式を求めるメソッド
public static double det(double[][] args){
final int size = args[0].length;
for(int i=0; i<args.length; i++){
if(args[i].length!=size)
throw new IllegalArgumentException();
}
double det = 0;
for(int i=0; i<size; i++){
double rightdown = 1;
double leftdown = 1;
for(int j=0; j<size; j++){
rightdown *= args[(i+j)%size][j%size];
leftdown *= args[(i+size-j)%size][j%size];
}
det += rightdown - leftdown;
}
return det;
}
// テストコード
public static void main(String[] args){
double[][] matrix = new double[][]{
{1,2,3},
{4,5,6},
{7,8,9}
};
System.out.println(det(matrix));
}
}
これは行列式を計算させる時によく用いられるサラスの方法 を実装したものです。引数として配列の配列を渡していますが、この配列のサイズは全て同じでないといけません。また、どちらが行でどちらが列とかそんなことは気にしなくても正しい結果が得られます*5 。
注意しなければいけないのが、サラスの方法では4次以上の正方行列の行列式は計算できない こと。4次以上の場合は、余因子展開をするなどして次数を落とす必要があります。
ちなみに、このコードはBSDライセンス です。とは言っても、普通に書いても同じようなコードになるんで、適当に利用してやってください。このコードで発生した不具合については、なんら責任を負いません。また、これに基づいたコードについては著作権表示を忘れないようにお願いします*6 。
続きを読む
2007年10月10日(水)
更新日:2008年01月10日(木)17:38
Java
Javaの実行環境で外部ライブラリを利用するには、クラスパスの中にライブラリを追加する必要があります。しかし、デフォルトのクラスパスに外部ライブラリを追加するのはなかなか抵抗があります。そこでここでは、自分のライブラリがある場所をクラスパスとして指定して、JVMに読み取ってもらう方法を紹介します。
[web] クラスパス完全理解 - JavaHouse-Brewers:21880
一時的にクラスパスを指定するだけなら、実行時に指定する方法もあります。その場合は、javaコマンドに対して、-classpath (もしくは-cp)オプションを指定します。
$ java -classpath .:~/lib test.HelloWorld
↑~/libにあるtest.HelloWorldクラスを実行する場合
しかし、毎回指定しなくても環境変数CLASSPATHに追加するだけで同様のことが実現できます。ただし、他のプログラムからも参照可能になるので、そのあたりは考慮する必要があります。
export CLASSPATH=.:~/lib
2007年8月28日(火)
更新日:2008年01月10日(木)17:32
Java
Javaを普段からよく使われている方なら、APIのリファレンスはよく参照されていることかと思います。ですが、このAPIのドキュメントにもごくまれに誤りがある場合があります。そんな時は、適当にスルー…、ではなくてSunの方に連絡すると修正してもらえます。
[web] jdk-api-ja Project Home Page
このページの「問題の報告」というタイトル以下に、誤訳などの問題の報告方法が書かれていますので、それに従って連絡しましょう。問題を指摘するだけなら、メールを1通だけ出すだけでOKです。どんなフォーマットでメールを書けばいいか分からない!という方は、過去にプロジェクトに送られてきたメールの履歴(メーリングリストの履歴)が公開されていますので、それを真似して送りましょう。
[web] jdk-api-ja メーリングリスト アーカイブ
2007年8月14日(火)
更新日:2008年01月10日(木)17:31
Java
今回紹介するクラスは、org.apache.commons.lang.CharSetUtils です。このクラスでは、引数に null を与えられても例外をスローしない方針を採っています。うまく使えば、ちょっと面倒な処理を簡単にできるかもしれません。
主なメソッドは次の通りです。
#count(String str,String set);
#delete(String str,String set);
#keep(String str,String set);
#squeeze(String str,String set);
このメソッドの共通点は、第1引数の文字列に対して、第2引数の文字列の範囲でなにかをするというところです。また、第2引数では、正規表現の一部分を利用したシンタックス(set syntax)を用いて、複数の文字を指定することができます。
1つ目の count メソッドでは、第1引数の文字列にどれだけ第2引数の文字列が含まれるかを返します。2つ目の delete メソッドでは、第2引数の文字を第1引数の文字列から削除します。3つ目の keep メソッドでは、delete メソッドとは逆に第2引数の文字を第1引数から残した文字列を返します。そして4つ目の squeeze メソッドでは、第2引数に指定した文字の重複を第1引数から削除します。
ちょっと日本語では使いづらいかもしれませんが、アルファベットをメインにした文書なら有効に使えると思います。
2007年8月 9日(木)
更新日:2008年01月10日(木)17:29
Java
Javaの世界ではかなり有名なプロジェクトであるApacheプロジェクト。そこで配布されているライブラリは、とても多くの場面で使用されています。そんなわけで、僕自身が利用できるライブラリがないかどうかを調べて、ここにメモしておくことにします。
まず初めは、org.apache.commons.lang.ArrayUtils からです。このクラスは、主にプリミティブ型の配列に対してよく行う処理をまとめたクラスです。フィールドやメソッドはだいたい以下の通りです。
#EMPTY_INT_ARRAY
#EMPTY_INTEGER_OBJECT_ARRAY
#EMPTY_OBJECT_ARRAY
#INDEX_NOT_FOUND
#add(int[] array, int element)
#add(int[] array, int index, int element)
#addAll(int[] array1, int[] array2)
#clone(int[] array)
#contains(int[] array, int valueToFind)
#indexOf(int[] array, int valueToFind)
#indexOf(int[] array, int valueToFind, int startIndex)
#isEmpty(int[] array)
#isSameLength(int[] array1, int[] array2)
#lastIndexOf(int[] array, int valueToFind)
#lastIndexOf(int[] array, int valueToFind, int startIndex)
#remove(int[] array, int index)
#removeElement(int[] array, int element)
#reverse(int[] array)
#subarray(int[] array, int startIndexInclusive, int endIndexExclusive)
#toObject(int[] array)
#toPrimitive(Integer[] array, int valueForNull)
ここに列挙したのはint型(Integer型)に関してですが、他のプリミティブ型(もしくはそのラッパークラス)も実装されています。clone メソッドは、Java6で追加されたArrays#copyOf メソッドとほぼ同じだと思うのですが、きっと null の場合の扱いが異なるのではないかと思っています。
ここに書かれているメソッドの多くの返り値は、プリミティブ型の配列です。また、引数で与えられた配列の内容は変更しないように実装されています。
[web] Apache Commons Lang
2007年8月 7日(火)
更新日:2008年01月10日(木)17:29
Java
研究の関係で、大量のテキストデータを扱う必要が出てきました。最近のエントリでいくつか扱ってきたように、その大量のデータはSGMLで記述されているのですが、それではテキストデータからプログラムに読み込むだけでリソースを多く消費してしまいます。それでは困るので、Javaで扱いやすいXML形式に変換してやろうというわけです。
とりあえず簡単に調べてみたところ、APIに java.beans.XMLEncoder と java.beans.XMLEncoder があることが分かりました。これを使ってXMLにデータを変換してやります。
[web] XMLEncoder - Java Platform SE 6
[web] XMLDecoder - Java Platform SE 6
XMLEncoderクラスが、オブジェクトからXMLに変換するクラスです。逆にXMLDecoderクラスが、XMLからオブジェクトに変換します。使い方は次の通り。
OutputStream out = new FileOutputStream("hoge.xml");
XMLEncoder encoder = new XMLEncoder(
new BufferedOutputStream(out));
encoder.writeObject(obj);
encoder.close();
InputStream in = newn BufferedInputStream(
new FileInputStream("test.xml"));
XMLDecoder decoder = new XMLDecoder(in);
Object obj = decoder.readObject();
decoder.close();
エンコードするオブジェクトには、いくつか制限があります。
(1)引数なしのコンストラクタを実装している。
(2)フィールドに対応したgetter/setterを実装している。
ただし、ObjectOutputStreamクラスの場合と異なり、Serializableインターフェースを実装する必要がありません。
2007年7月31日(火)
更新日:2008年01月10日(木)17:28
Java
Javaを用いていると、提供されているAPIの関係で○○Readerから○○InputStreamに変換しないといけない場合があります。ここでは、ReaderをInputStreamに変換するための簡単な指針をメモしておきます。
(1)InputStreamクラスを継承するサブクラスを作成します。(アダプタパターン)
(2)InputStream#read()メソッド内でCharBuffer.allocate(int)を用いて、適度な大きさのCharBufferインスタンスを生成します。
(3)変換するReaderから文字列を取得し、CharBuffer#put(String)メソッドによってバッファに文字列を格納します。
(4)Charset.forName(String)によってCharsetインスタンスを生成し、さらにCharset#newEncoder()メソッドでCharsetEncoderを生成します。
(5)CharsetEncoder#encode(CharBuffer)メソッドでByteBufferに変換します。
(6)ByteBuffer#get()でbyteを取得し、InputStream#read()メソッドの返り値として返します。バッファの含まれる残りのbyte数を知りたいときは、ByteBuffer#remainig()メソッドを用います。