xin9le.net

Microsoft の製品/技術が大好きな Microsoft MVP な管理人の技術ブログです。

Rx入門 (27) - 入力文字のサジェスト

昨今の検索ボックスは優秀で、当たり前のようにサジェスト機能が付いてますね。やはりサジェスト機能があると便利です。ということで、今回は文字入力におけるサジェストをやってみます。とはいえ、オートコンプリートを完璧に実装するのは結構手間なので今回はサジェスト結果を一覧表示するだけにします。(実は途中でめんどくさくなって逃げましたゴメンナサイ

サジェストの取得元

検索といえばGoogle!ここはありがたくGoogle Suggestを利用することにします。Google Suggestは、次のように特定のURLにクエリ文字列を繋げてリクエストを投げれば、XMLで結果が返ってきます。

http://www.google.com/complete/search?output=toolbar&q=google

返ってきたXMLのち、suggestionタグのdata属性からサジェスト文字列を取得します。

<?xml version="1.0"?>
<toplevel>
    <CompleteSuggestion>
        <suggestion data="google"/>
        <num_queries int="11100000000"/>
    </CompleteSuggestion>
    <CompleteSuggestion>
        <suggestion data="google map"/>
        <num_queries int="815000000"/>
    </CompleteSuggestion>
    <CompleteSuggestion>
        <suggestion data="google翻訳"/>
        <num_queries int="71500000"/>
    </CompleteSuggestion>
    <CompleteSuggestion>
        <suggestion data="google earth"/>
        <num_queries int="323000000"/>
    </CompleteSuggestion>
    <CompleteSuggestion>
        <suggestion data="google chrome"/>
        <num_queries int="462000000"/>
    </CompleteSuggestion>
    <CompleteSuggestion>
        <suggestion data="google カレンダー"/>
        <num_queries int="157000000"/>
    </CompleteSuggestion>
    <CompleteSuggestion>
        <suggestion data="google scholar"/>
        <num_queries int="83700000"/>
    </CompleteSuggestion>
    <CompleteSuggestion>
        <suggestion data="google translate"/>
        <num_queries int="223000000"/>
    </CompleteSuggestion>
    <CompleteSuggestion>
        <suggestion data="googleプラス"/>
        <num_queries int="64900000"/>
    </CompleteSuggestion>
    <CompleteSuggestion>
        <suggestion data="google apps"/>
        <num_queries int="335000000"/>
    </CompleteSuggestion>
</toplevel>

サンプルコード

以下にXAMLのコードを示します。検索文字列入力用TextBoxと、サジェスト一覧を表示するためのListBoxを並べています。

<Window x:Class="Sample47_GoogleSuggest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Sample47_GoogleSuggest" Height="300" Width="300">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition />
        </Grid.RowDefinitions>
        <TextBox Name="input" />
        <ListBox Grid.Row="1" ItemsSource="{Binding}" />
    </Grid>
</Window>

次に、サジェストの取得を行うためのコードを示します。文字入力がある度にサジェストの取得を行うのは非効率なので、最後の入力が行われてから200ミリ秒以内に入力がなければ検索を行うこととします。また、UIが固まらないようにバックグラウンドスレッドで検索を行うようにします。(厳密ではないかもしれませんが、とりあえずこれでOK)

using System;
using System.IO;
using System.Net;
using System.Reactive.Linq;
using System.Text;
using System.Threading;
using System.Windows;
using System.Xml.Linq;
 
namespace Sample47_GoogleSuggest
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            this.InitializeComponent();
            string url = "http://www.google.com/complete/search?output=toolbar&q=";
 
            Observable.FromEventPattern(this.input, "TextChanged")                                //--- イベントが発生する度に
            .Select(_ => this.input.Text)                                                         //--- 入力文字を取得
            .Throttle(TimeSpan.FromMilliseconds(200))                                             //--- 200[ms]以内の複数回処理は無視
            .Select(query => new Uri(url + query, UriKind.Absolute))                              //--- Googleへの問い合わせURI
            .SelectMany(uri => Observable.Return(WebRequest.Create(uri) as HttpWebRequest)        //--- リクエストを生成して
            .Select(request => request.GetResponse())                                             //--- レスポンスを取得
            .Select(response => new StreamReader(response.GetResponseStream(), Encoding.Default)) //--- エンコードを調整
            .Select(stream => XElement.Load(stream))                                              //--- xmlとして解析
            .SelectMany(element => element.Descendants("suggestion"))                             //--- suggestionタグの
            .Select(element => element.Attribute("data").Value)                                   //--- data属性の値を
            .ToArray()                                                                            //--- 全部取得
            .Catch(Observable.Never<string[]>()))                                                 //--- エラーが発生したら握りつぶす
            .ObserveOn(SynchronizationContext.Current)                                            //--- UIスレッドに戻して
            .Subscribe(suggestions => this.DataContext = suggestions);                            //--- 表示
        }
    }
}

コード中の右にコメントがありますが、これもまた流れるようにたった1行で記述できていますね。

実行例

実行すると以下のようになります。これをベースに検索をTextBoxのサジェストやオートコンプリートなど行えるようにすると良さそうです。

GoogleSuggest

効率的なサジェスト表示もこんなに簡単!ビバRx!