Pouya Yarandi
Pouya Yarandi
خواندن ۸ دقیقه·۵ سال پیش

توابع Higher Order برای آرایه‌ها در سوییفت

سلام. من دوباره با یه مقاله سوییفتی دیگه اومدم. تو این مقاله میخوام راجع به Higher Order Function ها در سوییفت صحبت کنم. بذارید تو شروع صحبتم بگم که اصلا Higher Order Function چیه. به طور کلی تابعی که ورودی یا خروجیش یه تابع دیگه باشه بهش میگن Higher Order Function. بعضی از این توابع، توابعی هستن که روی آرایه ها اعمال میشن. ما توی این مقاله میخوایم به چندتا از توابع Higher Order برای آرایه های سوییفتی بپردازیم. توابعی که ما میخوایم بررسیشون کنیم توابع زیر هستن:

  • تابع sorted: برای مرتب سازی آرایه
  • تابع filter: برای جستجو توی آرایه
  • تابع map: برای نگاشت عنصرهای آرایه
  • تابع compactMap: اینم برای نگاشت عناصره ولی با یه تفاوت کوچک که توضیح میدم
  • تابع reduce: برای اعمال محاسبه روی عناصر آرایه و در نهایت برگشت یه مقدار به ازای کل عناصر

اینم بگم که اگه هر کدوم از این توابع رو توی ایکس کد ببینید متوجه میشید که ورودیشون یه closure هست و به این دلیل جزو Higher Order ها محسوب میشن. خب موافقین بریم سراغ اولین تابع؟


تابع sorted

این تابع برای مرتب سازی آرایه استفاده میشه. فرض کنید یه آرایه از اعداد داریم و میخوایم اونو به صورت صعودی و نزولی مرتب کنیم. من این کارو برای هر کدوم از این مرتب سازی ها با یه سینتکس انجام میدم تا شما انواع تعریف closure برای این تابع رو ببینید:

let unsortedArray = [20, 5, 26, 40, 30, 50, 19] let sortedArrayAsc = unsortedArray.sorted { (first, second) -> Bool in return first < second } // [5, 19, 20, 26, 30, 40, 50]

خب همونطور که میبینید من برای ورودی تابع مرتب سازی از یه closure استفاده کردم که ورودیش دوتا مقدار از نوع داده عناصر آرایه مون هست و خروجی هم از جنس Bool یا همون بولینه. توی این closure ما در واقع تعریف میکنیم که به ازای چه مقادیری از هر دو عنصر (در مقایسه با هم) مقدار true برمیگرده. توی همین مثال ما گفتیم در شرایطی مقدار ture برمیگرده که عنصر اول از دوم کوچیکتر باشه. در نتیجه آرایه ما به صورت صعودی مرتب میشه یعنی به ازای هر دو عنصری که در نظر بگیرین اولی کوچیکتر از دومی هست.

برای تابع مرتب سازی میشه به یه شکل دیگه هم closure رو معرفی کرد که شاید یکم خوانایی پایینتری داشته باشه اما کوتاهتره و کدنویسی کمتری داره و اونم به شکل زیره:

let sortedArrayDesc = unsortedArray.sorted(by: { $0 > $1 }) // [50, 40, 30, 26, 20, 19, 5]

اگه دقت کنید closure من خیلی کوچیکتر شد. اول اینکه دیگه لازم نیست آرگومان های closure رو تعریف کنم. دوم اینکه لازم نیست چیزی return شه و فقط کافیه statement لازم رو بنویسید. به جای آرگومان ها هم از $ و شماره آرگومان استفاده میکنین. توی خط کد بالا من آرایه ای که بالاتر تعریف کرده بودم به صورت نزولی مرتب کردم.


تابع filter

این تابع کارش اینه که توی آرایه ما بر اساس شرطی که تعریف میکنیم بین عناصر آرایه جستجو کنه. چیزایی که در مورد تابع sorted گفتم برای این تابع و بقیه توابعم صادقه و اصول تعریف closure توی همه این توابع یکسانه. برای مثال من میخوام یه آرایه از مختصات (دوتایی های x و y) تعریف کنم و توی اون دوتا فیلتر اعمال کنم. یکی مختصات با y کمتر از ۱۶ و یکی مختصات با x کمتر از ۱۰:

let targetArray = [ (x: 10, y: 20), (x: 5, y: 8), (x: 7, y: 15), (x: 12, y: 31), (x: 3, y: 4) ] let pointsWithYLessThan16 = targetArray.filter { (tuple) -> Bool in return tuple.y < 16 } // [(x: 5, y: 8), (x: 7, y: 15), (x: 3, y: 4)] let pointsWithXGreaterThan10 = targetArray.filter({ $0.x > 10 }) // [(x: 12, y: 31)]

تابع map

این تابع عملکردش اینه که بر اساس مقدار بازگشتی توی closure همه عناصر آرایه رو به یک مقدار دیگه نگاشت میکنه. دیگه صحبتای تکراری که توی دوتا بخش قبلی انجام دادم تکرار نمیکنم فقط بدونید که همون شیوه های تعریف closure رو داریم. برای دیدن عملکرد این تابع من میخوام از همون آرایه مثال قبل استفاده کنم. فرض کنید آرایه ای از مختصات دارم و میخوام مقدار هر کدوم اونارو به فاصله اقلیدسی و فاصله منهتن اونها از مبدا مختصات (نقطه با مختصات صفر) نگاشت کنم. اینکه فاصله اقلیدسی و منهتن چیه بماند؛ چون موضوع بحث ما نیست. فقط همینقدر بگم که فاصله اقلیدسی جذر جمع توان دوی x و y هست و فاصله ی منهتن هم از جمع x و y نقطه بدست میاد:

let euclideanDistances = targetArray.map { (tuple) -> Double in return sqrt(pow(Double(tuple.x), 2) + pow(Double(tuple.y), 2)) } // [22.360679774997898, 9.433981132056603, 16.55294535724685, 33.24154027718932, 5.0] let manhattanDistances = targetArray.map { $0.x + $0.y } // [30, 13, 22, 43, 7]

یه نکته ریزم همینجا بگم در مورد شکل اختصاری تعریف closure اونم اینکه اگه قرار نیست خود تابع Higher Order که از اون استفاده میکنید توسط تابع دیگه ای فراخوانی بشه میتونید پرانتزهای دور closure رو نذارید. دقیقا مثل مثال بالا.


تابع compactMap

این تابعم دقیقا مثل تابع map عمل نگاشت عناصر رو انجام میده. منتها با این تفاوت که اگر تو این نگاشت عنصری nil شد اونو توی آرایه بازگشتی نمیاره. برای مثال فرض کنید من آرایه ای از String دارم میخوام عناصر اون رو نگاشت کنم به URL. و از اونجایی که init یک شی از جنس URL به وسیله String برای من یک URL آپشنال میسازه (یعنی ممکنه مقدارش nil باشه) از تابع compactMap به جای map استفاده میکنم:

let websites = [ &quothttps://google.com&quot, &quotit's not an address!&quot, &quothttps://apple.com&quot ] let validUrls = websites.compactMap { URL(string: $0) } // [https://google.com, https://apple.com]

تابع reduce

وظیفه این تابع اینه که با استفاده از closure تعریف شده میاد یه محاسبه کلی رو روی همه عناصر از اول تا اخر انجام میده و نهایتا یه result برمیگردونه. بذارید با یه مثال همه چیز رو روشن تر کنم. فرض کنید یه آرایه از بولین ها داریم و میخوایم ببینیم که and همه اونا true میشه یا نه. گرچه برای چنین کاری راه های دیگه هم هست ولی من چون میخوام از reduce استفاده کنم باید بیام بگم همه عناصر رو با هم and کن و در نهایت نتیجه رو برگردون. این کارو این طوری انجام میدم:

let someBooleans = [true, false, true, true] let isAllTrue1 = someBooleans.reduce(true) { (result, item) -> Bool in return result && item } // false

توی این تابع تعریف closure یه مقدار از باقی توابع متفاوته. اینجا ما یه result و یه item داریم که به ترتیب بیانگر مقدار بدست آمده از اعمال محاسبات روی عناصر قبلی و عنصر فعلی هستن. و مقدار برگشتی روی عنصر بعدی اعمال میشه تا آخر که به عنوان خروجی تابع ما return میشه. علاوه بر این، تابع reduce یه آرگومان دیگه هم به جز closure داره که اونم مقدار اولیه result هست. یعنی result ما قبل از اعمال محاسبات روی عنصر صفرم آرایه رو مشخص میکنه.

ولی اگه توی ایکس کد reduce رو بنویسید متوجه میشید که دو نوع ورودی میتونه بگیره این تابع. تفاوت این دوتا چیه به نظرتون. الان براتون میگم. توی نوع بالا result یه آرگومانه و همونطور که احتمالا میدونید آرگومان های closure ها به طور پیشفرض ثابت هستن. یعنی مقدارشون قابل تغییر نیست. برای همینم میبینید که حتما باید نتیجه محاسبه return بشه که حالا توی مثال ما خروجی Bool بود ولی هر نوع داده دیگه ای هم میتونه باشه. اما توی مثال پایین که یه جورایی بازنویسی همون بالایی هست میبینید که آرگومان result از نوع inout تعریف شده و در نتیجه امکان تغییر مقدار اون رو داریم. برای همین میبینید که توی مثال پایین نیازی به return کردن مقدار نداریم چون مستقیما تغییرات رو روی result اعمال میکنیم:

let allTrueBools = [true, true, true, true] let isAllTrue2 = allTrueBools.reduce(into: true) { (result, item) in result = result && item } // true

استفاده ترکیبی از توابع Higher Order

شما میتونید به صورت ترکیبی هم از توابعی که بالا معرفی کردیم استفاده کنید. مثلا تابع زیر میاد عناصر بزرگتر از 20 رو به صورت مرتب شده برمیگردونه:

let unsortedArray = [20, 5, 26, 40, 30, 50, 19] let sortedGreaterThan20 = unsortedArray .filter({ $0 > 20 }) .sorted(by: { $0 < $1 }) // [26, 30, 40, 50]

تمرین

خب برای اینکه مطمئن شید که خوب یاد گرفتین دو تا تمرین زیر رو توی ایکس کد بزنید و بعد با جواب چک کنید کد خودتون رو.

// array for question 1 let addresses = [ &quothttps://mail.google.com&quot, &quotit's not an address!&quot, &quothttps://apple.com&quot, &quotjust a fake address which is sooo long&quot ] // array for question 2 let values = [&quotPouya&quot, &quot40&quot, &quot67&quot, &quotPayam&quot, &quot93&quot, &quotNima&quot, &quot21&quot, &quot74&quot, &quot!&quot, &quot36&quot]
  1. از بین آدرس های موجود در آرایه رشته های زیر بزرگترین آدرس URL معتبر (آدرسی که قابل نگاشت به URL باشد) را پیدا کنید. در صورتی که هیچ آدرس معتبری وجود نداشت عبارت No valid address رو توی کنسول چاپ کنید. (راهنمایی: برای پیدا کردن بزرگترین کافیه آرایه رو نزولی مرتب کنید و عنصر اول رو با مشخصه first از آرایه برگردونید.)
  2. مجموع مقادیری را بیابید که اولا عددی هستن و ثانیا بزرگتر از ۵۰ باشن.

سعی کنید خودتون و بدون دیدن جواب کد سوالات بالا رو بزنید و بعدش جوابارو چک کنید. میتونیدم بعد از اینکه تلاشتون رو کردین کدای پایین رو توی ایکس کدتون کپی کنید و نتیجه ش رو ببینید.


پاسخ تمرینات

// answer 1 let longestValidAddress = addresses .compactMap({ URL(string: $0) }) .map({ $0.absoluteString }) .sorted(by: { $0.count > $1.count }) .first print(longestValidAddress ?? &quotNo valid address&quot) // answer 2 let sumOfNumericsGreaterThan50 = values .compactMap({ Int($0) }) .filter({ $0 > 50 }) .reduce(0) { (result, item) -> Int in return result + item } print(sumOfNumericsGreaterThan50)

نتیجه گیری

توی این مقاله با چند تا از Higher Order Function های پرطرفدار زبان سوییفت آشنا شدیم و نحوه نوشتن closure برای هر کدوم از اونا رو به شکل فرمال و اختصاری با هم دیدیم. خوندن این مقاله میتونه کمک کنه تا از این به بعد هم محاسبات روی آرایه ها رو در سوییفت با کدهای تمیزتر و کوتاه تری انجام بدید و هم درک خوبی از ساختار closure ها و توابع Higher Order داشته باشید. امیدوارم این مقاله براتون مفید باشه و مثل همیشه لایک فراموش نشه لطفا :)

سوییفتswiftioshigher order functions
شاید از این پست‌ها خوشتان بیاید