مسیرها
/
Julia
Julia
/
تمرین‌ها
/
گوروی نمره‌دهی
گوروی نمره‌دهی

گوروی نمره‌دهی

تمرین یادگیری

مقدمه

تا اینجا، برنامه‌ی درسی چیز زیادی درباره‌ی نوع‌ها نگفته است، اما بدیهی است که در جولیا وجود دارند:

julia> vals = (42, 4.3, π, "hello", 'Q')
(42, 4.3, π, "hello", 'Q')
julia> typeof(vals)
Tuple{Int64, Float64, Irrational{:π}, String, Char}

ما هیچ‌وقت نوع‌ها را مشخص نکردیم، اما جولیا به‌هرحال آن‌ها را تعیین کرد.

  1. جولیا types دارد که در طراحی‌اش نقشی محوری دارند.
  2. جولیا معمولاً می‌تواند نوع را با استفاده از Type Inference «حدس بزند».

کامپایلر JIT تمام کد را بررسی می‌کند، می‌بیند که یک متغیر چگونه استفاده می‌شود، و یک نوع پیش‌فرض مناسب و سازگار با آن کاربرد را استنتاج می‌کند.

تخصیص نوع

تکیه بر استنتاج نوع برای حل تمرین‌های آموزشی ساده اشکالی ندارد، اما در برنامه‌های بزرگ‌تر احتمالاً به کنترل دقیق‌تری نیاز پیدا می‌کنید.

روی بیشتر پردازنده‌های مدرن، نوع پیش‌فرض یک عدد صحیح Int64 است. در مفهوم Numbers دیدیم که می‌توان یک مقدار را به یک نوع مشخص و غیرپیش‌فرض تبدیل کرد.

julia> x = Int16(42)
42
julia> typeof(x)
Int16

با این حال، متغیر x همچنان می‌تواند به نوع دیگری بازتخصیص داده شود:

julia> x = "changed"
"changed"
julia> typeof(x)
String

این همان type instability است که:

  • در اسکریپت‌های کوچک بسیار راحت است.
  • برای کارایی و قابلیت اطمینان در برنامه‌های بزرگ‌تر بد است.

به‌جای آن، می‌توانیم نوع x را با استفاده از عملگر :: تعیین کنیم:

julia> y::Int16 = 42
42
julia> typeof(y)
Int16
julia> y = "changed"
ERROR: MethodError: Cannot `convert` an object of type String to an object of type Int16
The function `convert` exists, but no method is defined for this combination of argument types.

حالا y از نوع Int16 است و همیشه خواهد بود. در نتیجه، کامپایلر می‌داند چه تعداد بایت برای آن رزرو کند و می‌تواند بقیه‌ی کد را طوری بهینه کند که به یک نوع پایدار تکیه داشته باشد. از این نظر، این متغیر کم‌وبیش شبیه متغیرهای یک زبان با نوع‌دهی ایستا مانند C است.

سلسله‌مراتب نوع

Int64، Int16، String، Char: این نوع‌ها از کجا می‌آیند؟

در بسیاری از زبان‌های شیءگرا (OO)، هر نوع یک کلاس است، زیرکلاس‌بندی آن‌ها را در یک سلسله‌مراتب کلاس می‌چیند، و متدهای کلاس رفتارها را تعریف می‌کنند.

Java و Ruby نمونه‌های آشکار این الگو هستند، اما حتی Python هم در درون شبیه همین است.

جولیا کلاس ندارد.

دلیل مستندشده این است که ویژگی‌های شیءگرایی با کامپایلر JIT تداخل می‌کنند و به کارایی در زمان اجرا آسیب می‌زنند.

و با این حال، به این کد نگاه کنید:

julia> y::Int16 = 42
42
julia> typeof(y)
Int16
julia> supertype(Int16)
Signed
julia> supertypes(Int16)
(Int16, Signed, Integer, Real, Number, Any)

با کمی جزئیات بیشتر:

  • Int16 یک نوع است و می‌توانیم متغیرهایی از این نوع بسازیم.
  • Int16 زیرنوع Signed است و تابع supertype() این را به ما نشان می‌دهد.
  • سلسله‌مراتبی از نوع‌ها وجود دارد که از Integer، Real و Number بالا می‌رود تا در بالاترین نقطه به Any می‌رسد، و subtypes() این شاخه از سلسله‌مراتب را برای ما فهرست می‌کند.

همه‌ی شاخه‌ها به Any می‌رسند، که از این نظر یکتاست که ابرنوع خودش است.

julia> supertypes(String)
(String, AbstractString, Any)
julia> supertype(Any)
Any

پس جولیا سلسله‌مراتب class ندارد، اما سلسله‌مراتب type را دارد.

در نهایت خواهیم کوشید سر از کار این درآوریم، اما پیش از آن چیزهای بسیار بیشتری برای کاویدن هست.

بررسی نوع‌ها

می‌توانیم با typeof() برابری را به روش معمول بررسی کنیم.

julia> typeof(11)
Int64
julia> typeof(11) == Int64
true
julia> typeof(11) == Number
false

برابری نوع باید دقیق باشد، چون این شکل از مقایسه هیچ درکی از سلسله‌مراتب نوع ندارد.

به شکلی انعطاف‌پذیرتر، isa به ما می‌گوید که آیا یک مقدار همان نوع مقایسه‌کننده را دارد یا زیرنوعی از آن. این را می‌توان هم در شکل تابعی و هم در شکل میان‌وندی به کار برد.

julia> 12 isa Int64
true
julia> 12 isa Number
true
julia> isa(12, Number)
true
julia> 12 isa String
false

دقت کنید که isa در سمت چپ یک value انتظار دارد، نه یک type.

اگر بخواهید دو نوع را به این شکل مقایسه کنید، نتیجه‌ی غیرمنتظره‌ای می‌گیرید. عملگر درست <: است که در مفاهیم آینده بسیار بیشتر آن را خواهیم دید.

julia> Int64 isa Number  ## Don't do this!
false
julia> Int64 <: Number
true

نوع‌های انتزاعی در برابر نوع‌های مشخص

دیدیم که سلسله‌مراتب نوع یک ساختار درختی می‌سازد (به معنای علوم رایانه، با ریشه در بالا).

هر مورد در درخت یک node است و این‌ها را می‌توان به دسته‌هایی تقسیم کرد:

  1. به گره‌هایی که زیرنوع دارند abstract می‌گویند.
  2. به گره‌های برگ، که زیرنوع ندارند، concrete می‌گویند.
julia> subtypes(Integer)  # an abstract type
3-element Vector{Any}:
 Bool
 Signed
 Unsigned
julia> subtypes(Int64)  # a concrete type
Type[]

این تمایز مهمی است، چون فقط نوع‌های مشخص را می‌توان به‌عنوان متغیر instantiated کرد.

julia> a::Int16 = 42
42
julia> typeof(a)
Int16
julia> b::Integer = 42
42
julia> typeof(b)
Int64

دقت کنید که تلاش برای استفاده از یک نوع انتزاعی (در این مورد) هیچ پیام خطایی نمی‌دهد، اما کامپایلر یک نوع مشخص مناسب می‌سازد: Int64 به‌جای Integer.

دستورالعمل‌ها

در این تمرین می‌خواهید کمی مهندسی داده انجام دهید و داده‌های نمره‌دهی را پیش‌پردازش کنید. مدرسی با دانش‌آموزان بسیار، بسیار، بسیار (شاید هم زیادی؟) می‌خواهد تحلیلی روی نمره‌های دانش‌آموزان انجام دهد و قصد دارد این کار را به شکلی کارآمد انجام دهد. پس هدف، کم‌حجم کردن و مرتب کردن داده‌هاست.

مقیاس نمره‌دهی معمولاً از ۰ تا ۱۰ است، پس کارآمدترین راه استفاده از نوع داده‌ای است که برای هر نمره فقط هشت بیت داشته باشد.

  • برخی استادان از اعداد حقیقی استفاده می‌کنند (چون به دقت وسواس دارند) و شما باید آن‌ها را به نوع داده‌ی UInt8 تبدیل کنید.
  • استادان دیگری از اعداد صحیح با مقادیر منفی استفاده می‌کنند (تا دانش‌آموزان به‌ویژه سرکش را تنبیه کنند) و در این موارد باید آن‌ها را به نوع داده‌ی Int8 تبدیل کنید.

پس از پیاده‌سازی تابع تبدیل نمره‌ها، باید تابع دیگری بنویسید که مجموعه‌هایی را که نمره‌ها در آن‌ها نگهداری می‌شوند پردازش کند: نمره‌ها را تنزل دهد و آن‌ها را به ترتیب نزولی برگرداند.

  • برخی استادان برای نگهداری نمره‌ها از Vectorهایی استفاده می‌کنند که به ترتیب صعودی مرتب شده‌اند، چون به داده‌هایشان دقت زیادی دارند.
  • استادان دیگری از Setهای بدون ترتیب برای نگهداری نمره‌ها استفاده می‌کنند، چون کمی تنبل‌ترند.

در هر دو تابع باید ورودی‌های نامعتبر را با پرتاب یک MethodError مدیریت کنید.

Note

به مدیریت استثنا در یک مفهوم بعدی می‌پردازیم؛ پس برای این تمرین می‌توانید از نحوه‌ی نگارش زیر استفاده کنید:

throw(MethodError(f, args))

که در آن f تابع و args یک تاپل از آرگومان‌های ورودی به تابع است. در تابع demote(n)، f همان demote و args همان (n,) است:

throw(MethodError(demote, (n,)))

1. نمره‌ها را تنزل دهید

متد demote(n) را پیاده‌سازی کنید.

  • با ورودی Float64 باید آن را به نزدیک‌ترین عدد صحیح، به سمت بالا، گرد کند و یک نوع داده‌ی UInt8 برگرداند.
  • با یک Integer دلخواه باید همان عدد صحیح را در قالب نوع داده‌ی Int8 برگرداند.
  • همه‌ی ورودی‌های دیگر باید یک MethodError پرتاب کنند.
julia> demote(4.2)::UInt8
5

julia> demote(4)::Int8
4

julia> demote("hi")
MethodError: no method matching demote(::String)     #output truncated

2. داده‌ها را پیش‌پردازش کنید

متد preprocess(coll) را پیاده‌سازی کنید.

  • با ورودی Vector باید همه‌ی اعداد را تنزل دهد و ترتیب بردار را برعکس کند.
  • با ورودی Set باید همه‌ی اعداد را تنزل دهد و یک بردار مرتب‌شده به ترتیب نزولی برگرداند.
  • همه‌ی ورودی‌های دیگر باید یک MethodError پرتاب کنند.
julia> preprocess([1, 2, 3])
3-element Vector{Int8}:
 3
 2
 1

julia> preprocess(Set([2.2, 5.8, 3.4]))
3-element Vector{UInt8}:
 6
 4
 3

julia> preprocess(42)
MethodError: no method matching preprocess(::Int64)     #output truncated
ویرایش از طریق GitHub این لینک در پنجره یا زبانه‌ی جدیدی باز می‌شود
Julia Exercism

آماده‌اید گوروی نمره‌دهی را شروع کنید؟

در Exercism ثبت‌نام کنید تا Julia را همراه با 35 مفهوم128 تمرین و مربی‌گری انسانی واقعی یاد بگیرید و در آن استاد شوید، همه‌ی این‌ها رایگان.