سكالا …الدرس الثاني
- الدرس الثاني:
المجموعات Scala Collections
فى الدرس السابق توقفنا عند المجموعات Collections و تم عرض بعض Functional Methods مثل Map , Filler و اليوم كان المقدمه مع Reduceوهى دالة تأخذ دالة أخري ك argument و تنفذها على قائمة لترجع فى النهاية قيمة واحده فقط value
1 التصريح بالقائمة
1 Scala> val x=List(1,2,3,4,5)
2 x: List[Int] = List(1, 2, 3, 4, 5)
2 التصريح بالدالة المراد تطبيقها على القائمة
1 scala> def add(a:Int, b:Int):Int=a+b
2
add: (Int,Int)Int
3 استخدام reduceLeft و هذا يعني ان هناك reduceRight وهي تقوم بنفس الوظيفة و لكن من الاتجاه المعاكس
تقوم reduce بتنفيذ دالة add التي تأخذ 2 parameters على اول عنصرين من
القائمة لتنتج رقما واحدا تستخدمه كأول parameter والعنصر الثالث كثاني
parameter فى دالة add مرة اخري و هكذا حتى نهاية القائمة لتخرج قيمة و
احدة .
1 scala> x.reduceLeft(add)
2 res1: Int = 15
قراءة الملفات
سنقوم بعمل كود يأخذ ملف نصي بعد اسم البرنامج فى سطر الاوامر كـ
argument ويقوم بطباعة عدد احرف كل سطر و بعدها يقوم بطباعة السطر نفسه
1
2
3
4
5
6
| import scala.io.Source if (args.length > 0) { for (line print(line.length + "" +line) } else Console.err.println( "Please enter filename" ) |
شرح الكود
- نقوم باستيراد ال Source class من الحزمة scala.io
- نقوم باختبار لنتأكد ان المستخدم قد أدخل فعلا اسم ملف والا نقوم بطباعة please enter filename
- يقوم التعبير ((Source.fromFile(args(0 بفتح الملف و تعيينه لكائن object ننفذ عليه الدالة getLines لترجع لنا Iterator يرجع لنا سطر فى كل مرة
- تقوم for بالمرور على السطور و طباعة عدد احرف السطور ثم مسافة ثم السطر نفسه .
Class and Objects
نعرف جميعا ان الكلاس هى الخريطة التى تصف الكائن و هى تصفه عن طريق بعض الحقول (قيم و متغيرات ) و الدوال.- فلنتأمل الكود التالي
1 class ChecksumAccumulator }
2 var
sum
= 0
3 }
4 val acc = new ChecksumAccumulator
5 val csa = new ChecksumAccumulator
قمنا بتعريف كلاس و اضفنا بها حقل sum ثم أنشأنا منها كائن عن طريق الامر new
نلاحظ هنا ان sum هو متغير var و بالتالي يمكن تغيير قيمته كالتالى
نلاحظ هنا ان sum هو متغير var و بالتالي يمكن تغيير قيمته كالتالى
1 acc.
sum
=3
و هذا يجعلنا نلاحظ انه فى سكالا لايوجد كلمة public مثل جافا لأنها هى الوضع الافتراضي اذا لم نضع كلمة private.
قمنا باضافة دالتين هما add و checksum
ملحوظة هامة : تعامل الـ parameters الخاصة بالدوال كـ val وبالتالى لا يمكن تغيير قيمتها داخل الدالة اي لا يمكن ان نقوم بالاتي مثلا
1 def add(b: Byte): Unit= {
2 b=2
3 }
1 class ChecksumAccumulator }
2 private var
sum
= 0
3 def add(b: Byte) {
sum
+= b }
4 def checksum(): Int = ~(
sum
& 0xFF) + 1
5 }
Semicolon Interface
بالتاكيد لاحظنا ان فى سكالا لا يهم ان تكتب ; فى نهاية كل سطر الا
بالتاكيد اذا كنت تنوي كتابة اكثرمن جملة فى سطر واحد و ذلك يرجع الى قواعد
Semicolon inference و هى :-- يجب الا ينتهى السطر بـ Inflix operator مثل +
1 +X
2
Y
- يجب الا ينتهى السطر داخل احد الاقواس
1 X)
2 (y+
- أما فى هذه الحالة فلن يبتج ناتج الجمع و سيعامل كل سطر كجملة مستقلة حيث لم تنطبق اي من القواعد و كل سطر يمكن ان يكون له ناتج مستقل
1 X
2 y+
Singleton Objects
كائن الـ Singleton هو احد انماط التصميم design patterns التي يمكن
وصفها بأنها كلاس يستنسخ منها كائن واحد فقط و لا تسمح بأكثر من ذلك .فى سكالا يتم تعريفه باستخدام كلمة object بدلا من class
مثال
import
scala.collection.mutable.Map }
object ChecksumAccumulator
private val cache = Map[String, Int]()
def calculate(s: String): Int =
if
(cache.contains(s((
cache(s(
else
}
val acc = new ChecksumAccumulator
for
(c cs(
cs
}
}
- نلاحظ انه يأخد نفس اسم الكلاس التى قمنا بتعريفها من قبل و فى هذه الحالة يسمى كائن مرافق companion object وتسمى هي كلاس مرافقة companion class ولكن يجب ان يكونا معا فى نفس الملف المصدري و سيكون لدى كل منهما القدرة على استخدام محتويات بعضهم الخاصة private.
- يحوي هذا الكائن على دالة calculate التى تأخذ string كـ parameter
المراد استخراج checksum له ويحوي map المسمى cache يحفظ بداخله كل من الـ
strings و checksum الخاص به الذي تم استخراجه قبل ذلك
تبدأ الدالة بالبحث داخل cache لتري ان كان قد تم معالجة هذا string من قبل فاذا وجدته اعادت قيمته و ان لم تجد تقوم بحسابه عن طريق for loop التي تمر على الحوف المكونة للـ string وتقوم بعمل كائن من الكلاس المرافقة واستخدام الدوال الخاصة بها التى تقوم بالحساب (دالة add و دالة checksum )ثم تقوم بحفظ الناتج cs فى cache ليكون جاهز اذا تم طلبه مرة اخري و ذلك لتوفير موارد الجهاز حيث ان تلك العملية مكلفة ثم تقوم فى النهاية بارجاع القيمة للمستخدم. - يمكن استخدام هذا الكائن مباشرة حيث لايجوز عمل منه اي كائنات اخري باستخدام new وهذا هو الفرق الجوهري بينه و بين ال class
يتم استخدامه كالتالي
ChecksumAccumulator.calculate(
"hello"
)
Scala Compliled Application
- كي تقوم بمعاملة الكود ك application وتقوم بعمل له compile يجب ان يحتوي على دالة main التي تاخذ array من strings كـ parameter و لا ترجع شئ Unit
def main(args: Array[String]) {
}
و طبعا تلك ال array تمثل الـ arguments التى يمكن ان يأخذها البرنامج فى سطر الاوامر بعد كتابة اسم البرنامج.
مثال :
مثال :
object hello{
def main(args: Array[String])
println(
"hello world"
)
}
}
نقوم بعمل compilation عن طريق الامر scalac يليه اسم البرنامج
amr@Amr:~$ scalac hello.scala
نقوم بتشغيل البرنامج باستخدام الامر scala يليه اسم البرنامج بدون امتداد وفى الحقيقة انت مش بتكتب اسم البرنامج انت بتكتب اسم الكائن object الذي يحتوي داله main ولكن قمنا بتسمسة الملف بنفس اسمه وهذا ليس اجباريا فى سكالا عكس الجافا حيث يكون اجباريا ان يكون اسم الملف باسم ال class
amr@Amr:~$ scala hello
hello world
Types and Operation
الانواع الاساسية Basic typesType | size |
---|---|
Byte | 8-bit |
Short | 16-bit |
Int | 32-bit |
Long | 64-bit |
Char | 64-bit |
String | a sequence of Chars |
Float | 32-bit |
Double | 64-bit |
Boolean | true or false |
- و طبعا نقدر نعرف Int بنظام Hexadecimal
scala> val hex = 0x5
hex: Int = 5
scala> val hex2 = 0x00FF
hex2: Int = 255
او نظام octal
scala> val oct = 035
oct: Int = 29
scala> val nov = 0777
nov: Int = 511
او نظام decimal العادي
scala> val dec1 = 31
dec1: Int = 31
scala> val dec2 = 255
dec2: Int = 255
علامة التنصيص المفردة تعني حرف character عكس المزدوجة التى تعني string
scala> val a =
'A'
a: Char = A
- Special characters escaping sequences
مثل /n التي ترمز لسطر جديد (زر Enter) و /t التي ترمز لزر Tab و /” و /’ و //
- طريقة اخري لتفادى الرموز السابقة التى من السهل ان تصبح مزعجة اذا استخدمت بكثرة هى “”" “”" فبوضع النص داخل ثلاثة علامات تنصيص مزدوجة من كل ناحية يمكنك وضع كل العلامات الخاصة و المسافات و ستحفظ كما هي
scala> println(
""
"hello every body
| i just hit enter
""
")
hello every body
i just hit enter
قلنا قبل كده ان ال operators ما هى الا دوال يعنى عندما نقوم بالتى
scala> val
sum
= 1 + 2
sum
: Int = 3
فكل ما نفعله هو اننا نقوم بطلب الدالة + الموجودة فى كلاس Int
scala> val
sum
= (1).+(2)
sum
: Int = 3
مثال اخر ولكن تلك المرة على prefix operator
scala> -2
res2: Int = -2
scala> (2).unary_-
res3: Int = -2= -2
- نلاحظ ان تلك الخاصية تعطينا الكثير من المرونة فى كتابة الكود فيمكننا ان نصنع operators خاصة بنا
سكالا تسمح ان نستخدم كـ prefix oprerator العلانات التالية فقط + – ! ~
اما كـ inflix operator فتقريبا كل شئ مسموح
اما كـ inflix operator فتقريبا كل شئ مسموح
- المساواة Equality
نسستخدم == للتعبير عن يساوي و =! للتعبير عن لا يساوي و سنلاحظ ان فى
سكالا انه يقوم بالمقارنة على اساس التساوي فى القيمة و ليس فى المرجع
Value equality not reference equality
أمثلة
أمثلة
scala> 1 == 2
res31: Boolean =
false
scala> 1 != 2
res32: Boolean =
true
scala> 2 == 2
res33: Boolean =
true
scala> List(1, 2, 3) == List(1, 2, 3)
res34: Boolean =
true
scala> List(1, 2, 3) == List(4, 5, 6)
res35: Boolean =
false
scala> (
"he"
+
"llo"
) ==
"hello"
res40: Boolean =
true
- الاسبقية Operator precedence
و لكننا عرفنا قبل ذلك ان الـ operators ماهى دوال فكيف تقوم سكالا بتحديد الاسبقية ؟؟
تقوم سكالا بتحديد الاسبقية بناء على اول حرف فى الداله و ترتيبه فى جدول الاسبقيات
* / %
+ -
:
= !
<>
&
ˆ
|
(all letters)
(all assignment operators)
فاذا قمنا مثلا بتصنيع operator اسمه ++ و اخر اسمه *< سيكون لـ ++ الاسبقية لانه يبدأ بـ + بينما الاخر يبدأ بـ <
نلاحظ ان اخر القائمة هو assignment operator لانه لابد ان ينفذ بعد ان تتم كل العمليات الحسابية.
Functional Objects الكائنات الدالية
الفكرة الرئيسية هنا هي انها كائنات Immutable اي لا يمكن تغيير القيم بداخلها .مثال سنتعرف فيه على تلك الفكرة و نتعرف فيه اكثر عن البرمجة الكائنية Object-Oriented Programming فى سكالا
البرنامج يقوم بمعالجة الاعداد المنطقية او الكسرية
class Rational(n: Int, d: Int) {
require(d != 0)
private val g = gcd(n.abs, d.abs)
val numer = n / g
val denom = d / g
def this(n: Int) = this(n, 1)
def + (that: Rational): Rational =
new Rational(
numer * that.denom + that.numer * denom,
denom * that.denom ) def + (i: Int): Rational = new Rational(numer + i * denom, denom) def - (that: Rational): Rational = new Rational( numer * that.denom - that.numer * denom, denom * that.denom ) def - (i: Int): Rational = new Rational(numer - i * denom, denom) def * (that: Rational): Rational = new Rational(numer * that.numer, denom * that.denom) def * (i: Int): Rational = new Rational(numer * i, denom) def / (that: Rational): Rational = new Rational(numer * that.denom, denom * that.numer) def / (i: Int): Rational = new Rational(numer, denom * i) override def toString = numer +
"/"
+ denom private def gcd(a: Int, b: Int): Int =
if
(b == 0) a
else
gcd(b, a % b) }
نقوم الان بشرح الكود جزء جزء
- }(class Rational(n: Int, d: Int
- (require(d != 0
- private val g = gcd(n.abs, d.abs)
val numer = n / g
val denom = d / g
فى هذا الجزء نقوم بحساب القاسم المشترك الاكبر greatest common divisor عن طريق الدالة gcd و هى معرفة فى اخر الكلاس و تاخذ parameters القيمة المطلقة absolute value عن طريق دالة abs وهى دالة جاهزة فى سكالا معرفة فى الكلاس الخاصة بالـ Integers
private def gcd(a: Int, b: Int): Int =
(
if
(b == 0) a
else
gcd(b, a % b
بعد ذلك نقوم بقسمة كل من البسط و المقام على هذا القاسم لنحصل على اصغر قيمة لهم
مثال 2/4 تصبح ½ وهكذا
scala> val x =new Rational(2,4)
x: Rational = 1
/2
- (def this(n: Int) = this(n, 1
و نلاحظ ان فى سكالا الـ constructors عبارة عن سلسلة تنتهي دائما بـ primary constructor
scala> val x=new Rational(5)
x: Rational = 5
/1
- =def + (that: Rational): Rational
new Rational(
numer * that.denom + that.numer * denom,
denom * that.denom
)
تلك دالة تسمى + تاخذ parameter من نوع Rational و ترجع كائن جديد به مجموع العددين و هنا نلاحظ موضوع immutable حيث لم تضيف الدالى العدد الجديد على العدد الحالى و تغير قيمته و لكنها بدلا من ذلك قامت بخلق كائن جديد تحفظ فيه القيمة الجديدة
scala> val x=new Rational(1,2)
x: Rational = 1
/2
scala> val y=new Rational(1,4)
y: Rational = ¼
scala> x.+(y)
res3: Rational = 3
/4l
= 3
/4
طبعا عارفين الحركة الجاية دي سببها ايه ؟
scala> x + y
res4: Rational = ¾
المشكلة هنا ان دالة زي + بتاخد parameter من نوع Rational يعني لو اخدت Integer لن تعمل
scala> val x=new Rational(1,2)
x: Rational = 1
/2
scala> x+2
:7: error:
type
mismatch;
found : Int(2)
required: Rational
x+2
^
فكان الحل هو overloading وهو ان نقوم بالتصريح بدالة اخري بنفس الاسم و لكن بـ parameters مختلفة
و عند طلب الدالة يختار comiler الدالة التى يستخدمها حسب parameter المعطي .
def + (i: Int): Rational=
new Rational(numer + i * denom, denom))
و الان عند التجربة
Scala> val x = new Rational (1,2)
x: Rational = 1
/2
scala> x + 2
res2: Rational = 5
/2
5
/2
دعنا الان نجرب
scala> 2 + x
:7: error: overloaded method value + with alternatives (Double)Double (Float)Float (Long)Long (Int)Int (Char)Int (Short)Int (Byte)Int (java.lang.String)java.lang.String cannot be applied to (Rational) 2+x
^
لماذا حدث ذلك ؟؟
لاننا الان نستخدم الدالة + الموجودة فى كلاس Integer وليس فى كلاس
Rational و ليس من اختصاصها ان تاخذ Rational كـ parameter فما الحل ؟؟
الحل فى سكالا هو التحويل الضمني Implicit Conversions
التحويل الضمني هو اعطاء الصلاحيات لل compiler ان يقوم ببعض التحويلات ضمنيا بدون تدخل منك
مثال : سنقوم باضافة هذا السطر
scala> implicit def intToRational(x: Int) = new Rational(x)
scala> 2 + x
res6: Rational = 5/2
لان سكالا الان تقوم بتحويل كل Int ضمنيا الى Rational.
- override def toString = numer +”/”+ denom
toString هى دالة تقوم بتحويل الكائن الي string و فى الحقيقة فكل ما
تفعله هي انها تقوم بطباعة اسم الكلاس و بعدها علامة @ و بعدها رقم معين. و
هى اصلا دالة ترثها inherit اي كلاس من كلاس java.lang.Object
مثال للبرنامج من غير سطر override
مثال للبرنامج من غير سطر override
scala> val x = new Rational(1,2)
x: Rational = Rational@11126f6
تقوم كلمة override باستبدال تلك الدالة بدالة من صنعنا لتقوم بوظيفة افضل (طباعة العدد ).
مثال للبرنامج بعد اسستخدام overridescala> val x=new Rational(1,2)
x: Rational = 1
/2
اعداد الزميلة :
أمنية جمال
ليست هناك تعليقات:
إرسال تعليق