21

طبق هذه الخوارزمية بلغتك المفضلة ج1: الأعداد الأولية

في هذه السلسلة نطرح في كل مرة خوارزمية ونطبقها في لغات مختلفة مستفيدين من مزايا كل لغة. كوسيلة لعرض مزايا كل لغة بطريقة غير لغتي أفضل من لغتك.

المطلوب الآن هو تطبيق خوارزمية غربال Atkin لإيجاد الأعداد الأولية التي تقل عن العدد ن (10 آلاف مثلا)

http://en.wikipedia.org/wik...

الخورازمية تعمل هكذا

  • نرسم الأعداد التي من 2 وحتى ن-1

  • نأخذ أول عدد غير مشطوب (في أول دورة هو 2)

  • نعتبره أولي ونشطب كل مضاعفاته

  • نكرر

يرجى الدخول لحسابك أو تسجيل حساب لتستطيع إضافة تعليق
حساب جديد دخول

التعليقات

13

سأبدأ أنا بلغة بايثون

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 ثواني

أنا جربته على جهازي اللابتوب لكن جهازي الابتوب ويعمل على فيدورا لينكس!

هل يمكنك كتابة كود لفحص الأداء (benchmarking) لبايثون... أريد مقارنتها مع جافاسكربت على جهازي

هذه طريقة قياس الأداء باستعمال ما يسمى higher order functions

طريقة القياس غير صحيحة ..

السبب: المرجع من كل لفة (500 لفة ) هو iterator . والنتيجة لم تحسب بعد يجب عليك بكل لفة ليس فقط استدعاء الدالة يجب المرور بكل الاعداد الاولية.

لا بل أمر عليها جميعها 500 مرة هذا هو السطر الذي يقوم بذلك

  for p in f(*a, **kw): pass

ودونه فإن الزمن يكون 0.0003

هل عدلت على الكود :) كان هكذا ..

for i in xrange(n): f(*a, **kw)

اوه اخطلت على من كثر التعليقات :)

ها هو

كان رد على احد الاخوة

نعم أخي الحبيب هذا كان جزء من هذا الرد

النتيجة على جهازي 2.53796005249 ثانية مقارنة مع 824 ميكروثانية باستخدام Node.js

نعم يبدو أن جهازي أسرع من جهازك. كما قلت كود بايثون يستغرق حوالي ثانية ونصف عندي في حين 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);

في سي شارب من أجل 10000000 عشرة ملايين كان الزمن أقل من ثانيتين

ماذا تقصد بعشرة ملايين

عشرة ملايين مرة حساب للأعداد الأولية تحت الـ 10000 !!!

أم مرة واحدة للأعداد الأولية تحت العشرة ملايين

نعم مرة واحدة

قمت بتعديل الفحص لجعلها 500 مرة من أجل مجال 10000 فكانت النتيجة من أجل آخر منهج قدمته

هو 0.14 ثانية

هذا المثال كان تنفيذ حتى 10000 مكررة 500 مرة هو 0.00038 والسبب هو عدم استهلاك yield وهذا خطأ. عليك استهلاك محتويات yield.

لا أنا أعطيت الزمن مع التجسيد أي أجبرت المجسد على المرور على جميع العناصر

ولهذا كان الزمن 0.14 بينما بدون التجسيد أكيد سيكون أقل بكثير كالرقم الذي قلته أنت

ظهر عندي 0.00068

تصحيح بدلا من

getPrimes1(10000); 

استبدلها

getPrimes(10000); 

الآن بعد إقصاء الحلقات من أجل الأعداد التي شطبت من قبل تم تحسين المنهج

أي لا داعي لشطب مضاعفات 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 ثانية!

بالطبع يمكن تحسين الكود باستخدام Array comprehnsions لكن يبدو أن Node.js وFirefox لا يدعمانها بعد مع المولدات

أين محبي روبي؟

الكود بلغة لوا

استغرق حوالي ثانية ونصف على لوا و أقل من عشري ثانية على luajit.

طريقة بديلة لأن السابقة تعيدها غير مرتبة

مرحبا ...

Haskell

Erlang

انا غير مقتنع بطريقة القياس :) المقترحة من alsadi ..

لان من الممكن ان تكون النتائج محفوظة ب Cache او شئ من ذلك . خصوصا ان استدعاء الدالة دائما بنفس المعطى 10000

طريقة القياس وإن لم تكن الأفضل فهي تفي بالغرض لقياس الطرق غير المجدية كما في in_array

الكود بـ Haskell جداً مختصر ولكن على حساب سهولة الفهم

سهولة الفهم !! ...

الكود واضح جدا بنسبة لي :)

ال Haskell من اجمل اللغات التي رأيتها

السلام عليكم

على ماذا تتحدث هذه الدورة و ما الفائدة اذا قمت بتتبعها

كود بايثون المنفذ عبر مفسر بايثون المكتوب بلغة جافاسكربت 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

ال set مجرد Interface لذا استعملت HashSet.

ايضا هنا القياس خطأ كما في python

كلاهما صحيح. في جافا أنا استعملت toArrayList والتي تسير على كل العناصر دقق في هذا السطر

 while (iter.hasNext()) list.add(iter.next());

عدلت ..

يبدو ان رمضان لان تاثير علي :)

صيام هنا طويييل.

ما شاء الله

متابع بنهم

سلام عليكم ومرحبا !

لاحظت ماف شاركة بلغة الجافا فحبيت أشارككم ..

للي بينفذ الكود .. هذا الكود لـ 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);
}

من الواضح أنه حل غير صحيح فهو يحتوي استثناءات بعينها

سبقتك

لكننا ننتظل أهل روبي

يبدو لي أن هذه للجزء الثاني من السلسلة. وأنها لا تعمل. فضلا ممكن أن تضيف كود اختبار لنقل كود يطبع أول 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

مع العلم اني جربت اظهار الاعداد الاولية حتى ١٠٠

وتاكد من الاعداد انها صحيحة

غير صحيح. فهو مثلا لا يحتسب ما هو أكبر من 7 مثل 1111 أو 1113 (لم نقل أننا نريد أن نكون محدودين في أول 100)

فضلا اقرأ خوارزمية الغربال وطبقها.