چیزی به نام واژه وجود ندارد
از دو ابزار بخواهید واژههای یک پاراگراف را بشمارند و ممکن است دو عدد بگیرید. هیچکدام اشتباه نیست، چون واحد تعریف نشده است.
ببینید چه چیزهایی باید تصمیمگیری شود:
state-of-the-artیک واژه است یا چهار؟don'tیکی است یا دو تا؟- آیا
£1,250.00یک واژه است؟ https://example.com/a/bیک واژه است یا چند تا؟- وقتی دور خطتیره فاصلهای نیست،
sentence—anotherدو واژه است؟
واژهپردازها، ناشران و شیوهنامههای دانشگاهی به اینها متفاوت پاسخ میدهند، و هر پاسخی معقول است. ابزار باید یکی را انتخاب کند.
اصولیترین قاعدهٔ موجود، الگوریتم بخشبندی متن یونیکد است، که مرزهای واژه را به شکلی تعریف میکند که در همهٔ خطها کار میکند — از جمله خطهایی که در آنها فاصله اصلاً واژهها را از هم جدا نمیکند، مانند تایلندی و ژاپنی. ابزاری که از روی فاصله تقسیم میکند انگلیسی را کمابیش درست و آن زبانها را بهکلی اشتباه میشمارد.
پس پرسش مفید دربارهٔ شمارش واژه «آیا درست است» نیست، بلکه «آیا یکدست است». برای دنبال کردن یک پیشنویس در برابر یک سقف، قاعدهای پایدار بیش از قاعدهای که از نظر فلسفی ایدهآل باشد اهمیت دارد.
پیش از ادامهٔ مطلب
یک پاراگراف را در دو شمارندهٔ واژه میچسبانید و ۱۴۸ و ۱۵۱ میگیرید. کدامیک خراب است؟
هیچکدام. تعریف یگانهای از واژه وجود ندارد، بنابراین هر ابزاری قاعدهای انتخاب میکند: اینکه ترکیب خطتیرهدار یک واژه باشد یا چهار، شکل کوتاهشده یکی باشد یا دو تا، URL اصلاً حساب شود یا نه. پاسخهای معقولِ متفاوت جمعهای متفاوتی میدهند. پرسش مفید دربارهٔ شمارش واژه این نیست که آیا درست است، بلکه این است که آیا یکدست است.
خط کجا تمام میشود
سه قرارداد، که هر سه هنوز در گردشاند:
| بایتها | نام | کجا |
|---|---|---|
\n | LF | یونیکس، لینوکس، macOS از ۲۰۰۱ |
\r\n | CRLF | ویندوز، و بیشتر پروتکلهای اینترنتی |
\r | CR | Mac OS کلاسیک، پیش از ۲۰۰۱ |
دو فایلی که در ویرایشگر یکسان به نظر میرسند میتوانند در هر پایانخط با هم فرق داشته باشند، و پیامدهایش واقعی است: شمارش بایت متفاوت، چکسام متفاوت، و یک خط خالی اضافه وقتی تقسیمِ سادهانگارانه \r\n را دو پایاندهنده میپندارد.
رویکرد قابل اتکا این است که در ورودی نرمالسازی کنید — هر قراردادی را به یک خط جدید (LF) تنها تبدیل کنید — و از آنجا بشمارید. هر ابزار متنی در اینجا همین کار را میکند، و به همین دلیل یک بلوک متن ویندوزی که چسبانده شده و همان بلوک که تازه تایپ شده ارقام یکسانی میدهند.
مورد کلاسیک دیگر، خط جدید پایانی است. فایلی که با خط جدید تمام میشود، آن را بنا به قرارداد دارد نه از سر اتفاق، و اینکه آن رشتهٔ خالی آخر یک خط حساب شود یا نه تصمیمی است که ابزار باید بگیرد و اعلام کند.
نویسهها بایت نیستند
پیش از آنکه بتوان واژهها یا خطها را شمرد، دنبالهٔ بایت باید به نویسههای یونیکد تبدیل شود. UTF-8، UTF-16 little-endian و UTF-16 big-endian میتوانند یک متن را با بایتهای متفاوت نمایش دهند؛ نشانهٔ ترتیب بایت (BOM) میتواند ترتیب UTF-16 را مشخص کند، در حالی که ورودی بدون نشانه همچنان به قراردادی صریح نیاز دارد. باز کردن بایتهای UTF-8 به عنوان یک صفحهکد قدیمی همان راهی است که café به رشتهٔ آشنایی از نویسههای اشتباه تبدیل میشود.
به آن خرابی موجیبیکه میگویند، و دوباره ذخیره کردنش نگاشت اصلی را ترمیم نمیکند. یک تبدیل کدگذاری امن از یک کدگذاری مبدأ شناختهشده شروع میکند، دنبالههای بایتی را که زیر آن نامعتبرند رد میکند، سپس مقادیر اسکالر یونیکدِ حاصل را در قالب مقصد کدگذاری میکند. شکست سختگیرانه مفید است: درج بیسروصدای نویسههای جایگزین فایلی بهظاهر موفق میسازد که برگشتپذیر نیست.
پایانخطها لایهای جداگانهاند. تبدیل UTF-16 به UTF-8 میتواند CRLF را عیناً حفظ کند، یا گزینهای صریح میتواند آن را به LF نرمال کند. ادغام این تصمیمها زیر یک دکمهٔ توضیحدادهنشدهٔ «اصلاح متن» فهمیدن این را ناممکن میکند که چکسام به خاطر تغییر نویسهها عوض شده، یا کدگذاریشان، یا پایاندهندههایشان.
حروف بزرگ و کوچک یک نگاشت ساده نیست
بزرگ کردن حروف شبیه یک جدول جستوجو به نظر میرسد و نیست.
- اِس تیز آلمانی.
ßبهSSبزرگ میشود. رشته بلندتر میشود، پس هر کدی که فرض کند تبدیل حروف طول را حفظ میکند اشتباه است. - i ترکی. ترکی یک i نقطهدار و یک i بینقطه دارد، و این دو متفاوت از هر زبان دیگری بزرگ و کوچک میشوند. اعمال قواعد انگلیسی روی متن ترکی آن را خراب میکند.
- خطهای بدون حروف بزرگ و کوچک. عربی، عبری، چینی، ژاپنی، کرهای و تایلندی اصلاً حروف بزرگ و کوچک ندارند، بنابراین این عمل بیاثر است، نه یک خطا.
حالت عنوان (Title Case) بدتر است، چون پرسشی سبکی است نه پرسشی دربارهٔ نویسهها: اینکه کدام واژههای کوتاه با حرف کوچک بمانند تصمیمی است که به یک شیوهنامه تعلق دارد، و شیوهنامهها با هم اختلاف دارند.
CSV مشخصات دارد، و آن مشخصات «از روی کاما تقسیم کن» نیست
این قالب سادهترین چیز در رایانش به نظر میرسد و یک قاعدهٔ واقعاً دشوار دارد: یک فیلد میتواند شامل کاما، شکست خط یا نویسهٔ نقلقول باشد، و قالب این را با گذاشتن فیلد درون نقلقول و دو بار نوشتن هر نقلقولِ درون آن حل میکند.
name,note
Smith,"Lives at 12 High Street, Ipswich"
Jones,"She said ""no"" twice"
Brown,"Line one
Line two"
هر یک از آن سطرها معتبر است، و هر یک تجزیهکنندهای را که از روی کاما تقسیم میکند از کار میاندازد. سومی هر چیزی را که یک رکورد در هر خط فرض میکند از کار میاندازد.
چیزهایی را که مشخصات تعیین نمیکند اضافه کنید — جداکننده همیشه کاما نیست، کدگذاریها متفاوتاند، ممکن است نشانهٔ ترتیب بایت در ابتدای فایل باشد، و برخی خروجیگیرها همهچیز را در نقلقول میگذارند در حالی که برخی هیچچیز را — و روشن میشود که چرا تجزیهٔ واقعی CSV یک ماشین حالت است نه یک split.
این درس فراتر از CSV تعمیم مییابد. قالبهای متنیای که بدیهی به نظر میرسند همانجاییاند که حالتهای لبه پنهان میشوند، دقیقاً به این دلیل که پیادهسازی بدیهی روی اولین فایلی که امتحان میکنید کار میکند.
پرسشهای رایج
چرا شمارندههای واژه با هم اختلاف دارند؟
چون «واژه» تعریف یگانهای ندارد. ترکیبهای خطتیرهدار، شکلهای کوتاهشده، اعداد، URLها و خطتیرههای بلندِ بدون فاصله همگی محل قضاوتاند، و ابزارهای مختلف متفاوت قضاوت میکنند. ابزاری که از روی فاصله تقسیم میکند و ابزاری که از مرزهای واژهٔ یونیکد پیروی میکند روی یک پاراگراف نتیجهٔ متفاوتی میدهند، و هر دو قابل دفاعاند.
تفاوت CR، LF و CRLF چیست؟
سه قرارداد برای پایان دادن به یک خطاند. یونیکس و macOS امروزی از یک خط جدید (LF) تنها استفاده میکنند، ویندوز از یک بازگشت به سر سطر (CR) و پس از آن یک خط جدید، و Mac OS کلاسیک از یک بازگشت به سر سطر تنها. بنابراین متنی که یکسان به نظر میرسد میتواند در بایتهایش متفاوت باشد، که اندازهٔ فایل، چکسامها و شمارش سادهانگارانهٔ خطها را تغییر میدهد.
چرا بزرگ کردن حروف گاهی طول رشته را تغییر میدهد؟
چون نگاشت حروف بزرگ و کوچک در همهٔ زبانها یکبهیک نیست. حرف اِس تیز آلمانی به دو نویسه بزرگ میشود، و برخی خطها اصلاً حروف بزرگ و کوچک ندارند. ترکی یک i نقطهدار و یک i بینقطه دارد که متفاوت از هر زبان دیگری نگاشت میشوند، و به همین دلیل بزرگسازی حروف بدون آگاهی از زبانبوم میتواند متن ترکی را خراب کند.
چرا تجزیهٔ CSV سختتر از تقسیم کردن از روی کاماست؟
چون یک فیلد میتواند شامل کاما، شکست خط یا علامت نقلقول باشد، و قالب این را با گذاشتن فیلد درون نقلقول و دو بار نوشتن هر نقلقولِ درون آن حل میکند. تقسیم سادهانگارانه از روی کاما همان لحظهای از کار میافتد که مقداری شامل کاما باشد، که در دادههای واقعی یعنی بلافاصله — نشانیها، نام محصولها و اصولاً هر متن آزادی.
چه چیزی در یک فایل متنی موجیبیکه (mojibake) ایجاد میکند؟
موجیبیکه وقتی ظاهر میشود که بایتهایی که با یک کدگذاری نویسه نوشته شدهاند با کدگذاری دیگری رمزگشایی شوند. بایتها تصادفی نشدهاند؛ خواننده نگاشت اشتباهی به کار برده است. به جای ذخیرهٔ مکرر نویسههایی که از پیش اشتباه خوانده شدهاند، از یک کدگذاری مبدأ شناختهشده با مدیریت سختگیرانهٔ خطا تبدیل کنید.