Inledning
Vi har tidigare diskuterat Array (Fält) i artikeln Fält (Array) . Denna artikel skall handla om lagringsstrukturer som dök upp första gången i C# 2.0. Med C# 2.0 infördes templates (något som funnits länge i t.ex. C++). Med templates (sv. mallar) gavs nu möjlighet att skriva generiska klasser på engelska kallat generic classes. En av de mest grundläggande behoven i ett programmeringsspråk är att ha bra lagringsstrukturer och det kan man få med generiska klasser. Vi väljer att benämna dessa generiska klasser inriktade mot lagring som samlingsklasser.
Vi skall alltså titta på de samlingsklasser som numera finns i C# (sedan 2.0) hämtade från namnutrymmet System.Collections.Generic. Dessa klasser kommer du att ha stor nytta av i många situationer.
Vilka är klasserna?
De klasser vi tar upp och ger exempel på är följande:
- List
- Dictionary
- Queue
- Stack
- SortedList
Det finns ytterligare en del klasser men vi har valt att begränsa oss till de som vi anser vara de mest använda.
List
Detta kan nog vara den mest använda lagringsklassen. Vi börjar med ett exempel där vi skapar en lista av int som vi dynamiskt utökar för att sedan göra en loop och skriva ut alla värden.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Lagrinsstrukturer
{
class Program
{
static void Main(string[] args)
{
//Skapa en lista av int
List<int> lista = new List<int>();
//Fyll listan med några enstaka värden
lista.Add(5);
lista.Add(15);
lista.Add(25);
//Lägg till flera tal samtidigt genom att använda en Array med värden
lista.AddRange(new int[] { 1, 2, 3, 4, 5, 6 });
//"Iterera" genom listan och skriv ut varje enskilt värde och index.
//Indexeringen är precis som med Array men istället för .Length finns det en .Count
for (int i = 0; i < lista.Count; i++)
{
Console.WriteLine("Index: {0} Tal: {1}", i, lista[i]);
}
//Undersök om ett visst värde finns i listan
Console.WriteLine("Finns talet 5 i listan? " + lista.Contains(5));
Console.WriteLine("Finns talet 7 i listan? " + lista.Contains(7));
//Ta bort första förekomst av ett värde
lista.Remove(5);
//Ta bort värde på ett angivet index
lista.RemoveAt(3);
//"Iterera" igen, denna gång med en foreach loop
foreach (int tal in lista)
{
Console.WriteLine("Tal: {0}", tal);
}
}
}
}
Observera hur vi skapar vår List av int (rad 13). Tecknen < och > avslöjar att vi använder oss av en generisk klass. Inom < > anges den typ som klassen (i detta exempel Listan) skall använda. När vi arbetar med generiska lagringsstrukturer så anger typen vad strukturen skall kunna lagra. Efter skapandet går det ej att ändra typen annat än att skapa ett nytt objekt.
En List fungerar mer eller mindre som en bättre Array. I exemplet ovan visas några vanliga operationer som att lägga till och ta bort. Indexeringen fungerar även den som en Array med skillnaden att antalet element i listan ges av Count och inte av Length som det hade varit för en Array. Att skapa en List av en befintlig Array görs som på rad 21 i exemplet ovan. Du kan på så vis utnyttja listans fördelar, kanske manipulera innehållet för att senare konvertera listan tillbaka till Array med metoden ToArray() (ej med i exemplet ovan).
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Lagrinsstrukturer
{
class Program
{
static void Main(string[] args)
{
//Skapa en lista av double
List<double> lista = new List<double>();
//Lägg till flera tal samtidigt genom att använda en Array med värden
lista.AddRange(new double[] { 3.2, 5.1, 13, 7.4, 8.2, 2.8, 14.1 });
//Skriv ut lite information om innehållet i listan
Console.WriteLine("Summan av alla element: " + lista.Sum());
Console.WriteLine("Medelvärdet: " + lista.Average().ToString("0.00"));
Console.WriteLine("Största värdet: " + lista.Max());
Console.WriteLine("Minsta värdet: " + lista.Min());
//Vilket index hade det minsta värdet...?
Console.WriteLine("Minsta värdet fanns på index: " + lista.IndexOf(lista.Min()));
//Sortera listan
lista.Sort();
foreach (double d in lista)
{
Console.WriteLine("Värde: " + d);
}
}
}
}
Som visas i exemplet ovan så finns det metoder i för att beräkna summa, medelvärde, maxvärde och minimivärde (extension methods). Beroende på vilken typ du väljer att din lista skall lagra så kan dessa metoder skilja sig åt. Gör du t.ex. en lista av string så finns inte metoden Sum(). Vad en Extension method är tänkte vi inte ta upp i denna artikel men du bör kunna identifiera dem i Visual Studio (bild nedan) och lägga på minnet att dessa metoder kan du inte räkna med att alla typer har.
Det finns en metod, Sort(), för att sortera listan som kan vara användbar.
Dictionary
En Dictionary har en funktion liknande ett lexikon. Varje element i en Dictionary består av två delar; en nyckel (key) och ett värde. Du blir tvungen att ange två typer när du skapar en Dictionary, en typ som skall användas som nyckel och en typ som skall användas som värde.
Vi börjar med ett exempel där string används som nyckel och int som värde:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Lagrinsstrukturer
{
class Program
{
static void Main(string[] args)
{
//Skapa en dictionary med string som nyckel och int som värde
Dictionary<string, int> personer = new Dictionary<string, int>();
//Lägg till
personer.Add("Kalle", 10);
personer.Add("Sven", 12);
personer.Add("Oskar", 10);
personer.Add("Viggo", 13);
Console.WriteLine("Kalles ålder är: " + personer["Kalle"]);
//"Iterera" genom samlingen
foreach (KeyValuePair<string, int> kvp in personer)
{
Console.WriteLine("Nyckel: {0} Värde: {1}", kvp.Key, kvp.Value);
}
}
}
}
Tanken är att vi lagrar åldern hos varje person. Begreppet index får ett litet annan innebörd nu när andra typer än int kan användas som index. Kolla extra på rad 21. Vi indexerar med "Kalle" för att få ett värde.
En annan stor skillnad är hur en iteration genom samlingen går till. För att t.ex. lista allt innehåll måste vi använda klassen KeyValuePair i en foreach-loop. Vi behöver tala om hur värden och nycklar plockas ur samlingen.
När du jobbar med en Dictionary får du vara uppmärksammad på vissa saker. Anger du ett index fel, t.ex. om vi hade skrivit "kalle" på rad 21, så får vi ett Exception eftersom denna nyckel inte finns i samlingen (skillnad på stora och små bokstäver). Likaså får vi ett Exception om vi försöker lägga till en nyckel som redan finns i samlingen. Alltså gäller att
Nyckeln måste vara unik. Det kan inte finnas två "Kalle" i en Dictionary. Vi skall visa hur du håller koll på detta i nästa exempel.
I nästa exempel skall vi skapa ett histogram över simulerade tärningskast. Ett histogram mäter förekomsten av olika utfall/värden. Ett enkelt histogram över vädret skulle kunna vara hur många dagar på året som det har regnat. I exemplet nedan skall vi räkna hur många 1:or, 2:or, etc. som vi slår på en tärning. En Dictionary kan göra livet enklare:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Lagrinsstrukturer
{
class Program
{
static void Main(string[] args)
{
//Skapa en dictionary med int som nyckel och int som värde
Dictionary<int, int> resultat = new Dictionary<int, int>();
//skapa ett Random objekt för att slumpa
Random random = new Random();
//Gör tusen upprepnigar
for (int i = 0; i < 1000; i++)
{
//Slumpa tal mellan 1 och 6
int tal = random.Next(1, 7);
//Lägg nyckel om denna inte redan finns
if (!resultat.ContainsKey(tal))
resultat.Add(tal, 0);
//Öka förekomsten av tal
resultat[tal]++;
}
//Visa resultatet
foreach (KeyValuePair<int, int> kvp in resultat)
{
Console.WriteLine("Nyckel: {0} Värde: {1}", kvp.Key, kvp.Value);
}
}
}
}
Eftersom vi slumpar så kommer du inte att få exakt samma resultat som bilden visar. Vi vet på förhand att vi bara kommer att slumpa tal mellan 1 och 6 så vi skulle kunna lägga till dessa nycklar innan vi går in i loop'en. Det är dock så pass vanligt att på förhand inte veta sådan information så vi gör en extra kontroll på rad 25 om vi behöver skapa en ny nyckel.
I resultatet ser vi också att nycklar listas i den ordning som de skapas, dvs. 1 kommer ej först. Detta skulle vi kunna lösa genom att göra en for-loop från 1 till 6 och istället för att använda foreach och KeyValuePair.
Det finns tyvärr ingen Sort() metod i Dictionary vilket leder oss till nästa klass...
SortedList
Denna samlingsklass har samma egenskaper som Dictionary med skillnaden att den är sorterad. Med SortedList anger du alltså nycklar och värden precis som Dictionary men SortedList förblir sorterad efter det att du lagt till element i samlingen.
Nu skall det nämnas att det finns en klass som heter SortedDictionary vilket hade varit det pedagogiska alternativet. Faktum är att SortedList och SortedDictionary fungerar nästan helt identiskt. Anledningen till att vi väljer SortedList är att denna har något bättre prestanda.
Istället för exempel på denna klass så erbjuder vi en övning
Övning 1
Skriv om Exempel 2 - Dictionary till att använda SortedList. Kör programmet några gånger så du upptäcker skillnaden.Queue
En Queue (kö) fungerar precis som namnet antyder som en kö. Du kan köa element och sedan plocka ut dem i ordningen "först in", "först ut".
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Lagrinsstrukturer
{
class Program
{
static void Main(string[] args)
{
Queue<string> nummer = new Queue<string>();
nummer.Enqueue("ett");
nummer.Enqueue("två");
nummer.Enqueue("tre");
nummer.Enqueue("fyra");
nummer.Enqueue("fem");
// "Iteration" utan att ändra innehållet i kön
foreach (string number in nummer)
{
Console.WriteLine(number);
}
// "Iteration" samtidigt som kön töms
while(nummer.Count > 0)
{
Console.WriteLine(nummer.Dequeue());
}
}
}
}
Metoden Enqueue() köar ett element (att jämföra med Add()). Metoden Dequeue() returnerar första elementet i kön (det äldsta elementet i kön) samtidigt som detta element plockas bort från kön.
Stack
Detta är liksom Queue en samlingsklass som fungerar som en kö, skillnaden är att en Stack fungerar som "först in", "sist ut". (Liknande LAS = Lagen om anställningsskydd). Metoderna heter något annorlunda; Push() för att lägga saker på stacken och Pop() för att plocka saker.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Lagrinsstrukturer
{
class Program
{
static void Main(string[] args)
{
Stack<string> nummer = new Stack<string>();
nummer.Push("ett");
nummer.Push("två");
nummer.Push("tre");
nummer.Push("fyra");
nummer.Push("fem");
// "Iteration" utan att ändra innehållet i stacken
foreach (string number in nummer)
{
Console.WriteLine(number);
}
// "Iteration" samtidigt som stacken töms
while(nummer.Count > 0)
{
Console.WriteLine(nummer.Pop());
}
}
}
}
Sammanfattning
Gemensamt för generiska lagringsklasser är att de alla är mer dynamiska, dvs. de kan växa och minska efter behov på ett enkelt sätt. Jämför med en Array, vars storlek bestäms då den skapas, så är en Array alltså mer statisk.
Generiska klasser är också starkt typade. Med detta menas att en List
Alla lagringsklasser som vi har tagit upp i denna artikel är specialiserade på ett eller annat vis. De kanske två mest använda förblir nog List och Dictionary men i vissa tillämpningar så underlättar det något enormt om man har en lagringsklass som är mer specifikt anpassad.
Tja. Skulle någon vilja kolla igenom min kod och föreslå förbättringar. Känns alltid som att jag försvårar alla övningar och skriver massa onödig kod.
Det här är min kod för övning 4.
static void Main(string[] args)
{
List kort = new List();
Random tal1 = new Random();
List color = new List();
Dictionary korten = new Dictionary();
color.AddRange(new string[] {”H”,”K”,”S”,”R”});
for (int i=0;i<=3;i++)
{
kort.Add(color[i] + "Es");
for (int a = 2; a <= 10; a++)
{
kort.Add(color[i] + a.ToString());
}
kort.Add(color[i] + "Kn");
kort.Add(color[i] + "D");
kort.Add(color[i] + "K");
}
int value = 1;
for (int i=0; i<=(kort.Count – 1);i++)
{
korten.Add(kort[i], value);
if (value == 13)
value = 0;
value++;
}
Dictionary.KeyCollection keyColl=korten.Keys;
List par = new List();
foreach (string a in keyColl)
par.Add(a);
int counter = 0;
for (int i = 0; i < 1000; i++)
{
int slump1 = tal1.Next(0, korten.Count);
int slump2 = tal1.Next(0, korten.Count);
if (korten[par[slump2]] == korten[par[slump1]])
{
Console.WriteLine("Par!");
counter++;
}
}
Console.WriteLine(counter);
}
Det var ju länge sedan du postade detta, men här är en enklare lösning. Plus att du glömde att List och Dictionary är generiska klasser som behöver typspecificerare.
static void Main(string[] args)
{
Random tal1 = new Random();
var korten = new Dictionary();
var colors = new string[] {”H”,”K”,”S”,”R”};
for (int i = 0; i <= 3; i++)
{
korten.Add(colors[i] + "Es", 1);
for (int a = 2; a <= 10; a++)
{
korten.Add(colors[i] + a.ToString(), a);
}
korten.Add(colors[i] + "Kn", 11);
korten.Add(colors[i] + "D", 12);
korten.Add(colors[i] + "K", 13);
}
var keys = korten.Keys.ToArray();
int counter = 0;
for (int i = 0; i < 1000; i++)
{
int slump1 = tal1.Next(0, keys.Length);
int slump2 = tal1.Next(0, keys.Length);
if (korten[keys[slump2]] == korten[keys[slump1]])
{
Console.WriteLine("Par!");
counter++;
}
}
Console.WriteLine(counter);
}