एक स्ट्रिंग संग्रह में खोज करने का सबसे तेज़ तरीका


80

मुसीबत:

मेरे पास लगभग 120,000 उपयोगकर्ताओं (स्ट्रिंग्स) की एक पाठ फ़ाइल है , जिसे मैं एक संग्रह में संग्रहीत करना चाहता हूं और बाद में उस संग्रह पर खोज करने के लिए।

खोज विधि हर बार तब होती है जब उपयोगकर्ता एक का पाठ बदलता है TextBoxऔर परिणाम उस तार का होना चाहिए जिसमें पाठ शामिल है TextBox

मुझे सूची बदलने की ज़रूरत नहीं है, बस परिणाम खींचें और उन्हें एक में डाल दें ListBox

मैंने अब तक क्या प्रयास किया है:

मैंने दो अलग-अलग संग्रहों / कंटेनरों के साथ कोशिश की, जो मैं एक बाहरी पाठ फ़ाइल से स्ट्रिंग प्रविष्टियों को डंप कर रहा हूं (एक बार, निश्चित रूप से):

  1. List<string> allUsers;
  2. HashSet<string> allUsers;

निम्नलिखित LINQ क्वेरी के साथ:

allUsers.Where(item => item.Contains(textBox_search.Text)).ToList();

मेरी खोज घटना (जब उपयोगकर्ता खोज पाठ को बदलता है)

private void textBox_search_TextChanged(object sender, EventArgs e)
{
    if (textBox_search.Text.Length > 2)
    {
        listBox_choices.DataSource = allUsers.Where(item => item.Contains(textBox_search.Text)).ToList();
    }
    else
    {
        listBox_choices.DataSource = null;
    }
}

परिणाम:

दोनों ने मुझे खराब प्रतिक्रिया समय दिया (प्रत्येक कुंजी प्रेस के बीच 1-3 सेकंड के आसपास)।

सवाल:

आपको कहां लगता है कि मेरी अड़चन क्या है? जो संग्रह मैंने उपयोग किया है? खोज विधि? दोनों?

मैं बेहतर प्रदर्शन और अधिक धाराप्रवाह कार्यक्षमता कैसे प्राप्त कर सकता हूं?


10
HashSet<T>आप यहाँ मदद नहीं करेंगे, क्योंकि आप स्ट्रिंग का हिस्सा खोज रहे हैं ।
डेनिस


66
मत पूछो "सबसे तेज़ तरीका क्या है", क्योंकि यह सचमुच अनुसंधान के वर्षों तक ले जाएगा। इसके बजाय, "मुझे एक समाधान की आवश्यकता है जो 30 एमएस से कम में चलता है", या जो भी आपके प्रदर्शन का लक्ष्य है। आपको सबसे तेज़ डिवाइस की आवश्यकता नहीं है, आपको तेज़ पर्याप्त डिवाइस की आवश्यकता है।
एरिक लिपर्ट

44
इसके अलावा, एक प्रोफाइलर प्राप्त करें । धीमा भाग कहाँ है, इसके बारे में अनुमानलगाएं ; ऐसे अनुमान अक्सर गलत होते हैं। अड़चन कहीं आश्चर्य की बात हो सकती है।
एरिक लिपर्ट

4
@ बासीलेव्स: मैंने एक बार एक प्यारा ओ (1) हैश टेबल लिखा था जो अभ्यास में बेहद धीमा था। मैंने यह पता लगाने के लिए इसे क्यों और खोजा कि हर खोज पर यह एक विधि कह रहा था कि - कोई मजाक नहीं - रजिस्ट्री पूछना "क्या हम अभी थाईलैंड में हैं?"। कैशिंग नहीं है कि उपयोगकर्ता थाईलैंड में है कि हे (1) कोड में अड़चन थी। अड़चन का स्थान गहरा उल्टा हो सकता है । एक प्रोफाइलर का उपयोग करें।
एरिक लिपर्ट

जवाबों:


48

आप एक पृष्ठभूमि थ्रेड पर फ़िल्टरिंग कार्य करने पर विचार कर सकते हैं जो कॉलबैक विधि को लागू करेगा जब यह किया जाता है, या यदि इनपुट बदल जाता है तो बस फ़िल्टरिंग को पुनरारंभ करें।

सामान्य विचार इसे इस तरह उपयोग करने में सक्षम होना है:

public partial class YourForm : Form
{
    private readonly BackgroundWordFilter _filter;

    public YourForm()
    {
        InitializeComponent();

        // setup the background worker to return no more than 10 items,
        // and to set ListBox.DataSource when results are ready

        _filter = new BackgroundWordFilter
        (
            items: GetDictionaryItems(),
            maxItemsToMatch: 10,
            callback: results => 
              this.Invoke(new Action(() => listBox_choices.DataSource = results))
        );
    }

    private void textBox_search_TextChanged(object sender, EventArgs e)
    {
        // this will update the background worker's "current entry"
        _filter.SetCurrentEntry(textBox_search.Text);
    }
}

एक मोटा स्केच कुछ इस तरह होगा:

public class BackgroundWordFilter : IDisposable
{
    private readonly List<string> _items;
    private readonly AutoResetEvent _signal = new AutoResetEvent(false);
    private readonly Thread _workerThread;
    private readonly int _maxItemsToMatch;
    private readonly Action<List<string>> _callback;

    private volatile bool _shouldRun = true;
    private volatile string _currentEntry = null;

    public BackgroundWordFilter(
        List<string> items,
        int maxItemsToMatch,
        Action<List<string>> callback)
    {
        _items = items;
        _callback = callback;
        _maxItemsToMatch = maxItemsToMatch;

        // start the long-lived backgroud thread
        _workerThread = new Thread(WorkerLoop)
        {
            IsBackground = true,
            Priority = ThreadPriority.BelowNormal
        };

        _workerThread.Start();
    }

    public void SetCurrentEntry(string currentEntry)
    {
        // set the current entry and signal the worker thread
        _currentEntry = currentEntry;
        _signal.Set();
    }

    void WorkerLoop()
    {
        while (_shouldRun)
        {
            // wait here until there is a new entry
            _signal.WaitOne();
            if (!_shouldRun)
                return;

            var entry = _currentEntry;
            var results = new List<string>();

            // if there is nothing to process,
            // return an empty list
            if (string.IsNullOrEmpty(entry))
            {
                _callback(results);
                continue;
            }

            // do the search in a for-loop to 
            // allow early termination when current entry
            // is changed on a different thread
            foreach (var i in _items)
            {
                // if matched, add to the list of results
                if (i.Contains(entry))
                    results.Add(i);

                // check if the current entry was updated in the meantime,
                // or we found enough items
                if (entry != _currentEntry || results.Count >= _maxItemsToMatch)
                    break;
            }

            if (entry == _currentEntry)
                _callback(results);
        }
    }

    public void Dispose()
    {
        // we are using AutoResetEvent and a background thread
        // and therefore must dispose it explicitly
        Dispose(true);
    }

    private void Dispose(bool disposing)
    {
        if (!disposing)
            return;

        // shutdown the thread
        if (_workerThread.IsAlive)
        {
            _shouldRun = false;
            _currentEntry = null;
            _signal.Set();
            _workerThread.Join();
        }

        // if targetting .NET 3.5 or older, we have to
        // use the explicit IDisposable implementation
        (_signal as IDisposable).Dispose();
    }
}

इसके अलावा, आपको वास्तव में _filterउदाहरण का निपटान करना चाहिए जब माता-पिता Formका निपटान किया जाता है। यह आपको खोलना चाहिए साधन और संपादित अपने Forms ' Disposeविधि (अंदर YourForm.Designer.csफाइल) की तरह कुछ देखने के लिए:

// inside "xxxxxx.Designer.cs"
protected override void Dispose(bool disposing)
{
    if (disposing)
    {
        if (_filter != null)
            _filter.Dispose();

        // this part is added by Visual Studio designer
        if (components != null)
            components.Dispose();
    }

    base.Dispose(disposing);
}

मेरी मशीन पर, यह बहुत जल्दी काम करता है, इसलिए आपको अधिक जटिल समाधान के लिए जाने से पहले यह परीक्षण और प्रोफाइल करना चाहिए।

कहा जा रहा है कि, एक "अधिक जटिल समाधान" संभवतः परिणामों के अंतिम दो को एक शब्दकोश में संग्रहीत करने के लिए होगा, और उसके बाद ही उन्हें फ़िल्टर करें यदि यह पता चले कि नई प्रविष्टि केवल अंतिम वर्ण के पहले से भिन्न होती है।


मैंने अभी आपके समाधान का परीक्षण किया है और यह पूरी तरह से काम करता है! अच्छी तरह से किया। मेरे पास एकमात्र समस्या यह है कि मैं _signal.Dispose();संकलन नहीं कर सकता (सुरक्षा-स्तर के बारे में त्रुटि)।
etaiso

@etaiso: यह अजीब है, जहाँ आपको बुलावा आता _signal.Dispose()है कि क्या यह BackgroundWordFilterकक्षा के बाहर है ?
ग्रू

1
@Groo यह स्पष्ट कार्यान्वयन है, जिसका अर्थ है कि आप इसे सीधे नहीं कह सकते। आप या तो एक usingब्लॉक का उपयोग करने वाले हैं , या कॉल करेंWaitHandle.Close()
मैथ्यू वॉटसन

1
ठीक है, अब यह समझ में आता है, यह विधि .NET 4 में सार्वजनिक की गई थी। .NET 4 के लिए MSDN पृष्ठ इसे सार्वजनिक विधियों के अंतर्गत सूचीबद्ध करता है , जबकि .NET 3.5 के लिए पृष्ठ इसे संरक्षित लोगों के अंतर्गत दिखाता है। यह भी बताता है कि WaitHandle के लिए मोनो स्रोत में एक सशर्त परिभाषित क्यों है ।
ग्रू

1
@Groo क्षमा करें, मुझे उल्लेख करना चाहिए था कि मैं .Net के पुराने संस्करण के बारे में बात कर रहा था - भ्रम के बारे में क्षमा करें! हालांकि ध्यान दें कि उसे कास्ट करने की ज़रूरत नहीं है - वह .Close()इसके बजाय कॉल कर सकता है , जो खुद कॉल करता है .Dispose()
मैथ्यू वॉटसन

36

मैंने कुछ परीक्षण किए हैं, और 120,000 वस्तुओं की एक सूची की खोज की है और प्रविष्टियों के साथ एक नई सूची को पॉप्युलेट करने में समय की एक नगण्य राशि (लगभग 1/50 वीं सेकंड के सभी तार मेल होने पर) लेता है।

आपके द्वारा देखी जा रही समस्या इसलिए डेटा स्रोत की आबादी से आ रही है, यहाँ होना चाहिए:

listBox_choices.DataSource = ...

मुझे संदेह है कि आप सूची में बहुत अधिक आइटम डाल रहे हैं।

शायद आपको इसे पहले 20 प्रविष्टियों तक सीमित करने की कोशिश करनी चाहिए, जैसे:

listBox_choices.DataSource = allUsers.Where(item => item.Contains(textBox_search.Text))
    .Take(20).ToList();

यह भी ध्यान दें (जैसा कि अन्य ने बताया है) कि आप TextBox.Textप्रत्येक आइटम के लिए संपत्ति तक पहुंच रहे हैं allUsers। इसे इस प्रकार आसानी से तय किया जा सकता है:

string target = textBox_search.Text;
listBox_choices.DataSource = allUsers.Where(item => item.Contains(target))
    .Take(20).ToList();

हालाँकि, मैंने समय दिया कि TextBox.Text500,000 बार तक पहुँचने में कितना समय लगता है और इसे केवल 0.7 सेकंड लगे, ओपी में वर्णित 1 - 3 सेकंड से भी कम। फिर भी, यह एक सार्थक अनुकूलन है।


1
धन्यवाद मैथ्यू। मैंने आपके समाधान की कोशिश की, लेकिन मुझे नहीं लगता कि समस्या लिस्टबॉक्स की आबादी के साथ है। मुझे लगता है कि मुझे एक बेहतर दृष्टिकोण की आवश्यकता है क्योंकि इस तरह की फ़िल्टरिंग बहुत भोली है (उदाहरण के लिए - "एबीसी" के लिए खोज 0 परिणाम देता है, फिर मुझे "एबीसीएक्स" और इतने पर भी नहीं दिखना चाहिए)
एटैसो

@etaiso सही (भले ही मैथ्यू का समाधान बहुत अच्छा काम कर सकता है यदि आपको वास्तव में सभी मैचों को पूर्व निर्धारित करने की आवश्यकता नहीं है), यही कारण है कि मैंने हर बार पूर्ण खोज करने के बजाय खोज को परिष्कृत करने के लिए दूसरे चरण के रूप में सुझाव दिया
Adriano Repetti

5
@etaiso खैर, खोज समय नगण्य है जैसे मैंने कहा। मैंने इसे 120,000 स्ट्रिंग्स के साथ आज़माया और बहुत लंबे स्ट्रिंग की खोज की, जिसमें कोई मैच न हो और बहुत कम स्ट्रिंग हो, जिसने कई मैच दिए, दोनों ने 1/50 वें सेकंड के अंतर्गत लिया।
मैथ्यू वॉटसन

3
क्या textBox_search.Textसमय के लिए एक औसत दर्जे की राशि का योगदान है? हो रही Textएक बार 120 कि तार से प्रत्येक के लिए एक पाठ बॉक्स पर संपत्ति शायद संपादित नियंत्रण विंडो को 120 कि संदेश भेजता है।
गाबे

@Gabe हाँ यह करता है। विवरण के लिए मेरा उत्तर देखें।
एंड्रीस

28

प्रत्यय वृक्ष का प्रयोग करेंसूचकांक के रूप में । या इसके बजाय सिर्फ एक छांटे गए शब्दकोश का निर्माण करें जो हर नाम के हर प्रत्यय को संबंधित नामों की सूची से जोड़ते हैं।

इनपुट के लिए:

Abraham
Barbara
Abram

संरचना की तरह दिखेगा:

a -> Barbara
ab -> Abram
abraham -> Abraham
abram -> Abram
am -> Abraham, Abram
aham -> Abraham
ara -> Barbara
arbara -> Barbara
bara -> Barbara
barbara -> Barbara
bram -> Abram
braham -> Abraham
ham -> Abraham
m -> Abraham, Abram
raham -> Abraham
ram -> Abram
rbara -> Barbara

खोज एल्गोरिथ्म

उपयोगकर्ता इनपुट "ब्रा" मान लें।

  1. द्विविभाजित करना उपयोगकर्ता इनपुट पर शब्दकोश उपयोगकर्ता इनपुट या स्थिति जहां यह जा सकते हैं खोजने के लिए। इस तरह से हम "बारबरा" पाते हैं - "ब्रा" की तुलना में अंतिम कुंजी। इसे "ब्रा" के लिए निचला बाउंड कहा जाता है। खोज में लघुगणक समय लगेगा।
  2. उपयोगकर्ता कुंजी अब मेल नहीं खाती, तब तक पाया गया कुंजी से Iterate। इससे "ब्राम" -> अब्राम और "ब्राहम" -> अब्राहम को मिलेगा।
  3. कॉनटेटनेट इटरेशन रिजल्ट (अब्राम, अब्राहम) और इसे आउटपुट करते हैं।

इस तरह के पेड़ सब्सट्रिंग की त्वरित खोज के लिए डिज़ाइन किए गए हैं। यह प्रदर्शन O (लॉग एन) के करीब है। मुझे विश्वास है कि यह दृष्टिकोण जीयूआई थ्रेड द्वारा सीधे उपयोग किए जाने के लिए पर्याप्त तेजी से काम करेगा। इसके अलावा यह तेजी से काम करेगा फिर थ्रेडेड सॉल्यूशन के ओवरहेड होने के कारण थ्रेडेड सॉल्यूशन।


मुझे पता है कि प्रत्यय सरणियों आमतौर पर प्रत्यय पेड़ों की तुलना में एक बेहतर विकल्प हैं। लागू करने के लिए आसान और स्मृति उपयोग कम है।
कोडइन्चोस

मैं SortedList का प्रस्ताव करता हूं जो कि मेमोरी ओवरहेड की लागत को बनाने और बनाए रखने में बहुत आसान है जिसे सूचियों की क्षमता प्रदान करके कम से कम किया जा सकता है।
बसिलेव्स

इसके अलावा, ऐसा लगता है कि एरेज़ (और मूल एसटी) को बड़े ग्रंथों को संभालने के लिए डिज़ाइन किया गया है, जबकि यहाँ हमारे पास बड़ी मात्रा में छोटे विखंडन हैं जो अलग-अलग कार्य हैं।
बसिलेव्स

अच्छे दृष्टिकोण के लिए +1, लेकिन मैं मैन्युअल सूची खोजने के बजाय हैश मैप या वास्तविक खोज ट्री का उपयोग करूंगा।
ऑरेंजडॉग

क्या उपसर्ग वृक्ष के बजाय प्रत्यय वृक्ष का उपयोग करने का कोई फायदा है?
jnovacho

15

आपको या तो एक पाठ खोज इंजन (जैसे Lucene.Net ), या डेटाबेस की आवश्यकता होती है (आप SQL CE , SQLite , इत्यादि जैसे एक एम्बेडेड पर विचार कर सकते हैं )। दूसरे शब्दों में, आपको एक अनुक्रमित खोज की आवश्यकता है। हैश-आधारित खोज यहां लागू नहीं होती है, क्योंकि आप उप-स्ट्रिंग की खोज करते हैं, जबकि हैश-आधारित खोज सटीक मान की खोज के लिए अच्छी तरह से है।

अन्यथा यह संग्रह के माध्यम से पाशन के साथ एक पुनरावृति खोज होगी।


अनुक्रमण है एक हैश आधारित खोज। आप केवल मूल्य के बजाय सभी उप-स्ट्रिंग्स को कुंजियों के रूप में जोड़ते हैं।
ऑरेंजडॉग

3
@OrangeDog: असहमत। अनुक्रमित कुंजियों द्वारा अनुक्रमित खोज को हैश-आधारित खोज के रूप में लागू किया जा सकता है, लेकिन यह आवश्यक नहीं है, और यह स्ट्रिंग मूल्य द्वारा ही हैश-आधारित खोज नहीं है।
डेनिस

@ डेनिस सहमत। +1 भूत को रद्द करने के लिए -1।
उपयोगकर्ता

+1 क्योंकि एक पाठ खोज इंजन की तरह कार्यान्वयन स्मार्ट (एर) अनुकूलन की तुलना में है string.Contains। अर्थात। के लिए खोज baमें bcaaaabaaएक (अनुक्रमित) को छोड़ सूची का परिणाम देगा। पहला bमाना जाता है, लेकिन मेल नहीं खाता क्योंकि अगला एक है c, इसलिए यह अगले पर छोड़ देगा b
कारमीरियल

12

यह एक "पराजय" प्रकार की घटना के लिए भी उपयोगी हो सकता है। यह थ्रॉटलिंग से भिन्न है कि यह घटना को फायर करने से पहले समाप्त होने के लिए समय की अवधि (उदाहरण के लिए, 200 एमएस) की प्रतीक्षा करता है।

डेब्यू और थ्रोटल देखें: डिबगिंग के बारे में अधिक जानकारी के लिए एक दृश्य स्पष्टीकरण । मैं सराहना करता हूं कि यह लेख C # के बजाय जावास्क्रिप्ट केंद्रित है, लेकिन सिद्धांत लागू होता है।

इसका लाभ यह है कि जब आप अभी भी अपनी क्वेरी दर्ज कर रहे हैं तो यह खोज नहीं करता है। इसके बाद एक साथ दो खोजों को करने का प्रयास करना बंद कर देना चाहिए।


एल्गोरिथ्म लाइब्रेरी में एक इवेंट थ्रॉटलर ईवेंट थ्रोटलर क्लास के एक सी # कार्यान्वयन के लिए देखें: github.com/SolutionsDesign/Algorithmia/blob/master/…
बोमा

11

खोज को किसी अन्य थ्रेड पर चलाएँ, और उस थ्रेड के चलते समय कुछ लोडिंग एनीमेशन या एक प्रगति बार दिखाएं।

आप LINQ क्वेरी को समानांतर करने का भी प्रयास कर सकते हैं ।

var queryResults = strings.AsParallel().Where(item => item.Contains("1")).ToList();

यहाँ एक मानदंड है जो AsParallel () के प्रदर्शन लाभों को प्रदर्शित करता है:

{
    IEnumerable<string> queryResults;
    bool useParallel = true;

    var strings = new List<string>();

    for (int i = 0; i < 2500000; i++)
        strings.Add(i.ToString());

    var stp = new Stopwatch();

    stp.Start();

    if (useParallel)
        queryResults = strings.AsParallel().Where(item => item.Contains("1")).ToList();
    else
        queryResults = strings.Where(item => item.Contains("1")).ToList();

    stp.Stop();

    Console.WriteLine("useParallel: {0}\r\nTime Elapsed: {1}", useParallel, stp.ElapsedMilliseconds);
}

1
मुझे पता है कि यह एक संभावना है। लेकिन मेरा सवाल यहाँ है और अगर मैं इस प्रक्रिया को छोटा कैसे कर सकता हूँ?
etaiso

1
@etaiso यह वास्तव में एक समस्या नहीं होनी चाहिए जब तक आप कुछ बहुत कम अंत हार्डवेयर पर विकसित नहीं कर रहे हैं, सुनिश्चित करें कि आप डिबगर, CTRL + F5 नहीं चल रहे हैं
animaonline

1
यह PLINQ का अच्छा उम्मीदवार नहीं है क्योंकि यह विधि String.Containsमहंगी नहीं है। msdn.microsoft.com/en-us/library/dd997399.aspx
टिम श्मेल्टर

1
@TimSchmelter जब हम टन के तारों के बारे में बात कर रहे हैं, तो यह है!
एनिमोनलाइन

4
@TimSchmelter मुझे पता नहीं है कि आप जो साबित करने की कोशिश कर रहे हैं, जो कोड मैंने प्रदान किया है, वह ओपी के लिए प्रदर्शन को बढ़ाएगा, और यहां एक बेंचमार्क है जो यह प्रदर्शित करता है कि यह कैसे काम करता है: pastebin.com/ATYa2BGt - अवधि - -
एनिमालाइन

11

अपडेट करें:

मैंने कुछ प्रोफाइलिंग की।

(अपडेट 3)

  • सूची सामग्री: 0 से 2.499.999 तक की संख्या
  • फ़िल्टर टेक्स्ट: 123 (20.477 परिणाम)
  • कोर i5-2500, विन 7 64 बिट, 8 जीबी रैम
  • VS2012 + JetBrains डॉटट्रेस

2.500.000 रिकॉर्ड्स के लिए प्रारंभिक परीक्षण ने मुझे 20.000ms लिया।

नंबर एक अपराधी textBox_search.Textअंदर की पुकार है Contains। यह get_WindowTextटेक्स्टबॉक्स की महंगी विधि के लिए प्रत्येक तत्व के लिए एक कॉल करता है । बस कोड बदल रहा है:

    var text = textBox_search.Text;
    listBox_choices.DataSource = allUsers.Where(item => item.Contains(text)).ToList();

निष्पादन समय को घटाकर 1.858ms कर दिया है

अपडेट 2:

अन्य दो महत्वपूर्ण बोतल-नेक अब string.Contains(निष्पादन समय का लगभग 45%) और set_Datasource(30%) में लिस्टबॉक्स तत्वों के अपडेट के लिए कॉल हैं ।

हम Suffix ट्री बनाकर गति और मेमोरी उपयोग के बीच एक व्यापार बंद कर सकते हैं क्योंकि Basilevs ने आवश्यक तुलना की संख्या को कम करने और खोज से कुछ प्रसंस्करण समय को एक कुंजी-प्रेस के बाद फ़ाइल से नाम लोड करने के लिए धक्का देने का सुझाव दिया है उपयोगकर्ता के लिए बेहतर हो सकता है।

लिस्टबॉक्स में तत्वों को लोड करने के प्रदर्शन को बढ़ाने के लिए मैं केवल पहले कुछ तत्वों को लोड करने का सुझाव दूंगा और उपयोगकर्ता को यह संकेत दूंगा कि वहां और भी तत्व उपलब्ध हैं। इस तरह आप उपयोगकर्ता को एक प्रतिक्रिया देते हैं कि परिणाम उपलब्ध हैं ताकि वे अधिक अक्षरों में प्रवेश करके अपनी खोज को परिष्कृत कर सकें या एक बटन के प्रेस के साथ पूरी सूची को लोड कर सकें।

के निष्पादन समय में उपयोग BeginUpdateऔर EndUpdateकोई बदलाव नहीं किया गया set_Datasource

जैसा कि अन्य लोगों ने यहां उल्लेख किया है, LINQ क्वेरी ही काफी तेज चलती है। मेरा मानना ​​है कि आपकी बॉटल-नेक लिस्टबॉक्स का अपडेट है। आप कुछ इस तरह की कोशिश कर सकते हैं:

if (textBox_search.Text.Length > 2)
{
    listBox_choices.BeginUpdate(); 
    listBox_choices.DataSource = allUsers.Where(item => item.Contains(textBox_search.Text)).ToList();
    listBox_choices.EndUpdate(); 
}

आशा है कि ये आपकी मदद करेगा।


मुझे नहीं लगता कि इससे कुछ भी सुधार होगा BeginUpdateऔर EndUpdateवस्तुओं को व्यक्तिगत रूप से या उपयोग करते समय उपयोग करने का इरादा है AddRange()
etaiso

यह निर्भर करता है कि DataSourceसंपत्ति कैसे लागू की जाती है। इसे कोशिश करने में लाभ हो सकता है।
एंड्रीज

आपके प्रोफाइलिंग परिणाम मेरे से बहुत अलग हैं। मैं 30k में 120k स्ट्रिंग्स की खोज करने में सक्षम था, लेकिन उन्हें लिस्टबॉक्स में जोड़कर 4500ms लिया। ऐसा लगता है कि आप 600 मीटर से कम के लिस्टबॉक्स में 2.5M स्ट्रिंग्स जोड़ रहे हैं। वो कैसे संभव है?
गाबे

@Gabe प्रोफाइलिंग के दौरान मैंने एक इनपुट का उपयोग किया जहां फ़िल्टर टेक्स्ट ने मूल सूची के एक बड़े हिस्से को समाप्त कर दिया। यदि मैं एक इनपुट का उपयोग करता हूं, जहां फ़िल्टर पाठ सूची से कुछ भी नहीं निकालता है तो मुझे आपके समान परिणाम मिलते हैं। मैंने जो भी मापा है उसे स्पष्ट करने के लिए मैं अपनी प्रतिक्रिया अपडेट करूंगा।
एंड्रीस

9

यह मानते हुए कि आप केवल उपसर्गों से मेल खा रहे हैं, आप जिस डेटा संरचना की तलाश कर रहे हैं, उसे एक ट्राइ कहा जाता है , जिसे "उपसर्ग" भी कहा जाता है। IEnumerable.Whereअब आप जिस पद्धति का उपयोग कर रहे हैं, उसे प्रत्येक एक्सेस पर आपके शब्दकोश में सभी मदों के माध्यम से पुनरावृत्त करना होगा।

यह थ्रेड दिखाता है कि C # में ट्राय कैसे करें।


1
यह मानते हुए कि वह उपसर्ग के साथ अपने रिकॉर्ड को फ़िल्टर कर रहा है।
तारेक

1
ध्यान दें कि वह String.StartsWith () के बजाय String.Contains () पद्धति का उपयोग कर रहा है, इसलिए यह ठीक वैसा नहीं हो सकता है जैसा हम खोज रहे हैं। फिर भी - उपसर्ग परिदृश्य में StartsWith () विस्तार के साथ साधारण फ़िल्टरिंग की तुलना में आपका विचार निस्संदेह बेहतर है।
तारेक

यदि वह इसका मतलब शुरू करता है, तो बेहतर प्रदर्शन के लिए ट्राई को पृष्ठभूमि कार्यकर्ता दृष्टिकोण के साथ जोड़ा जा सकता है
Lyndon White

8

WinForms ListBox नियंत्रण वास्तव में यहाँ आपका दुश्मन है। यह रिकॉर्ड लोड करने के लिए धीमा होगा और स्क्रॉलबार आपको सभी 120,000 रिकॉर्ड दिखाने के लिए लड़ेगा।

एक पुराने स्तंभों वाले DataGridView डेटा को एक डेटा कॉलम के साथ उपयोग करने की कोशिश करें, जिसमें एक एकल कॉलम [UserName] है, जो आपके डेटा को रखने के लिए है:

private DataTable dt;

public Form1() {
  InitializeComponent();

  dt = new DataTable();
  dt.Columns.Add("UserName");
  for (int i = 0; i < 120000; ++i){
    DataRow dr = dt.NewRow();
    dr[0] = "user" + i.ToString();
    dt.Rows.Add(dr);
  }
  dgv.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
  dgv.AllowUserToAddRows = false;
  dgv.AllowUserToDeleteRows = false;
  dgv.RowHeadersVisible = false;
  dgv.DataSource = dt;
}

फिर डेटा को फ़िल्टर करने के लिए अपने TextBox की TextChanged घटना में एक DataView का उपयोग करें:

private void textBox1_TextChanged(object sender, EventArgs e) {
  DataView dv = new DataView(dt);
  dv.RowFilter = string.Format("[UserName] LIKE '%{0}%'", textBox1.Text);
  dgv.DataSource = dv;
}

2
+1 जबकि हर कोई उस खोज को ऑप्टिमाइज़ करने की कोशिश कर रहा था जो केवल 30ms लेता है, आप एकमात्र व्यक्ति हैं जिसने पहचाना कि समस्या वास्तव में सूची बॉक्स को भरने में है।
गाबे

7

पहले मैं ListControlबदलूंगा कि आपके डेटा स्रोत को कैसे देखता है, आप परिणाम IEnumerable<string>को परिवर्तित कर रहे हैं List<string>। खासकर जब आपने कुछ अक्षर टाइप किए हैं तो यह अक्षम (और अनावश्यक) हो सकता है। अपने डेटा की विस्तृत प्रतियां न बनाएं

  • मैं .Where()एक संग्रह के लिए परिणाम को लपेटता हूं जो केवल उसी चीज को लागू करता है जो IList(खोज) से आवश्यक है । यह आपको प्रत्येक वर्ण के लिए एक नई बड़ी सूची बनाने के लिए बचाएगा।
  • विकल्प के रूप में मैं LINQ से बचूंगा और मैं कुछ और विशिष्ट (और अनुकूलित) लिखूंगा। अपनी सूची को स्मृति में रखें और मिलान किए गए सूचकांकों की एक सरणी बनाएं, सरणी का पुन: उपयोग करें ताकि आपको प्रत्येक खोज के लिए इसे पुनः प्राप्त न करना पड़े।

दूसरा कदम बड़ी सूची में खोज नहीं करना है जब छोटा एक पर्याप्त है। जब उपयोगकर्ता ने "एब" टाइप करना शुरू कर दिया और उसने "सी" जोड़ दिया तो आपको बड़ी सूची में शोध करने की आवश्यकता नहीं है, फ़िल्टर की गई सूची में खोज पर्याप्त (और तेज) है। हर बार खोज को परिष्कृत करें, हर बार पूर्ण खोज न करें।

तीसरा चरण कठिन हो सकता है: जल्दी खोजे जाने के लिए व्यवस्थित डेटा रखें । अब आपको अपने डेटा को संग्रहीत करने के लिए आपके द्वारा उपयोग की जाने वाली संरचना को बदलना होगा। इस तरह एक पेड़ की कल्पना करो:

एबीसी
 बेहतर छत जोड़ें
 बोन कंटूर के ऊपर

यह केवल एक सरणी के साथ लागू किया जा सकता है (यदि आप एएनएसआई नामों के साथ काम कर रहे हैं अन्यथा एक शब्दकोश बेहतर होगा)। इस तरह सूची बनाएँ (चित्रण के उद्देश्य, यह स्ट्रिंग की शुरुआत से मेल खाता है):

var dictionary = new Dictionary<char, List<string>>();
foreach (var user in users)
{
    char letter = user[0];
    if (dictionary.Contains(letter))
        dictionary[letter].Add(user);
    else
    {
        var newList = new List<string>();
        newList.Add(user);
        dictionary.Add(letter, newList);
    }
}

पहले वर्ण का उपयोग करके खोज की जाएगी:

char letter = textBox_search.Text[0];
if (dictionary.Contains(letter))
{
    listBox_choices.DataSource =
        new MyListWrapper(dictionary[letter].Where(x => x.Contains(textBox_search.Text)));
}

कृपया ध्यान दें कि मैं MyListWrapper()पहले चरण में सुझाव के रूप में इस्तेमाल किया गया था (लेकिन मैं संक्षिप्तता के लिए 2 सुझाव द्वारा छोड़ा गया था, यदि आप शब्दकोश कुंजी के लिए सही आकार चुनते हैं तो आप प्रत्येक सूची को छोटा और तेज़ रख सकते हैं - शायद - और कुछ भी बचने के लिए)। इसके अलावा ध्यान दें कि आप अपने शब्दकोश के लिए पहले दो वर्णों का उपयोग करने की कोशिश कर सकते हैं (अधिक सूचियाँ और छोटी)। यदि आप इसे बढ़ाते हैं तो आपके पास एक पेड़ होगा (लेकिन मुझे नहीं लगता कि आपके पास इतनी बड़ी संख्या में आइटम हैं)।

कर रहे हैं कई अलग अलग एल्गोरिदम स्ट्रिंग खोज (संबंधित डेटा संरचनाओं के साथ) के लिए बस कुछ ही उल्लेख करने के लिए,:

  • परिमित राज्य ऑटोमेटोन आधारित खोज : इस दृष्टिकोण में, हम एक नियतात्मक परिमित ऑटोमेटन (डीएफए) का निर्माण करके पीछे हटने से बचते हैं जो संग्रहीत खोज स्ट्रिंग को पहचानता है। ये निर्माण के लिए महंगे हैं - वे आमतौर पर पावरसेट निर्माण का उपयोग करके बनाए जाते हैं - लेकिन उपयोग करने के लिए बहुत जल्दी हैं।
  • स्टब्स : नथ-मॉरिस-प्रैट एक डीएफए की गणना करता है जो एक प्रत्यय के रूप में खोज करने के लिए स्ट्रिंग के साथ इनपुट को पहचानता है, बॉयर-मूर सुई के अंत से खोजना शुरू कर देता है, इसलिए यह आमतौर पर प्रत्येक चरण में पूरी सुई-लंबाई से आगे कूद सकता है। Baeza-Yates इस बात पर नज़र रखती है कि क्या पिछले j अक्षर खोज स्ट्रिंग का उपसर्ग थे, और इसलिए यह फ़ज़ी स्ट्रिंग खोज के लिए अनुकूल है। बिटप एल्गोरिथ्म Baeza-Yates 'दृष्टिकोण का एक अनुप्रयोग है।
  • सूचकांक विधियाँ : तेज खोज एल्गोरिदम पाठ के प्रीप्रोसेसिंग पर आधारित हैं। एक विकल्प सूचकांक के निर्माण के बाद, उदाहरण के लिए एक प्रत्यय वृक्ष या प्रत्यय सरणी, एक पैटर्न की घटनाओं को जल्दी से पाया जा सकता है।
  • अन्य प्रकार : कुछ खोज विधियाँ, उदाहरण के लिए, ट्रिग्राम खोज, का उद्देश्य "मेल / न-मैच" के बजाय खोज स्ट्रिंग और पाठ के बीच एक "निकटता" स्कोर खोजना है। इन्हें कभी-कभी "फ़ज़ी" खोजों कहा जाता है।

समानांतर खोज के बारे में कुछ शब्द। यह संभव है, लेकिन यह शायद ही कभी तुच्छ है क्योंकि इसे समानांतर बनाने के लिए ओवरहेड आसानी से बहुत अधिक हो सकता है जो खुद को खोजते हैं। मैं खुद को समानांतर (विभाजन और तुल्यकालन में खोज नहीं करूंगा) जल्द ही व्यापक और शायद जटिल भी हो जाएगा, लेकिन मैं एक अलग धागे की खोज करूंगा । यदि मुख्य थ्रेड व्यस्त नहीं है, तो आपके उपयोगकर्ता टाइप करते समय किसी भी देरी को महसूस नहीं करेंगे (वे नोट नहीं करेंगे यदि सूची 200 एमएस के बाद दिखाई देगी, लेकिन वे टाइप करने के बाद 50 एमएस इंतजार करना होगा तो वे असहज महसूस करेंगे) । बेशक खोज अपने आप में पर्याप्त तेज़ होनी चाहिए, इस मामले में आप खोज को गति देने के लिए थ्रेड का उपयोग नहीं करते हैं लेकिन अपने UI को उत्तरदायी बनाए रखने के लिए । कृपया ध्यान दें कि एक अलग धागा आपकी क्वेरी नहीं बनाएगातेजी सेयह UI लटकाएगा नहीं, लेकिन यदि आपकी क्वेरी धीमी थी, तब भी यह एक अलग थ्रेड में धीमी होगी (इसके अलावा आपको कई अनुक्रमिक अनुरोधों को संभालना होगा)।


1
जैसा कि कुछ पहले ही बता चुके हैं, ओपी परिणामों को केवल उपसर्गों तक सीमित नहीं करना चाहता (यानी वह उपयोग करता है Contains, नहीं StartsWith)। साइड नोट पर, आमतौर पर सामान्य ContainsKeyपद्धति का उपयोग करना बेहतर होता है जब बॉक्सिंग से बचने के लिए एक कुंजी की खोज की जाती है, और TryGetValueदो लुकअप से बचने के लिए उपयोग करना भी बेहतर होता है ।
ग्रू

2
@ फिर भी आप सही हैं, जैसा कि मैंने कहा कि यह केवल दृष्टांत उद्देश्यों के लिए है। उस कोड का बिंदु एक कार्यशील समाधान नहीं है लेकिन एक संकेत है: यदि आपने बाकी सब कुछ आज़माया है - प्रतियों से बचें, खोज को परिष्कृत करें, इसे किसी अन्य थ्रेड पर ले जाएं - और यह पर्याप्त नहीं है तो आपको डेटा संरचना का उपयोग करना होगा। । उदाहरण केवल सरल रहने के लिए स्ट्रिंग की शुरुआत के लिए है।
Adriano Repetti

@ Adriano स्पष्ट और विस्तृत जवाब के लिए धन्यवाद! मैं आपके द्वारा बताई गई अधिकांश चीजों से सहमत हूं लेकिन जैसा कि ग्रू ने कहा, डेटा को व्यवस्थित रखने का अंतिम हिस्सा मेरे मामले में लागू नहीं है। लेकिन मुझे लगता है कि निहित अक्षर के साथ एक समान शब्दकोश रखने के लिए शायद (हालांकि अभी भी डुप्लिकेट होंगे)
etaiso

एक त्वरित जांच और गणना के बाद "निहित पत्र" विचार केवल एक चरित्र के लिए अच्छा नहीं है (और यदि हम दो या दो से अधिक के संयोजन के लिए जाते हैं तो हम एक बहुत बड़ी हैश तालिका के साथ समाप्त हो जाएंगे)
etaiso

@etaiso हाँ, आप दो अक्षरों की एक सूची रख सकते हैं (उप-सूचियों को जल्दी से कम करने के लिए) लेकिन एक सच्चा पेड़ बेहतर काम कर सकता है (प्रत्येक अक्षर इसके उत्तराधिकारियों से जुड़ा हुआ है, कोई फर्क नहीं पड़ता कि यह स्ट्रिंग के अंदर है "घर" के लिए तो आपके पास है) "H-> O", "O-> M" और "M-> E"। यदि आप "om" की खोज कर रहे हैं, तो यह जल्दी मिल जाएगा। समस्या यह है कि यह बहुत अधिक जटिल हो जाता है और यह बहुत अधिक हो सकता है। आपके लिए परिदृश्य (IMO)
एड्रियानो रेपेती

4

आप PLINQ (समानांतर LINQ) का उपयोग करके देख सकते हैं । हालांकि यह गति को बढ़ावा नहीं देता है, इसके लिए आपको परीक्षण और त्रुटि का पता लगाने की आवश्यकता है।


4

मुझे संदेह है कि आप इसे तेजी से बनाने में सक्षम होंगे, लेकिन निश्चित रूप से आपको यह करना चाहिए:

a) AsParallel LINQ एक्सटेंशन विधि का उपयोग करें

a) फ़िल्टरिंग में देरी के लिए किसी प्रकार के टाइमर का उपयोग करें

b) दूसरे थ्रेड पर फ़िल्टरिंग विधि रखें

string previousTextBoxValueकहीं न कहीं किसी न किसी तरह रखें । 1000 एमएस की देरी के साथ एक टाइमर बनाएं, जो previousTextBoxValueआपके textbox.Textमूल्य के समान होने पर टिक पर खोज करता है । यदि नहीं - previousTextBoxValueवर्तमान मान को पुन: असाइन करें और टाइमर रीसेट करें। पाठ प्रारंभ परिवर्तित कार्यक्रम में टाइमर सेट करें, और यह आपके एप्लिकेशन को सुचारू बना देगा। 1-3 सेकंड में 120,000 रिकॉर्ड को छानना ठीक है, लेकिन आपका यूआई उत्तरदायी रहना चाहिए।


1
मैं इसे समानांतर बनाने के लिए सहमत नहीं हूं, लेकिन मैं अन्य दो बिंदुओं से बिल्कुल सहमत हूं। यूआई आवश्यकताओं को पूरा करने के लिए यह पर्याप्त हो सकता है।
एड्रियानो रेपेती

उल्लेख करना भूल गया, लेकिन मैं .NET 3.5 का उपयोग कर रहा हूं इसलिए AsParallel एक विकल्प नहीं है।
एटैसो

3

आप BindingSource.Filter फ़ंक्शन का उपयोग करके भी प्रयास कर सकते हैं । मैंने इसका उपयोग किया है और यह अभिलेखों के गुच्छा से फ़िल्टर करने के लिए एक आकर्षण की तरह काम करता है, हर बार इस संपत्ति को टेक्स्ट की खोज के साथ अपडेट करता है। टेक्स्टबॉक्स नियंत्रण के लिए AutoCompleteSource का उपयोग करने का एक और विकल्प होगा ।

आशा है कि इससे सहायता मिलेगी!


2

मैं संग्रह को क्रमबद्ध करने की कोशिश करूँगा, केवल कुछ भाग के आधार पर खोज शुरू करूँगा और खोज को सीमित करूँगा।

इतने पर ininialization

allUsers.Sort();

और खोज

allUsers.Where(item => item.StartWith(textBox_search.Text))

शायद आप कुछ कैश जोड़ सकते हैं।


1
वह स्ट्रिंग की शुरुआत के साथ काम नहीं कर रहा है (इसीलिए वह String.Contains ()) का उपयोग कर रहा है। युक्तियों के साथ () एक क्रमबद्ध सूची प्रदर्शन को नहीं बदलती है।
Adriano Repetti

हां, 'कॉन्टेन्स' के साथ यह बेकार है। मुझे उपसर्ग के पेड़ के साथ सुझाव पसंद है stackoverflow.com/a/21383731/994849 थ्रेड में बहुत दिलचस्प जवाब है, लेकिन यह निर्भर करता है कि वह इस कार्य पर कितना समय बिता सकता है।
हार्डस्की

1

समानांतर का उपयोग करें LINQPLINQLINQ का सामानांतर समानांतर कार्यान्वयन है। PLINQ T: System.Linq नाम स्थान के विस्तार विधियों के रूप में LINQ मानक क्वेरी ऑपरेटरों का पूरा सेट लागू करता है और समानांतर संचालन के लिए अतिरिक्त ऑपरेटर है। PLINQ समानांतर प्रोग्रामिंग की शक्ति के साथ LINQ वाक्य रचना की सरलता और पठनीयता को जोड़ती है। जैसे कोड जो टास्क पैरेलल लाइब्रेरी को लक्षित करता है, PLINQ क्वेरीज़ होस्ट कंप्यूटर की क्षमताओं के आधार पर संगामिति की डिग्री में स्केल करता है।

PLINQ का परिचय

PLINQ में स्पीडअप को समझना

इसके अलावा आप Lucene.Net का उपयोग कर सकते हैं

Lucene.Net, Lucene सर्च इंजन लाइब्रेरी का एक पोर्ट है, जो C # में लिखा गया है और .NET रनटाइम उपयोगकर्ताओं पर लक्षित है। लुसीन खोज पुस्तकालय एक उल्टे सूचकांक पर आधारित है। Lucene.Net के तीन प्राथमिक लक्ष्य हैं:


1

मैंने जो देखा है उसके अनुसार मैं सूची को क्रमबद्ध करने के तथ्य से सहमत हूं।

हालाँकि जब सूची का निर्माण होता है तो बहुत धीमी गति से, सॉर्ट करते समय, आपके पास बेहतर निष्पादन समय होगा।

अन्यथा यदि आपको सूची प्रदर्शित करने या आदेश रखने की आवश्यकता नहीं है, तो हैशमैप का उपयोग करें।

हैशमैप आपके स्ट्रिंग को हैश करेगा और सटीक ऑफसेट पर खोज करेगा। मुझे लगता है कि यह तेज होना चाहिए।


किस कुंजी के साथ हशमप? मैं उन कीवर्ड्स को ढूंढना चाहता हूं जो स्ट्रिंग्स में समाहित हैं।
एटैसो

कुंजी के लिए आप सूची में नंबर डाल सकते हैं, यदि आप अधिक जटिल चाहते हैं तो आप नंबर जोड़ सकते हैं नाम पसंद आपकी है।
दादा

बाकी के लिए या तो मैंने सब कुछ नहीं पढ़ा था या तो एक बुरी व्याख्या थी (शायद दोनों?) [उद्धरण] लगभग 120,000 उपयोगकर्ताओं (तार) की एक पाठ फ़ाइल है जिसे मैं एक संग्रह में संग्रहीत करना चाहता हूं और बाद में एक प्रदर्शन करने के लिए उस संग्रह पर खोज करें। [/ उद्धरण] मैंने सोचा था कि यह सिर्फ एक स्ट्रिंग खोज थी।
दादा

1

बाइनरीसर्च विधि का उपयोग करने का प्रयास करें यह तेजी से काम करना चाहिए फिर इसमें विधि शामिल है।

इसमें एक O होगा (n) बाइनरीसर्च एक O (lg (n)) है

मुझे लगता है कि सॉर्ट किए गए संग्रह को नए तत्वों को जोड़ने पर खोज और धीमी गति से काम करना चाहिए, लेकिन जैसा कि मैंने समझा कि आपके पास केवल पूर्णता समस्या खोज है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.