क्या किसी एकल चरित्र के लिए कोई अच्छी खोज एल्गोरिदम है?


23

मैं कई बुनियादी स्ट्रिंग-मेलिंग एल्गोरिदम जैसे केएमपी या बोयर-मूर को जानता हूं, लेकिन वे सभी खोज करने से पहले पैटर्न का विश्लेषण करते हैं। हालांकि, यदि किसी का एकल चरित्र है, तो विश्लेषण करने के लिए बहुत कुछ नहीं है। तो क्या पाठ के हर चरित्र की तुलना करने की भोली खोज की तुलना में कोई बेहतर एल्गोरिदम है?


13
आप इस पर SIMD निर्देश फेंक सकते हैं, लेकिन आपको O (n) से बेहतर कुछ नहीं मिलेगा।
कोडइन्चौस

7
एक ही खोज या एक ही स्ट्रिंग में कई खोजों के लिए?
क्रिस्टोफ

केएमपी निश्चित रूप से कुछ ऐसा नहीं है जिसे मैं "बुनियादी" स्ट्रिंग-मिलान एल्गोरिथ्म कहूंगा ... मुझे यकीन भी नहीं है कि यह इतनी जल्दी या तो है, लेकिन यह ऐतिहासिक रूप से महत्वपूर्ण है। यदि आप कुछ बुनियादी चाहते हैं Z एल्गोरिथ्म का प्रयास करें।
मेहरदाद

मान लीजिए कि एक वर्ण स्थिति थी जिसे खोज एल्गोरिथ्म ने नहीं देखा था। तब यह उस स्थिति में सुई चरित्र के साथ तार के बीच अंतर करने में सक्षम नहीं होगा, और उस स्थिति में एक अलग चरित्र के साथ तार।
user253751

जवाबों:


29

यह समझा जा रहा है कि सबसे खराब स्थिति यह है O(N), कुछ बहुत अच्छे सूक्ष्म अनुकूलन हैं।

भोली विधि एक चरित्र तुलना और प्रत्येक चरित्र के लिए एक पाठ की तुलना करती है।

एक प्रहरी का उपयोग करना (यानी पाठ के अंत में लक्ष्य चरित्र की एक प्रति) का उपयोग करके प्रति वर्ण की तुलना की संख्या कम हो जाती है।

बिट ट्विडलिंग स्तर पर है:

#define haszero(v)      ( ((v) - 0x01010101UL) & ~(v) & 0x80808080UL )
#define hasvalue(x, n)  ( haszero((x) ^ (~0UL / 255 * (n))) )

यह जानने के लिए कि किसी शब्द में किसी बाइट xका कोई विशिष्ट मान है ( n)।

v - 0x01010101ULजब भी संबंधित बाइट vशून्य या उससे अधिक होती है , तो सबफ़्रेशन , किसी भी बाइट में एक उच्च बिट सेट का मूल्यांकन करता है 0x80

उप-अभिव्यक्ति ~v & 0x80808080ULबाइट्स में सेट किए गए उच्च बिट्स का मूल्यांकन करती है जहां बाइट का vअपना उच्च बिट सेट नहीं है (इसलिए बाइट से कम था0x80 )।

इन दो उप-अभिव्यक्तियों को क्रमबद्ध करके ( haszeroपरिणाम) उच्च बिट्स सेट है जहां बाइट vशून्य में थे, क्योंकि उच्च बिट्स एक मान से अधिक होने के कारण सेट होते हैं0x80 पहले उप अभिव्यक्ति में दूसरा द्वारा बंद से छुपाया जाता है (27 अप्रैल, 1987 एलन माईक्रॉफ्ट द्वारा)।

अब हम xउस शब्द के साथ परीक्षण के लिए XOR ( ) कर सकते हैं जो बाइट मान के साथ भरा गया है जिसमें हम रुचि रखते हैं ( n)। क्योंकि ज़ीरो बाइट और नॉनज़ेरो में अपने आप में एक मूल्य होने के कारण, हम परिणाम को पास कर सकते हैं haszero

इस बार एक ठेठ में प्रयोग किया जाता है strchrकार्यान्वयन।

(स्टीफन एम बेनेट ने 13 दिसंबर 2009 को यह सुझाव दिया था। प्रसिद्ध बिट ट्विडलिंग हैक्स में आगे का विवरण )।


पुनश्च

यह कोड किसी के 1111बगल में किसी भी संयोजन के लिए टूट गया है0

हैक जानवर बल परीक्षण पास करता है (बस धैर्य रखें):

#include <iostream>
#include <limits>

bool haszero(std::uint32_t v)
{
  return (v - std::uint32_t(0x01010101)) & ~v & std::uint32_t(0x80808080);
}

bool hasvalue(std::uint32_t x, unsigned char n)
{
  return haszero(x ^ (~std::uint32_t(0) / 255 * n));
}

bool hasvalue_slow(std::uint32_t x, unsigned char n)
{
  for (unsigned i(0); i < 32; i += 8)
    if (((x >> i) & 0xFF) == n)
      return true;

  return false;
}

int main()
{
  const std::uint64_t stop(std::numeric_limits<std::uint32_t>::max());

  for (unsigned c(0); c < 256; ++c)
  {
    std::cout << "Testing " << c << std::endl;

    for (std::uint64_t w(0); w != stop; ++w)
    {
      if (w && w % 100000000 == 0)
        std::cout << w * 100 / stop << "%\r" << std::flush;

      const bool h(hasvalue(w, c));
      const bool hs(hasvalue_slow(w, c));

      if (h != hs)
        std::cerr << "hasvalue(" << w << ',' << c << ") is " << h << '\n';
    }
  }

  return 0;
}

एक उत्तर के लिए बहुत सारे उत्थान जो धारणा बनाता है एक करारा बैक्टीरिया = एक बाइट, जो आजकल मानक से अधिक नहीं है

टिप्पणी के लिए धन्यवाद।

जवाब कुछ भी हो सकता था, लेकिन मल्टी-बाइट / वैरिएबल-चौड़ाई एन्कोडिंग्स पर एक निबंध :-) (सभी निष्पक्षता में जो मेरी विशेषज्ञता का क्षेत्र नहीं है और मुझे यकीन नहीं है कि यह ओपी देख रहा था)।

वैसे भी यह मुझे लगता है कि उपरोक्त विचारों / चालों को कुछ हद तक MBE के लिए अनुकूलित किया जा सकता है (विशेषकर सेल्फ-सिंकिंग एन्कोडिंग ):

  • जैसा कि जोहान की टिप्पणी में उल्लेख किया गया है कि हैक को आसानी से डबल बाइट्स या किसी भी चीज़ के लिए बढ़ाया जा सकता है (निश्चित रूप से आप इसे बहुत ज्यादा नहीं बढ़ा सकते हैं);
  • एक विशिष्ट फ़ंक्शन जो एक मल्टीबाइट कैरेक्टर स्ट्रिंग में एक वर्ण का पता लगाता है:
    • strchr/ strstr(जैसे GNUlib coreutils mbschr ) में कॉल शामिल हैं
    • उन्हें अच्छी तरह से तैयार होने की उम्मीद है।
  • प्रहरी तकनीक का उपयोग थोड़ी दूरदर्शिता के साथ किया जा सकता है।

1
यह एक गरीब आदमी का SIMD ऑपरेशन है।
रुस्लान

@ रसेलन बिल्कुल! यह अक्सर प्रभावी बिट ट्विगलिंग हैक के लिए मामला है।
मैलियो

2
अच्छा जवाब। एक पठनीयता के पहलू से, मुझे समझ नहीं आता कि आप 0x01010101ULएक पंक्ति ~0UL / 255में और अगले में क्यों लिखते हैं । यह धारणा देता है कि उन्हें अलग-अलग मूल्य होने चाहिए, अन्यथा, इसे दो अलग-अलग तरीकों से क्यों लिखें?
hvd

3
यह शांत है क्योंकि यह एक बार में 4 बाइट्स की जांच करता है, लेकिन इसके लिए कई (8?) निर्देशों की आवश्यकता होती है, क्योंकि #defineएस का विस्तार होगा ( (((x) ^ (0x01010101UL * (n)))) - 0x01010101UL) & ~((x) ^ (0x01010101UL * (n)))) & 0x80808080UL )। क्या सिंगल-बाइट की तुलना तेज नहीं होगी?
जैद शहाफ

1
@DocBrown, कोड को आसानी से डबल बाइट्स (यानी हाफवर्ड्स) या नीबल्स या कुछ भी काम करने के लिए बनाया जा सकता है। (मेरे द्वारा उल्लेखित चेतावनी को ध्यान में रखते हुए)।
जोहान - मोनिका को बहाल करना

20

किसी भी पाठ खोज एल्गोरिथ्म जो किसी दिए गए पाठ में एकल वर्ण की प्रत्येक घटना को खोजता है, पाठ के प्रत्येक चरित्र को कम से कम एक बार पढ़ना पड़ता है, यह स्पष्ट होना चाहिए। और चूंकि यह एक बार की खोज के लिए पर्याप्त है, इसलिए इस मामले के लिए कोई बेहतर एल्गोरिथ्म (जब रन टाइम ऑर्डर के संदर्भ में सोच रहा हो, जिसे "रैखिक" या O (N) कहा जा सकता है, जहां N वर्णों की संख्या है) के माध्यम से खोज करने के लिए)।

हालांकि, वास्तविक कार्यान्वयन के लिए, निश्चित रूप से बहुत सारे माइक्रो-ऑप्टिमाइज़ेशन संभव हैं, जो पूरे समय रन रन ऑर्डर को नहीं बदलते हैं, लेकिन वास्तविक रन समय को कम करते हैं। और यदि लक्ष्य किसी एक वर्ण की प्रत्येक घटना को खोजने का नहीं है, लेकिन केवल पहला है, तो आप निश्चित रूप से पहली घटना को रोक सकते हैं। फिर भी, उस मामले के लिए, सबसे खराब स्थिति अभी भी है कि आप जिस चरित्र की तलाश कर रहे हैं, वह पाठ का अंतिम चरित्र है, इसलिए इस लक्ष्य के लिए सबसे खराब स्थिति रन टाइम ऑर्डर अभी भी O (N) है।


8

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

यदि आपको केवल यह जानने की आवश्यकता है कि क्या खोजा गया पैटर्न मौजूद है, तो एक सरल काउंटर मदद कर सकता है। यह उस स्थिति (ओं) को शामिल करने के लिए बढ़ाया जा सकता है, जिस पर प्रत्येक चरित्र को धड़ में पाया जाता है, या पहली घटना की स्थिति।

string haystack = "agtuhvrth";
array<int, 256> histogram{0};
for(character: haystack)
     ++histogram[character];

if(histogram['a'])
    // a belongs to haystack

1

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

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

उदाहरण के लिए: निम्नलिखित स्ट्रिंग के लिए कोई इसे 4 भागों में विभाजित कर सकता है (प्रत्येक 11 वर्ण लंबा), और प्रत्येक भाग के लिए एक खिल फ़िल्टर (शायद 4 बाइट बड़ी) के लिए उस भाग के पात्रों के साथ भरें:

The quick brown fox jumps over the lazy dog 
          |          |          |          |

आप अपनी खोज को गति दे सकते हैं, जैसे चरित्र के लिए a: ब्लूम फ़िल्टर के लिए अच्छे हैश फ़ंक्शंस का उपयोग करके, वे आपको बताएंगे कि - उच्च संभावना के साथ - आपको न तो पहले, दूसरे और तीसरे भाग में खोजना होगा। इस प्रकार आप अपने आप को 33 अक्षरों की जाँच करने से बचाते हैं और इसके बजाय केवल 16 बाइट्स (4 ब्लूम फ़िल्टर के लिए) की जाँच करनी होती है। यह अभी भी है O(n), बस एक स्थिर (आंशिक) कारक के साथ (और इसके लिए प्रभावी होने के लिए आपको बड़े भागों को चुनने की आवश्यकता होगी, खोज चरित्र के लिए हैश कार्यों की गणना के ओवरहेड को कम करने के लिए)।

एक पुनरावर्ती का उपयोग करते हुए, पेड़ जैसा दृष्टिकोण आपको पास होना चाहिए O(log n):

The quick brown fox jumps over the lazy dog 
   |   |   |   |   |   |   |   |---|-X-|   |  (1 Byte)
       |       |       |       |---X---|----  (2 Byte)
               |               |-----X------  (3 Byte)
-------------------------------|-----X------  (4 Byte)
---------------------X---------------------|  (5 Byte)

इस विन्यास में एक (फिर से, हमें भाग्यशाली मानते हुए और एक फिल्टर से एक झूठी सकारात्मक नहीं मिला) की जांच करने की आवश्यकता है

5 + 2*4 + 3 + 2*2 + 2*1 bytes

अंतिम भाग में जाने के लिए (जहां किसी को ढूंढने तक 3 वर्णों की जांच करने की आवश्यकता होती है a)।

एक अच्छे (उपर्युक्त के रूप में बेहतर) उपखंड योजना का उपयोग करके आपको उसके साथ बहुत अच्छे परिणाम प्राप्त करने चाहिए। (ध्यान दें: पेड़ की जड़ में ब्लूम फिल्टर पत्तियों के करीब से बड़ा होना चाहिए, जैसा कि उदाहरण में दिखाया गया है, कम झूठी सकारात्मक संभावनाएं प्राप्त करने के लिए)


प्रिय downvoter, कृपया समझाएं कि आपको क्यों लगता है कि मेरा उत्तर सहायक नहीं है।
डैनियल जर्स

1

यदि स्ट्रिंग को कई बार (विशिष्ट "खोज" समस्या) खोजा जा रहा है, तो समाधान O (1) हो सकता है। समाधान एक सूचकांक का निर्माण करना है।

जैसे:

मानचित्र, जहाँ कुंजी वर्ण है और मान स्ट्रिंग में उस वर्ण के सूचकांकों की सूची है।

इसके साथ, एक सिंगल मैप लुकअप उत्तर प्रदान कर सकता है।

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