کلید مقایسه

کلید مقایسه

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

مقدمه

برابری

Common Lisp «محمول»های برابری گوناگونی دارد. این با سایر زبان‌های برنامه‌نویسی فرق می‌کند، چون آن‌ها ممکن است فقط یکی یا دو تا داشته باشند (مثلاً == و ===). بعضی از این محمول‌ها در Common Lisp مخصوص نوع‌های خاص‌اند و بعضی دیگر عمومی. در اینجا به دسته‌ی دوم می‌پردازیم.

چهار محمول برابری عمومی وجود دارد که در میزان سختگیری نسبت به آنچه «برابر» در نظر می‌گیرند با هم فرق دارند. این‌ها به ترتیب از سخت‌گیرترین تا کم‌سخت‌گیرترین عبارت‌اند از: eq، eql، equal و equalp.

تعریف‌های فشرده‌ی زیر (که چند جزئیات را نادیده می‌گیرد) به این شکل‌اند:

  • eq: برابری را چنین تعریف می‌کند که دو شیء یکسان باشند. مثلاً:
(eq 'a 'a)                 ; => T
(eq (list 1 2) (list 1 2)) ; => NIL
  • eql: برابری را چنین تعریف می‌کند: دو عدد با نوع و مقدار یکسان، یا دو کاراکتری که یکسان‌اند، و برای هر چیز دیگری اگر eq باشند. مثلاً:
(eql 1 1)     ; => T
(eql #\a #\a) ; => T
(eql 1 1.0)   ; => NIL
(eql #\a #\A) ; => NIL
  • equal: برابری را چنین تعریف می‌کند: دو لیست equal هستند اگر هر عنصرشان هم equal باشد؛ دو رشته equal هستند اگر هر عنصرشان eql باشد؛ دو آرایه equal هستند اگر eq باشند؛ هر چیز دیگری equal است اگر eql باشد. مثلاً:
(equal (list 1 2) (list 1 2)) ; => T
(equal #(1 2) #(1 2))         ; => NIL
(equal "foo" "foo")           ; => T
  • equalp: برابری را چنین تعریف می‌کند: رشته‌ها و کاراکترها بدون در نظر گرفتن بزرگی و کوچکی حروف مقایسه می‌شوند؛ اعداد با مقداری تبدیل نوع مقایسه می‌شوند؛ لیست‌ها و آرایه‌ها equalp هستند اگر هر عنصرشان هم equalp باشد، ساختارها اگر از یک نوع باشند و همه‌ی اسلات‌هایشان equalp باشد، و جدول‌های هش اگر کلیدها و مقدارهایشان equalp باشند. مثلاً:
(equalp "foo" "FoO")                          ; => T
(equalp 3 3.0)                                ; => T
(equalp (make-a-structure :slot1 1 :slot2 2) 
        (make-a-structure :slot1 1 :slot2 2)) ; => T

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

لزلی، موجود فضایی Lisp، مشغول برنامه‌نویسی ربات هزارتوی خود است (کاری که موجودات فضایی جوان Lisp معمولاً انجام می‌دهند) و کمی کمک لازم دارد. هزارتو از مجموعه‌ای از اتاق‌ها تشکیل شده است. در هر اتاق چند در هست که می‌توان از میان آن‌ها انتخاب کرد. هر در به یک کلید نیاز دارد. اگر کلید اشتباهی را برای دری استفاده کنید، احتمالاً منفجر می‌شود (نگران ربات نباشید؛ آسیبی نمی‌بیند، اما پاک کردن جای سوختگی برای لزلی واقعاً آزاردهنده است). اگر ربات هیچ کلیدی نداشته باشد که دری را باز کند، آن‌وقت کل اتاق منفجر می‌شود! (باز هم نگران نباشید؛ آسیبی نمی‌رساند، اما تمیز کردنش آزاردهنده است.)

کاری که برای کمک به لزلی باید انجام دهید این است که کلید درست هر اتاق را به ربات بدهید. ربات کلید را به ترتیب روی هر در اتاق امتحان می‌کند و از نخستین دری که باز شود عبور می‌کند.

هر تست نماینده‌ی یک هزارتو است و هر assertion یکی از اتاق‌ها. باید توابع محمولی بنویسید که وقتی آرگومان‌ها برابرند به T و وقتی برابر نیستند به NIL ارزیابی شوند.

1. هزارتوی برابری شیءها

این هزارتو فقط یک اتاق دارد. باید تابعی بنویسید که بتواند تشخیص دهد آیا دو شیء همان شیء واحد هستند یا نه.

برای مثال:

a                         ; => "pizza"
b                         ; => "pizza"

(key-object-identity a a) ; => T
(key-object-identity a b) ; => NIL

چون با اینکه a و b به نظر یکسان می‌آیند، همان شیء واحد نیستند.

2. هزارتوی اعداد

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

برای مثال:

a                                    ; => 13
b                                    ; => 13.0
c                                    ; => 13

(key-numbers a b)                    ; => NIL
(key-numbers a c)                    ; => T
(key-numbers-of-different-types a b) ; => T

چون با اینکه a و b از نظر عددی یکسان‌اند، از دو نوع متفاوت هستند.

3. هزارتوی کاراکترها

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

برای مثال:

a                                       ; => #\X
b                                       ; => #\x
c                                       ; => #\X

(key-characters a b)                    ; => NIL
(key-characters a c)                    ; => T
(key-characters-case-insensitively a b) ; => T

چون فقط محمول‌های برابری خیلی آسان‌گیر هستند که هنگام مقایسه‌ی دو کاراکتر، بزرگی و کوچکی حروف را نادیده می‌گیرند.

4. هزارتوی رشته‌ها

مثل هزارتوی کاراکترها، اتاق اول به کلیدی نیاز دارد که بررسی کند آیا دو رشته با هم برابرند؛ اتاق دوم به کلیدی نیاز دارد که مقایسه را بدون توجه به بزرگی و کوچکی حروف انجام دهد.

برای مثال:

a                                    ; => "pizza"
b                                    ; => "PIZZA"
c                                    ; => "pizza"

(key-strings a b)                    ; => NIL
(key-strings a c)                    ; => T
(key-strings-case-insensitively a b) ; => T

چون فقط محمول‌های برابری خیلی آسان‌گیر هستند که هنگام مقایسه‌ی دو رشته، بزرگی و کوچکی حروف را نادیده می‌گیرند.

5. هزارتوی consها

این هزارتوی بزرگی است با اتاق‌های فراوان. هر اتاق به کلیدی نیاز دارد که بررسی کند آیا consها چیزهایی را در خود دارند که با هم برابرند. هر اتاق برابری consهایی با محتویات متفاوت را بررسی می‌کند: نمادها، اعداد، کاراکترها؛ و بعضی از آن‌ها به تعریف‌های برابری انعطاف‌پذیرتری برای این محتویات نیاز دارند.

برای مثال:

syms-a                                     ; => (left . right)
syms-b                                     ; => (up . down)
syms-c                                     ; => (left . right)
chars-a                                    ; => (#\x . #\y)
chars-b                                    ; => (#\x . #\Y)
chars-c                                    ; => (#\x . #\y)
nums-a                                     ; => (13 . 23)
nums-b                                     ; => (13 . 23.0)
nums-c                                     ; => (13 . 23)


(key-conses-of-symbols syms-a syms-b)      ; => NIL
(key-conses-of-symbols syms-a syms-c)      ; => T

(key-conses-of-characters chars-a chars-b) ; => NIL
(key-conses-of-characters chars-a chars-c) ; => T
(key-conses-of-characters-case-insensitively 
  chars-a chars-b)                         ; => T

(key-conses-of-numbers nums-a nums-b)      ; => NIL
(key-conses-of-numbers nums-a nums-c)      ; => T
(key-conses-of-numbers-of-different-types 
  nums-a nums-b)                           ; => T

چون حتی با یک محمول برابری که داخل یک cons را هم بررسی می‌کند، ممکن است هنوز به‌قدر کافی آسان‌گیر نباشد که هنگام مقایسه، بزرگی و کوچکی حروف یا نوع عددی را نادیده بگیرد.

6. هزارتوی آرایه‌ها

این هزارتو ساده‌تر است و فقط دو اتاق دارد. اتاق اول به کلیدی نیاز دارد که بررسی کند آیا آرایه‌ها همان آرایه‌های واحد هستند. اتاق دوم به کلیدی نیاز دارد که محتویات آرایه‌ها را بررسی کند.

برای مثال:

a                        ; => #[13 23]
b                        ; => #[13 23]
c                        ; => #[13 23.0]

(key-arrays a b)         ; => NIL
(key-arrays a c)         ; => NIL
(key-arrays-loosely a b) ; => T
(key-arrays-loosely a c) ; => T

چون فقط بررسی‌کننده‌های برابری خیلی آسان‌گیر هستند که محتویات آرایه را هم بررسی می‌کنند.

ویرایش از طریق GitHub این لینک در پنجره یا زبانه‌ی جدیدی باز می‌شود
Common Lisp Exercism

آماده‌اید کلید مقایسه را شروع کنید؟

در Exercism ثبت‌نام کنید تا Common Lisp را همراه با 27 مفهوم92 تمرین و مربی‌گری انسانی واقعی یاد بگیرید و در آن استاد شوید، همه‌ی این‌ها رایگان.