ログインしてさらにmixiを楽しもう

コメントを投稿して情報交換!
更新通知を受け取って、最新情報をゲット!

Javaの課題丸投げコミュの読み込んだファイルを置換して違うファイルに書き込む場合

  • mixiチェック
  • このエントリーをはてなブックマークに追加
いつもこのコミュでお世話になっております。
今回もよろしくお願いいたします。
・実行時引数の第一引数には、文字列を読み込むファイルを指定。
・同じく第二引数のファイルに置換後のファイルを作成する。
・第三引数以降は『置換前:置換後』をフォーマットとする文字列を指定し、
 いくつでも設定可能とする。そして、":"を区切り文字とする。
・置換前と置換後の文字列は区切り文字で分解し、HashMapで持たせる。
という課題です。

HashMapに置換前後の文字列を持たせ、読み込んだ文字列を置換していくという処理のループがうまくいきません。
読み込むファイルのテキストファイルは複数行の文字列で構成されているのですが、
・HashMapに持たせたすべての置換条件で、読み込んだ文字列を一行ずつ置換して書き込む
という処理ができません。
まだ、動作の確認段階で全てメイン内での処理になってますが、悪しからず。

どうかお力添えお願いいたします。

public class Sample {

public static void main(String[] args) throws IOException {
Pattern pattern;
Matcher matcher = null;
HashMap<String,String> map = new HashMap<String,String>();
/* ---------------------読込・書込ファイルの指定--------------
* (1)FileReaderオブジェクトinFileを生成
* 読み込むファイルを指定する
*/
FileReader inFile = new FileReader(args[0]);
/*
* (2)FileWriterオブジェクトoutFileを生成
* 置換後に作成するファイルを指定する
*/
FileWriter outFile = new FileWriter(args[1]);
//(3)BufferedReaderオブジェクトinBufferを生成
BufferedReader inBuffer = new BufferedReader(inFile);
//(4)BufferedWriterオブジェクトoutBufferを生成
BufferedWriter outBuffer = new BufferedWriter(outFile);
String line;
/* --------------------置換条件の設定------------------------- */
for(int i=2; i<args.length; i++){
// 分割条件の設定(引数ごとの分割)
String[] Argument = args[i].split(" ");
// 分割条件の設定(分割した引数を“:”でさらに分割
int j =0;
String[] divideArgument = Argument[j].split(":",-1);
// HashMapに格納
map.put(divideArgument[j],divideArgument[(j+1)]);
}
/* ----------------------HashMapに格納------------------------ */
Set<String> s = map.keySet();
Iterator<String> is = s.iterator();
String key = null;
StringBuffer sb = new StringBuffer();
while ((line = inBuffer.readLine())!= null) {
while(is.hasNext()){
key = is.next();
System.out.println("キー:" + key + " 値:" + map.get(key));
// 置換対象の条件
pattern = Pattern.compile(key);
// 検索される文字列
matcher = pattern.matcher(line);
while(matcher.find()){
matcher.appendReplacement(sb, map.get(key));
}
}
matcher.appendTail(sb);
outBuffer.write(sb.toString());
outBuffer.newLine();
}
//(7)バッファをフラッシュ
outBuffer.flush();
//(8)読み込みストリームのクローズ
inBuffer.close();
//(9)書き込みストリームのクローズ
outBuffer.close();
}
}

コメント(18)

PatternとMatcher使うなら、String#replaceAllのほうが楽です。
引数が正規表現として処理されてしまいますが。


import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

public class Sample {

public static void main(String[] args) throws IOException {
HashMap<String, String> map = new HashMap<String, String>();
/*
* ---------------------読込・書込ファイルの指定--------------
* (1)FileReaderオブジェクトinFileを生成 読み込むファイルを指定する
*/
FileReader inFile = new FileReader(args[0]);
/*
* (2)FileWriterオブジェクトoutFileを生成 置換後に作成するファイルを指定する
*/
FileWriter outFile = new FileWriter(args[1]);
// (3)BufferedReaderオブジェクトinBufferを生成
BufferedReader inBuffer = new BufferedReader(inFile);
// (4)BufferedWriterオブジェクトoutBufferを生成
BufferedWriter outBuffer = new BufferedWriter(outFile);
String line;
/* --------------------置換条件の設定------------------------- */
for (int i = 2; i < args.length; i++) {
// 分割条件の設定(引数ごとの分割)
String tArgument = args[i];
// 分割条件の設定(分割した引数を“:”でさらに分割
String[] divideArgument = tArgument.split(":", -1);
// HashMapに格納
map.put(divideArgument[0], divideArgument[1]);
}
/* ----------------------HashMapに格納------------------------ */
while ((line = inBuffer.readLine()) != null) {
outBuffer.write(replaceString(line, map));
outBuffer.newLine();
}
// (7)バッファをフラッシュ
outBuffer.flush();
// (8)読み込みストリームのクローズ
inBuffer.close();
// (9)書き込みストリームのクローズ
outBuffer.close();
}

private static String replaceString(String aSource, Map<String, String> aReplaceStringMap){
String tSource = aSource;
Map<String, String> tMap = aReplaceStringMap;
for (Entry<String, String> tEntry : tMap.entrySet()) {
String tTarget = tEntry.getKey();
String tReplacement = tEntry.getValue();

tSource = tSource.replaceAll(tTarget, tReplacement);
}

return tSource;
}
}
2には間違いがあったので消しました。

適当な検証コード


import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.List;

public class SampleMain {
public static void main(String[] aArg) throws Exception {
File tSourceFile = new File("source.txt");
File tDestFile = new File("dest.txt");
List<String> tSourceList = new ArrayList<String>();
List<String> tDestList = new ArrayList<String>();

tSourceList.add("実行時引数の第一引数には、文字列を読み込むファイルを指定。");
tSourceList.add("同じく第二引数のファイルに置換後のファイルを作成する。");
tSourceList.add("第三引数以降は『置換前:置換後』をフォーマットとする文字列を指定し、いくつでも設定可能とする。そして、\":\"を区切り文字とする。");
tSourceList.add("・置換前と置換後の文字列は区切り文字で分解し、HashMapで持たせる。");

tDestList.add("実行時引数の第一引数には、文字列を読み込むふぁいるを指定。");
tDestList.add("同じく第二引数のふぁいるに置換後のふぁいるを作成する。");
tDestList.add("第三引数以降は『置換前:置換後』をふぉーまっととする文字列を指定し、いくつでも設定可能とする。そして、\":\"を区切り文字とする。");
tDestList.add("・置換前と置換後の文字列は区切り文字で分解し、HashMapで持たせる。");

BufferedWriter tWriter = null;
try {
tWriter = new BufferedWriter(new FileWriter(tSourceFile, false));

for (String tLine : tSourceList) {
tWriter.write(tLine);
tWriter.newLine();
}
} finally {
if (tWriter != null)
tWriter.close();
}

Sample.main(new String[] { tSourceFile.getAbsolutePath(), tDestFile.getAbsolutePath(), "ファイル:ふぁいる", "フォーマット:ふぉーまっと" });

BufferedReader tReader = null;
try {
tReader = new BufferedReader(new FileReader(tDestFile));
String tLine;
int i = 0;
while ((tLine = tReader.readLine()) != null) {
String tExpected = tDestList.get(i);
i++;
if (!tLine.equals(tExpected)) {
throw new RuntimeException("予想(=" + tExpected + ")と結果(=" + tLine + ")が違います。");
}
}
} finally {
if (tReader != null)
tReader.close();

tSourceFile.delete();
tDestFile.delete();
}

System.out.println("完了");
}
}
まず仕様不足について確認.

- ":" を含んだ文字列を置換したいときはどうするんだろ?
- また, 置換したい文字列が行を跨がってる可能性は排除していいんだろうか?
- 複数の置換条件にマッチする場合や, 置換した結果, その次以降の条件にマッチするようになった場合ってどうしたらいいんだ?
それについての仕様はないの? > トピ主

とりあえず,
- 置換前後の文字列に ":" は無しで, それらが行を跨ぐことも無い.
- 一番初めにマッチした置換ルールだけ適用.
という前提でやります.


> while ((line = inBuffer.readLine())!= null) {
> while(is.hasNext()){

ここの部分が問題なんだけど, 各行に対して同じ Iterator オブジェクトと StringBuffer オブジェクトと Matcher オブジェクトを使い回しているよね?
そのせいで, 2行目以降は置換が行われず, appendTail のおかげで1行目の残りの部分が累積していくという変な出力になってます.

Iterator を使うときは
-----
for (Iterator iter = map.keySet().iterator(); iter.hasNext();) {

}
-----
というふうに書いて変数のスコープをできるだけ狭めましょう. 他の変数についても同様です.

ついでにコードレビュー.
> // 分割条件の設定(引数ごとの分割)
> String[] Argument = args[i].split(" ");

ここの操作の目的は?

$ java Sample " hoge:fuga"
って実行したときにルールが無視されてしまうから, 実行時引数チェックにもなってないよね?

「置換条件の設定」ってコメントしてある部分は,

for (int i = 2; i < args.length; i++) {
  String[] rule = args[i].split(":");
  if (rule.length == 2) { // とりあえずフォーマットに厳しく
    map.put(rule[0], rule[1]);
  }
}

でいいんじゃないかな?

文字列置換の処理の部分は,
String replacedLine = oldLine.replaceAll("before", "after");
と書くと読みやすいし, 上記のバグも起こりづらいんじゃないかな?
>seraphさん
ありがとうございます。
HashMapに格納する際の"Entry"や"拡張for文"、HashMapの使い方等まだまだ勉強しなければいけないことが出てきました。
一通り勉強してみますので、その後、また御教授お願いします。
>杵搗き餅@醸造中さん
ありがとうございます。

>- ":" を含んだ文字列を置換したいときはどうするんだろ?
>- また, 置換したい文字列が行を跨がってる可能性は排除していいんだろうか?
これらに対して特別な指示は出ていません。
・第三引数以降のフォーマットは「置換前文字列:置換後文字列」とする。
・半角の":"を区切り文字とする。
・区切り文字を指定されなかった、もしくは指定されていても置換後文字列が指定されていない場合、置換処理では置換前文字列を消すだけにする。
・置換前文字列には正規表現を使用可能とする。

という条件しか指定されていません...
特に3番目の処理の実装の仕方がわからなかったので、まずはそれ以外の部分をと思って飛ばしました。

>> // 分割条件の設定(引数ごとの分割)
>> String[] Argument = args[i].split(" ");
>ここの操作の目的は?

これは私の消し忘れでした。すみません。

置換条件の設定方法や置換処理の部分は参考にさせていただきたいと思います。

>- 複数の置換条件にマッチする場合や, 置換した結果, その次以降の条件にマッチするようになった場合ってどうしたらいいんだ?
複数の条件にマッチし、すべての条件を適用して置換する場合はどのようにしたらよいでしょうか?
> ・区切り文字を指定されなかった、もしくは指定されていても置換後文字列が指定されていない場合置換処理では置換前文字列を消すだけにする。
> ・置換前文字列には正規表現を使用可能とする。

この条件、トピックに書いてないんですが…
私の書いたコードは、後者は勝手に正規表現になっているのでいいですが、前者は実装されていません。


>>- 複数の置換条件にマッチする場合や, 置換した結果, その次以降の条件にマッチするようになった場合ってどうしたらいいんだ?
> 複数の条件にマッチし、すべての条件を適用して置換する場合はどのようにしたらよいでしょうか?

それは仕様で決めるべき内容です。
『複数の置換条件にマッチする場合』とは、以下のような場合を指していると思われます。

置換対象文章:『あしたはいいてんき』
置換条件:『した:うえ, あした:きょう』

このような条件の時、両方の置換を適用することはできないため、どのように置換を行うかを決める必要があります。
私の書いたコードは、最初にマッチした条件が適用されます。


『置換した結果, その次以降の条件にマッチするようになった場合』とは以下のような場合です。

置換対象文字列:『あしたはいいてんき』
置換条件:『あした:きょう, うは:うへ』

この場合、『あした:きょう』の変換を適用したあとの文字列は『うは:うへ』の条件にマッチするようになります。
この場合、『うは:うへ』による置換を行うかどうかが課題には記述がありません。
私の書いたコードは、置換を行います。
>seraphさん
説明の記載不足でした。すみません。
いまいち、自分で問題の内容をしっかりと理解できていませんでした。
申し訳ありません。

複数の条件がマッチした場合に関してはやはり、詳しい仕様がありません。
あす、学校で聞いてきたいと思います。

お忙しい中、私の質問にお答えいただいているのに、
言葉足らずで本当にすみませんでした。
>7
ashMap#entrySetで取り出す際に、Hash値の順に取り出されるのではないですか?
置換対象文章:『あしたはいいてんき』
置換条件:『した:うえ, あした:きょう』
の結果が、『あうえはいいてんき』か『きょうはいいてんき』になるかは
Hash値次第であって、定義した順に置換されるわけではないのではないですか?
>9

はい、順序は不定になると考えたほうがいいと思います。
その点で>7の説明は不適切でした。
>seraphさん
今回の課題の仕様では、複数のマッチする条件に関してはそこまで追求していないので、
記述していただいたコードで大丈夫です。

あと、
>private static String replaceString(String aSource, Map<String, String> aReplaceStringMap){
>String tSource = aSource;
>Map<String, String> tMap = aReplaceStringMap;
>for (Entry<String, String> tEntry : tMap.entrySet()) {
>String tTarget = tEntry.getKey();
>String tReplacement = tEntry.getValue();
>tSource = tSource.replaceAll(tTarget, tReplacement);
>}
>return tSource;
>}
の"Entry<String, String> tEntry"の部分はジェネリックですか?
"Entry"について調べた際に"Map.entry"しかなかったので...
>11
どういう返答が適切なのか分かりませんが、その部分はジェネリックを使用しています。


>12
今回の課題においては、HashMapを使用することが指定されているので、LinkedHashMapは使用できません。
Map map = new LinkedHashMap();
if (map instanceof HashMap) {
  System.out.println("OK");
} else {
  System.out.println("NG");
}

とすると "OK" と表示されるが, それでも LinkedHashMap 使っちゃだめなんかな??

>11: しりきれとんぼさん
> 今回の課題の仕様では、複数のマッチする条件に関してはそこまで追求していないので、
> 記述していただいたコードで大丈夫です。

これは課題の仕様の確認をしてきた, と理解すればいいのかな?
>seraphさん
わかりました。それでは、ジェネリックについても勉強してみます!

>杵搗き餅@醸造中さん
>これは課題の仕様の確認をしてきた, と理解すればいいのかな?
確認しました!
>15: しりきれとんぼさん

だとしたらこの課題の出し方からすると, 置換ルールの適用順序についてはそこそこいい加減な仕様でもいいレベルの課題なのかもね.
>杵搗き餅@醸造中さん
課題に取り組む身としてはコメントしづらいです(笑)。
ただ、今回の課題でこういうのもあるぞっていうのは何と無くわかります。
> 14

継承関係があっても、クラス名を明示して指定されている場合、使うべきでないと考えます。
まぁ、題意を考えると、その程度の変更なら減点対象ではなさそうではありますが。

ログインすると、残り1件のコメントが見れるよ

mixiユーザー
ログインしてコメントしよう!

Javaの課題丸投げ 更新情報

Javaの課題丸投げのメンバーはこんなコミュニティにも参加しています

星印の数は、共通して参加しているメンバーが多いほど増えます。

人気コミュニティランキング