C# అర్రేలు (1D, 2D Rectangular & Jagged Arrays)
స్వాగతం అధ్యాయం 11 కి! 🗃️ కంప్యూటర్ మెమరీలో డేటా వరుసలు & గ్రిడ్స్
మీ దగ్గర 50 మంది విద్యార్థుల మార్కులు లేదా 1000 మంది ఉద్యోగుల శాలరీల వివరాలు ఉన్నాయనుకుందాం. వాటన్నింటి కోసం 1000 వేర్వేరు వేరియబుల్స్ (emp1, emp2, emp3...) సృష్టించడం అసాధ్యం. ఒకే పేరుతో, ఒకే రకమైన డేటా విలువలను కంప్యూటర్ మెమరీలో వరుసగా (Contiguous Memory) భద్రపరిచే అత్యంత ప్రాథమికమైన డేటా స్ట్రక్చరే అర్రే (Array)!
కానీ C# లో అర్రేల వ్యవస్థ సాధారణ భాషల కంటే ఎంతో వైవిధ్యమైనది. ఇది సాధారణ 1-డైమెన్షనల్ అర్రేలతో పాటు — పక్కా దీర్ఘచతురస్ర ఆకారంలో ఉండే 2D రెక్టాంగ్యులర్ అర్రేస్ (int[,]), మరియు ప్రతి అడ్డువరుసలో వేర్వేరు సంఖ్యలో అంశాలను అనుమతించే శక్తిమంతమైన జాగ్డ్ అర్రేస్ (int[][] - Array of Arrays) లను అందిస్తుంది.
అంతేకాకుండా, సరికొత్త C# 12 లో వచ్చిన Collection Expressions ([1, 2, 3]) మరియు స్ప్రెడ్ ఆపరేటర్ (..) అర్రేలను డిక్లేర్ చేసే పద్ధతినే సమూలంగా మార్చివేశాయి! ఈ అధ్యాయంలో మనం మెమరీ లేఅవుట్స్, క్యాష్ లొకాలిటీ, సార్టింగ్ అల్గారిథమ్స్ నేర్చుకొని — ఒక మల్టీప్లెక్స్ సినిమా థియేటర్ సీటింగ్ బుకింగ్ ఇంజిన్ను నిర్మిస్తాం!
1 🗃️ కంప్యూటర్ మెమరీలో డేటా వరుసలు & Cache Locality
అర్రే యొక్క అతిపెద్ద బలం: కంటిగ్యుయస్ మెమరీ అలోకేషన్ (Contiguous Allocation). అంటే ఒక అర్రే కోసం మెమరీ హీప్లో కేటాయించబడిన బ్లాక్లో అన్ని ఎలిమెంట్స్ ఒకదాని ప్రక్కన ఒకటి ఎటువంటి ఖాళీ లేకుండా వరుసగా అమర్చబడి ఉంటాయి.
CPU ప్రాసెసర్ arr[0] ని చదివినప్పుడు, అది కేవలం ఆ ఒక్క ఎలిమెంట్ను మాత్రమే తీసుకురాదు! మెమరీ బస్ ద్వారా దాని ప్రక్కనే ఉన్న arr[1], arr[2], arr[3] లను కూడా ఒకేసారి అతివేగవంతమైన L1/L2 CPU Cache లోకి లోడ్ చేస్తుంది. దీనివల్ల లూప్ తిరుగుతున్నప్పుడు రామ్ (RAM) వరకు వెళ్లే పనిలేకుండా ప్రాసెసర్ మెరుపు వేగంతో లెక్కింపులు పూర్తి చేస్తుంది.
అర్రేలు ఎల్లప్పుడూ 0-ఇండెక్స్డ్ (Zero-based indexing) ఆధారంగా పనిచేస్తాయి. మొదటి ఎలిమెంట్ 0 వద్ద, చివరి ఎలిమెంట్ Length - 1 వద్ద ఉంటుంది.
2 📐 1D అర్రేస్ (Single-Dimensional Arrays)
ఒకే వరుసలో ఉండే డేటాను భద్రపరచడానికి 1D అర్రేస్ ఉపయోగిస్తారు.
C# లో ఒక అర్రేను సృష్టించినప్పుడు అందులోని ప్రతి స్థానం సంబంధిత డేటా టైప్ యొక్క డీఫాల్ట్ విలువతో (Default Value) ఆటోమేటిక్గా నింపబడుతుంది:
- సంఖ్యలకు (
int,double,decimal): 0 - బూలియన్లకు (
bool): false - రిఫరెన్స్ టైప్స్కు (
string, ఆబ్జెక్ట్స్): null
// Style 1: సైజు పేర్కొని ఆ తర్వాత విలువలు ఇవ్వడం
int[] scores = new int[3]; // [0, 0, 0]
scores[0] = 85;
scores[1] = 92;
scores[2] = 78;
// Style 2: సృష్టించే సమయంలోనే విలువలు ఇవ్వడం
string[] cities = { "హైదరాబాద్", "విజయవాడ", "విశాఖపట్నం", "తిరుపతి" };
Console.WriteLine($"నగరాల సంఖ్య: {cities.Length}");
Console.WriteLine($"రెండవ నగరం : {cities[1]}"); // విజయవాడ
3 ⏹️ 2D రెక్టాంగ్యులర్ అర్రేస్ (Multidimensional Arrays: int[,])
గణిత మ్యాట్రిక్స్, ఎక్సెల్ షీట్లు లేదా చెస్ బోర్డుల లాంటి గ్రిడ్ డేటాను భద్రపరచడానికి C# ప్రత్యేకంగా రెక్టాంగ్యులర్ అర్రేస్ (Rectangular Arrays) ను అందిస్తుంది.
ఇక్కడ బ్రాకెట్ల మధ్య కామా పెడతారు: int[,] matrix = new int[rows, cols];. దీనిలో ప్రతి అడ్డువరుసలోనూ సరిగ్గా ఒకే సంఖ్యలో నిలువు వరుసలు (Columns) ఉంటాయి.
// 3 అడ్డు వరుసలు, 3 నిలువు వరుసలతో కూడిన 3x3 మ్యాట్రిక్స్
int[,] matrix = {
{ 1, 2, 3 },
{ 4, 5, 6 },
{ 7, 8, 9 }
};
// డైమెన్షన్ పొడవును పొందడానికి GetLength() వాడాలి:
int rows = matrix.GetLength(0); // 3 (Rows)
int cols = matrix.GetLength(1); // 3 (Columns)
Console.WriteLine("2D మ్యాట్రిక్స్ విజువలైజేషన్:");
for (int r = 0; r < rows; r++)
{
for (int c = 0; c < cols; c++)
{
Console.Write($"{matrix[r, c],3} ");
}
Console.WriteLine();
}
4 🪜 జాగ్డ్ అర్రేస్ (Jagged Arrays: int[][])
రెక్టాంగ్యులర్ అర్రేలలో అన్ని వరుసలలోనూ ఒకే సంఖ్యలో కాలమ్స్ ఉండాలి. కానీ నిజ జీవితంలో ఒక కాలేజీలో సెక్షన్ A లో 30 మంది, సెక్షన్ B లో 45 మంది, సెక్షన్ C లో 20 మంది ఉండవచ్చు. రెక్టాంగ్యులర్ అర్రే వాడితే ఖాళీ మెమరీ వృధా అవుతుంది.
ఈ సమస్యకు పరిష్కారమే జాగ్డ్ అర్రే (Jagged Array - అర్రేలలోని అర్రే / Array of Arrays)! దీనిలో ప్రతి రో తన స్వంత సైజును కలిగి ఉంటుంది:
// 3 సెక్షన్లు కలిగిన జాగ్డ్ అర్రే
int[][] studentSections = new int[3][];
studentSections[0] = new int[] { 85, 90, 78 }; // 3 మంది
studentSections[1] = new int[] { 95, 88, 72, 91, 84 }; // 5 మంది
studentSections[2] = new int[] { 60, 75 }; // 2 మంది
for (int s = 0; s < studentSections.Length; s++)
{
Console.Write($"సెక్షన్ {s + 1} మార్కులు ({studentSections[s].Length} మంది): ");
foreach (int mark in studentSections[s])
{
Console.Write($"{mark} ");
}
Console.WriteLine();
}
5 🔍 Array క్లాస్ మెథడ్స్ పవర్హౌస్ (Sort, Search, Copy)
.NET లోని System.Array క్లాస్ అర్రేలతో పనిచేయడానికి అద్భుతమైన స్టాటిక్ మెథడ్స్ను అందిస్తుంది:
int[] numbers = { 45, 12, 85, 32, 89, 21 };
// 1. సార్టింగ్ (ఆరోహణ క్రమం)
Array.Sort(numbers);
Console.WriteLine($"సార్ట్ చేసిన అర్రే: {string.Join(", ", numbers)}");
// 2. రివర్సింగ్ (అవరోహణ క్రమం)
Array.Reverse(numbers);
Console.WriteLine($"రివర్స్ చేసిన అర్రే: {string.Join(", ", numbers)}");
// 3. బైనరీ సెర్చ్ (సార్ట్ అయి ఉన్నప్పుడు మాత్రమే పనిచేస్తుంది - O(log N))
Array.Sort(numbers);
int foundIndex = Array.BinarySearch(numbers, 45);
Console.WriteLine($"45 ఉన్న ఇండెక్స్: {foundIndex}");
// 4. క్లియరింగ్ (విలువలను డీఫాల్ట్ 0 కి మార్చడం)
Array.Clear(numbers, 0, 2); // మొదటి 2 ఎలిమెంట్స్ 0 అవుతాయి
Console.WriteLine($"క్లియర్ చేసిన తర్వాత: {string.Join(", ", numbers)}");
6 🎯 మోడ్రన్ C# 12 కలెక్షన్ ఎక్స్ప్రెషన్స్ ([]) & Spread (..)
C# 12 లో వచ్చిన అత్యంత ఆధునిక ఫీచర్ Collection Expressions. ఇకపై new int[] { 1, 2 } అని పెద్దగా రాయవలసిన అవసరం లేకుండా — కేవలం బ్రాకెట్లు [1, 2, 3] తో అర్రేలు, లిస్ట్లు, స్పాన్లు అన్నింటినీ ఒకేలా సృష్టించవచ్చు!
అదనంగా స్ప్రెడ్ ఆపరేటర్ (Spread ..) ద్వారా రెండు వేర్వేరు అర్రేలను ఒక్క లైన్లో విలీనం (Merge) చేయవచ్చు:
// C# 12 క్లీన్ సింటాక్స్:
int[] batchA = [10, 20, 30];
int[] batchB = [40, 50];
// స్ప్రెడ్ ఆపరేటర్ (..) తో రెండు అర్రేలను కలపడం:
int[] mergedBatches = [..batchA, 999, ..batchB];
Console.WriteLine($"కలిపిన అర్రే: {string.Join(", ", mergedBatches)}");
7 📏 ఇండెక్స్ (^) మరియు రేంజ్ (..) తో అర్రే స్లైసింగ్
C# 8 లో వచ్చిన ఇండెక్స్ మరియు రేంజ్ ఆపరేటర్లు అర్రేల భాగాలను కత్తిరించడానికి (Slicing) ఎంతో సహకరిస్తాయి:
^1: చివరి ఎలిమెంట్ (Last Element).^2: చివరి నుండి రెండవది.arr[1..4]: ఇండెక్స్ 1 నుండి ప్రారంభమై ఇండెక్స్ 4 కు ముందు (1, 2, 3) ఉన్న ఎలిమెంట్స్ ను ఒక కొత్త అర్రేగా స్లైస్ చేస్తుంది.
string[] colors = { "ఎరుపు", "ఆకుపచ్చ", "నీలం", "పసుపు", "తెలుపు" };
Console.WriteLine($"చివరి రంగు: {colors[^1]}"); // తెలుపు
// మధ్యలోని 3 రంగులను స్లైస్ చేయడం (ఇండెక్స్ 1 నుండి 3 వరకు):
string[] middleColors = colors[1..4];
Console.WriteLine($"మధ్య రంగులు: {string.Join(", ", middleColors)}");
8 ⚡ అర్రేస్ వర్సెస్ Span<T> — జీరో-కాపీ పవర్హౌస్
సాధారణ అర్రేను colors[1..4] అని స్లైస్ చేసినప్పుడు — మెమరీ హీప్లో ఒక కొత్త అర్రే అలోకేట్ అయి డేటా కాపీ అవుతుంది.
కానీ హై-పెర్ఫార్మెన్స్ సాఫ్ట్వేర్లో మెమరీ కాపీయింగ్ లేకుండా నేరుగా అసలు అర్రే పైనే ఒక వ్యూ (View) ఏర్పాటు చేయడానికి Span<T> వాడతారు:
int[] massiveData = { 100, 200, 300, 400, 500, 600 };
// Span తో జీరో-అలోకేషన్ స్లైసింగ్: హీప్లో కొత్త అర్రే పుట్టదు!
Span<int> sliceView = massiveData.AsSpan(2, 3); // 300, 400, 500
sliceView[0] = 999; // sliceView లో మార్చితే అసలు massiveData లోని 300 కూడా 999 గా మారుతుంది!
Console.WriteLine($"అసలు అర్రేలో మారిన విలువ: {massiveData[2]}"); // 999
9 📊 తులనాత్మక పట్టిక: 1D vs 2D Rectangular vs Jagged Arrays
మూడు అర్రే రకాల సమగ్ర సాంకేతిక పోలిక:
| ఫీచర్ / కొలమానం | 1D Array | 2D Rectangular Array ([,]) | Jagged Array ([][]) |
|---|---|---|---|
| సింటాక్స్ | int[] a |
int[,] a |
int[][] a |
| మెమరీ లేఅవుట్ | సింగిల్ కంటిగ్యుయస్ బ్లాక్ | సింగిల్ పర్ఫెక్ట్ బ్లాక్ (No fragmentation) | హీప్లో బహుళ స్వతంత్ర అర్రేలు (Pointers to arrays) |
| రో సైజులు | వర్తించదు | ఖచ్చితంగా సమానంగా ఉండాలి | ప్రతి రో వేర్వేరు సైజు ఉండవచ్చు |
| పెర్ఫార్మెన్స్ | అత్యుత్తమం (JIT BCE) | మంచిది | ఇండైరెక్షన్ పాయింటర్ వల్ల కొద్దిగా స్లో |
| ఉత్తమ వినియోగం | వరుస జాబితాలకు | మ్యాట్రిక్స్, గేమింగ్ టైల్స్, ఇమేజ్ పిక్సెల్స్ | విభిన్న పొడవులు గల టేబుల్స్, మల్టీ-లెవల్ డేటా |
10 🔄 అర్రే ఇమ్మ్యుటబిలిటీ భ్రమ & ReadOnlySpan రక్షణ
చాలామంది జూనియర్ డెవలపర్లు చేసే పొరపాటు: readonly int[] grades = { 10, 20 }; అని రాస్తే అర్రే లోపలి అంశాలు మారవు అనుకుంటారు.
ఇక్కడ readonly అనేది కేవలం అర్రే వేరియబుల్ మరొక కొత్త అర్రేకు మారకుండా పాయింటర్ను మాత్రమే లాక్ చేస్తుంది! కానీ grades[0] = 99; అని లోపలి కంటెంట్ను సులభంగా మార్చేయవచ్చు.
పరిష్కారం: లోపలి కంటెంట్ కూడా 100% మారకూడదు అనుకుంటే ReadOnlyCollection<T> లేదా ReadOnlySpan<T> వాడాలి.
11 🚀 రియల్-వరల్డ్ ప్రాజెక్ట్: "Multiplex Cinema Seat Booking & Reservation Engine"
2D రెక్టాంగ్యులర్ అర్రేస్, మెథడ్స్, లూప్స్, మరియు కండిషనల్ లాజిక్ ఉపయోగించి — ఒక మల్టీప్లెక్స్ సినిమా థియేటర్లో సీట్ల రిజర్వేషన్, ధరల లెక్కింపు, మరియు లైవ్ థియేటర్ గ్రిడ్ విజువలైజర్ ఇంజిన్ను తయారు చేద్దాం!
using System;
Console.OutputEncoding = System.Text.Encoding.UTF8;
Console.WriteLine("=================================================");
Console.WriteLine("🎬 మన మల్టీప్లెక్స్ - స్మార్ట్ స్క్రీన్-1 సీటింగ్ బుకింగ్ v1.0");
Console.WriteLine("=================================================\n");
// 4 అడ్డువరుసలు, 6 నిలువు వరుసలతో కూడిన 4x6 థియేటర్ సీటింగ్ అర్రే
// 'O' = ఖాళీ సీటు (Open), 'X' = బుక్ అయిన సీటు (Booked)
char[,] cinemaSeats = {
{ 'O', 'O', 'X', 'O', 'O', 'O' }, // Row 0: ప్లాటినం (₹350)
{ 'O', 'X', 'X', 'O', 'O', 'O' }, // Row 1: గోల్డ్ (₹250)
{ 'O', 'O', 'O', 'O', 'X', 'O' }, // Row 2: సిల్వర్ (₹150)
{ 'O', 'O', 'O', 'O', 'O', 'O' } // Row 3: సిల్వర్ (₹150)
};
void DisplayTheatreLayout(char[,] seats)
{
Console.WriteLine(" [======== స్క్రీన్ ఇటువైపు ఉంది ========]\n");
Console.WriteLine(" Col 0 Col 1 Col 2 Col 3 Col 4 Col 5");
Console.WriteLine(" -----------------------------------------");
string[] tierNames = { "ప్లాటినం", "గోల్డ్ ", "సిల్వర్1", "సిల్వర్2" };
for (int r = 0; r < seats.GetLength(0); r++)
{
Console.Write($"Row {r} ({tierNames[r]}): ");
for (int c = 0; c < seats.GetLength(1); c++)
{
char status = seats[r, c];
if (status == 'X')
{
Console.ForegroundColor = ConsoleColor.Red;
Console.Write($" [{status}] ");
Console.ResetColor();
}
else
{
Console.ForegroundColor = ConsoleColor.Green;
Console.Write($" [{status}] ");
Console.ResetColor();
}
}
Console.WriteLine();
}
Console.WriteLine();
}
bool BookSeat(char[,] seats, int row, int col, out decimal ticketPrice)
{
ticketPrice = 0m;
if (row < 0 || row >= seats.GetLength(0) || col < 0 || col >= seats.GetLength(1))
{
Console.WriteLine("❌ చెల్లని సీటు నంబర్!");
return false;
}
if (seats[row, col] == 'X')
{
Console.WriteLine($"❌ క్షమించండి! Row {row}, Col {col} ఇప్పటికే బుక్ చేయబడింది.");
return false;
}
// సీటు బుకింగ్ కన్ఫర్మ్ చేయడం
seats[row, col] = 'X';
ticketPrice = row switch
{
0 => 350.00m,
1 => 250.00m,
_ => 150.00m
};
return true;
}
// ప్రారంభ లేఅవుట్ చూపించడం
DisplayTheatreLayout(cinemaSeats);
// ఒక సీటును బుక్ చేయడానికి ప్రయత్నించడం (Row 0, Col 4 - ప్లాటినం)
int targetRow = 0, targetCol = 4;
Console.WriteLine($"కస్టమర్ కోరిన సీటు: Row {targetRow}, Col {targetCol}...");
if (BookSeat(cinemaSeats, targetRow, targetCol, out decimal finalPrice))
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine($"🎉 బుకింగ్ విజయవంతమైంది! టికెట్ ధర: ₹{finalPrice:N2}");
Console.ResetColor();
}
Console.WriteLine("\nనవీకరించబడిన తాజా థియేటర్ సీటింగ్ స్థితి:");
DisplayTheatreLayout(cinemaSeats);
Console.WriteLine("=================================================\n");
రో మరియు కాలమ్ నంబర్లను మార్చి సీటు రిజర్వేషన్ ఎలా మారుతుందో, ఇప్పటికే బుక్ అయిన సీటును ఎంచుకుంటే ఎర్రర్ ఎలా వస్తుందో ఆన్లైన్ ఎడిటర్లో లైవ్గా పరిశీలించండి!
12 🛑 బిగినర్స్ చేసే 7 ఘోరమైన అర్రే తప్పులు
అర్రేలతో పనిచేసేటప్పుడు జూనియర్ డెవలపర్లు చేసే 7 ప్రధాన తప్పులు:
5 ఎలిమెంట్స్ ఉన్న అర్రేలో arr[5] అని పిలిస్తే ఎర్రర్ వస్తుంది. ఆఖరి ఎలిమెంట్ ఎల్లప్పుడూ arr[4] లేదా arr[^1] మాత్రమే.
int[][] a = new int[3][]; a[0][0] = 5; రాస్తే NullReferenceException వస్తుంది! ముందుగా a[0] = new int[5]; అని కేటాయించాలి.
3x4 మ్యాట్రిక్స్ లో matrix.Length మొత్తం 12 ఎలిమెంట్స్ ఇస్తుంది. రోస్ కోసం GetLength(0), కాలమ్స్ కోసం GetLength(1) వాడాలి.
అర్రేలు స్థిరమైన సైజు గలవి (Fixed Size). సైజు పెంచడం లేదా తగ్గించడం అవసరమైతే List<T> వాడాలి.
బైనరీ సెర్చ్ పనిచేయాలంటే అర్రే ఖచ్చితంగా ఆరోహణ క్రమంలో సార్ట్ అయి ఉండాలి. లేకపోతే తప్పుడు ఇండెక్స్ లేదా నెగటివ్ నంబర్ ఇస్తుంది.
int[] b = a; రాస్తే కొత్త కాపీ తయారు కాదు, రెండూ ఒకే అర్రేను పాయింట్ చేస్తాయి. నిజమైన కాపీ కోసం (int[])a.Clone() లేదా Array.Copy() వాడాలి.
int[] arr = [1, "రెండు"]; కుదరదు. అర్రేలోని అన్ని ఎలిమెంట్స్ ఒకే డేటా టైప్ కు చెందినవి అయి ఉండాలి.
13 🧠 ఇంటరాక్టివ్ సెల్ఫ్-అసెస్మెంట్ క్విజ్ (5 లోతైన ప్రాక్టికల్ ప్రశ్నలు & వివరణలు)
అర్రేలపై మీ పరిజ్ఞానాన్ని ఈ 5 ప్రాక్టికల్ ప్రశ్నలతో పరీక్షించుకోండి:
❓ ప్రశ్న 1: int[,] (రెక్టాంగ్యులర్) మరియు int[][] (జాగ్డ్) అర్రేల మధ్య ప్రధాన మెమరీ వ్యత్యాసం ఏమిటి?
int[,] అనేది మెమరీ హీప్లో ఒకే ఒక్క పర్ఫెక్ట్ కంటిగ్యుయస్ బ్లాక్ (Single allocation) గా కేటాయించబడుతుంది మరియు అన్ని రోస్ సమాన సైజు కలిగి ఉంటాయి. int[][] అనేది అర్రేల యొక్క అర్రే; అంటే బయటి అర్రేలో పాయింటర్లు ఉంటాయి, ప్రతి రో హీప్లో స్వతంత్రంగా వేర్వేరు సైజులతో అలోకేట్ అవుతుంది.
❓ ప్రశ్న 2: int[] a = new int[5]; డిక్లేర్ చేసినప్పుడు అందులోని ఎలిమెంట్స్ ప్రారంభంలో ఏ విలువను కలిగి ఉంటాయి?
❓ ప్రశ్న 3: C# 12 Collection Expressions లో .. (స్ప్రెడ్ ఆపరేటర్) ఏమి చేస్తుంది?
[..a, ..b] ద్వారా రెండు అర్రేలను లూప్ లేకుండా సులభంగా కలపవచ్చు.
❓ ప్రశ్న 4: int[] b = a; అని రాసి b[0] = 500; అని మార్చితే a[0] మారుతుందా?
b = a అన్నప్పుడు కేవలం మెమరీ పాయింటర్ మాత్రమే కాపీ అవుతుంది, కొత్త అర్రే పుట్టదు. కాబట్టి రెండూ ఒకే మెమరీ లొకేషన్ను సూచిస్తాయి.