字符串搜索树支持使用字符串对对象建立索引,以便于查询。 这些字符串应该都是从对象的属性上获取的。而且可以按照指定的规则将字符串分解为单词。
Insert方法用来在一个单词和指定对象之间建立关联。
Remove方法用来在索引树中删除一个指定对象。
Prune方法用于在删除了大量对象后,优化索引树,删除那些没有作用的节点。
Find方法用来查找对象;支持通配符查找, 通配符为*,而且只能放在查找字符串的开始。
public class StringIndexTree<T>
{
public bool Insert(string key, T value);
public bool Remove(T value);
public void Prune();
public IEnumerable<T> Find(string filter);
}
为了测试, 演示窗口中还显示出了整个搜索树结构,包括每个节点下包含的对象个数。 下面就是demo的代码:
<Window x:Class="MySolution.Controls.Demo.StringIndexingWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:infra="clr-namespace:Infrastructure;assembly=Infrastructure"
Title="StringIndexingWindow" Height="478" Width="862">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid Grid.Column="1">
<TreeView Name="tv">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<Grid Width="100">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Key}"/>
<TextBlock Grid.Column="1" Text="{Binding Values.Count}" HorizontalAlignment="Right"/>
</Grid>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBox Grid.Row="0" Name="filterTextBox" TextChanged="filterTextBox_TextChanged"/>
<StackPanel Grid.Row="1">
<Button Content="Delete First Row" Click="Button_Click"/>
</StackPanel>
<ListView Name="lv" Grid.Row="2">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}"/>
<GridViewColumn Header="Birth Date" DisplayMemberBinding="{Binding BirthDate}"/>
<GridViewColumn Header="Address" DisplayMemberBinding="{Binding Address}"/>
</GridView>
</ListView.View>
</ListView>
</Grid>
</Grid>
</Window>
后置代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using Infrastructure;
using System.Collections.ObjectModel;
namespace MySolution.Controls.Demo
{
/// <summary>
/// Interaction logic for StringIndexingWindow.xaml
/// </summary>
public partial class StringIndexingWindow : Window
{
public StringIndexingWindow()
{
InitializeComponent();
Loaded += new RoutedEventHandler(StringIndexingWindow_Loaded);
}
private ObservableCollection<Employee> emps;
void StringIndexingWindow_Loaded(object sender, RoutedEventArgs e)
{
emps = new ObservableCollection<Employee>();
Random r = new Random();
for (int i = 0; i < 10000; i++)
{
Employee emp = new Employee();
emp.Name = "zhang san" + i;
emp.Address = "Beijing";
emp.BirthDate = DateTime.Now.Subtract(TimeSpan.FromDays(r.Next(1000, 10000)));
emps.Add(emp);
}
lv.ItemsSource = emps;
BuildStringIndex();
tv.ItemsSource = _indexs.Select(i => i.Root);
}
private List<StringIndexTree<Employee>> _indexs = new List<StringIndexTree<Employee>>();
private void BuildStringIndex()
{
StringIndexTree<Employee> nameIndex = new StringIndexTree<Employee>();
StringIndexTree<Employee> dateIndex = new StringIndexTree<Employee>();
StringIndexTree<Employee> addrIndex = new StringIndexTree<Employee>();
foreach (var item in emps)
{
foreach (var word in item.Name.Split(new [] { ' '}, StringSplitOptions.RemoveEmptyEntries))
{
nameIndex.Insert(word, item);
}
foreach (var word in item.BirthDate.ToString().Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries))
{
dateIndex.Insert(word, item);
}
foreach (var word in item.Address.ToString().Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries))
{
addrIndex.Insert(word, item);
}
}
_indexs.Add(nameIndex);
_indexs.Add(dateIndex);
_indexs.Add(addrIndex);
}
private void filterTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
if (filterTextBox.Text != string.Empty)
{
lv.ItemsSource = _indexs.SelectMany(i => i.Find(filterTextBox.Text)).Distinct();
}
else
{
lv.ItemsSource = emps;
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Employee emp = emps[0];
emps.RemoveAt(0);
//_index.Remove(emp);
//_index.Prune();
//tv.ItemsSource = new[] { _index.Root };
}
}
public class Employee
{
public string Name { get; set; }
public DateTime BirthDate { get; set; }
public string Address { get; set; }
}
}
StringIndexTree的代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Infrastructure
{
public class StringIndexTree<T>
{
public const char WildcardChar = '*';
public StringIndexTree()
{
Root = new StringIndexTreeNode<T>(string.Empty);
}
public StringIndexTreeNode<T> Root { get; private set; }
public bool Insert(string key, T value)
{
return Root.Insert(key, value);
}
public bool Remove(T value)
{
return Root.Remove(value);
}
public void Prune()
{
Root.Prune();
}
public IEnumerable<T> Find(string filter)
{
if (string.IsNullOrWhiteSpace(filter))
{
throw new ArgumentException("key cannot be empty or white space", "key");
}
if (filter[0] == WildcardChar)
{
filter = filter.Substring(1);
if (filter == string.Empty)
{
return Root.SelfAndDecendentValues;
}
else
{
var retNodes = Root.FindWithContain(filter);
var result = new List<T>();
foreach (var retNode in retNodes)
{
result.AddRange(retNode.SelfAndDecendentValues);
}
return result;
}
}
else
{
var node = Root.FindFromStart(filter);
return node != null ? node.SelfAndDecendentValues : new List<T>();
}
}
}
}
StringIndexTreeNode的代码: