خوارزمية فعالة لتحديد العناصر بشكل عشوائي مع التردد
سؤال
نظرا لمجموعة من n
أزواج تردد الكلمات:
[ (w0, f0), (w1, f1), ..., (wن-1, fن-1) ]
أين wأنا
هي كلمة، fأنا
هو تردد عدد صحيح، ومجموع الترددات ∑fأنا = m
,
أريد استخدام منشئ أرقام عشوائية زائفة (pRNG) للاختيار p
كلمات wي0, wي1, ..., wيص-1
مثل أن احتمال اختيار أي كلمة يتناسب مع تواترها:
P(wأنا = wيك) = P(i = jك) = fأنا / m
(ملاحظة، هذا اختيار مع استبدال، لذا فإن نفس الكلمة استطاع يتم اختياره في كل مرة).
لقد توصلت إلى ثلاث خوارزميات حتى الآن:
إنشاء مجموعة من الحجم
m
, ، واملأها بذلك الأولf0
الإدخالات هيw0
, ، التاليf1
الإدخالات هيw1
, ، وهكذا، وهكذا الأخيرfص-1
الإدخالات هيwص-1
.[ w0, ..., w0, w1,..., w1, ..., wص-1, ..., wص-1 ]
ثم استخدم pRNG للاختيارp
المؤشرات في النطاق0...m-1
, ، والإبلاغ عن الكلمات المخزنة في تلك الفهارس.
هذا يأخذO(n + m + p)
العمل، وهو ليس رائعًا، منذ ذلك الحينm
يمكن أن يكون أكبر بكثير من n.قم بالمرور عبر مصفوفة الإدخال مرة واحدة، وقم بالحوسبة
mأنا = ∑h<ifح = mأنا-1 + fأنا
وبعد الحوسبةmأنا
, ، استخدم pRNG لإنشاء رقمxك
في النطاق0...mأنا-1
لكلk
في0...p-1
وحددwأنا
لwيك
(ربما تحل محل القيمة الحالية لـwيك
) لوxك < fأنا
.
هذا يتطلبO(n + np)
عمل.- إحصاء - عد
mأنا
كما هو الحال في الخوارزمية 2، وقم بإنشاء المصفوفة التالية على عدد ثلاثي من الكلمات ذات التردد الجزئي:[ (w0, f0, m0), (w1, f1, m1), ..., (wن-1, fن-1, mن-1) ]
وبعد ذلك، لكل k في0...p-1
, ، استخدم pRNG لإنشاء رقمxك
في النطاق0...m-1
ثم قم بإجراء بحث ثنائي على مجموعة الثلاثيات للعثور علىi
شارع.mأنا-fأنا ≤ xك < mأنا
, ، وحددwأنا
لwيك
.
هذا يتطلبO(n + p log n)
عمل.
سؤالي هو:هل هناك خوارزمية أكثر كفاءة يمكنني استخدامها لهذا، أم أنها جيدة كما هي؟
المحلول 3
حسنًا، لقد وجدت خوارزمية أخرى: طريقة الاسم المستعار (كما ذكر في هذه الإجابة).يقوم في الأساس بإنشاء قسم من مساحة الاحتمال مثل:
- هناك
n
أقسام، كلها بنفس العرضr
شارع.nr = m
. - يحتوي كل قسم على كلمتين بنسبة ما (يتم تخزينهما مع القسم).
- لكل كلمة
wأنا
,fأنا = ∑أقسام t s.t wأنا ∈ ر r × ratio(t,wأنا)
نظرًا لأن جميع الأقسام لها نفس الحجم، فإن تحديد القسم الذي يمكن إجراؤه من خلال العمل المستمر (اختر فهرسًا من 0...n-1
عشوائيًا)، ويمكن بعد ذلك استخدام نسبة القسم لتحديد الكلمة المستخدمة في العمل المستمر (مقارنة رقم pRNGed مع النسبة بين الكلمتين).إذن هذا يعني p
يمكن إجراء التحديدات في O(p)
العمل، نظرا لمثل هذا التقسيم.
سبب وجود مثل هذا التقسيم هو وجود كلمة wأنا
شارع. fأنا < r
, ، إذا وفقط إذا كانت هناك كلمة wأنا'
شارع. fأنا' > r
, حيث أن r هو متوسط الترددات.
نظرا لمثل هذا الزوج wأنا
و wأنا'
يمكننا استبدالها بكلمة زائفة w'أنا
من تكرار f'أنا = r
(الذي يمثل wأنا
مع الاحتمال fأنا/r
و wأنا'
مع الاحتمال 1 - fأنا/r
) وكلمة جديدة w'أنا'
من التردد المعدل f'أنا' = fأنا' - (r - fأنا)
على التوالى.سيظل متوسط تكرار جميع الكلمات هو r، وستظل القاعدة من الفقرة السابقة سارية.نظرًا لأن الكلمة الزائفة لها تردد r ومكونة من كلمتين بتردد ≠ r، فإننا نعلم أنه إذا كررنا هذه العملية، فلن نصنع أبدًا كلمة زائفة من كلمة زائفة، ويجب أن ينتهي هذا التكرار بـ تسلسل n من الكلمات الزائفة التي تمثل القسم المطلوب.
لبناء هذا القسم في O(n)
وقت،
- قم بالاطلاع على قائمة الكلمات مرة واحدة، وقم بإنشاء قائمتين:
- إحدى الكلمات ذات التردد ≥ r
- إحدى الكلمات ذات التردد> r
- ثم اسحب كلمة من القائمة الأولى
- إذا كان تردده = r، فاجعله قسمًا مكونًا من عنصر واحد
- وإلا، فاسحب كلمة من القائمة الأخرى، واستخدمها لملء قسم مكون من كلمتين.ثم ضع الكلمة الثانية مرة أخرى في القائمة الأولى أو الثانية حسب تكرارها المعدل.
هذا في الواقع لا يزال يعمل إذا كان عدد الأقسام q > n
(عليك فقط إثبات ذلك بشكل مختلف).إذا كنت تريد التأكد من أن r جزء لا يتجزأ، ولا يمكنك العثور على عامل بسهولة q
ل m
شارع. q > n
, ، يمكنك وسادة جميع الترددات بعامل n
, ، لذا f'أنا = nfأنا
, ، الذي يقوم بالتحديث m' = mn
ومجموعات r' = m
متى q = n
.
على أية حال، هذه الخوارزمية تأخذ فقط O(n + p)
العمل، الذي يجب أن أعتقد أنه الأمثل.
في روبي:
def weighted_sample_with_replacement(input, p)
n = input.size
m = input.inject(0) { |sum,(word,freq)| sum + freq }
# find the words with frequency lesser and greater than average
lessers, greaters = input.map do |word,freq|
# pad the frequency so we can keep it integral
# when subdivided
[ word, freq*n ]
end.partition do |word,adj_freq|
adj_freq <= m
end
partitions = Array.new(n) do
word, adj_freq = lessers.shift
other_word = if adj_freq < m
# use part of another word's frequency to pad
# out the partition
other_word, other_adj_freq = greaters.shift
other_adj_freq -= (m - adj_freq)
(other_adj_freq <= m ? lessers : greaters) << [ other_word, other_adj_freq ]
other_word
end
[ word, other_word , adj_freq ]
end
(0...p).map do
# pick a partition at random
word, other_word, adj_freq = partitions[ rand(n) ]
# select the first word in the partition with appropriate
# probability
if rand(m) < adj_freq
word
else
other_word
end
end
end
نصائح أخرى
يبدو هذا مثل اختيار عجلة الروليت، والذي يستخدم بشكل أساسي في عملية الاختيار في الخوارزميات الجينية/التطورية.
يمكنك إنشاء المصفوفة المستهدفة، ثم تكرار الكلمات لتحديد احتمالية اختيارها، واستبدال الكلمات الموجودة في المصفوفة وفقًا لرقم عشوائي.
بالنسبة للكلمة الأولى فإن الاحتمال هو f0/م0 (حيث من= و0+..+ون)، أي.100%، لذلك سيتم ملء جميع المواضع في المصفوفة المستهدفة بـ w0.
بالنسبة للكلمات التالية، ينخفض الاحتمال، وعندما تصل إلى الكلمة الأخيرة، تمتلئ المصفوفة المستهدفة بكلمات منتقاة عشوائيًا وفقًا للتكرار.
رمز المثال في C#:
public class WordFrequency {
public string Word { get; private set; }
public int Frequency { get; private set; }
public WordFrequency(string word, int frequency) {
Word = word;
Frequency = frequency;
}
}
WordFrequency[] words = new WordFrequency[] {
new WordFrequency("Hero", 80),
new WordFrequency("Monkey", 4),
new WordFrequency("Shoe", 13),
new WordFrequency("Highway", 3),
};
int p = 7;
string[] result = new string[p];
int sum = 0;
Random rnd = new Random();
foreach (WordFrequency wf in words) {
sum += wf.Frequency;
for (int i = 0; i < p; i++) {
if (rnd.Next(sum) < wf.Frequency) {
result[i] = wf.Word;
}
}
}