2015

バブルソート (Bubble Sort)

Java

今日も簡単なアルゴリズムのエントリーです。

私が初めて覚えたソートアルゴリズムです。

当時はこれを考えた人はもの凄い頭のいい人だと感動しました。

バブルソート (Bubble Sort) は読んで字のごとく泡のソートです。(^_^;)

つまりソートの過程がデータが下から上へ移動する様子が、泡が浮かんでいくように見えることから名付けられたとされています。

最悪計算時間が O(n2) と遅いのですがシンプルで理解しやすい優しいアルゴリズムです。

そして安定であることが特徴の一つでもあります。

実際どんなことをしているかというと隣り合う要素の大きさを比較して並べ替えていくという単純なものです。

これを要素数 –1 回繰り返すことでソートを行なうことによって完了します。

バブルソートのアルゴリズムを解説したサイトは数多く存在します。

たいていの場合は「要素数-1回繰り返すことでソートを行う」を素直にコードにして紹介されています。

これはバブルソートの解説には解りやすくて良いのですが無駄が発生するので良くありません。

なので今回、私は涙ぐましい改良を施されたバブルソートを組みました。

ソートの部分的完了を検出し、比較範囲の限定を行っています。

ついでに配列が並び変わっていく様子を表示させてみました。(比較、交換作業は除く)

プログラムの実行結果は下図のようになります。

1

ザックリ説明すると例えば次のような配列が与えられた時

1, 2, 5, 8, 7, 9, 6

最初の while ループは

1, 2, 5, 8, 7, 9 ← 6

1, 2, 5, 8, 7 ← 6, 9

1, 2, 5, 8 ← 6, 7, 9

1, 2, 5 – 6, 8, 7, 9

1, 2 – 5, 6, 8, 7, 9

1 – 2, 5, 6, 8, 7, 9 (赤字は比較交換、緑字は比較未交換)

となり、配列のインデクスナンバー 3 まではソート完了となります。

1, 2, 5, 6, 8, 7, 9 (ピンク色の数字はソート完了済み)

よって、while ループの条件を可変することにより無駄な処理をへらせます。

また、内側の for ループも最後に要素交換した右側のインデックスナンバー(ソートが確定していない)を利用して

無駄に比較捜査をしないようにさせています。要するにソート済み部分の再捜査をしないことによって効率を上げようというものです。

次の while ループ処理は

1, 2, 5, 6, 8, 7 – 9

1, 2, 5, 6, 8 ← 7, 9

1, 2, 5, 6, 7, 8, 9

次の While ループで

1, 2, 5, 6, 7, 8 – 9

1, 2, 5, 6, 7, 8, 9

ソート完了!

もし、涙ぐましい改良がされていなければ無駄な比較処理がたくさん行われることになったでしょう。

今回は一般的なバブルソートの改良版を紹介しましたがバブルソートの亜種はまだあります。

シェーカーソートはバブルソートの亜種で安定のまま高効率化が達成?されています。

これはバブルソートを先頭からと末尾から交互に捜査して並べ替えていくものです。

だからシェーカーなんですね。(^_^)

アルゴリズムの基本的な考え方はバブルソートそのものなので興味のあるかたはプログラムを組んで楽しんでください。(^_^)

Hatena タグ:

単純挿入ソート (Shuttle Sort)

Java

ブログエントリーさぼらないために比較的よく知られているシンプルなアルゴリズムを書いてみた。

単純挿入ソート (Shuttle Sort) を書いてみました。

特徴としてトランプのカードを順番に並び替えるようなアルゴリズムであり、安定であること。

効率はあまり良くなく、O(n^2) です。

具体的には i 番目の要素(0番目からじゃない) に着目して i –1 番目と比較してそれより小さければ後ろにずらして挿入する。

これを繰り返しているだけの単純なアルゴリズムです。

特別にトリッキーなことはしてなくて理解しやすいアルゴリズムですね。

プログラムにはソートされていく過程をトレースして出力させています。

このプログラムの実行結果は下図のようになります。

1

ちなみに Java8 を使っているので for 文でいいところを IntStream を使ったりしてます。

比較、入れ替えの for 文のところを Stream API を使ってできないものかと考えてみたのですが良いアイディアが浮かびませんでした。

配列の要素を並び替えるだけなので出来そうな気がしたのですが・・・(力不足、知恵不足、お小遣い不足)(ヲヒ

あっ、Stream API にはもちろん標準で sort の機能はあります。

だから本当はこんなアルゴリズム使う必要はありません!

そこんところ ヨロシク! by 永ちゃん

あと、乱数配列の生成は public IntStream ints(long streamSize,int randomNumberOrigin,int randomNumberBound) メソッドを使って生成しました。

超便利です!

今回もありきたりネタでした。(^_^)

Hatena タグ:

Java 8 時代の総和の求め方

Java

Blog エントリーさぼりがちになるのでしょうもないネタを書いときます。

Java 8 になって Stream API がめっちゃ便利に使えてよろこんでいる開発者たちがたくさんいますよね!

中には仕事で未だに古い Java を使わざるを得ない非常に可愛そうな環境のひとも・・・(ヲヒ

で、おそらく春に入社してくるフレッシュな新人プログラマには Java 8 の Stream API を使った新しい構文を勉強してもらうことになりますよね?

今さら、JDK 1.4, JDK 1.5, JDK1.6, JDK1.7 なんてことはないですよね。ねっ!ねっ!

そこで、1 から 1000 までの偶数だけの総和を求め。

2 の 0 乗から 2 の 32 乗までの総和を求める。

この二つを題材にして超シンプルなプログラムを Java 8 で組むとどうなるでしょうか?

次のような感じで組んでしまったら、あなたは新人さんに影で static おじさん じゃない・・・ Stream おじさん と呼ばれることになるかもしれません。

えっ?

こんな感じのプログラムしか思いうかばないって!

Stream おじさん に認定します!

このプログラムは Stream API の説明サンプルには問題ないのですが効率が悪いのです。

まじめに勉強をしてきた新人さんほどこういったところを突いてくると思うので先に古典的な手法で求める方法を見せといてから

無理に Stream API を使うとこうなるよって言っとくのが無難だと思います。

新人プログラマの多くは次のようなプログラムを当たり前のように組むでしょう。

Stream API なんて使わずに古典的な手法を用いてシンプルに!

いつの時代になってもこういった定番の処理方法は不滅です!

ちなみに偶数の総和は「日経ソフトウェア3月号」の「アルゴリズム&テクニック」で紹介されてます。

今月は他にも面白ネタが紹介されていておもしろかった。

この総和を求めるアルゴリズムは古典的な手法で広く知られているようで

「アルゴリズムパズル プログラマのための数学パズル」という本でもちょこっとだけ紹介されていました。

たとえ、カビが生えているような技術でもいいものはいつまでも使われるってことですね! (^_^)

以上、しょうもないネタでした。

Hatena タグ:

TextField を 0 から 9 までの数値しか入力できないようにしてみた

JavaFX

このプログラムは TextField に入力制限をかけたいなぁって思ってどうすればいいんだろう?ってネットサーフィンしていたときに見つけたものです。

Java 8u40 では Formatted Text が追加されるらしいのですが、お試し版を使ったサンプルコードの紹介がまだ見当たらない現状なのでφ(..)メモメモ

ちなみに Alert Dialogs はみんな待っているのでサンプルコードはもう出ていますね。

あと、Spinner も追加されるようです。

JavaFX はまだまだ足りないものがあるとあちこちで言われていますが着実に進化しているようです。

それでは忘備録として下記コードを書き留めておきます。

このプログラムは TextField の lengthProperty にリスナーを登録して入力した値を判定して処理してます。

これだと何かしらのイベント発生時に TextField の入力値を取得して正規表現で判定して問題あればアラートダイアログ出して入力し直してもらうという方法より場合によってはいいかもしれません。

小ネタですがもう暫くは活躍してもらえそうです。(^_^)

Hatena タグ:

CompletableFuture で遊ぶ

Java

あけましておめでとうございます!

ずいぶん遅い新年の挨拶となりますが本年もよろしくお願いします。

それでは新年1発目のネタは私の愛する Java です!

Java 8 の新機能である CompletableFuture の詳細が下記サイトで解説されはじめました。

詳解 Java SE 8 第19回 Concurrency Utilitiesのアップデート その1

Java 8 が正式リリースされる前から気にはなっていたのですが海外のサイトでも情報量が少なく英語がよく解らないので正しい使い方が解らずにいました。

おまけに Java API ドキュメントもその当時は日本語のものはありませんでした。

現在は日本語の API ドキュメントも用意されているのでうれしい限りです。

それではとりあえず何か適当にプログラムを組んでみることにします。

お正月ということでちょっとふざけた内容としました。

ネタがネタだけにごめんなさいと先に行っておきます。(ヲヒ

今回のネタは CompletableFuture を使ってプログラムを高速化するです。(そんな大袈裟なものじゃないし、使わなくても可能なのは秘密です。)

あなたは、ある IT 企業に勤めています。今回あるプロジェクトのリーダーを任されました。

あなたは何人かメンバーを選出しなければなりません。

そこで希望者を募ったところ、たくさんの魅力的な女性プログラマ達があなたを取り囲みました。

予想外の出来事にあなたは大喜びで全員をプロジェクトチームに加えようとしましたが・・・

なんと女性達は隣の女性をチームに加えるなら私は辞退すると言います。

たとえば、A子、B子、C子、D子、E子 と言う具合にあなたを中心に取り囲んでいるとしたら

A子をメンバーに加えると、B子、E子はチームに加えることは出来ないということです。

B子をメンバーに加えると、A子と C子はチームに加えることはできない。

困りました。

そこであなたは胸ポケットから「おっぱいスカウター」という秘密兵器を取り出し女性達のバストを計測できるようにしました。

そう、あなたは、おっぱい星人だったのです。

それを利用してチームに加える女性達のバストの合計値が最大になるように選出することにします。

これからもこういうことがちょくちょくあるかもしれないのであなたはプログラムを組むことにします。

さて、あなたならどんなプログラムを組むでしょう?(Java 8 で組むこと)

女性プログラマ達のバストのサイズは配列に乱数を生成して格納します。

配列の要素数は女性プログラマの人数となります。

さて、あなたならどんなコードを書くでしょうか?

最も簡単な例は次のようなコードになると思います。

デバッグ用に必要の無いものがありますが気にしないでください。

一見、実にシンプルでいて合理的なように見えます。

それでは動かしてみます。

1

順番に処理されているのが解ります。

処理時間は OppaiSearch クラスの getOppaiTask() メソッドの中のループ処理中にランダムなスリープを少し挟んでいるので気にしないでください。

このプログラムを高速化するには getOppaiTask() メソッドを並行処理してしまうのが手っ取り早いですね!

そこで CompletableFuture を使って楽に高速化してみます。

OppaiAlien クラスを次のように変更します。

見慣れないコードがありますね。

次のように supplyAsync() メソッドを使って CompletableFutureオブジェクトを生成します。

CompletableFuture<Integer> future0 = CompletableFuture.supplyAsync(() -> oppai_0.getOppaiTask());
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> oppai_1.getOppaiTask());

そして supplyAsync() メソッドで作った二つの CompletableFutureオブジェクト を非同期で処理させます。

supplyAsync() メソッドは API ドキュメントでは次のように説明されています。

public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)

ForkJoinPool.commonPool()で実行されているタスクが指定されたサプライヤを呼び出して取得した値を使用して非同期的に完了する新しいCompletableFutureを返します。

それでは次にこれらから得られる値の大きなほうが最終的な結果となります。

実はこのプログラムはちょっと遊びが入っているので本来の目的だけを達成するには次のコードで完了させることができます。

try {
    System.out.println(“おっぱいのサイズの最大総和は ” + Math.max(future0.get(), future1.get()) + “です。”);
    System.out.println(“プログラムを終了します。”);
    } catch (InterruptedException | ExecutionException ex) {
    Logger.getLogger(OppaiAlien.class.getName()).log(Level.SEVERE, null, ex);
}

get() メソッドは処理が完了するまで待つので両方の非同期処理の結果を取得してから Math.max() メソッドは実行されます。

このプログラムでは thenCombine() メソッドを使って、CompletableFuture<Integer> future0 と CompletableFuture<Integer> future1 の両方の処理が終わるまで待って

それらを使って処理をして結果を返すようにしています。

// 両方の処理が終わってから計算値が大きい方を返す。
CompletableFuture<Integer> f = future0.thenCombine(future1, (f0, f1) -> {
    System.out.println(Thread.currentThread().getName() + ” : CompletableFuture<Integer> f”);
    return Math.max(f0, f1);
});

thenCombine() メソッドは API ドキュメントによると次のように説明されています。

public <U,V> CompletableFuture<V> thenCombine(CompletionStage<? extends U> other,
                                              BiFunction<? super T,? super U,? extends V> fn)

このステージと指定された他のステージの両方が正常終了した際に実行される新しいCompletionStageを返します(実行時には、指定された関数の引数として2つの結果が使用される)。

それでは先に進みましょう。

このプログラムではさらに余計なことをしています。

get() メソッドにタイムリミットを設定しました。(^_^;)

result = f.get(3, TimeUnit.SECONDS);

タイムアウトしたら

f.complete(-1);

と、CompletableFuture<Integer> f に –1 を設定します。

実はこのプログラムではこんなことをせずにタイムアウトが発生したら result に –1 を代入すればいいだけなんですが complete() メソッドを使いたかったからこうなっただけです。( ̄。 ̄;)

それではこのプログラムの実行結果を見てみましょう。

2

Fork/Join Framework が使われて並行処理されているのが解ります。

また、両方の処理が完了されてから最終的な処理もされているのが確認できます。

今回の目的である並行処理によるプログラムの高速化を CompletableFuture を使ってすることができました。(^_^)

ただ、これでいいのか?それとももっと良い使い方があるのかは不明です。

これからの 詳解 Java SE 8 第19回 Concurrency Utilitiesのアップデート の記事に注目していきましょう!

ここからはおまけです。

今回のプログラムでは両方の処理が完了するのを待ってました。

先に終了した方を表示するだけの場合も試してみます。

最終処理をこちらに変更します。

applyToEither() メソッドを使います。

これはどちらかが処理結果を得られれば、その結果を指定された関数に渡します。

// 処理が早く終わった方を返す。
CompletableFuture<Integer> f = future0.applyToEither(future1, x -> {
    return x;
});

applyToEither() メソッド は API ドキュメントでは次のように説明されています。

public <U> CompletableFuture<U> applyToEither(CompletionStage<? extends T> other,
                                              Function<? super T,U> fn)

このステージまたは指定された他のステージが正常に完了したときに、対応する結果を指定された関数への引数に設定して実行される新しいCompletionStageを返します。

それでは実行結果を見てみましょう。

3

まだ片方しか処理が終わってないのに最終処理がされているのが確認できます。

目的のプログラムとしては駄目ですが、あくまで参考ということで!

間違い、もしくはもっと COOL な方法があれば教えていただければ幸いです。

Hatena タグ:

新しい記事 »