در این مطلب، ابتدا مساله رنگآمیزی گراف و انواع آن و سپس، رنگ آمیزی گراف به روش حریصانه مورد بررسی قرار گرفته است. همچنین، الگوریتم مذکور در زبانهای برنامهنویسی ++C و «جاوا» (Java) پیادهسازی شده است. مساله رنگ آمیزی گراف، به بحث تخصیص رنگ به راسهای گراف بر اساس محدودیتهای مشخصی میپردازد. انواع مسائل رنگآمیزی گراف در ادامه بیان شدهاند.
رنگآمیزی راسها: این مساله، یکی از معروفترین مسائل رنگآمیزی گراف است. در مساله رنگآمیزی راسها، هدف آن است که با تعداد m رنگ داده شده، راسهای گراف به گونهای رنگ شوند که هیچ دو راس مجاوری دارای رنگ مشابه نباشند. دیگر مسائل رنگآمیزی گراف مانند رنگآمیزی یالها (هیچ راسی بین دو یال با رنگ مشابه وجود نداشته باشد) و رنگآمیزی چهره (رنگآمیزی نقشه جغرافیایی) را میتوان به مساله رنگآمیزی راسها تبدیل کرد. پیش از این، در مطلب «حل مساله رنگ آمیزی گراف با الگوریتم پس گرد»، این مساله با استفاده از الگوریتم «پسگرد» (Backtracking) حل شد و در این مطلب، این مساله با استفاده از الگوریتم «حریصانه» (Greedy) حل خواهد شد.
شماره کروماتیک: کوچکترین عدد از (تعداد) رنگها که برای رنگآمیزی گراف G مورد نیاز است، شماره کروماتیک نامیده میشود. برای مثال، گراف زیر را میتوان حداقل با 3 رنگ، رنگآمیزی کرد.

مساله پیدا کردن عدد کروماتیک برای یک گراف داده شده، «انپی کامل» (NP-Complete) است.
مساله رنگآمیزی گراف، کاربردهای بسیار زیادی دارد که به برخی از آن ها در زیر اشاره شده است.
برنامهریزی روی جدول زمانی: فرض میشود که قرار است برنامه امتحانات یک دانشگاه تنظیم شود. لیستی از موضوعات مختلف وجود دارد و دانشجویان گوناگون دروس مختلف را ثبت کردهاند. بسیاری از دانشجویان دروس مشترکی را اخذ کردهاند (در واقع برخی از دروس، دانشجویان مشترکی دارند). چطور میتوان امتحانات را به گونهای برنامهریزی کرد که هیچ دو امتحانی که یک دانشآموز مشترک دارند، در یک زمان واحد نباشند؟ حداقل بازههای زمانی مورد نیاز برای برگزاری کلیه امتحانات چه میزان است؟ این مساله را میتوان به عنوان مساله گراف نشان داد که در آن، هر راس یک موضوع و هر یال بین دو راس به معنی وجود دانشآموز مشابه است. پس برنامهریزی جدول زمانی امتحانات یک مساله رنگآمیزی گراف است که در آن حداقل بازههای زمانی برابر با عدد کروماتیک گراف است.
تخصیص فرکانس رادیویی موبایل: هنگام تخصیص فرکانسهای رادیویی به برجها، این شرط وجود دارد که فرکانسهای تخصیص پیدا کرده به کلیه برجها در موقعیت یکسان، باید متفاوت باشند. چگونه میتوان فرکانسها را با این محدودیت تخصیص داد؟ حداقل تعداد فرکانسهای مورد نیاز چند تا است؟ این مساله نیز نمونهای از مسائل رنگآمیزی گراف است که در آن، هر برج نشانگر یک راس و یال بین دو برج نشانگر آن است که آنها در رِنج یکدیگر قرار دارند.
سودوکو: سودوکو نیز نوعی از مسائل رنگآمیزی گراف محسوب میشود که در آن، هر سلول نشانگر یک راس است و اگر راسها در سطر، ستون یا بلوک مشابهی باشند، یک یال بین آنها وجود دارد.
تخصیص ثبات: در بهینهسازی کامپایلر، «تخصیص ثبات» (Register Allocation) فرآیند تخصیص تعداد بالایی از متغیرهای برنامه هدف در تعداد کمی از ثباتهای «واحد پردازش مرکزی» است. این مساله نیز از جمله مسائل رنگآمیزی گراف است.
گراف دو بخشی: میتوان با رنگآمیزی یک گراف تنها با استفاده از دو رنگ، بررسی کرد که آیا گراف دو بخشی است یا خیر. اگر بتوان گراف را تنها با دو رنگ، رنگآمیزی کرد، گراف دو بخشی است.
رنگآمیزی نقشه: رنگآمیزی نقشههای جغرافیایی کشورها یا شهرهای یک کشور به صورتی که در آن هیچ دو کشور یا شهر همجواری دارای رنگ مشابه نباشند نیز از جمله مسائل رنگآمیزی گراف است. بر اساس «قضیه چهار رنگ» (Four Color Theorem)، برای رنگآمیزی هر نقشه به صورتی که هیچ دو کشور/شهر مجاوری دارای رنگ مشابه نباشند، فقط چهار رنگ کافی است. در نقشههای سادهتر، تنها سه رنگ کافی است و از رنگ چهارم برای برخی نقشهها استفاده میشود.
سایر کاربردها: مساله رنگآمیزی گراف، کاربردهای متعدد و متنوع دیگری نیز دارد. برای مثال، شبکهای از هزاران سرور وجود دارد که از آنها برای توزیع محتوا در اینترنت استفاده میشود. آنها هر هفته نرمافزارها یا به روز رسانیهای جدیدی را نصب میکنند. به روز رسانیها نمیتواند روی همه سرورها به طور همزمان اتفاق بیفتد زیرا ممکن است فعالیت سرور طی مراحل نصب متوقف شود. همچنین، نباید هر بار تنها یکی از سرورها به روز رسانی شود زیرا این کار زمان زیادی میبرد. بنابراین، سرورهای زیادی هستند که نباید همزمان فعالیت آنها متوقف شود زیرا فعالیتهای حیاتی انجام میدهند و در عین حال به دلیل زمانبر بودن پروسه بیان شده برای تک به تک آنها به طور جداگانه، این کار نیز ممکن نیست. مساله بیان شده، حالت متداولی از کاربرد مساله رنگآمیزی گراف برای انجام زمانبندی است. شایان توجه است که برای گرافی با ۷۵۰۰۰ گره، تنها ۸ رنگ برای رنگآمیزی کافی است. بنابراین، در مساله مربوط به سرورها نیز در این شرایط، میتوان کار به روز رسانی را در هشت گذر انجام داد.
در مساله رنگآمیزی راسهای گراف، چنانکه پیش از این نیز بیان شد، هدف رنگ کردن راسها به شیوهای است که هیچ دو راس مجاوری دارای رنگ مشابه نباشند. هیچ الگوریتم کارآمدی برای رنگآمیزی گراف با حداقل رنگهای ممکن وجود ندارد و این مساله از جمله مسائل «انپی کامل» (NP Complete) محسوب میشود. «الگوریتمهای تقریبی» (Approximate Algorithms) گوناگونی برای حل این مساله وجود دارند. در ادامه، روش حریصانه برای مساله رنگآمیزی گراف مورد بررسی قرار گرفته است. این روش تضمین نمیکند که کمترین تعداد رنگها را استفاده کند، اما حد بالا در تعداد رنگها را تضمین میکند. الگوریتم پایه هرگز نمیتواند بیش از d+1 رنگ که در آن d برابر با درجه بیشینه راسها در گراف داده شده است را استفاده کند.
Coloring of graph 1 Vertex 0 ---> Color 0 Vertex 1 ---> Color 1 Vertex 2 ---> Color 2 Vertex 3 ---> Color 0 Vertex 4 ---> Color 1 Coloring of graph 2 Vertex 0 ---> Color 0 Vertex 1 ---> Color 1 Vertex 2 ---> Color 2 Vertex 3 ---> Color 0 Vertex 4 ---> Color 3
پیچیدگی زمانی الگوریتم بالا در بدترین حالت برابر با (O(V2 + E است.
الگوریتم بالا، از کمترین تعداد ممکن رنگها استفاده نمیکند. همچنین، تعداد رنگهای انتخاب شده گاهی بستگی به ترتیب راسهای پردازش شده دارند. برای مثال، دو گراف زیر را میتوان در نظر گرفت. در گراف پایین، راسهای 3 و 4 جا به جا شدهاند. اگر راسهای ۰، 1، 2، 3 و 4 در گراف بالا در نظر گرفته شوند، میتوان گراف را با استفاده از 3 رنگ، رنگآمیزی کرد. اما در صورتی که راسهای ۰، 1، 2، 3 و 4 در گراف پایین در نظر گرفته شوند، نیاز به چهار رنگ خواهد بود.

بنابراین، ترتیبی که بر اساس آن راسها انتخاب میشوند، حائز اهمیت است. پژوهشگران گوناگون، راههای مختلفی را پیدا کردهاند که نسبت به الگوریتم میانگین، پاسخ بهتری دارد. از جمله شناخته شدهترین این روشها، الگوریتم «وِلش-پاول» (Welsh–Powell Algorithm) است که راسها را به ترتیب نزولی درجات در نظر میگیرد.
در اینجا، d حداکثر درجه در گراف داده شده است. به دلیل آنکه d حداکثر درجه است، یک راس نمیتواند به بیش از d راس متصل شده باشد. هنگامی که یک راس رنگآمیزی میشود، حداکثر d رنگ ممکن است توسط همسایگان آن راس مورد استفاده قرار گرفته باشند. برای رنگآمیزی این راس، نیاز به انتخاب کمترین تعداد رنگها است که توسط راسهای مجاور استفاده نشدهاند. اگر راسها با اعداد 1، 2 و … رنگآمیزی شده باشند، مقدار کوچکترین عدد باید چیزی بین 1 تا d+1 باشد (توجه به این نکته لازم است که اعداد در حال حاضر توسط راسهای مجاور گرفته شدهاند). این مساله را میتوان از طریق استنتاج نیز ثابت کرد.
ممکن است تاکنون خواسته باشید اپلیکیشنی بنویسید که کاربرانتان بتوانند آن را در «حالت کیوسک» (Kiosk Mode) اجرا کنند تا همچنان امکان اجرای اپلیکیشنهای دیگر را روی دستگاههای خود داشته باشند. هدف از این کار آن نیست که اپلیکیشنهای ثانویه از طریق SDK یا دیگر ارتباطهای درون پردازشی با هم یکپارچه شوند، بلکه قصد ما صرفاً این است که بتوانیم این اپلیکیشنهای ثانویه را از داخل اپلیکیشن اولیه باز یا لانچ کنیم. در واقع این اپلیکیشن یک لانچر اندروید خواهد بود.
شاید بهتر باشد ابتدا مفهوم حالت کیوسک را توضیح دهیم. زمانی که یک اپلیکیشن در حالت کیوسک اجرا میشود، کاربر نمیتواند از اپلیکیشن خارج شود. در این حالت صفحه همواره روشن است. اپلیکیشن همواره در پیشزمینه است. نوار نوتیفیکیشن معمولاً پنهان یا غیرفعال شده و تقریباً همیشه دکمه Home غیرفعال میشود. این وضعیت عملاً موجب میشود که کاربر اپلیکیشن همواره درون همان اپلیکیشن منفرد باقی بماند. بنابراین با این توضیح احتمالاً اکنون میتوانید متوجه شوید که چرا لازم است اپلیکیشنهای ثانویه از داخل اپلیکیشن اصلی اجرا شوند.
لانچ کردن اپلیکیشنهای دیگر از داخل یک اپلیکیشن روی اندروید، خود چالش بزرگی محسوب نمیشود. قطعه کدهای زیادی در بستر آنلاین وجود دارند که شیوه اجرای این کار را مدیریت میکنند. با این حال، یافتن مثال پایداری از شیوه پیادهسازی قابلیتی مانند این و همزمان پیروی از اصولی که گوگل برای توسعه اندروید توصیه میکند، مانند نوشتن کد در کاتلین با استفاده از الگوی معماری MVVM، استفاده از «اتصال داده» (data binding) و بهرهگیری از «کامپوننتهای معماری اندروید» (AAC) مانند ViewModel و Navigation در صورت امکان، کار سادهای محسوب نمیشود.
بنابراین بدون هیچ توضیح اضافی در ادامه مثال سادهای از طراحی سطح بالای یک قابلیت لانچر اپلیکیشن را میبینید که در ادامه هر یک از کامپوننتهای شمارهگذاری شده در نمودار نیز توضیح داده شدهاند. چند کلاس کمکی مانند ابزار Logger به جهت کاستن از پیچیدگی حذف شدهاند، اما شما میتوانید کد کامل پروژه را در این ریپوی گیتهاب (+) مشاهده کنید.

هدف از هر کامپوننت شمارهگذاری شده در نمودار فوق در ادامه توضیح داده شده است.
کامپوننت AppLauncherDialog، کادر محاورهای که برای کاربر نمایش پیدا میکند را شامل میشود و مجموعه اپلیکیشنهایی که میتوان اجرا کرد را ارائه میکند. این کلاس از اتصالهای داده استفاده میکند و AppLauncherViewModel را «مشاهده» (Observe) میکند، به طوری که تغییرات دادهها بیدرنگ در UI بازتاب مییابند. کلاس این کادر محاورهای در زمینه همه اکشنها (مانند لانچ کردن اپلیکیشن) مختصر است و از سوی «مدل نما» (View Model) مدیریت میشود. در نهایت باید اشاره کرد که در پروژه نمونه این کادر محاوره از داشبورد قالببندی باز میشود و به منظور نمایش ناوبری از فرگمان به یک کادر محاورهای با کامپوننت معماری Navigation طراحی شده است.
کلاس AppLauncherViewModel شامل دادههایی است که درون نما (AppLauncherDialog) عرضه میشوند. دادههای ارائه شده از طریق واداشتن AppLauncherViewModel برای تماشای مدل داده (AppLauncherModel) به دست آمدهاند. تا پیش از ارائه، دادهها انتقال مییابند و هم قابلیت تعامل با اکشن مییابند و هم قابلیت عرضه پیدا میکنند. در مثال ما این وضعیت شامل استخراج اطلاعات لانچ از هر یک از پکیجهای اپلیکیشن در مدل داده است و بدین ترتیب از ایجاد نسخه تکراری پکیجها خودداری میشود و هر پکیجی که روی دستگاه نباشد حذف میشود. در نهایت اپلیکیشنها بر مبنای نام مرتبسازی میشوند. قطعه کد زیر شیوه استخراج اطلاعات اپلیکیشن از طریق نام پکیج را نمایش میدهد.
AppLauncherModel شامل دادههای از پیش تبدیل یافته یا خامی است که از سوی AppLauncherViewModel مورد مشاهده (Obderve) قرار میگیرند و در نهایت در AppLauncherDialog ارائه میشوند. این دادهها صرفاً مجموعهای از نامهای پکیج محسوب میشوند که از سوی لانچر مورد پشتیبانی قرار میگیرند. همچنین فلگی وجود دارد که تعیین میکند آیا اپلیکیشن باید به صوت نرمال لانچ شود یا باید آن را در حالت «آزاد» (free form) یا «شناور» (floating) لانچ کنیم. این حالتها صرفاً روی دستگاههایی پشتیبانی میشوند که اندروید Nougat و بالاتر را اجرا میکنند. البته متأسفانه همه دستگاههایی که اندروید Nougat یا بالاتر را اجرا میکنند، امکان اجرای این حالت را ندارند.
قابلیت لانچر اپلیکیشن باید بداند کدام اپلیکیشنها برای کاربر نمایش پیدا میکنند. این اطلاعات میتواند از چندین محل تأمین شود. همه این موارد به شیوهای که میخواهید این قابلیت را کنترل کنید وابسته هستند. آیا این قابلیت باید از سوی شما کنترل شود و یا میتوان کنترل آن را به کاربر سپرد؟ اگر این قابلیت از شما کنترل شود، در این صورت آیا دادهها برای نمونه در یک پایگاه داده ابری مانند Firebase Cloud Store موجود هستند یا نه. اگر قرار است از سوی کاربر کنترل شوند، در این صورت آیا اطلاعات در یک پایگاه داده محلی مانند SQLite (با بهرهگیری از Room) با حتی در بخش Shared Preferences موجود هستند و کاربر میتواند اپلیکیشنهای جدید را در زمان اجرا به این قابلیت اضافه کند یا نه. ما در مثال نسبتاً ساده خودمان ذخیره پیکربندی در قالب یک فایل JSON در دایرکتوری داده اپلیکیشن را انتخاب کردیم.
ما از کلاس سرویس AppLauncherFileMonitor برای نظارت بر فایل پیکربندی که قبلاً اشاره کردیم استفاده میکنیم و به این ترتیب زمانی که تغییری ایجاد شود، یک رویداد با استفاده از EventBus تحریک میشود. کلاس AppLauncherModel در این رویدادها ثبت نام کرده و مدل دادهها را در موارد مقتضی بهروزرسانی میکند و سپس همه این مسیر تا نما (AppLauncherDialog) را طی میکند. قطعه کد زیر روش نظارت بر فایل و تحریک رویداد برای گزارشگیری که در مثال مربوطه استفاده شده را نمایش میدهد:
اگر بخواهیم همه این موارد را کنار هم قرار دهیم، ما اکنون مشغول ساخت یک قابلیت لانچر در اپلیکیشن ابتدایی خود هستیم. تصویر زیر دموی لانچر اپلیکیشن نمونه را در حالت اجرا نمایش میدهد.
اساساً همه کار همین است و به این ترتیب این مقاله به پایان میرسد.
در این مقاله قصد داریم یک روش بهتر، آسانتر و کارآمدتر برای حفظ یکپارچگی در میان درخواستهای pull گیتهاب و حتی سفارشیسازی کامل آنها در یک تیم توسعه معرفی کنیم. امکان این کار به کمک قالب های گیت هاب فراهم شده است.
اگر تاکنون به عنوان یک توسعهدهنده نرمافزار با گیتهاب کارکرده باشید، چه این کار را به صورت انفرادی انجام داده باشید و چه بخشی از یک تیم بوده باشید، احتمالاً با مشکلاتی که در ادامه معرفی خواهیم کرد آشنا هستید.
نخستین مشکل رایج پیام کامیت بیمعنی است. این پیامها عموماً چندین بار ارسال میشوند، چون ما توسعهدهندگان تنبلی هستیم و عادت داریم تغییرات مختلف را با پیامهای یکسانی به گیتهاب پوش کنیم و هیچ توجه نداریم که آن تغییرات هیچ ارتباطی با پیام مربوطه ندارند.

نکته: اگر میخواهید برخی پیامهای کامیت جالب و سرگرمکننده را بخوانید، سری به این وبسایت (+) بزنید. با رفرش کردن صفحه پیامهای کامیت عجیبوغریبی را مشاهده خواهید کرد.
احتمالاً یک درخواست pull مانند درخواست زیر را در یک ریپو دیدهاید (و یا ارسال کردهاید). تلاش نکنید انکار کنید! نام و جزییات جهت حفظ حریم خصوصی پنهان شدهاند:

تصویر فوق یک درخواست pull واقعی روی گیتهاب است و پیامهای کامیت فراوانی دارد که معنی بسیار کمی دارند و برای کسی که به آنها نگاه میکند جزییات چندانی از آنچه در حال اتفاق افتادن است ارائه نمیکنند.
در این مرحله، ممکن است با خود فکر کنید، این کار در واقع شبیه همان کاری است که خودتان قبلاً دیدهاید (انجام دادهاید) و مشکل چندانی ایجاد نمیکند.
اگر یک توسعهدهنده منفرد هستید که روی پروژه شخصی خود کار میکنید، همواره از همه چیز در کدبیس اطلاع دارید و شاید حق با شما باشد. اما اگر در حال کار با تیمی متشکل از 10 یا 20 توسعهدهنده دیگر هستید و یک اپلیکیشن میسازید که از سوی صدها یا هزاران نفر استفاده میشود و توسعهدهندگان دیگر باید تغییراتی که شما ایجاد کردهاید را ببینند و تشخیص دهند که کد منطقی است یا نه و در ادامه هیچ مشکلی در شاخه master کد ایجاد نمیکند، در این صورت با یک مشکل بسیار بزرگ مواجه هستیم.
برای حل این مشکلها چند راهحل مختلف وجود دارند که ممکن است گذشته تجربه کرده باشید.
یک مجموعه نسبی از الزامات داریم که باید به هر درخواست pull جدید اضافه کنیم. این موارد چیزهایی مانند این هستند:
این راهحل به درستی کار میکند، مگر این که افراد اضافه کردن یکی یا چند مورد از این لینکها را فراموش کنند (یا به آن اهمیت ندهند) و یک سیستم صورتبندی برای اعضای جدید که به تیم ملحق میشوند نداشته باشیم تا بدانند قالب PR-هایشان باید چگونه باشد.
تلاش دوم ما کمی غیر دستیتر است. در این روش از یک قالب درخواست pull استفاده میشود که تیم میتواند در یک فایل txt. در اسلک نگهداری کند و آن را در یکی از کانالهای تیم توسعه پین کند. ظاهر آن میتواند به صورت زیر باشد:
Story
Jira, Pivotal Tracker, (link to where the story you worked on lives)
PR Branch
URL to the automated branch build for feature testing
Code Coverage
URL to the automated code coverage report run during the automated build process
e2e
URL to the automated end to end tests run against the feature branch
UX Approved
yes / no
Newman
yes / no
Related PR’s
TBD
اما در این حالت هم اعضای تیم باید بروند و دنبال قالب بگردند، قالببندی نشانهگذاری آن را کپی کنند و آن را درون همه درخواستهای pull وارد کرده و محتوای آن را نیز پر کنند.
این گزینه بهتری محسوب میشود، زیرا دستکم قالب تضمین میکند که احتمال فراموش کردن موارد مختلف کمتر است، اما همچنان مستعد خطا است. در اغلب موارد افراد تیم به سراغ PR-های قدیمی میروند، آن را باز میکنند و محتوای آن را کپی کرده و در PR جدید میچسبانند. البته این کار اشکالی ندارد، اما گاهی افراد فراموش میکنند که لینک استوری را بهروز کنند یا لینک سربهسر تست را درج نمیکنند و مشکل پیش میآید.
این بهترین راهحلی بود که اعضای تیم میتوانستند داشته باشند تا این که برخی توسعهدهندگان تازهکار به تیم ما اضافه شدند. زمانی که راهحل بهینه خود یعنی قالب PR را که استفاده میکردیم به اعضای جدید تیم نشان دادیم، آنها پرسیدند که چرا از قالبهای گیتهاب استفاده نمیکنیم؟ پاسخ ما این بود که: «چون هیچ یک از ما تاکنون چیزی در مورد آن نشنیدهایم!» در نهایت بدین ترتیب با این مفهوم به شدت جالب و البته آسان آشنا شدیم.
این گزینه راهحل بهتری محسوب میشود و در واقع راهحلی است که هرکس باید استفاده کند. قالبهای گیتهاب ابداع بسیار جالبی از سوی گیتهاب برای درخواستهای pull و همچنین issue-ها محسوب میشوند. از آنجا که PR-ها دغدغه هر تیم توسعهای هستند، به مستندات گیتهاب مراجعه کردیم تا در مورد آن بیشتر بدانیم:
زمانی که یک قالب درخواست pull به ریپازیتوری خود اضافه کنید، مشارکتکنندگان در پروژه به صورت خودکار محتوای قالب را در بدنه درخواست pull خود مشاهده خواهند کرد.
این همان چیزی است که هر تیم توسعهای نیاز دارد. این روش تضمین میکند که همه درخواستهای pull یک شکل هستند و لازم نیست اعضای تیم در مورد آن نگرانی داشته باشند. راهاندازی آن نیز بسیار آسان است.
با این که راهاندازی یک قالب PR بسیار سرراست است، اما برای سهولت هر چه بیشتر در ادامه مراحل آن را توضیح دادهایم.
گام 1: افزودن یک پوشه /github. به ریشه پروژه
مستندات این بخش بیان میکنند که میتوان یک قالب PR را در خود ریشه دایرکتوری پروژه یا در پوشهای به نام /docs یا /github. اضافه کرد. از آنجا که ما میخواهیم پوشه PR را در اغلب موارد که مشغول توسعه هستیم نادیده بگیریم. تصمیم گرفتیم پوشه قالب را درون پوشه /github. بسازیم:

گام 2: افزودن فایل نشانهگذاری pull_request_template.md درون پوشه
برای این که گیتهاب به صورت خودکار قالببندی را از فایل نشانهگذاری برای قالبهای PR بردارد، باید نام فایل را pull_request_template.md بگذارید. در ادامه نمونهای از قالبی که تیم ما استفاده میکند را میبینید:
Tracker ID: #ADD LINK TO PIVOTAL STORY
Unit tests completed?: (Y/N)
PR Branch #ADD LINK TO PR BRANCH
Code Coverage & Build Info #ADD LINK TO JENKINS CONSOLE
E2E Approved #ADD LINK TO PASSING E2E TESTS
Windows Testing #HAS WINDOWS BEEN TESTED?
Related PR #ADD ANY RELATED PULL REQUESTS
UX Approved #ADD UX APPROVAL IF NEEDED
گام 3: افزودن این فایلها به شاخه master و آمادهسازی همه درخواستهای PR
در ادامه تصویری از یک ریپازیتوری را میبینید که قالب درخواست PR به آن اضافه شده و یک درخواست pull نیز به عنوان نمونه ایجاد شده است:

افزودن یک قالب PR استاندارد به هر ریپازیتوری دقیقاً به همین سادگی است.
زمانی که با تیمی از توسعهدهندگان سر و کار داریم که روی محصولی حساس مشغول کار هستند، درخواستهای pull یک ضرورت حیاتی محسوب میشوند، اما به کمک امکان pull_request_templates در گیتهاب این مشکل میتواند به آسانی مرتفع شود.
مزیتهای قالبهای صورتبندی شده PR زیاد هستند، اما برخی از آنها که میتوان اشاره کرد میزان انعطافپذیری آنها است. مهم نیست که تیم شما چه چیزی را مورد بررسی و پذیرش قرار میدهد، یک درخواست Pull را میتوان به سادگی در هر ریپازیتوری جای داده و پیادهسازی کرد. این همان نوع از سهولت کار توسعهدهندهها است که برای صورتبندی موارد ضروری مانند PR-ها به آن نیاز داریم.
آن روزها که استفاده از قابلیتهای یادگیری ماشین تنها روی کلود امکان داشت و این مسئله نیازمند توان محاسباتی بالا، سختافزار پیشرفته و مواردی از این دست بود، گذشته است. امروزه دستگاههای موبایل بسیار قدرتمندتر شدهاند و الگوریتمهای ما نیز کارایی بالاتری یافتهاند. همه این موارد منجر به این نتیجه شده است که بهرهگیری از یادگیری ماشین روی دستگاههای همراه امکان یافته است و دیگر صرفاً یک نظریه عملی-تخیلی محسوب نمیشود. در این نوشته به بررسی عملی یک پروژه یادگیری ماشین در اندروید با استفاده از ML Kit برای «فایربیس» (Firebase) میپردازیم.
یادگیری ماشین امروزه روی دستگاههای گوناگون در همه جا استفاده میشود. نمونههایی از این مسئله به شرح زیر هستند:
این فهرست را میتوان بسیار ادامه داد، بنابراین به این مقدار اکتفا میکنیم. در این مقاله به بررسی شیوههای استفاده از یادگیری ماشین در اپلیکیشنهای اندروید و هوشمندتر ساختن آنها میپردازیم.
ML Kit برای فایربیس یک SDK موبایل است که گنجاندن ظرفیتهای یادگیری ماشین در اپلیکیشنها را برای توسعهدهندگان موبایل آسانتر ساخته است. این کیت شامل API های پیشساخته زیر است:
ML Kit در اصل و به زبان ساده، راهکاری است که با آن میتوانید از پیچیدگیها بهرهگیری از یادگیری ماشین در اپلیکیشنهای خود برای تلفنهای هوشمند بکاهید.
ما در این مقاله از ظرفیت تشخیص چهره ML Kit برای شناسایی چهرهها در یک تصویر استفاده میکنیم. بدین منظور تصاویر را از طریق دوربین میگیریم و اینترفیس را روی آن اجرا میکنیم.
برای استفاده از ML Kit لازم است که یک حساب «فایربیس» (Firebase) داشته باشید. اگر چنین حسابی ندارید به این صفحه (+) مراجعه کنید و یک حساب باز کنید. توجه داشته باشید در زمان نگارش مقاله، این حساب برای دسترسی ایرانیان مسدود بوده است و برای دسترسی به آن نیاز به استفاده از پراکسی وجود دارد.
زمانی که یک حساب کاربری روی فایربیس باز کردید، به این صفحه (+) بروید و روی add project کلیک کنید.
یک نام به پروژه خود بدهید و روی Create کلیک کنید. ممکن است چند ثانیه طول بکشد تا پروژه شما آماده شود. سپس در ادامه یک اپلیکیشن اندروید به پروژه خود اضافه میکنیم.
اندروید استودیو را باز کنید و یک پروژه جدید با یک اکتیویتی خالی اضافه کنید. سپس روی Tools -> Firebase کلیک کنید. بدین ترتیب دستیار فایربیس در پنل سمت راست باز میشود. در فهرست گزینهها، ML Kit را انتخاب کرده و روی گزینه زیر کلیک کنید:
Use ML Kit to detect faces in images and video
احتمالاً از شما درخواست میشود که اندروید استودیو خود را به یک اپلیکیشن در فایربیس وصل کنید. روی connect کلیک کرده و در پروژه فایربیس خود sign in کنید. زمانی که اندروید استودیو اجازه دسترسی به پروژه فایربیس را داد، از شما خواسته میشود که یک پروژه انتخاب کنید. پروژهای را که در گام قبلی ایجاد کردید انتخاب کنید. پس از آن باید ML Kit را به اپلیکیشن خود اضافه کنید که یک فرایند تک کلیکی محسوب میشود. زمانی که کار اضافه کردن وابستگیها به پایان رسید، آماده هستید که اپلیکیشن خود را ایجاد کنید.
شما باید فایل AndroidManifest.xml خود را طوری ویرایش کنید که امکان یادگیری ماشین آفلاین در اپلیکیشن اندروید خود را داشته باشید. متا-تگ زیر را درست زیر تگ اپلیکیشن خود وارد کنید.
ضمناً قصد داریم یک قابلیت دوربین به اپلیکیشن خود اضافه کنیم و از این رو یک تگ permission در مانیفست اضافه میکنیم.
برای افزودن دوربین به اپلیکیشن باید CameraKit مربوط به WonderKiln را اضافه کنیم. برای استفاده از CameraKit وابستگیهای زیر را در فایل build.gradle سطح اپلیکیشن اضافه میکنیم.
نکته: اگر میخواهید از کاتلین در اپلیکیشن اندروید خود استفاده کنید، باید وابستگیهای کاتلین نیز در آن گنجانده شوند.
پروژه را Sync کنید تا وابستگیها دانلود شوند. سپس یک CameraKitView در فایل لیآوت activity_main.xml اضافه میکنیم. به این منظور کد زیر را در فایل لیآوت اضافه کنید.
همچنین یک دکمه به زیر صفحه اضافه میکنیم تا به وسیله آن عکس بگیریم.
ما دوربین را در فایل MainActivity.kt مقداردهی میکنیم. به این منظور برخی متدهای چرخه عمر را override خواهیم کرد و درون این متدها، متدهای چرخه عمر را برای دوربین فراخوانی میکنیم:
از اندروید نسخه M به بعد باید مجوزهای زمان اجرا را نیز تقاضا کنیم. این وضعیت از سوی خود کتابخانه CameraKit مربوط به WonderKiln مدیریت میشود. به این منظور کافی است متد زیر را override کنیم:
ما با فشردن دکمه یک عکس میگیریم و آن را به دتکتور ارسال میکنیم. سپس یک مدل یادگیری ماشین روی تصویر اعمال میکنیم تا چهره را شناسایی کنیم. یک onclicklistener روی دکمه و درون تابع onClick تنظیم میکنیم و درون آن تابع captureImage مربوط به ویوی Camera را فراخوانی میکنیم.
در ادامه مقدار byteArray دریافتی را در یک bitmap دیکود میکنیم و سپس bitmap را به یک اندازه معقول مقیاسبندی مینماییم. این اندازه اساساً باید به اندازه camera_view ما باشد.
اکنون زمان آن فرا رسیده است که دتکتور ما روی bitmap اجرا شود. به این منظور تابع جداگانه runDetector() اجرا میکنیم.
این بخش اصلی اپلیکیشن ما است. ما یک دتکتور روی تصویر ورودی اجرا میکنیم. ابتدا یک FirebaseVisionImage از bitmap دریافتی تهیه میکنیم.
سپس نوبت آن میرسد که برخی گزینهها مانند performanceMode ،countourMode ،landmarkMode و غیره را اضافه کنیم.
اکنون دتکتور خود را با استفاده از گزینهها میسازیم.
در نهایت، نوبت آن میرسد که فرایند شناسایی را شروع کنیم. ما با استفاده از این دتکتور تابع detectInImage را فراخوانی کرده و تصویر را به آن ارسال میکنیم. در ادامه callback-هایی برای موفقیت یا شکست دریافت میکنیم.
طرز کار تابع runDetector به صورت زیر است:
برای ایجاد یک کادر پیرامونی که چهرههای شناساییشده را احاطه کند، باید دو view ایجاد کنیم. ویوی اول یک ویوی رویی شفاف است که روی آن کادر را رسم میکنیم. سپس کادر واقعی را روی تصویر قرار میدهیم.
فایل GraphicOverlay.java به صورت زیر است:
کادر مستطیلی واقعی به صورت زیر است:
اینک زمان آن رسیده که این ویوی graphicOverlay را در فایل لیآوت activity_main.xml خود اضافه کنیم.
در نهایت فایل activity_main.xml به صورت زیر درمیآید:
اینک زمان آن است که کادر پیرامونی چهرههای شناسایی شده را رسم کنیم. ما با استفاده از دتکتور چهرهها را در تابع ()processFaceResult دریافت کردهایم.
در ادامه حلقهای روی همه چهرهها تعریف کرده و یک کادر روی هر چهره به صورت زیر اضافه میکنیم:
در نهایت اپلیکیشن ما چیزی مانند تصویر زیر خواهد بود:
شما میتوانید سورس کد این اپلیکیشن را از این صفحه گیتهاب (+) دانلود کنید. روی این کد کار کنید و ایدههای مختلفی که برای پیادهسازی یادگیری ماشین در اپلیکیشنهای اندرویدی دارید امتحان نمایید.