JavaScript میں arrays کے ساتھ کام کرنے کا مطلب اب شاذ و نادر ہی یہ ہوتا ہے کہ آپ کو for loop لکھنے کی ضرورت پڑے۔ اب یہ زبان آپ کو ایسے پر اثر ٹولز فراہم کرتی ہے جو پیچیدہ اور شور مچاتے ہوئے ڈیٹا کو صاف ستھرے اور پڑھنے کے قابل تبدیلیوں (transformations) میں بدل دیتے ہیں۔ یہ گائیڈ وہاں سے شروع ہوتی ہے جہاں بنیادی باتیں ختم ہوتی ہیں—اس کا مرکز flattening، reducing، querying، اور نئے immutable patterns پر ہے جو آپ کے اصل ڈیٹا کو غیر تبدیل شدہ رکھتے ہیں۔

flat() اور flatMap() کے ذریعے Nested Structures کو Flatten کرنا

حقیقی دنیا کا ڈیٹا اکثر nested (ایک کے اندر ایک) ہوتا ہے۔ چاہے آپ کسی API سے ریکارڈ حاصل کریں، CSV کو parse کریں، یا کوئی configuration فائل پڑھیں، آپ کو اکثر arrays کے اندر arrays ملیں گے۔ flat() ان تہوں کو ایک واحد لکیری فہرست (linear list) میں سمونے کے لیے موجود ہے۔

کسی nested array پر flat() کال کریں اور یہ ڈیفالٹ طور پر صرف ایک تہہ (level) کو flatten کرتا ہے:

const nested = [[1, 2], [3, 4]];
const flat = nested.flat();
// [1, 2, 3, 4]

اصل nested array بالکل ویسا ہی رہتا ہے جیسا وہ تھا۔ اگر آپ کی nesting زیادہ گہری ہے، تو flat(2) جیسا depth argument پاس کریں یا پھر flat(Infinity) استعمال کریں جب آپ کو معلوم نہ ہو کہ کتنی تہیں موجود ہیں۔ یہ ہاتھ سے recursive helper functions لکھنے سے کہیں بہتر ہے۔

جب آپ کو عناصر کو تبدیل کرنے کے ساتھ ساتھ نتیجے کو flatten کرنے کی ضرورت ہو، تو flatMap() یہ دونوں کام ایک ہی بار میں کر دیتا ہے۔ یہ ہر عنصر پر ایک mapping function چلاتا ہے، اور پھر واپس آنے والے arrays کو ایک تہہ تک flatten کر دیتا ہے۔ اسے الگ الگ .map().flat() کالز کے ذریعے کرنا ممکن ہے، لیکن flatMap() درمیانی array کو چھوڑ دیتا ہے، جس سے میموری بچتی ہے اور دوسرا iteration بھی نہیں کرنا پڑتا۔ اس کا ایک کلاسک استعمال جملوں کے array کو انفرادی الفاظ میں تقسیم کرنا ہے:

const lines = ['hello world', 'foo bar'];
const words = lines.flatMap(line => line.split(' '));
// ['hello', 'world', 'foo', 'bar']

Filtering اور Mapping: فرق کو سمجھیں

filter() ایک نیا array تخلیق کرتا ہے جس میں صرف وہی آئٹمز ہوتے ہیں جو آپ کے ٹیسٹ پر پورا اترتے ہیں۔ callback کو عنصر کو برقرار رکھنے کے لیے truthy value، یا اسے نکالنے کے لیے falsy value واپس کرنی چاہیے۔ یہ کبھی بھی اصل (source) array کو تبدیل (mutate) نہیں کرتا۔

const users = [
  { name: 'Ajay', active: true },
  { name: 'Bina', active: false }
];
const activeUsers = users.filter(u => u.active);

filter() کو map() کے ساتھ خلط ملط کرنا آسان ہے کیونکہ دونوں نئے arrays واپس کرتے ہیں اور callbacks قبول کرتے ہیں۔ فرق ان کے کام کرنے کے طریقے (contract) میں ہے۔ map() اسی لمبائی کا ایک نیا array واپس کرتا ہے۔ ہر عنصر کو تبدیل کریں، ایک کنڈیشنل چیک چلائیں، اور آپ واقعی ہر جگہ true یا false دیکھیں گے، کیونکہ map() کبھی بھی آئٹمز کو نہیں نکالتا—یہ صرف انہیں تبدیل کرتا ہے۔ دوسری طرف، filter() ایک ممکنہ طور پر چھوٹا array واپس کرتا ہے جس میں صرف وہی عناصر ہوتے ہیں جو آپ کے ٹیسٹ پر پورا اترتے ہیں۔ اگر آپ ڈیٹا کو ہٹانا چاہتے ہیں، تو filter() کا استعمال کریں۔ اگر آپ تعداد برقرار رکھتے ہوئے ڈیٹا کے ہر حصے کو تبدیل کرنا چاہتے ہیں، تو map() استعمال کریں۔

reduce() کے ذریعے Arrays کو سمٹانا (Collapsing)

جہاں map() اور filter() arrays واپس کرتے ہیں، وہیں reduce() پوری فہرست کو ایک ہی ویلیو میں بدل دیتا ہے۔ یہ بائیں سے دائیں چلتا ہے، اور ہر مرحلے پر ایک accumulator کو ساتھ لے کر چلتا ہے۔

const totals = [10, 20, 30];
const sum = totals.reduce((acc, current) => acc + current, 0);
// 60

دوسرا argument—اوپر دی گئی مثال میں 0—ابتدائی accumulator value ہے۔ اگر آپ اسے چھوڑ دیں، تو JavaScript پہلے عنصر کو آغاز کے طور پر استعمال کرتا ہے اور index 1 سے iteration شروع کرتا ہے۔ یہ لوگوں کو الجھا دیتا ہے جب array خالی ہو، کیونکہ خالی array پر ابتدائی ویلیو کے بغیر reduce() کال کرنے سے TypeError آتا ہے۔

Reduce صرف حساب کتاب (arithmetic) کے لیے نہیں ہے۔ آپ اسے frequency tables بنانے، objects کو ضم کرنے، زیادہ سے زیادہ ویلیوز (maximum values) تلاش کرنے، یا ڈیٹا کو متعدد تبدیلیوں کے ذریعے گزارنے کے لیے استعمال کر سکتے ہیں۔ چونکہ یہ بہت لچکدار ہے، اس لیے بہتر ہے کہ callback کو چھوٹا رکھیں اور اپنے variables کے نام واضح رکھیں۔ ایک پیچیدہ reduce() بلاک اس loop کے مقابلے میں پڑھنے میں زیادہ مشکل ہو جاتا ہے جسے اس نے تبدیل کیا ہے۔

Boolean Queries: every() اور some()

کبھی کبھی آپ کو نئے array کی ضرورت نہیں ہوتی؛ بلکہ آپ کو مجموعہ (collection) کے بارے میں ہاں یا نہ میں جواب چاہیے ہوتا ہے۔ every() صرف تب true واپس کرتا ہے جب تمام عناصر آپ کی شرط پر پورا اتریں۔ اگر پہلا عنصر ہی ناکام ہو جائے، تو یہ فوراً رک جاتا ہے اور false واپس کر دیتا ہے۔

const scores = [85, 92, 78];
const allPassed = scores.every(s => s >= 50);

some() اس کا بالکل الٹ ہے۔ یہ true واپس کرتا ہے اگر کم از کم ایک عنصر ٹیسٹ پاس کر لے۔ اگر کوئی میچ مل جائے، تو یہ وہیں رک جاتا ہے۔

const hasPerfectScore = scores.some(s => s === 100);

یہ طریقے validation چیک کے لیے دستی (manual) loops کی جگہ لے لیتے ہیں۔ کیا آپ کو تصدیق کرنی ہے کہ فارم کا ہر فیلڈ بھرا ہوا ہے؟ every() استعمال کریں۔ کیا آپ کو جاننا ہے کہ کیا کسی صارف کے پاس admin حقوق ہیں؟ some() استعمال کریں۔

Mutation کے بغیر معائنہ اور اپ ڈیٹ کرنا

جدید JavaScript immutability (غیر تبدیلی) کی طرف زیادہ جھکاؤ رکھتی ہے۔ یہ طریقے آپ کو اصل ڈیٹا کو چھوئے بغیر arrays کا معائنہ کرنے یا انہیں اپ ڈیٹ کرنے میں مدد دیتے ہیں۔

keys() ایک iterator object واپس کرتا ہے جس میں array کے indices ہوتے ہیں۔ entries() [index, value] کے جوڑوں کا ایک iterator واپس کرتا ہے۔ یہ for...of loops میں بہت کارآمد ثابت ہوتے ہیں جب آپ صاف ستھری destructuring چاہتے ہوں:

const fruits = ['apple', 'banana'];
for (const [index, fruit] of fruits.entries()) {
  console.log(index, fruit);
}

with() زبان میں ایک نیا اضافہ ہے جو ایک مخصوص انڈیکس پر ایک عنصر کو تبدیل کرتا ہے اور ایک بالکل نیا ارے (array) واپس کرتا ہے۔ یہ بریکٹ اسائنمنٹ—arr[index] = value—کی اسی ضرورت کو پورا کرتا ہے، سوائے اس کے کہ یہ اصل ارے کو تبدیل کیے بغیر اسے برقرار رکھتا ہے۔ React جیسے فریم ورکس میں، جہاں براہ راست میوٹیشن (mutation) چینج ڈیٹیکشن (change detection) کو متاثر کرتی ہے، with() ایک محفوظ راستہ ہے:

const original = [10, 20, 30];
const updated = original.with(1, 99);
// original: [10, 20, 30]
// updated:  [10, 99, 30]

Spread اور Rest: ... کی دو سمتیں

Spread سنٹیکس اور rest پیرامیٹر دونوں میں وہی تین نقطے استعمال ہوتے ہیں، لیکن وہ ڈیٹا کو مخالف سمتوں میں منتقل کرتے ہیں۔

ایک ارے کو انفرادی عناصر میں پھیلانے کے لیے spread operator کا استعمال کریں۔ یہ ارےز کو ضم کرنے یا انہیں کلون (clone) کرنے کا سب سے بہترین طریقہ ہے:

const front = [1, 2];
const back = [3, 4];
const merged = [...front, ...back];

const copy = [...merged];

آپ آسانی سے اضافی آئٹمز بھی شامل کر سکتے ہیں: [0, ...front, 99, ...back]۔

rest parameter متعدد عناصر کو ایک ہی ارے میں جمع کرتا ہے۔ فنکشن سگنیچر (function signature) کے اندر، یہ باقی رہ جانے والے تمام آرگومنٹس کو اکٹھا کر لیتا ہے:

function logFirst(first, ...rest) {
  console.log('First:', first);
  console.log('Rest:', rest);
}
logFirst('a', 'b', 'c');
// First: a
// Rest: ['b', 'c']

یہ مل کر متغیر ان پٹ (variadic input) کو قبول کرنے، اسے ایک ارے کے طور پر پروسیس کرنے، اور پھر اسے فنکشن کالز یا نئے ڈیٹا اسٹرکچرز میں دوبارہ پھیلانے کے عام کام کو سنبھالتے ہیں۔

اصل نچوڑ

ان طریقوں کا مقصد ون لائنرز (one-liners) سے ساتھیوں کو متاثر کرنا نہیں ہے۔ بلکہ اس کا مقصد آپ کے ارادے کو واضح کرنا ہے۔ جب کوئی دوسرا ڈویلپر filter() دیکھتا ہے، تو وہ جان جاتا ہے کہ آپ آئٹمز کو ہٹا رہے ہیں۔ جب وہ reduce() دیکھتے ہیں، تو وہ جانتے ہیں کہ آپ ڈیٹا کو کمپریس (collapse) کر رہے ہیں۔ جب وہ with() دیکھتے ہیں، تو وہ جانتے ہیں کہ اصل ارے کہیں نہ کہیں محفوظ ہے۔ ان ٹولز میں مہارت حاصل کرنے کا مطلب ایسا کوڈ لکھنا ہے جو خود اپنی وضاحت کرے، اور یہ کسی بھی چالاک لوپ (loop) کے مقابلے میں کہیں زیادہ وقت بچاتا ہے۔