Home > Software design >  Filter lists that hit search words C#
Filter lists that hit search words C#

Time:05-04

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.

  • Related