অলসভাবে অনুমোদনের ব্যবস্থা করা


88

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

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

এরকম অ্যালগরিদম কি আছে? আমি দেখেছি বেশিরভাগ ক্রিয়মোটেশন-জেনারেটর অ্যালগরিদমগুলি সেগুলি একবারে তৈরি করা হয় (সাধারণত পুনরাবৃত্তভাবে), যা খুব বড় সেটে স্কেল করে না। ক্লোজারে (বা অন্য কোনও কার্যকরী ভাষা) একটি প্রয়োগকরণ সহায়ক হবে তবে আমি সিউডোকোড থেকে এটি বের করতে পারি।

উত্তর:


140

হ্যাঁ, হয় একটি "পরবর্তী বিন্যাস" অ্যালগরিদম, এবং এটি খুব বেশ সহজ। সি ++ স্ট্যান্ডার্ড টেম্পলেট লাইব্রেরি (এসটিএল) এমনকি একটি ফাংশনও বলে next_permutation

অ্যালগরিদম আসলে পরবর্তী ক্রমানুসন্ধান খুঁজে পায় - অভিধানের পরেরটি xic ধারণাটি হ'ল: ধরুন আপনাকে একটি অনুক্রম দেওয়া হয়েছে, "32541" বলুন। পরের আদেশটি কী?

আপনি যদি এটি সম্পর্কে ভাবেন, তবে আপনি দেখতে পাবেন যে এটি "34125"। এবং আপনার চিন্তাভাবনাগুলি সম্ভবত এটি ছিল: "32541" তে,

  • "321" স্থির রাখার কোনও উপায় নেই এবং "541" অংশে পরের আদেশটি সন্ধান করতে হবে, কারণ যে ক্রমটিশনটি ইতিমধ্যে 5,4 এর জন্য শেষ এক, এবং 1 - এটি ক্রমহ্রাসমান ক্রমে সাজানো হয়।
  • সুতরাং আপনাকে "2" কে আরও বড় কিছুতে পরিবর্তন করতে হবে - প্রকৃতপক্ষে, "541" অংশের চেয়ে বড় সংখ্যায় সবচেয়ে বড় সংখ্যায়, 4 হিসাবে to
  • এখন, একবার আপনি সিদ্ধান্ত নিয়েছেন যে অনুমতিটি "34" হিসাবে শুরু হবে, বাকি সংখ্যাগুলি ক্রমবর্ধমান ক্রমে হওয়া উচিত, সুতরাং উত্তরটি "34125"।

অ্যালগরিদম হল যুক্তিটির সঠিকভাবে লাইনটি কার্যকর করা:

  1. ক্রমবর্ধমান ক্রম অনুসারে দীর্ঘতম "লেজ" সন্ধান করুন। ("541" অংশ।)
  2. লেজের ("2") এর সামান্যতম সংখ্যায় লেজ (4) এর চেয়ে বড় সংখ্যায় পরিবর্তন করুন।
  3. ক্রমবর্ধমান ক্রমে লেজটি সাজান।

আগের উপাদানটি বর্তমান উপাদানটির চেয়ে ছোট না হওয়া পর্যন্ত আপনি শেষে শুরু করে এবং পিছনের দিকে যেতে দক্ষতার সাথে করতে পারেন। আপনি "২" দিয়ে "4" অদলবদল করে (2.) করতে পারেন, সুতরাং আপনার কাছে "34521" থাকবে Once ক্রমহ্রাসমান ক্রমে সাজানো ছিল এবং এখনও রয়েছে (এটি সম্পর্কে ভাবেন), সুতরাং এটি কেবল বিপরীত হওয়া দরকার।

সি ++ কোডটি সুনির্দিষ্টভাবে এটি করে ( /usr/include/c++/4.0.0/bits/stl_algo.hআপনার সিস্টেমে উত্সটি দেখুন বা এই নিবন্ধটি দেখুন ); এটি আপনার ভাষায় অনুবাদ করা সহজ হওয়া উচিত: [আপনি যদি সি ++ রিটারেটরগুলির সাথে পরিচিত না হন তবে "পয়েন্টার" হিসাবে "বিডেরিশিয়ালআইটার" পড়ুন। falseপরবর্তী অনুমানের ব্যবস্থা না থাকলে কোডটি ফেরত দেয় , যেমন আমরা ইতিমধ্যে হ্রাস ক্রমে রয়েছি]]

template <class BidirectionalIterator>
bool next_permutation(BidirectionalIterator first,
                      BidirectionalIterator last) {
    if (first == last) return false;
    BidirectionalIterator i = first;
    ++i;
    if (i == last) return false;
    i = last;
    --i;
    for(;;) {
        BidirectionalIterator ii = i--;
        if (*i <*ii) {
            BidirectionalIterator j = last;
            while (!(*i <*--j));
            iter_swap(i, j);
            reverse(ii, last);
            return true;
        }
        if (i == first) {
            reverse(first, last);
            return false;
        }
    }
}

দেখে মনে হতে পারে যে অনুমতি অনুসারে এটি O (n) সময় নিতে পারে, তবে আপনি যদি আরও যত্ন সহকারে এটি চিন্তা করেন তবে আপনি প্রমাণ করতে পারেন যে সমস্ত আদেশের জন্য ও (এন!) সময় লাগে, সুতরাং কেবল ও (1) - ধ্রুবক সময় - অনুগতি অনুসারে।

ভাল জিনিস হ'ল অ্যালগরিদম এমনকি যখন আপনার পুনরাবৃত্ত উপাদানগুলির সাথে ক্রম থাকে: "232254421" এর সাথে এটি লেজটিকে "54421" হিসাবে আবিষ্কার করবে, "2" এবং "4" স্যুপ করবে (সুতরাং "232454221" ), "232412245" প্রদান করে বাকীটি উল্টো করুন, যা পরবর্তী অনুমানের অনুমতি।


4
উপাদানগুলির উপর আপনার মোট ক্রম রয়েছে তা ধরে নিয়েই এটি কাজ করবে।
ক্রিস কনওয়ে

10
আপনি যদি একটি সেট দিয়ে শুরু করেন, আপনি ইচ্ছামত উপাদানগুলির উপর মোট ক্রম সংজ্ঞায়িত করতে পারেন; উপাদানগুলিকে পৃথক সংখ্যায় ম্যাপ করুন। :-)
শ্রীবত্সারআর

4
এই উত্তরটি কেবল পর্যাপ্ত পরিমাণে পায় না, তবে আমি এটি একবারই উপস্থাপন করতে পারি ... :-)
ড্যানিয়েল সি সোব্রাল

4
@ ম্যাস: ঠিক নয় ... মোটামুটি, আপনি 1 থেকে বড় সংখ্যায় যেতে পারেন। উদাহরণটি ব্যবহার করে: 32541 দিয়ে শুরু করুন The লেজটি 541 the 52 এর দৈর্ঘ্যের 2। তারপর এটি 34215 (লেজ দৈর্ঘ্য 1), 34251 (লেজ দৈর্ঘ্য 2), 34512 (দৈর্ঘ্য 1), 34521 (দৈর্ঘ্য 3), 35124 (দৈর্ঘ্য 1), ইত্যাদি হয়ে যায় আপনি লেজটি ঠিক বলেছেন বেশিরভাগ সময় ছোট, একারণে অ্যালগরিদমের একাধিক কলের জন্য ভাল পারফরম্যান্স রয়েছে।
শ্রীভাতসারআর

4
@ স্যামস্টোয়েলিংগা: আপনি ঠিক বলেছেন, আসলেই। ও (এন লগ এন) হ'ল ও (লগ এন!)। আমার ও (এন!) বলা উচিত ছিল।
শ্রীভাতসার

43

ধরে নেওয়া যাক যে আমরা মান দেওয়া হচ্ছে সেগুলি নিয়ে অভিধানিক ক্রমের কথা বলছি, দুটি সাধারণ পন্থা রয়েছে যা আপনি ব্যবহার করতে পারেন:

  1. উপাদানগুলির একটি ক্রমকে পরের অনুমানে রূপান্তর করুন (যেমন শ্রীভাতসার পোস্ট করেছেন), বা
  2. 0 থেকে উপরের nদিকে গণনা করার সময় সরাসরি th তর অনুক্রমের গণনা করুন n

তাদের জন্য (আমার মত ;-) যারা সি ++ নেটিভ হিসাবে কথা বলেন না, তাদের নীচের সিউডো কোড থেকে 1 টি প্রয়োগ করা যেতে পারে, বামে সূচক শূন্যের সাথে একটি অ্যারের শূন্য-ভিত্তিক সূচিকরণ ধরে নেওয়া (কিছু অন্যান্য কাঠামো স্থির করে) যেমন একটি তালিকা হিসাবে, "অনুশীলন হিসাবে বাকি আছে" ;-):

1. scan the array from right-to-left (indices descending from N-1 to 0)
1.1. if the current element is less than its right-hand neighbor,
     call the current element the pivot,
     and stop scanning
1.2. if the left end is reached without finding a pivot,
     reverse the array and return
     (the permutation was the lexicographically last, so its time to start over)
2. scan the array from right-to-left again,
   to find the rightmost element larger than the pivot
   (call that one the successor)
3. swap the pivot and the successor
4. reverse the portion of the array to the right of where the pivot was found
5. return

এখানে সিএডিবির বর্তমান অনুমানের সাথে শুরু করে একটি উদাহরণ রয়েছে:

1. scanning from the right finds A as the pivot in position 1
2. scanning again finds B as the successor in position 3
3. swapping pivot and successor gives CBDA
4. reversing everything following position 1 (i.e. positions 2..3) gives CBAD
5. CBAD is the next permutation after CADB

দ্বিতীয় পদ্ধতির ( nতম অনুগতির প্রত্যক্ষ গণনা ) জন্য মনে রাখবেন যে উপাদানগুলির N!ক্রমশক্তি রয়েছে N। অতএব, আপনি যদি Nউপাদানগুলিকে অনুমতি দিচ্ছেন, তবে প্রথম (N-1)!আদেশটি অবশ্যই ক্ষুদ্রতম উপাদানের সাথে শুরু হওয়া উচিত, পরবর্তী (N-1)!আদেশগুলি দ্বিতীয় ক্ষুদ্রতম দিয়ে শুরু হওয়া উচিত এবং এই জাতীয়। এটি নিম্নলিখিত পুনরাবৃত্তিমূলক পদ্ধতির দিকে পরিচালিত করে (আবার সিউডো কোডে, 0 থেকে ক্রমান্বয়ে এবং অবস্থানগুলি সংখ্যাযুক্ত):

To find permutation x of array A, where A has N elements:
0. if A has one element, return it
1. set p to ( x / (N-1)! ) mod N
2. the desired permutation will be A[p] followed by
   permutation ( x mod (N-1)! )
   of the elements remaining in A after position p is removed

সুতরাং, উদাহরণস্বরূপ, এবিসিডি এর 13 তম অনুমান নিম্নলিখিত হিসাবে পাওয়া যায়:

perm 13 of ABCD: {p = (13 / 3!) mod 4 = (13 / 6) mod 4 = 2; ABCD[2] = C}
C followed by perm 1 of ABD {because 13 mod 3! = 13 mod 6 = 1}
  perm 1 of ABD: {p = (1 / 2!) mod 3 = (1 / 2) mod 2 = 0; ABD[0] = A}
  A followed by perm 1 of BD {because 1 mod 2! = 1 mod 2 = 1}
    perm 1 of BD: {p = (1 / 1!) mod 2 = (1 / 1) mod 2 = 1; BD[1] = D}
    D followed by perm 0 of B {because 1 mod 1! = 1 mod 1 = 0}
      B (because there's only one element)
    DB
  ADB
CADB

ঘটনাচক্রে, উপাদানগুলির "অপসারণ" বুলিয়ানগুলির সমান্তরাল অ্যারে দ্বারা প্রতিনিধিত্ব করা যেতে পারে যা নির্দেশ করে যে কোন উপাদানগুলি এখনও উপলব্ধ, তাই প্রতিটি পুনরাবৃত্তির কলটিতে একটি নতুন অ্যারে তৈরি করা প্রয়োজন হয় না।

সুতরাং, এবিসিডি এর অনুক্রমের পুনরাবৃত্তি করতে, কেবল 0 থেকে 23 (4! -1) পর্যন্ত গণনা করুন এবং সরাসরি সম্পর্কিত ক্রমান্বন গণনা করুন।


4
++ আপনার উত্তর অপরিবর্তিত রয়েছে। গৃহীত উত্তর থেকে সরিয়ে না নেওয়ার জন্য, তবে দ্বিতীয় পদ্ধতিটি আরও শক্তিশালী কারণ এটি সংমিশ্রণগুলিতেও সাধারণীকরণ করা যায়। একটি সম্পূর্ণ আলোচনা অনুক্রম থেকে সূচকে বিপরীত ফাংশন দেখায়।
শে

4
প্রকৃতপক্ষে. আমি পূর্ববর্তী মন্তব্যে একমত - যদিও আমার উত্তর জিজ্ঞাসা করা নির্দিষ্ট প্রশ্নের জন্য কিছুটা কম অপারেশন করে, এই পদ্ধতিটি আরও সাধারণ, যেহেতু এটি উদাহরণস্বরূপ কোনও প্রদত্ত উত্তর থেকে কে পদক্ষেপের দূরে অবস্থিত ক্রমানুসন্ধান সন্ধান করে।
শ্রীভাতসার

4

আপনার উইকিপিডায় পারমুটেশন নিবন্ধটি পরীক্ষা করা উচিত । এছাড়াও, ফ্যাকটোরেডিক সংখ্যাগুলির ধারণা রয়েছে ।

যাইহোক, গাণিতিক সমস্যা বেশ শক্ত।

ইন C#আপনি একটি ব্যবহার করতে পারেন iterator, এবং ব্যবহার বিন্যাস অ্যালগরিদম থামাতে yield। এটির সাথে সমস্যা হ'ল আপনি পিছনে পিছনে যেতে পারবেন না, বা একটি ব্যবহার করতে পারবেন না index


4
"যাইহোক, গাণিতিক সমস্যা বেশ শক্ত।" না এটি নয় :-)
শ্রীভাত্সার

ঠিক আছে, এটি .. যদি আপনি ফ্যাক্টোরেডিক সংখ্যাগুলি না জানেন তবে কোনও গ্রহণযোগ্য সময়ে আপনি সঠিক অ্যালগরিদম নিয়ে আসতে পারবেন না। এটি পদ্ধতি না জেনে চতুর্থ ডিগ্রী সমীকরণ সমাধান করার চেষ্টা করার মতো।
বোগদান ম্যাক্সিম

4
ওহ দুঃখিত, আমি ভেবেছিলাম আপনি মূল সমস্যাটি নিয়ে কথা বলছেন। আমি এখনও দেখতে পাচ্ছি না কেন আপনার "ফ্যাকটোরেডিক নম্বরগুলি" দরকার কেন ... এন এর প্রত্যেককে একটি সংখ্যা নির্ধারণ করা খুব সহজ! একটি নির্দিষ্ট সেট ক্রিয়াকলাপ, এবং একটি সংখ্যা থেকে একটি ক্রম নির্মান। [
সবেমাত্র

4
কথ্য সি #, একটি পুনরুক্তিকারীর সঠিকভাবে একটি হিসাবে উল্লেখ করা হয় গণনাকারী
ড্রয় নোকস

@ শ্রীভাতসার: আপনি কীভাবে সমস্ত অনুমতি তৈরির সংক্ষিপ্তসারটি করবেন? উদাহরণস্বরূপ, যদি আপনার n! ম পেরুমান উত্পন্ন করার প্রয়োজন হয়।
জ্যাকব

3

এগুলি উত্পন্ন করার জন্য ক্রমচারণের অ্যালগরিদমের আরও উদাহরণ।

সূত্র: http://www.ddj.com/architect/201200326

  1. ফাইকের অ্যালগরিদম ব্যবহার করুন, এটি দ্রুততম পরিচিত।
  2. অ্যালগো টু লেক্সোগ্রাফিক ক্রম ব্যবহার করে।
  3. ননলেক্সোগ্রাফিক ব্যবহার করে তবে আইটেম 2 এর চেয়ে দ্রুত চলে।

ঘ।


PROGRAM TestFikePerm;
CONST marksize = 5;
VAR
    marks : ARRAY [1..marksize] OF INTEGER;
    ii : INTEGER;
    permcount : INTEGER;

PROCEDURE WriteArray;
VAR i : INTEGER;
BEGIN
FOR i := 1 TO marksize
DO Write ;
WriteLn;
permcount := permcount + 1;
END;

PROCEDURE FikePerm ;
{Outputs permutations in nonlexicographic order.  This is Fike.s algorithm}
{ with tuning by J.S. Rohl.  The array marks[1..marksizn] is global.  The   }
{ procedure WriteArray is global and displays the results.  This must be}
{ evoked with FikePerm(2) in the calling procedure.}
VAR
    dn, dk, temp : INTEGER;
BEGIN
IF 
THEN BEGIN { swap the pair }
    WriteArray;
    temp :=marks[marksize];
    FOR dn :=  DOWNTO 1
    DO BEGIN
        marks[marksize] := marks[dn];
        marks [dn] := temp;
        WriteArray;
        marks[dn] := marks[marksize]
        END;
    marks[marksize] := temp;
    END {of bottom level sequence }
ELSE BEGIN
    FikePerm;
    temp := marks[k];
    FOR dk :=  DOWNTO 1
    DO BEGIN
        marks[k] := marks[dk];
        marks[dk][ := temp;
        FikePerm;
        marks[dk] := marks[k];
        END; { of loop on dk }
    marks[k] := temp;l
    END { of sequence for other levels }
END; { of FikePerm procedure }

BEGIN { Main }
FOR ii := 1 TO marksize
DO marks[ii] := ii;
permcount := 0;
WriteLn ;
WrieLn;
FikePerm ; { It always starts with 2 }
WriteLn ;
ReadLn;
END.

ঘ।


PROGRAM TestLexPerms;
CONST marksize = 5;
VAR
    marks : ARRAY [1..marksize] OF INTEGER;
    ii : INTEGER;
    permcount : INTEGER;

PROCEDURE WriteArray; VAR i : INTEGER; BEGIN FOR i := 1 TO marksize DO Write ; permcount := permcount + 1; WriteLn; END;

PROCEDURE LexPerm ; { Outputs permutations in lexicographic order. The array marks is global } { and has n or fewer marks. The procedure WriteArray () is global and } { displays the results. } VAR work : INTEGER: mp, hlen, i : INTEGER; BEGIN IF THEN BEGIN { Swap the pair } work := marks[1]; marks[1] := marks[2]; marks[2] := work; WriteArray ; END ELSE BEGIN FOR mp := DOWNTO 1 DO BEGIN LexPerm<>; hlen := DIV 2; FOR i := 1 TO hlen DO BEGIN { Another swap } work := marks[i]; marks[i] := marks[n - i]; marks[n - i] := work END; work := marks[n]; { More swapping } marks[n[ := marks[mp]; marks[mp] := work; WriteArray; END; LexPerm<> END; END;

BEGIN { Main } FOR ii := 1 TO marksize DO marks[ii] := ii; permcount := 1; { The starting position is permutation } WriteLn < Starting position: >; WriteLn LexPerm ; WriteLn < PermCount is , permcount>; ReadLn; END.

ঘ।


PROGRAM TestAllPerms;
CONST marksize = 5;
VAR
    marks : ARRAY [1..marksize] of INTEGER;
    ii : INTEGER;
    permcount : INTEGER;

PROCEDURE WriteArray; VAR i : INTEGER; BEGIN FOR i := 1 TO marksize DO Write ; WriteLn; permcount := permcount + 1; END;

PROCEDURE AllPerm (n : INTEGER); { Outputs permutations in nonlexicographic order. The array marks is } { global and has n or few marks. The procedure WriteArray is global and } { displays the results. } VAR work : INTEGER; mp, swaptemp : INTEGER; BEGIN IF THEN BEGIN { Swap the pair } work := marks[1]; marks[1] := marks[2]; marks[2] := work; WriteArray; END ELSE BEGIN FOR mp := DOWNTO 1 DO BEGIN ALLPerm<< n - 1>>; IF > THEN swaptemp := 1 ELSE swaptemp := mp; work := marks[n]; marks[n] := marks[swaptemp}; marks[swaptemp} := work; WriteArray; AllPerm< n-1 >; END; END;

BEGIN { Main } FOR ii := 1 TO marksize DO marks[ii] := ii permcount :=1; WriteLn < Starting position; >; WriteLn; Allperm < marksize>; WriteLn < Perm count is , permcount>; ReadLn; END.


2

Clojure.contrib.lazy_seqs এ অনুমতিপ্রাপ্ত ফাংশন ইতিমধ্যে কেবল এটি করার দাবি করে।


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

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