Filter lists that hit search words C#
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.
2 answers
-
answered 2022-05-04 09:48
JonasH
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)
withs.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.
-
answered 2022-05-04 09:59
Victor
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.
do you know?
how many words do you know