في هذه السلسلة نطرح في كل مرة خوارزمية ونطبقها في لغات مختلفة مستفيدين من مزايا كل لغة. كوسيلة لعرض مزايا كل لغة بطريقة غير لغتي أفضل من لغتك.
المطلوب الآن هو تطبيق خوارزمية غربال Atkin لإيجاد الأعداد الأولية التي تقل عن العدد ن (10 آلاف مثلا)
http://en.wikipedia.org/wik...الخورازمية تعمل هكذا
نرسم الأعداد التي من 2 وحتى ن-1
نأخذ أول عدد غير مشطوب (في أول دورة هو 2)
نعتبره أولي ونشطب كل مضاعفاته
نكرر
التعليقات
سأبدأ أنا بلغة بايثون
def get_primes(n):
compo=set()
for i in xrange(2, n):
if i in compo: continue
yield i
compo.update(xrange(i*2, n,i))
جوانب التفوق:
لا تستخدم return بل yield مما يوفر الذاكرة
يستخدم xrange (أو range في بايثون 3) وهي تعيد كائن يسير على الأعداد ولا يحجزها في الذاكرة (بمعنى لا نعمل منظومة array من 10 آلاف عنصر)
تستخدم تركيد البيانات المسمى set وهو تركيب يفحص الوجود من عدمه بسرعة (ربما O1 أو Ologn) وهو أسرع من in_array في php والتي تسريع على كل العناصر أي On
جوانب القصور
- قد يكون تحديث منظومة الأعداد المركبة عملية مكلفة خصوصا أن بايثون لا تسمح لنا أن نقدم لها نصيحة مسبقة بالحجم الأقصى كما في جافا
في لغة php الجواب هو
<?php
function get_primes($n) {
$primes=array();
$compo=array();
for($i=2;$i<$n;++$i) {
if (!isset($compo[$i])) $compo[$i]=false;
if ($compo[$i]) continue;
$primes[]=$i;
for($j=$i*2;$j<$n;$j+=$i) $compo[$j]=true;
}
return $primes;
}
جوانب التفوق:
- استعمال منظومة المفاتيح والقيم associative أسرع من استعمال in_array وهو يصلح كبديل عن set بايثون لأن الوصول عبر المفتاح أسرع في حين أن in_array تفحص كل عناصر المنظومة
جوانب القصور:
منظومة المفاتيح والقيم أبطأ من المجموعة set
استهلاك عالي من الذاكرة حيث أن أن كامل الأقرام تكون محجوزة في المنظومتين primes و compo
مقارنة بايثون حسب 10 آلاف 500 مرة في أقل من ثانية ونصف أما كود php أعلاه فاستغرق 5 ثوان وربع. لكن عند استعمال نسخة تستعمل in_array كان تستغرق أكثر من 10 دقائق يعني أن استعمال طريقة منظومة المفاتيح بأثر من 130 مرة من in_array.
هذا هو الكود غير المجدي الذي يستعمل in_array
هل جربت ذلك على سرفير ام محلي ؟؟
انتظرت اكثر من 5 ثواني ولم يحدث شئ :) ..
واستهلاك الذاكرة جدا عالي :) ..
نعم الكود غير المجدي استغرق أكثر 10 دقائق. الكود المجدي هو الذي قبله وقد استغرق 5 ثواني
أنا جربته على جهازي اللابتوب لكن جهازي الابتوب ويعمل على فيدورا لينكس!
هذه طريقة قياس الأداء باستعمال ما يسمى higher order functions
طريقة القياس غير صحيحة ..
السبب: المرجع من كل لفة (500 لفة ) هو iterator . والنتيجة لم تحسب بعد يجب عليك بكل لفة ليس فقط استدعاء الدالة يجب المرور بكل الاعداد الاولية.
لا بل أمر عليها جميعها 500 مرة هذا هو السطر الذي يقوم بذلك
for p in f(*a, **kw): pass
ودونه فإن الزمن يكون 0.0003
نعم أخي الحبيب هذا كان جزء من هذا الرد
نعم يبدو أن جهازي أسرع من جهازك. كما قلت كود بايثون يستغرق حوالي ثانية ونصف عندي في حين node الذي كتبه IAli استغرق ربع ثانية
لكني من السهل تسريع كود بايثون عبر حساب الدالة قبل الحلقة التكرارية واستعمال pypy
في هذه الحالة أصبحت سرعة بايثون هي نصف ثانية
هذا المنهج يعيد مجموعة الأعداد الأولية ضمن مجال معين
بلغة سي شارب #C
public static IEnumerable<int> GetPrimes(int range)
{
bool[] isNotPrimes = new bool[range];
for(int i = 2; i < range; i++)
for(int j = i * 2; j < range; j += i)
isNotPrimes[j] = true;
for(int k = 0; k < range; k++)
if(!isNotPrimes[k])
yield return k;
}
ولطباعتها في تطبيق كونسول
static void Main(string[] args)
{
var primes = GetPrimes(10000);
foreach(var number in primes)
{
Console.Write(number + " | ");
}
}
وهذا شكل مبسط بحيث يتم الفحص أثناء المرور الأول
public static IEnumerable<int> GetPrimes(int range)
{
bool[] isNotPrimes = new bool[range];
for(int i = 2; i < range; i++)
{
if(!isNotPrimes[i])
yield return i;
for(int j = i * 2; j < range; j += i)
isNotPrimes[j] = true;
}
}
للعلم لم يشكل فارق كبير بالزمن مقارنة مع الشكل الأول
وكلاهما من أجل 10000000 عشرة ملايين كان الزمن أقل من ثانيتين
نعم وكأنك نقلت مثال php إلى C#
ملاحظاتي:
استعملت تركيب بيانات منظومة المفتاح -> القيمة وهي طريقة إلتفافية لأن لغة php لا تحتوي set. فهل هذا يدل أن C# لا تحتوي ما يشبه set. وحبذا لو قارنت بينه وبين استعمال ما يشبه in_array
فقط نقارن الزمن على نفس الجهاز. بمعنى إن قمت بعمل خوارزميتين أو طريقتين للتنفيذ
لا يجوز قياس وقت استدعاء الدالة فقط عند استعمال yield بل يجب قياس وقت استهلاك كافة العناصر وهذا سبب الوقت السريع جدا عندك حيث أنك قست وقت استدعاء الدالة حتى أول عنصر فقط.
تم تحسين الخوارزمية
وإنقاص الزمن وذلك بتجاهل المجموعات التي هي مشطوبة يقينا
public static IEnumerable<int> GetPrimes(int range)
{
bool[] isNotPrimes = new bool[range];
for(int i = 2; i <= 3; i++)
{
yield return i;
for(int j = i * 2; j < range; j += i)
isNotPrimes[j] = true;
}
for(int k = 6; k < range; k += 6)
{
int i = k - 1;
if(!isNotPrimes[i])
{
yield return i;
for(int j = i * 2; j < range; j += i)
isNotPrimes[j] = true;
}
i = k + 1;
if(!isNotPrimes[i])
{
yield return i;
for(int j = i * 2; j < range; j += i)
isNotPrimes[j] = true;
}
}
}
حيث بدلا من تمرير الحلقة الخارجية على جميع الأعداد نجعلها تمر على مضاعفات 6 فقط ومن ثم نفحص الذي قبله والذي بعده وما عداها فهي قد شطبت بمضاعفات 2 و 3 و 5
لأن القاعدة تقول أن أي عدد أولي أكبر من 3 هو إما 6K-1 أو 6K+1
وبالتالي نمرر حلقتين على 2 و 3 لأنهما استثناء للقاعدة
ثم نمرر الحلقة على مضاعفات الـ 6 و نفحص مرة i-1 و مرة i+1
وهذه المرة كان الزمن 0.1
فكرة جميلة جدا جدا لكن أفضل أن نظل على نفس الخوارزمية. ونبين جوانب التفوق في اللغة لا في الخوارزمية. مثلا اقترحت عليك استعمال HashSet
بالنسبة HashSet أجبتك عليه من قبل
وبالنسبة لتفوق اللغة إن كان من ناحية الزمن فلا ننسى أن لكل جهاز مواصفات مختلفة فالمقارنة لن تكون منصفة
عذرا أخطأت باسمه الخوارزمية فهي غربال Eratosthenes لكن وصفها صحيح.
مثال بالجافا سكريبت لا شئ مميز بخلاف ان زمن التنفيذ لـ 500 مرة أقل من نصف ثانية في متصفح كروم و nodejs ولو أن الأخيرة أسرع قليلا من المتصفح
function getPrimes(max){
var primes = [] ,
numbers= {};
for ( i = 2 ; i < max ; ++i){
if (!numbers[i]) numbers[i] = false
if (numbers[i]) continue;
primes.push(i)
for (j=i*2; j<max; j+=i )
numbers[j] = true;
}
return primes;
}
function benchmarck(count) {
d = new Date();
for (var i = 0 ; i < count; ++i)
getPrimes1(10000);
console.log((new Date()) - d);
}
benchmarck(500);
ماذا تقصد بعشرة ملايين
عشرة ملايين مرة حساب للأعداد الأولية تحت الـ 10000 !!!
أم مرة واحدة للأعداد الأولية تحت العشرة ملايين
نعم مرة واحدة
قمت بتعديل الفحص لجعلها 500 مرة من أجل مجال 10000 فكانت النتيجة من أجل آخر منهج قدمته
هو 0.14 ثانية
هذا المثال كان تنفيذ حتى 10000 مكررة 500 مرة هو 0.00038 والسبب هو عدم استهلاك yield وهذا خطأ. عليك استهلاك محتويات yield.
الآن بعد إقصاء الحلقات من أجل الأعداد التي شطبت من قبل تم تحسين المنهج
أي لا داعي لشطب مضاعفات 4 أو 6 أو 8 أو 9 أو .. الخ لأنهما شطبتا من قبل عندما مررنا على أصغر قاسم أولي لها أي 2 و 3 و 5 .. الخ
public static IEnumerable<int> GetPrimes(int range)
{
bool[] isNotPrimes = new bool[range];
for(int i = 2; i < range; i++)
{
if(!isNotPrimes[i])
{
yield return i;
for(int j = i * 2; j < range; j += i)
isNotPrimes[j] = true;
}
else
continue;
}
}
وانخفض الزمن إلى الربع
أي من أجل عشرة ملايين نزل الزمن ليصبح نصف ثانية
جرب أن تستعمل HashSet مكان isNotPrimes
لا أخي يبقى التعامل مع المصفوفة الخام أسرع بكثير من أي مجموعة مبنية عليها
فالهاش سيت هي سريعة لأغراض المقارنة والعمليات من إضافة وحذف والوصول
أما في منهجنا فنحن لا نريد سوى الوصول لخانة مفهرسة ولا شك أن المصفوفة الخام هي أسرع شيء
فالهاش سيت هي سريعة لأغراض المقارنة والعمليات من إضافة وحذف والوصول
بل هي سريعة للوصول. لايمكن ل key / value أن يكون أسرع من key. المفروض أن ال HashSet هي فهرس لمفاتيح O1 فحص الوجود فيها سريع جدا. إن تبين العكس يكون السبب هو أنها تغيير حجمها وهو سبب البطئ.
لا أخي يبقى التعامل مع المصفوفة الخام أسرع بكثير من أي مجموعة مبنية عليها
بمعنى أنك استعملت مصفوفة محجوزة مسبقا وهذا ما لم أنتبه له من قبل حيث أنني ظننتك استعملت ما يشبه HashArray أو DynamicArray لذا نعم كلامك صحيح.
هذه الخوارزمية بجافاسكربت 1.7 مستخدمة المولدات (generators):
'use strict';
function* getPrimes(n) {
let compos = [];
for (let i = 2; i <= n; i++) {
if (!!compos[i]) continue;
yield i;
for (let j = i* 2; j <= n; j += i) {
compos[j] = true;
}
}
}
function benchmark(n, iterations) {
let start = new Date;
for (let j = 0; j < iterations; j++) {
let generator = getPrimes(n), array = [];
for (let prime of generator) {
array.push(prime);
}
// console.log(array);
}
return new Date - start;
}
console.log(benchmark(10000, 500));
لتنفيذ البرنامج تحتاج Node.js بنسخة 0.11.23 مع الوسم --harmony
تم حساب الأعداد حتى 10000 مكررة 500 مرة وكانت النتيجة 778 ميكروثانية.
في فايرفوكس Nightly استغرق التنفيذ حوالي 12 ثانية!
الكود بلغة لوا
استغرق حوالي ثانية ونصف على لوا و أقل من عشري ثانية على luajit.
طريقة بديلة لأن السابقة تعيدها غير مرتبة
مرحبا ...
Haskell
Erlang
انا غير مقتنع بطريقة القياس :) المقترحة من alsadi ..
لان من الممكن ان تكون النتائج محفوظة ب Cache او شئ من ذلك . خصوصا ان استدعاء الدالة دائما بنفس المعطى 10000
كود بايثون المنفذ عبر مفسر بايثون المكتوب بلغة جافاسكربت repl.it استغرق الكثير من الوقت تقريبا أكثر من نصف دقيقة (على كروم ولم ينتهي على فايرفوكس 26 مع أني أعطيته أكثر من 10 دقائق)
كذلك لو قمنا بتغير الخوارزمية لاستعمال منظومة عادية عوضا عن المجموعة بل إن الوقت زاد 20%
/1
مع أنها في مفسر بايثون بلغة سي استغرق بهذه الطريقة الأخيرة ثانتين وعلى مفسر بايثون المكتوب بلغة بايثون pypy مع JIT أقل من 300 ميليثانية من الثانية.
لوا lua عبر جافاسكربت من خلال خدمة repl.it ومتصف كروم استغرقت حوالي ثلث ساعة مع أن أنها عبر المفسر العادي ثلاث ثواني وعبر luajit ربع ثانية.
كنت أظنه السلاح السري لكنه كان سريع لكن ليس بشكل كبير وصادم. إليكم سي/سي++
يمكننا إخراج عمل حجز الذاكرة alloc لمتغير compo خارج الحلقة وتمريره كمعامل لكني أعتبر هذا غش
أين محبي جافا؟ يمكنك استعمال هذه المكتبة
لكني أفضل أن أرى تنفيذ دون استعمالها.
بما أنه لم يتطوع أحد إليكم الجواب في جافا java باستعمال HashSet وعلى شكل iterator و iterable
جوانب التفوق:
- يمكن تقديم تلميح مسبق للغة عن حجم ال hash
جوانب القصور:
أن اللغة لا توفر yield وأنني اضطررت أن أحاكي ذلك.
الكود غير مقروء
الزمن على جهازي ل 10000 مكرر 500 هو أقل من نصف ثانية بقليل.
استخدم Set كما استخدمته في python
سلام عليكم ومرحبا !
لاحظت ماف شاركة بلغة الجافا فحبيت أشارككم ..
للي بينفذ الكود .. هذا الكود لـ Java8 يعني لازم يكون عندك آخر نسخة من جافا عشان تدعم الــ APIs يا اللي استخدمتها و الميزات الثانية ..
هذا هو الكود .. ما بعرف اذا طريقة تطبيقي صحيحة أو خطأ .. أو ممكن أكون فهمت المسألة غلط .. يريت تقارنوا التنفيذ .. و الكود عشان توروني فين خطأي ..
package com.matar.alog.primes;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Iterator;
import java.util.function.Predicate;
public class AtkinAlogrithmWithJava {
/* Class Body */
private static int $limitPoint = 0;
private static long $startTime = 0;
private static long $endTime = 0;
private static HashSet<Integer> primes;
public static void main(String... mo9){
BufferedReader buffer = new BufferedReader(new InputStreamReader(System.in));
try {
System.err.println("Enter LimitPoint Please : ");
$limitPoint = Integer.parseInt(buffer.readLine());
} catch (NumberFormatException e) {
System.err.println("Inter Number Please ! <.Error.>");
} catch (IOException e) {
System.err.println("Can't Read With BufferReader ! <.Excp.>");
}
calculatePrimes($limitPoint);
}
private static void calculatePrimes(int $limitPoint){
$startTime = System.currentTimeMillis();
primes = new HashSet<Integer>();
for(int $num = 2; $num<= $limitPoint ;$num++){
primes.add($num);
}
System.out.println("Before ... ");
System.out.println(primes);
System.out.println("^__^.");
System.out.println("After ... ");
Object arr[] = primes.toArray();
for(int x = 0 ; x <= arr.length-1;x++){
int p = (int)arr[x];
primes.removeIf(new Predicate<Integer>(){
@Override
public boolean test(Integer t) {
boolean result = (
t == p * 2||
t == p * 3||
t == p * 4||
t == p * 5||
t == p * 6||
t == p * 7||
t == p * 8||
t == p * 9
) ? true:false;
return result;
}
}
);
}
System.out.println(primes);
$endTime = System.currentTimeMillis();
long $long = $endTime - $startTime;
System.err.println("Time = "+$long);
}
}
(^)_(^)
محمد مطر
ضع اربع مسافات قبل كل سطر للتنسيق
package com.matar.alog.primes;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Iterator;
import java.util.function.Predicate;
public class AtkinAlogrithmWithJava {
/* Class Body */
private static int $limitPoint = 0;
private static long $startTime = 0;
private static long $endTime = 0;
private static HashSet<Integer> primes;
public static void main(String... mo9){
BufferedReader buffer = new BufferedReader(new InputStreamReader(System.in));
try {
System.err.println("Enter LimitPoint Please : ");
$limitPoint = Integer.parseInt(buffer.readLine());
} catch (NumberFormatException e) {
System.err.println("Inter Number Please ! <.Error.>");
} catch (IOException e) {
System.err.println("Can't Read With BufferReader ! <.Excp.>");
}
calculatePrimes($limitPoint);
}
private static void calculatePrimes(int $limitPoint){
$startTime = System.currentTimeMillis();
primes = new HashSet<Integer>();
for(int $num = 2; $num<= $limitPoint ;$num++){
primes.add($num);
}
System.out.println("Before ... ");
System.out.println(primes);
System.out.println("^__^.");
System.out.println("After ... ");
Object arr[] = primes.toArray();
for(int x = 0 ; x <= arr.length-1;x++){
int p = (int)arr[x];
primes.removeIf(new Predicate<Integer>(){
@Override
public boolean test(Integer t) {
boolean result = (
t == p * 2||
t == p * 3||
t == p * 4||
t == p * 5||
t == p * 6||
t == p * 7||
t == p * 8||
t == p * 9
) ? true:false;
return result;
}
}
);
}
System.out.println(primes);
$endTime = System.currentTimeMillis();
long $long = $endTime - $startTime;
System.err.println("Time = "+$long);
}
(Complexity: O(nlogn
يبدو لي أن هذه للجزء الثاني من السلسلة. وأنها لا تعمل. فضلا ممكن أن تضيف كود اختبار لنقل كود يطبع أول 100 عدد أولي
لا لا لهذا الجزء :)
اللغة Python. استخدمت range بدل xrange لأن النسخة الموجودة عندي 3. وكسلت أعدلها في المتصفح لـ xrange
هذا السطر زائد:
char += 2 ** (i % 8 -1)
لأني استخدمت نفس الخوارزمية في حل الجزء الثاني ثم نسختها لهذا الجزء ونسيت أن أمسحه. هذا ما سبب لك الالتباس، المعذرة.
الكود ممسوح منه السطر:
انا مبتدأ بروبي
و هذا تطبيقي
puts "enter your maxium number"
n = gets.to_i
puts "prime number : "
if n == 2
puts 2
elsif n < 2
puts "no prime number less than 2"
else
puts 2
puts 3
end
for i in 4..n
next if i % 2 == 0 or i % 3 == 0
puts i
end
لا الطريقة صحيحة ولا هي تنفيذ لهل الخوارزمية.
فأنت تفحص باقي القسمة على 2 و 3 فقط. مثلا 25 لا تقسم لا على 2 ولا على 3.
اخي ، هذا تعديلي على الكود
puts "enter your maxium number"
n = gets.to_i
puts "prime number : "
if n < 2
puts "there isn't prime number less than 2"
elsif n == 2
puts 2
elsif n == 3
puts 2
puts 3
elsif n == 5
puts 2
puts 3
puts 5
else
puts 2
puts 3
puts 5
puts 7
end
for i in 4..n
next if i % 2 == 0 or i % 3 == 0 or i % 5 == 0 or i % 7 == 0
puts i
end
مع العلم اني جربت اظهار الاعداد الاولية حتى ١٠٠
وتاكد من الاعداد انها صحيحة