I am writing a program that stored a list of string using C#. And want to filter and display all hits of string from the whole list by typing a few characters. eg. Typing "ab" in search text box will list all available strings from the list that start with ab.
CodePudding user response:
To do a linear search thru a list, matching all parts of the input string you could do something like:
var inputWords = inputString.Split(new []{' '}, StringSplitOptions.RemoveEmptyEntries);
var hits = myStringList.Where(s => inputWords.All(w => s.Contains(w));
If you only want to match from the start of the strings you could replace inputWords.All(w => s.Contains(w)
with s.StartsWith (inputString)
.
Since this is a linear search it will not scale with very large number of strings. For that you need a database, an index, or some kind of search tree. But a linear search should work fairly well at least up to tens of thousands of items.
CodePudding user response:
I have a TextBox extension to delay a bit the search/filter. Is not a response to your question but something that is useful as part of your filtering.
using System;
using System.Collections.Generic;
using System.Windows.Forms;
namespace Utilities
{
public static class TextBoxExtends
{
/// <summary>
/// List of TextBoxes and their associated information.
/// </summary>
private readonly static List<TextBoxTypeInfo> TextBoxesInfo = new List<TextBoxTypeInfo>();
/// <summary>
/// Timer to control the time between events.
/// </summary>
private readonly static Timer Timer = CreateTimer();
public static void OnTypingChanged(
this TextBox textBox, int interval, Action<TextBox> action = null)
{
var index = TextBoxesInfo.FindIndex(info => info.TextBox == textBox);
if (interval <= 0)
{
if (index >= 0)
{
var remove = false;
var info = TextBoxesInfo[index];
if (action != null)
{
var actionIndex = info.Actions.FindIndex(a => a.Action == action);
if (actionIndex >= 0)
{
info.Actions.RemoveAt(actionIndex);
remove = info.Actions.Count == 0;
}
}
else
{
remove = true;
}
if (remove)
{
TextBoxesInfo.RemoveAt(index);
if (TextBoxesInfo.Count == 0)
{
Timer.Enabled = false;
}
}
}
}
else if (action != null)
{
Timer.Enabled = true;
if (index < 0)
{
TextBoxesInfo.Add(new TextBoxTypeInfo(textBox, interval, action));
}
else
{
var info = TextBoxesInfo[index];
info.SetAction(action, interval);
}
}
}
private static void OnTimer_Tick(object sender, EventArgs e)
{
foreach (var info in TextBoxesInfo)
{
info.OnTimer(false);
}
}
/// <summary>
/// Creates the timer that checks the times of the TextBoxes.
/// </summary>
private static Timer CreateTimer()
{
var timer = new Timer { Enabled = false, Interval = 100 };
timer.Tick = OnTimer_Tick;
return timer;
}
#region Nested classes
private class TextBoxTypeInfo
{
public TextBoxTypeInfo(TextBox textBox, int interval, Action<TextBox> action)
{
this.TextBox = textBox;
this.TextBox.TextChanged = this.OnTextBox_TextChanged;
this.TextBox.Disposed = this.OnTextBox_Disposed;
this.Actions = new List<ActionInfo>
{
new ActionInfo(action, interval)
};
}
public TextBox TextBox { get; set; }
public List<ActionInfo> Actions { get; set; }
private void OnTextBox_TextChanged(object sender, EventArgs e)
{
var now = DateTime.UtcNow;
foreach (var info in this.Actions)
{
info.LastTime = now;
}
}
private void OnTextBox_Disposed(object sender, EventArgs e)
{
this.TextBox.OnTypingChanged(-1);
}
/// <summary>
/// Sets or updates the indicated action.
/// </summary>
/// <param name="action">Action to be added or updated..</param>
/// <param name="interval">Interval between keystroke and action execution.</param>
public void SetAction(Action<TextBox> action, int interval)
{
int index = this.Actions.FindIndex(a => a.Action == action);
if (index < 0)
{
// New action: add it
this.Actions.Add(new ActionInfo(action, interval));
}
else
{
// Existing action: update the interval
this.Actions[index].Interval = interval;
}
}
/// <summary>
/// Check events.
/// </summary>
/// <param name="force">Force execution of related actions.</param>
internal void OnTimer(bool force)
{
foreach (var actionInfo in this.Actions)
{
var elapsed = DateTime.UtcNow - actionInfo.LastTime;
var runAction = elapsed.TotalMilliseconds >= actionInfo.Interval;
if (!runAction && force)
{
runAction = actionInfo.LastTime != DateTime.MaxValue;
}
if (runAction)
{
actionInfo.Action(this.TextBox);
actionInfo.LastTime = DateTime.MaxValue;
}
}
}
}
private class ActionInfo
{
public ActionInfo(Action<TextBox> action, int interval)
{
this.Action = action;
this.Interval = interval;
this.LastTime = DateTime.MaxValue;
}
public Action<TextBox> Action { get; set; }
public int Interval { get; set; }
/// <summary>
/// Date on which the TextBox was written or the associated action was executed.
/// </summary>
public DateTime LastTime { get; set; }
}
#endregion
}
}
This extension allows to execute an action some time after a keystroke in the TextBox. So you can write your full search text and then apply the search instead do a search in each keystroke.
You can setup in form constructor:
this.textBox1.OnTypingChanged(500, textBox => this.ApplyFilter(textBox.Text));
500 milliseconds after last keystroke, you run your filter with the Text of TextBox.
private void ApplyFilter(string text)
{
// Apply your filter
}
As I said, it's not a solution to your answer but a complement to it.