چرا دو نام فارسی یکسان در کد برابر نیستند؟ یکدستسازی نام برای مقایسه
نام فارسی را پیش از مقایسه با نام بانکی باید یکدست کنید: ی و ک عربی، نیمفاصله، فاصلهٔ اضافه، علامتهای جهت و ترتیب نام.
دو رشتهٔ «علی رضایی» که روی صفحه عیناً یکیاند، در کد میتوانند برابر نباشند. اگر نام برگشتی از یک استعلام بانکی را با === به نام ورودی کاربر مقایسه میکنید، بخشی از کاربران درست را رد میکنید و هیچ خطایی هم نمیبینید.
این نوشته یک تابع یکدستسازی نام فارسی را قدمبهقدم میسازد و نشان میدهد هر قدم چه مشکل واقعیای را میگیرد. هر جا نام صاحب کارت یا حساب را از استعلام نام صاحب کارت میگیرید و با ورودی کاربر مقایسه میکنید، همین تابع لازم است، حتی اگر مقایسه فقط یک if باشد.
چرا دو نام یکسان برابر نیستند
سه علت، به ترتیب شیوع:
۱. حروف عربی در برابر فارسی. یای عربی (U+064A) در برابر «ی» فارسی (U+06CC)، و کاف عربی (U+0643) در برابر «ک» فارسی (U+06A9). روی صفحه فرقشان تقریباً دیده نمیشود، ولی کد دو نویسهٔ جدا میبیند. دادهای که از سامانههای مختلف میآید گاهی یکی و گاهی دیگری را دارد.
۲. فاصله. یک فاصلهٔ اضافه در ابتدا یا انتها، دو فاصلهٔ پیاپی بین نام و نام خانوادگی، یا فاصلهٔ معمولی بهجای نیمفاصله (و برعکس) در نامهای مرکب.
۳. ارقام و علامتها. نامها رقم ندارند، ولی ورودی کاربر ممکن است با کاراکترهای نامرئی بیاید (نشانههای جهت راستبهچپ که از کپیکردن همراه میآیند).
قدمبهقدم
ts function normalizeName(input: string): string { return input .normalize('NFKC') .replace(/[-]/g, '') // نشانههای جهت .replace(/\u064A/g, '\u06CC') // یای عربی به فارسی .replace(/\u0643/g, '\u06A9') // کاف عربی به فارسی .replace(//g, ' ') // نیمفاصله به فاصله .replace(/\s+/g, ' ') .trim(); }
چند توضیح روی انتخابها:
NFKCشکلهای ترکیبی و نمایشی حروف را به شکل استاندارد میبرد. برای فارسی عموماً بیخطر است.- نیمفاصله را به فاصلهٔ معمولی تبدیل میکنیم، فقط برای مقایسه. «محمدرضا» و «محمد رضا» را میخواهیم یکی بدانیم. اما نتیجه را برای نمایش به کاربر هرگز ذخیره نکنید؛ نسخهٔ اصلی را نمایش بدهید.
- حذف علامتهای جهت مهم است. بدون آن، نامی که از یک چت یا پیدیاف کپی شده، با خودش برابر نیست.
ترتیب نام و نام خانوادگی
یکدستسازی نویسهها کافی نیست. «رضایی علی» و «علی رضایی» هر دو در عمل دیده میشوند، چون بعضی سامانهها نام خانوادگی را اول نگه میدارند. اگر ترتیب را نمیدانید، یک راه ساده این است که واژهها را مرتب کنید و رشتهٔ مرتبشده را مقایسه کنید:
ts const key = (s: string) => normalizeName(s).split(' ').sort().join(' ');
این راه یک عارضه دارد: «علی محمد» با «محمد علی» یکی میشود، که دو نام مختلفاند. برای بیشتر کاربردهای پیشازواریز، این ریسک قابلقبول است چون مقایسهٔ بعدی (کد ملی، یا بررسی دستی) باقی ریسک را میگیرد. اما اگر مقایسهٔ نام تنها لایهٔ دفاعی شماست، تصمیم دیگری بگیرید: ترتیب را ثابت فرض کنید و فقط وقتی تطبیق نشد، حالت برعکس را امتحان کنید.
القاب و پیشوندها
«دکتر»، «مهندس»، «سید» و «سیده» گاهی در نام ثبتشده هستند و گاهی نه. اینها را از ابتدای نام حذف نکنید مگر یک فهرست کوتاه و روشن داشته باشید، چون حذف کورکورانه «سید» میتواند بخشی از یک نام واقعی را هم ببرد. به نظر من بهتر است هر دو شکل را امتحان کنید: یک بار با لقب، یک بار بدون آن، و فقط زمانی نتیجه را «مطابق» بدانید که یکی از دو برابر شد.
آستانه بهجای برابری
حتی بعد از همهٔ اینها، دو نامی که یک نفرند ممکن است یک حرف فرق کنند («فاطمه» و «فاطیمه»، «حسین» و «حسن» که البته دو نام مختلفاند). برای همین دو آستانه بگذارید:
- فاصلهٔ ویرایشی صفر: مطابق.
- فاصلهٔ یک یا دو حرف، با نام نسبتاً بلند: نیازمند بررسی دوم، نه تأیید و نه رد.
- بیشتر از آن: غیرمطابق.
عددها را از دادههای خودتان بسنجید، نه از هیچ توصیهٔ کلی. اگر مقایسه برایتان حیاتی است و نمیخواهید این منطق را خودتان نگه دارید، سرویسهای تطابق (تطابق کارت و نام) این مقایسه را انجام میدهند و فقط نتیجه را میدهند؛ ولی همچنان نام ورودی خودتان را پیش از ارسال یکدست کنید، چون کیفیت ورودی بر نتیجه اثر دارد.
آنچه این تابع حل نمیکند
- نامهایی که با الفبای لاتین نوشته شدهاند (ترجمهنویسی).
- نامهای کوتاهشده و لقبها («علیاکبر» در برابر «اکبر»).
- نام شرکتها که ساختار متفاوتی دارند.
برای هر کدام از اینها قاعدهٔ جدا بنویسید و آزمون داشته باشید.
یک آزمون کوچک
پیش از هر مقایسهٔ واقعی، چند زوج را به تابع بدهید و مطمئن شوید نتیجه همان است که انتظار دارید. چند ورودی نمونه برای شروع:
- همان نام با یای عربی و با یای فارسی باید برابر شود.
- نام با نیمفاصله و بدون آن (مثل نامهای مرکب) باید برابر شود.
- نام با فاصلهٔ اضافهٔ ابتدا و انتها باید برابر شود.
- «علی رضایی» و «علی رضایینژاد» نباید برابر شوند.
مورد آخر مهم است: تابع یکدستسازی نباید بیش از حد مهربان باشد. هدفش حذف تفاوتهای ظاهری است، نه ادغام نامهای مختلف. اگر آزمونی بنویسید که دو نام واقعاً متفاوت را برابر نشان بدهد، قاعده را سختتر کنید.
کجا این تابع را بگذارید
یک بار، در لایهٔ ورودی. نام کاربر را همان لحظهٔ دریافت یکدست کنید و دو فیلد نگه دارید: نام اصلی برای نمایش، کلید یکدستشده برای مقایسه. نام برگشتی از سرویس بیرونی را هم از همان تابع بگذرانید. وقتی مقایسه در چند جای کد انجام میشود، هر جا نسخهٔ خودش را بنویسد، بعد از مدتی نتیجهها با هم نمیخوانند.
خروجی سرویس استعلام نام صاحب کارت متن خام بانکی است؛ همانطور که هست نمایش بدهید ولی با نسخهٔ یکدستشده مقایسه کنید.
فارسیسازی ارقام و علامتها
اگر نامها ارقام ندارند، لازم نیست نگرانشان باشید. ولی ورودی کاربر گاهی علامتهایی دارد که در نام نیست: نقطه پس از حرف اول (مثل «ع. رضایی»)، خط تیره بین نامهای مرکب، یا پرانتزی که برای توضیح نوشته شده. برای مقایسهٔ پیش از واریز، اینها را حذف کنید یا به فاصله تبدیل کنید. و اگر نامی بعد از حذف خالی شد، آن را معتبر ندانید.
نکتهٔ آخر
- هر دو سمت مقایسه را از یک تابع یکدستسازی بگذرانید.
- نسخهٔ اصلی را برای نمایش نگه دارید؛ فقط کلید مقایسه یکدست باشد.
- ترتیب نام را فرض نکنید.
- بین «مطابق» و «غیرمطابق» یک مسیر «بررسی دوم» بگذارید.
- یک مجموعهٔ آزمون از نامهای دشوار داشته باشید و با هر تغییر اجرا کنید.
وبسرویس مرتبط با این راهنما
API استعلام نام صاحب کارت
نام دارنده یک شماره کارت را ببینید.
راهنماهای مرتبط
پرسش و تجربه کاربران
اگر این راهنما جوابتان را نداد، همینجا بپرسید. پرسشها را تیمملی ایپیآی پاسخ میدهد و هر نظر پیش از انتشار بررسی میشود.
هنوز پرسشی ثبت نشده، اولین نفر باشید.
