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) 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.

  • 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.

How many English words
do you know?
Test your English vocabulary size, and measure
how many words do you know
Online Test
Powered by Examplum