দীর্ঘতম উপসর্গ সন্ধানের জন্য অ্যালগরিদম


11

আমার দুটি টেবিল আছে

প্রথমটি হ'ল উপসর্গযুক্ত একটি সারণী

code name price
343  ek1   10
3435 nt     4
3432 ek2    2

দ্বিতীয়টি হল ফোন নম্বর সহ কল ​​রেকর্ড

number        time
834353212     10
834321242     20
834312345     30

আমার একটি স্ক্রিপ্ট লিখতে হবে যা প্রতিটি রেকর্ডের জন্য উপসর্গ থেকে দীর্ঘতম উপসর্গ খুঁজে পায় এবং তৃতীয় টেবিলে এই সমস্ত ডেটা লিখতে হবে:

 number        code   ....
 834353212     3435
 834321242     3432
 834312345     343

834353212 সংখ্যার জন্য আমাদের অবশ্যই '8' কে ছাঁটাই করতে হবে এবং তারপরে 343 উপসর্গের সারণি থেকে দীর্ঘতম কোডটি খুঁজে পাওয়া
উচিত We

আমি খুব খারাপ পদ্ধতিতে অনেক আগে এই কাজটি সমাধান করেছি। এটি ছিল ভয়ানক পার্ল স্ক্রিপ্ট যা প্রতিটি রেকর্ডের জন্য অনেক কোয়েরি করে। এই লিপি:

  1. কল টেবিল থেকে একটি নম্বর নিন, লুপটিতে দৈর্ঘ্য (সংখ্যা) থেকে 1 => f উপসর্গের স্ট্রিং করুন

  2. ক্যোরিটি করুন: উপসর্গ থেকে গণনা (*) নির্বাচন করুন যেখানে কোড '$ উপসর্গ' এর মতো

  3. যদি গণনা> 0 হয় তবে প্রথমে উপসর্গ নিন এবং টেবিলে লিখুন

প্রথম সমস্যাটি হল কোয়েরি গণনা - এটি call_records * length(number)। দ্বিতীয় সমস্যাটি হল LIKEএক্সপ্রেশন। আমি ভয় করি যে এগুলি ধীর।

আমি দ্বিতীয় সমস্যাটি সমাধান করার চেষ্টা করেছি:

CREATE EXTENSION pg_trgm;
CREATE INDEX prefix_idx ON prefix USING gist (code gist_trgm_ops);

এটি প্রতিটি প্রশ্নের গতি বাড়িয়ে তোলে, তবে সাধারণভাবে সমস্যার সমাধান করেনি।

আমার কাছে এখন 20k উপসর্গ এবং 170k নম্বর আছে এবং আমার পুরানো সমাধানটি খারাপ। দেখে মনে হচ্ছে লুপ ছাড়া আমার কিছু নতুন সমাধান দরকার।

প্রতিটি কল রেকর্ড বা এই জাতীয় কিছু জন্য শুধুমাত্র একটি ক্যোয়ারী।


2
codeপ্রথম টেবিলের পরে যদি উপসর্গের মতো হয় তবে সত্যই আমি নিশ্চিত নই । আপনি দয়া করে এটি পরিষ্কার করতে পারেন? এবং উদাহরণস্বরূপ ডেটা এবং কাঙ্ক্ষিত আউটপুট (যাতে আপনার সমস্যাটি অনুসরণ করা সহজতর হয়) এর কিছু সংশোধনও স্বাগত হবে।
dezso

হাঁ। তুমি ঠিক. আমি ভুলে গিয়েছিলাম '8' সম্পর্কে লিখতে। ধন্যবাদ.
কোরজাভিন ইভান

2
উপসর্গটি শুরুতে থাকতে হবে, তাই না?
dezso

হ্যাঁ. দ্বিতীয় স্থান থেকে। 8 $ উপসর্গ $ সংখ্যা
Korjavin ইভান

আপনার টেবিলগুলির কার্ডিনালিটিটি কী? 100k নম্বর? কতটি উপসর্গ?
এরউইন ব্র্যান্ডস্টেটার

উত্তর:


21

আমি textপ্রাসঙ্গিক কলামগুলির জন্য ডেটা টাইপ ধরে নিচ্ছি ।

CREATE TABLE prefix (code text, name text, price int);
CREATE TABLE num (number text, time int);

"সরল" সমাধান

SELECT DISTINCT ON (1)
       n.number, p.code
FROM   num n
JOIN   prefix p ON right(n.number, -1) LIKE (p.code || '%')
ORDER  BY n.number, p.code DESC;

মূল উপাদান:

DISTINCT ONএসকিউএল স্ট্যান্ডার্ডের পোস্টগ্রিস এক্সটেনশন DISTINCTএসও সম্পর্কিত এই সম্পর্কিত উত্তরের ব্যবহৃত ক্যোয়ারী কৌশলটির বিশদ বিবরণ খুঁজুন ।
ORDER BY p.code DESCদীর্ঘতম ম্যাচটি বাছাই করে, কারণ এর '1234'পরে বাছাই '123'(আরোহী ক্রমে)।

সাধারণ এসকিউএল ফিডল

সূচি ব্যতীত, ক্যোয়ারীটি খুব দীর্ঘ সময়ের জন্য চলবে (এটি শেষ হওয়ার অপেক্ষায় নেই)। এটি দ্রুত করার জন্য আপনার সূচী সমর্থন প্রয়োজন। অতিরিক্ত মডিউল দ্বারা সরবরাহিত আপনার ট্রিগার সূচকগুলি pg_trgmভাল প্রার্থী a আপনাকে জিআইএন এবং জিএসটি সূচকের মধ্যে নির্বাচন করতে হবে। সংখ্যার প্রথম অক্ষরটি কেবল শব্দ এবং এটি সূচক থেকে বাদ দেওয়া যেতে পারে, এটি এটিকে ছাড়াও কার্যকরী সূচক হিসাবে তৈরি করে।
আমার পরীক্ষাগুলিতে, একটি কার্যকরী ট্রিগার জিআইএন সূচক একটি ট্রিগার জিআইএসটি সূচক (প্রত্যাশারূপে) এর উপরে দৌড় জিতেছে:

CREATE INDEX num_trgm_gin_idx ON num USING gin (right(number, -1) gin_trgm_ops);

উন্নত dbfiddle এখানে

সমস্ত পরীক্ষার ফলাফল হ'ল স্থানীয় পোস্টগ্রিস 9.1 পরীক্ষা ইনস্টলেশন থেকে হ্রাস সেটআপ সহ: 17 কে সংখ্যা এবং 2 কে কোড:

  • মোট রানটাইম: 1719.552 এমএস (ট্রিগ্রাম জিএসটি)
  • মোট রানটাইম: 912.329 এমএস ( ট্রিগার জিএন )

এখনও অনেক দ্রুত

সাথে ব্যর্থ প্রচেষ্টা text_pattern_ops

একবার যদি আমরা বিভ্রান্তিকর প্রথম শব্দের চরিত্রটিকে উপেক্ষা করি, তবে এটি নীচে নেমে আসে বাম অ্যাঙ্কার্ড প্যাটার্ন ম্যাচটি। সুতরাং আমি অপারেটর ক্লাসেরtext_pattern_ops সাথে একটি কার্যকরী বি-ট্রি সূচক চেষ্টা করেছি (কলামের ধরণ ধরে text)।

CREATE INDEX num_text_pattern_idx ON num(right(number, -1) text_pattern_ops);

এটি একটি একক অনুসন্ধান শব্দটির সাথে সরাসরি অনুসন্ধানের জন্য দুর্দান্তভাবে কাজ করে এবং ত্রিগ্রাম সূচকে তুলনায় খারাপ দেখায়:

SELECT * FROM num WHERE right(number, -1) LIKE '2345%'
  • মোট রানটাইম: ৩.৮১ ms এমএস (ট্রিজিএম_জিন_আইডিএক্স)
  • মোট রানটাইম: 0.147 এমএস (টেক্সট_প্যাটার্ন_আইডিএক্স)

তবে ক্যোয়ারী পরিকল্পনাকারী দুটি সারণিতে যোগদানের জন্য এই সূচকটি বিবেচনা করবেন না। আমি এই সীমাবদ্ধতা আগেও দেখেছি। এর জন্য আমার এখনও কোনও অর্থপূর্ণ ব্যাখ্যা নেই।

আংশিক / ক্রিয়ামূলক বি-ট্রি সূচক

আংশিক সূচকগুলির সাথে আংশিক স্ট্রিংগুলিতে সমতা পরীক্ষার বিকল্প এটি। এটি একটি ব্যবহার করা যেতে পারে JOIN

যেহেতু সাধারণত আমাদের কেবল different lengthsউপসর্গের জন্য সীমিত সংখ্যক থাকে তাই আমরা এখানে আংশিক সূচকগুলির সাথে অনুরূপ সমাধান তৈরি করতে পারি ।

বলুন, আমাদের 1 থেকে 5 টি অক্ষর পর্যন্ত উপসর্গ রয়েছে । বেশ কয়েকটি আংশিক ক্রিয়ামূলক সূচক তৈরি করুন, প্রতিটি পৃথক উপসর্গ দৈর্ঘ্যের জন্য একটি:

CREATE INDEX prefix_code_idx5 ON prefix(code) WHERE length(code) = 5;
CREATE INDEX prefix_code_idx4 ON prefix(code) WHERE length(code) = 4;
CREATE INDEX prefix_code_idx3 ON prefix(code) WHERE length(code) = 3;
CREATE INDEX prefix_code_idx2 ON prefix(code) WHERE length(code) = 2;
CREATE INDEX prefix_code_idx1 ON prefix(code) WHERE length(code) = 1;

যেহেতু এগুলি আংশিক সূচক, তাই এগুলির সবগুলিই একক সম্পূর্ণ সূচকের চেয়ে সবেমাত্র বড়।

সংখ্যার জন্য মিলে যাওয়া সূচকগুলি যুক্ত করুন (শীর্ষস্থানীয় শব্দের চরিত্রটি অ্যাকাউন্টে নেওয়া):

CREATE INDEX num_number_idx5 ON num(substring(number, 2, 5)) WHERE length(number) >= 6;
CREATE INDEX num_number_idx4 ON num(substring(number, 2, 4)) WHERE length(number) >= 5;
CREATE INDEX num_number_idx3 ON num(substring(number, 2, 3)) WHERE length(number) >= 4;
CREATE INDEX num_number_idx2 ON num(substring(number, 2, 2)) WHERE length(number) >= 3;
CREATE INDEX num_number_idx1 ON num(substring(number, 2, 1)) WHERE length(number) >= 2;

এই সূচীগুলি কেবল প্রতিটি স্ট্রিং ধারণ করে এবং আংশিক হলেও প্রতিটি সারণির বেশিরভাগ বা সমস্তটি কভার করে। সুতরাং তারা একক মোট সূচকের চেয়ে একসাথে অনেক বড় - দীর্ঘ সংখ্যা বাদে। এবং তারা লেখার ক্রিয়াকলাপের জন্য আরও কাজ চাপিয়ে দেয়। আশ্চর্যজনক গতির জন্য এটিই ব্যয়

যদি সেই খরচটি আপনার জন্য খুব বেশি হয় (লেখার পারফরম্যান্স গুরুত্বপূর্ণ / অনেকগুলি রাইটিং অপারেশন / ডিস্ক স্পেস কোনও সমস্যা) তবে আপনি এই সূচিগুলি এড়িয়ে যেতে পারেন। বাকিগুলি এখনও দ্রুততর, যদি এটি তত দ্রুত না হয় তবে ...

যদি সংখ্যাগুলি কখনই সংক্ষিপ্ত না হয় তবে nঅক্ষরগুলি, WHEREকিছু বা সমস্ত থেকে অপ্রয়োজনীয় ধারাগুলি বাদ দিন এবং WHEREনীচের সমস্ত প্রশ্নের থেকে সংশ্লিষ্ট ধারাটি বাদ দিন drop

পুনরাবৃত্তি সিটিই

এখনও অবধি সমস্ত সেটআপ নিয়ে আমি প্রত্যাবর্তনকারী সিটিই দিয়ে খুব মার্জিত সমাধানের আশা করছিলাম :

WITH RECURSIVE cte AS (
   SELECT n.number, p.code, 4 AS len
   FROM   num n
   LEFT    JOIN prefix p
            ON  substring(number, 2, 5) = p.code
            AND length(n.number) >= 6  -- incl. noise character
            AND length(p.code) = 5

   UNION ALL 
   SELECT c.number, p.code, len - 1
   FROM    cte c
   LEFT   JOIN prefix p
            ON  substring(number, 2, c.len) = p.code
            AND length(c.number) >= c.len+1  -- incl. noise character
            AND length(p.code) = c.len
   WHERE    c.len > 0
   AND    c.code IS NULL
   )
SELECT number, code
FROM   cte
WHERE  code IS NOT NULL;
  • মোট রানটাইম: 1045.115 এমএস

যাইহোক, যদিও এই কোয়েরিটি খারাপ নয় - এটি একটি ট্রিগার জিআইএন সূচক সহ সাধারণ সংস্করণ হিসাবে প্রায় ভাল সঞ্চালন করে - এটি আমার লক্ষ্যটি কী ছিল তা সরবরাহ করে না। পুনরাবৃত্তির শব্দটি একবারেই পরিকল্পনা করা হয়, সুতরাং এটি সেরা সূচকগুলি ব্যবহার করতে পারে না। কেবল পুনরাবৃত্তিযোগ্য শব্দটিই পারে।

ইউনিয়ন সব

যেহেতু আমরা অল্প সংখ্যক পুনরাবৃত্তি নিয়ে কাজ করছি আমরা কেবল তাদের পুনরাবৃত্তভাবে বানান করতে পারি। এটি তাদের প্রত্যেকের জন্য অনুকূলিত পরিকল্পনার অনুমতি দেয়। (যদিও আমরা ইতিমধ্যে সফল সংখ্যার পুনরাবৃত্তি বর্জনটি হারাচ্ছি, সুতরাং উন্নতির জন্য এখনও কিছু জায়গা রয়েছে, বিশেষত উপসর্গের দীর্ঘ দৈর্ঘ্যের জন্য):

SELECT DISTINCT ON (1) number, code
FROM  (
   SELECT n.number, p.code
   FROM   num n
   JOIN   prefix p
            ON  substring(number, 2, 5) = p.code
            AND length(n.number) >= 6  -- incl. noise character
            AND length(p.code) = 5
   UNION ALL 
   SELECT n.number, p.code
   FROM   num n
   JOIN   prefix p
            ON  substring(number, 2, 4) = p.code
            AND length(n.number) >= 5
            AND length(p.code) = 4
   UNION ALL 
   SELECT n.number, p.code
   FROM   num n
   JOIN   prefix p
            ON  substring(number, 2, 3) = p.code
            AND length(n.number) >= 4
            AND length(p.code) = 3
   UNION ALL 
   SELECT n.number, p.code
   FROM   num n
   JOIN   prefix p
            ON  substring(number, 2, 2) = p.code
            AND length(n.number) >= 3
            AND length(p.code) = 2
   UNION ALL 
   SELECT n.number, p.code
   FROM   num n
   JOIN   prefix p
            ON  substring(number, 2, 1) = p.code
            AND length(n.number) >= 2
            AND length(p.code) = 1
   ) x
ORDER BY number, code DESC;
  • মোট রানটাইম: 57.578 এমএস (!!)

একটি যুগান্তকারী, অবশেষে!

এসকিউএল ফাংশন

এটি একটি এসকিউএল ফাংশনে মোড়ানো পুনরাবৃত্ত ব্যবহারের জন্য ক্যোয়ারী প্ল্যানিং ওভারহেডটিকে সরিয়ে দেয়:

CREATE OR REPLACE FUNCTION f_longest_prefix()
  RETURNS TABLE (number text, code text) LANGUAGE sql AS
$func$
SELECT DISTINCT ON (1) number, code
FROM  (
   SELECT n.number, p.code
   FROM   num n
   JOIN   prefix p
            ON  substring(number, 2, 5) = p.code
            AND length(n.number) >= 6  -- incl. noise character
            AND length(p.code) = 5
   UNION ALL 
   SELECT n.number, p.code
   FROM   num n
   JOIN   prefix p
            ON  substring(number, 2, 4) = p.code
            AND length(n.number) >= 5
            AND length(p.code) = 4
   UNION ALL 
   SELECT n.number, p.code
   FROM   num n
   JOIN   prefix p
            ON  substring(number, 2, 3) = p.code
            AND length(n.number) >= 4
            AND length(p.code) = 3
   UNION ALL 
   SELECT n.number, p.code
   FROM   num n
   JOIN   prefix p
            ON  substring(number, 2, 2) = p.code
            AND length(n.number) >= 3
            AND length(p.code) = 2
   UNION ALL 
   SELECT n.number, p.code
   FROM   num n
   JOIN   prefix p
            ON  substring(number, 2, 1) = p.code
            AND length(n.number) >= 2
            AND length(p.code) = 1
   ) x
ORDER BY number, code DESC
$func$;

কল করুন:

SELECT * FROM f_longest_prefix_sql();
  • মোট রানটাইম: 17.138 এমএস (!!!)

ডায়নামিক এসকিউএল সহ পিএল / পিজিএসকিউএল ফাংশন

এই plpgsql ফাংশনটি উপরের পুনরাবৃত্ত সিটিইর মতো, তবে গতিশীল এসকিউএল EXECUTEকোয়েরিটিকে প্রতিটি পুনরাবৃত্তির জন্য পুনরায় পরিকল্পনা করতে বাধ্য করে। এখন এটি সমস্ত তৈরি সূচকগুলি ব্যবহার করে।

অতিরিক্তভাবে এটি কোনও উপসর্গ দৈর্ঘ্যের জন্য কাজ করে । ফাংশনটি ব্যাপ্তির জন্য দুটি পরামিতি নেয়, তবে আমি এটি DEFAULTমান সহ প্রস্তুত করেছি , সুতরাং এটি সুস্পষ্ট পরামিতি ছাড়াইও কাজ করে:

CREATE OR REPLACE FUNCTION f_longest_prefix2(_min int = 1, _max int = 5)
  RETURNS TABLE (number text, code text) LANGUAGE plpgsql AS
$func$
BEGIN
FOR i IN REVERSE _max .. _min LOOP  -- longer matches first
   RETURN QUERY EXECUTE '
   SELECT n.number, p.code
   FROM   num n
   JOIN   prefix p
            ON  substring(n.number, 2, $1) = p.code
            AND length(n.number) >= $1+1  -- incl. noise character
            AND length(p.code) = $1'
   USING i;
END LOOP;
END
$func$;

চূড়ান্ত পদক্ষেপটি সহজেই একটি ফাংশনে আবৃত করা যায় না। হয় কেবল এটিকে কল করুন:

SELECT DISTINCT ON (1)
       number, code
FROM   f_longest_prefix_prefix2() x
ORDER  BY number, code DESC;
  • মোট রানটাইম: 27.413 এমএস

অথবা মোড়ক হিসাবে অন্য একটি এসকিউএল ফাংশন ব্যবহার করুন:

CREATE OR REPLACE FUNCTION f_longest_prefix3(_min int = 1, _max int = 5)
  RETURNS TABLE (number text, code text) LANGUAGE sql AS
$func$
SELECT DISTINCT ON (1)
       number, code
FROM   f_longest_prefix_prefix2($1, $2) x
ORDER  BY number, code DESC
$func$;

কল করুন:

SELECT * FROM f_longest_prefix3();
  • মোট রানটাইম: 37.622 এমএস

ওভারহেডের প্রয়োজনীয় পরিকল্পনার কারণে কিছুটা ধীর। তবে এসকিউএল এর চেয়ে বেশি বহুমুখী এবং দীর্ঘ উপসর্গের জন্য সংক্ষিপ্ত।


আমি এখনও চেক করছি, তবে দুর্দান্ত দেখাচ্ছে! আপনার ধারণাটি "বিপরীত" অপারেটরের মতো - উজ্জ্বল। আমি কেন এত বোকা; (
করজাবিন ইভান

5
whoah! এটি বেশ সম্পাদনা। আমি আশা করি আমি আবার upvote করতে পারে।

3
আমি আপনার আশ্চর্যজনক উত্তরটি গত দুই বছরের চেয়ে বেশি শিখেছি। আমার লুপ সমাধানে কয়েক ঘন্টা বিপরীতে 17-30 এমএস? একটি যাদু আছে।
কোরজভিন ইভান

1
@ কোরজাভিনআইওয়ান: ঠিক আছে, নথি অনুসারে, আমি 2k উপসর্গ / 17 কে সংখ্যার হ্রাস সেটআপ দিয়ে পরীক্ষা করেছি। তবে এটি বেশ ভাল স্কেল করা উচিত এবং আমার পরীক্ষা মেশিনটি একটি ছোট সার্ভার ছিল। সুতরাং আপনার বাস্তব জীবনের ক্ষেত্রে আপনার এক সেকেন্ডের অধীনে ভাল থাকা উচিত।
এরউইন ব্র্যান্ডস্টেটার

1
উত্তম উত্তর ... আপনি কি দিমিত্রিের উপসর্গের এক্সটেনশন জানেন ? আপনি কি নিজের পরীক্ষার ক্ষেত্রে তুলনা করতে পারেন?
ম্যাথিউস ওল

0

স্ট্রিং এস একটি স্ট্রিং টির একটি প্রিফিক্স, যদি টিফ এস এবং এসজেডের মধ্যে থাকে যেখানে জেড অন্য যে কোনও স্ট্রিংয়ের তুলনায় ডিক্সিকোগ্রাফিকভাবে বড় হয় (উদাহরণস্বরূপ 99999999 ডেটাসেটের সবচেয়ে দীর্ঘতম ফোন নম্বরটি ছাড়িয়ে যাওয়ার জন্য বা কখনও কখনও 0xFF কাজ করবে)।

যে কোনও টির জন্য দীর্ঘতম সাধারণ উপসর্গটিও অভিধানিকভাবে সর্বাধিক, সুতরাং একটি সাধারণ গোষ্ঠী এটি সন্ধান করবে।

select n.number, max(p.code) 
from prefixes p
join numbers n 
on substring(n.number, 2, 255) between p.code and p.code || '99999999'
group by n.number

যদি এটি ধীর হয় তবে সম্ভবত এটি গণনাযুক্ত অভিব্যক্তির কারণে হয়ে থাকে, তাই আপনি p.code || '999999' এর নিজস্ব সূচি ইত্যাদির সাথে কোড টেবিলের কলামেও বাস্তবায়িত করতে পারেন etc.

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.