Samlingsklasser

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.

Exempel 1: List

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);
            }
        }
    }
}

bild

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).

Exempel 2: List

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);
            }
        }
    }
}

bild

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.

bild

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:

Exempel 1: Dictionary

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);
            }

        }
    }
}

bild

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:

Exempel 2: Dictionary

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);
            }

        }
    }
}

bild

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".

Exempel : Queue

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.

Exempel : Stack

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 bara kan lagra värden av typen int. Tidigare lagringsklasser (C# 1.1) som t.ex. ArrayList var inte starkt typad men hade de användbara dynamiska egenskaper som 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.

Övning 2

Skapa ett program där du tillåts att mata in tal tills dess att du matar in 0 då avslutas programmet. Efter varje inmatat tal skall du skriva ut medelvärdet av alla tal som matats in.

Övning 3

Använd en lista av strängar för att symbolisera en kortlek. T.ex. "k5" betyder klöver 5, "hKn" hjärter knekt, "sE" spader Ess, "rD" ruter dam, etc. Var effektiv när du fyller listan och använd loop'ar. Skriv sedan ett program som slumpvis drar kort från korleken tills dess att alla 52 kort är dragna. Skriv ut varje kort du "drar".

Övning 4

Bygg vidare på Övning 3 men använd en Dictionary istället. Som nyckel använder du "k5" etc eftersom dessa är unika i en kortlek på 52 kort. Som värde lagrar du int, Ess = 1, Kung = 13, etc. Dra nu två kort åt gången utan att "minska" kortleken och skriv ut "PAR" de gånger du drar två lika kort, dvs att värdet är lika. För att lösa detta måste du slumpvis kunna välja en nyckel i Dictionary vilket du kan genom att gå på Keys i Dictionary som i sig är en lista. Försök till sist beräkna hur många gånger det blir "PAR" om du gör 1000 dragningar.

Övning 5

Skriv ett program som räknar ut förekomsten av ord i en text(histogram). Indata kan lösas via konsollen eller genom att läsa en txt-fil. Resultatet presenteras sorterat med flest förekomster först. Var noga med att skala bort punkter, kommatecken, frågetecken och utropstecken samt tänk på/lös att "Det" inte är samma som "det".

2 comments on Samlingsklasser

  1. Nisse skriver:

    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);

    }

    1. Mikael Börjesson skriver:

      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);
      }

Lämna ett svar till Nisse Avbryt svar

Din e-postadress kommer inte publiceras. Obligatoriska fält är märkta *

Scroll to top