Gå till innehållet

Kapitel 10 - List

Introduktion till kapitlet

Hittills så har vi använt arrayer för att spara många variabler av samma typ. Arrayer har en del begränsningar som gör dem jobbiga att använda i vissa fall, t.ex. så kan man inte ändra storleken av en array efter att den har skapats. Vi kommer i detta kapitel se hur man kan använda List, d.v.s. listor, för att spara många variabler av samma typ utan att behöva säga hur många variabler man vill ha när man skapar listan.

List

En array är inte alltid smidig att använda. Om man inte vet hur många element arrayen ska innehålla, vilket kan vara fallet om den ska fyllas av information som användaren skriver in, så behöver man skapa en array med onödigt stor kapacitet så att den inte blir överfull. Detta problem slipper man när man använder en List samtidigt som List har många andra inbyggda metoder.

Under ytan så innehåller varje List en array, när man lägger till ett element i en List så läggs elementet till i listans array. Om den dolda arrayen är full och du försöker lägga till ytterligare ett element till listan så kommer listan att skapa en ny array med dubbelt så många platser. Därefter så kopieras alla element från den urpsrungliga arrayen till den nya arrayen och då finns det även plats för det nya elementet som du vill lägga till. Allt detta sker under ytan men det kan ända vara bra att känna till att en List egentligen använder sig av en array. En List har också metoder som t.ex. sorterar listan i storleksordning eller som söker igenom listan efter ett specifikt värde.

I exemplet nedan visas hur man skapar en List med heltal. Notera att det sätt som man anger vilken typ listan ska innehålla liknar det sätt som man anger vad man vill ladda in med metoden Content.Load i MonoGame.

List<int> favorittal = new List<int>();
favorittal.Add(150);
favorittal.Add(4);
favorittal.Add(42);
favorittal.Add(100);
favorittal.Add(16);
favorittal.Add(8);
favorittal.Add(23);
favorittal.Add(15);

// Ta bort talet 100 från listan
favorittal.Remove(100);

// Hitta vilket index talet 150 har i listan
// Ta bort talet på detta index
int index = favorittal.IndexOf(150);
favorittal.RemoveAt(index);

// Sortera listan med favorittal
favorittal.Sort();

Console.WriteLine("Här är alla mina favorittal (for-loop)");
for (int i = 0; i < favorittal.Count; i++)
{
    Console.WriteLine(favorittal[i]);
}

Console.WriteLine("Här är alla mina favorittal (foreach-loop)");
foreach (int tal in favorittal)
{
    Console.WriteLine(tal);
}

Console.ReadKey();

I exemplet så läggs några tal till i en lista, notera att man inte behöver ange listans storlek när man skapar den, listans storlek är alltid lika stor som antalet variabler som är lagrade i den. Det man kallar för listans storlek, d.v.s. antalet variabler i listan, behöver alltså inte vara detsamma som storleken av den dolda arrayen som listan använder.

När talen har lagts till i listan så tas två av dem bort på två olika sätt. Det första sättet använder metoden Remove för att ange vilket tal som ska tas bort, om samma tal finns på flera platser i listan tas bara det första av dessa bort. Den andra metoden tar först reda på vilket index som talet 150 har (det första indexet om 150 finns på flera platser) och därefter tas talet på detta index bort från listan. I detta exempel så finns det inte någon fördel med att ta reda på talets index och sedan ta bort det, detta sätt visas för att du ska se hur man kan använda metoden IndexOf som kan vara användbar i andra sammanhang. Något att notera är att när man tar bort ett föremål från en lista så tas detta föremål bort i den underliggande arrayen. Efter att föremålet i den underliggande arrayen har tagits bort så flyttas alla element som fanns efter detta föremål neråt en plats i listan så att det inte ska finnas några ”hål” eller tomma platser mitt i den dolda arrayen. Detta medför att många anrop av Remove kan påverka ett programs prestanda negativt.

Listan sorteras med den inbyggda metoden Sort som kan användas för att sortera listor av t.ex. tal eller text och sedan skrivs alla tal i listan ut, dels med en for-loop och dels med en foreach-loop. En sammanfattning av de metoder och egenskaper som listor har som exemplet använde visas i tabellen nedan. Vi har även tagit med metoden Clear() som tömmer en lista på alla dess element vilket kan vara väldigt användbart.

Metod eller egenskap Beskrivning
Add(T element) Lägger till ett element av variabeltypen T till listan.
Remove(T element) Tar bort elementet av variabeltypen T från listan.
IndexOf(T element) Hittar det lägsta index som ett element
RemoveAt(int index) Tar bort ett elementet med det angivna indexet från listan.
Sort() Sorterar listan i storleks- eller bokstavsordning.
Clear() Tömmer listan på alla dess element
Count Antal element i listan

En lista kan alltså användas istället för en array, men hur ska man veta om man bör använda en array eller en List när man behöver lagra många variabler av samma typ? Som regel så bör man alltid använda en List så länge som det inte finns något annat skäl som man kan komma på för att använda en array.

Var hittar man fler av de metoder en List har?

När du skriver punkten efter namnet på en listvariabel i Visual Studio får du upp en lista med förslag på alla metoder som en List har. Du kan också kolla i dokumentationen av C# som Microsoft har gjort, den finns här. Det är bra att träna på att använda dokumentationen av C# eftersom den är väldigt omfattande och hjälpsam när man vill ha reda på mer om någonting.

Uppgift 10.1

Skapa ett program där användaren får skriva in ett valfritt antal städer, användaren ska inte ange hur många städer som hen vill skriva in i början av programmet. Alla städerna ska sparas i en lista med textsträngar. Användaren ska avsluta sin inmatning av städer genom att skriva in en tom rad. Efter att alla städer är inmatade ska listan sorteras i bokstavsordning och därefter skrivas ut.

Lösningstips 10.1

Skapa en string i ditt program, string nyStad = " ". Använd en loop för att läsa in nya strängar från användaren, loopen ska köras så länge som nyStad inte är "".

Lösningsförslag 10.1

Uppgift 10.2

Välj ett program som du har gjort tidigare som har använt en array. Skriv om programmet så att det använder en List istället för en array.

List i MonoGame

En lista går att använda i MonoGame för att lösa samma problem som man kan lösa med hjälp av en array med samma fördelar jämfört med en array som hittills har nämnts. Om man vill använda en List i MonoGame måste man lägga till ett using-uttryck som inte finns med i ett MonoGame-program som standard, det ser ut så här:

using System.Collections.Generic;

Vi ska i nästa exempel skapa ett program som använder en lista av Vector2 för att rita en bild på många olika platser. Börja därför med att lägga till detta using-uttryck i ditt nya MonoGame-program. Härnäst så deklarerar vi några medlemsvariabler som ska användas i spelet. Bilden som används heter parrot.png och är inlagd i MonoGames Content Pipeline Tool.

GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Texture2D parrotBild;
List<Vector2> parrotPositioner = new List<Vector2>();

Vi deklarerar här en Lista och en variabel för att spara bilden i. Eftersom vi i detta exempel vill generera de vektorer som ska finnas i listan en gång när spelet startar så blir det lämpligt att göra detta i metoden Initialize.

for (int i = 0; i < 10; i++)
{
    parrotPositioner.Add(new Vector2(i * 70, 200));
}

base.Initialize();

Vektorerna som läggs till i listan har alla samma y-värde men olika x-värden.

Variabeln parrotBild tilldelas ett värde i metoden LoadContent på samma sätt som i alla tidigare program, du får själv skriva koden som gör detta.

För att rita bilden på alla positioner som finns i listan så använder vi en loop, i detta fall en foreach-loop. foreach-loopen är vanlig när man vill göra något med varje element i en lista, den används på samma sätt som när man loopar igenom en array.

GraphicsDevice.Clear(Color.CornflowerBlue);

spriteBatch.Begin();
foreach (Vector2 parrotPosition in parrotPositioner)
{
    spriteBatch.Draw(parrotBild, parrotPosition, Color.White);
}
spriteBatch.End();

base.Draw(gameTime);

När man nu kör programmet så kommer samma bild att ritas upp på 10 olika platser.

Värdetyper och List

Det finns några saker som man måste känna till med hur listor fungerar, särskilt när det gäller värdetyper. Hur värdetyper fungerar när man använder listor är viktigt att veta när man gör MonoGame-program eftersom variabeltyperna Vector2 och Rectangle är värdetyper och inte referenstyper.

Så tar du reda på om en variabeltyp är en värdetyp eller referenstyp

Om du håller musen över en variabeltyp i Visual Studio så får man information om variabeltypen i en ruta som dyker upp. Om variabeltypen är en struct eller enum så är den en värdetyp, är den en class så är den en referenstyp. En array är alltid en referenstyp oavsett om arrayen innehåller en värdetyp eller referenstyp.

På detta sätt kan man se att både Vector2 och Rectangle tillhör kategorin struct, alltså är de värdetyper.

Tänk nu att vi vill bygga ut vårat program så att alla bilderna i det förra exemplet rör på sig när programmet körs. Vi kan göra detta med hjälp av en loop i programmets Update-metod. Vi använder i detta fall en for-loop istället för en foreach-loop eftersom man inte kan ändra värdet av den variabel som man loopar igenom med foreach-loopen. Trots detta så kommer inte koden i följande exempelruta att fungera, programmet kan inte kompileras med denna kod.

for (int i = 0; i < parrotPositioner.Count; i++)
{
    parrotPositioner[i].X++;
}

base.Update(gameTime);

Anledningen till att man inte kan använda denna kod har att göra med att Vector2 är en värdetyp och inte en referenstyp. Inuti listan med vektorer så finns det en dold array som sparar alla vektorer i listan. När man vill komma åt parrotPositioner[i] så anropas en särskild metod som List har. Denna metod hämtar en vektor i den dolda arrayen, men eftersom Vector2 är en värdetyp så hämtas en kopia av själva vektorn istället för en kopia av referensen till vektorn. Det viktiga här är alltså att vi får en kopia av vektorn, och när vi skriver att vi vill öka X-koordinaten för vektorn med 1 så är den denna kopia som kommer att få en större X-koordinat, inte den vektor som finns i listan. Om Vector2 hade varit en referenstyp så hade man inte stött på detta problem eftersom man då hade fått en kopia av referensen till vektorn i listans dolda array, och med hjälp av denna referens hade man kunnat ändra vektorn som finns inuti listan.

Hur ska man då göra för att öka X-koordinaterna för alla bilder i listan? Det finns några olika alternativ:

  • Vi kan spara kopian i en ny Vector2-variabel, ändra värdet av kopian och sedan spara kopian på rätt plats i listan.
  • Vi kan använda en array istället för en List. Då förlorar vi alla de fördelar som en List har jämfört med en array men vi har då tillgång till varje Vector2 direkt istället för att bara få kopior av vektorerna som vi får när vi använder en List.

Vi ska se hur man kan skriva kod som löser problemet på det första sättet, du får själv testa att skriva om programmet så att det använder en array i en uppgift.

Vector2 temp;
for (int i = 0; i < parrotPositioner.Count; i++)
{
    temp = parrotPositioner[i];
    temp.X++;
    parrotPositioner[i] = temp;
}

base.Update(gameTime);

Denna Update-metod kommer att åstadkomma det som vi tidigare ville. Vi har deklarerat en variabel som vi kallar för temp, ett namn som man ofta använder för en variabel som behövs tillfälligt eller temporärt. Denna variabel kan med fördel kunnat deklareras som en medlemsvariabel så att den inte behöver skapas på nytt i varje Update. Vår for-loop sparar kopian av vektorn som vi får från listan i variabeln temp. Därefter så ökar vi värdet av temp-variabelns X-koordinat, och slutligen så sparar vi värdet av temp-variabeln på den plats i listan som loopen jobbar med.

Andra sätt att lösa problemen

Det finns andra sätt att lösa problemet med värdetyper och List, men de ingår inte i Programmering 1. Du kan vara lugn med att om du fortsätter läsa programmering så behöver du inte använda temp-variabler med listor så ofta. När du lär dig att skapa egna klasser, som ingår i Programmering 2, så kan man undvika detta problem smidigt.

Uppgift 10.3

Skriv om det senaste exemplet så att det använder sig av en array istället för en List. Eftersom du har direkt åtkomst till varje element av en värdetyp i en array till skillnad från i en List så kan det vara lättare att använda en array i just detta fall. Programmet ska inte längre använda sig av en temp-variabel som det gjorde när det hade en List.

Lösningsförslag 10.3

Uppgift 10.4

Gör ett program som ritar upp samma bild på tio olika platser i spelfönstret. Man ska kunna styra alla bilderna samtidigt med vänster och höger piltangenter. Alla bilderna ska flyttas med olika hastigheter när man håller ner någon av dessa tangenter. Detta program ska använda sig av en List, inte en array.

Lösningstips 10.4

Eftersom du ska använda dig av en list i detta program och Vector2 är en värdetyp så måste du använda dig av en temp-variabel när du ska flytta bilderna.

Lösningsförslag 10.4

Blandade upgifter till kapitel 10

Uppgift 10.5

Skapa ett konsollprogram där användaren ska få skriva in namn. Användaren ska inte behöva ange hur många namn som ska skrivas in när programmet börjar. Användaren ska få mata in nya namn ända tills hen skriver en tom rad. Alla de inmatade namnen ska skrivas ut i omvänd bokstavsordning.

Lösningstips 10.5

När du ska skriva ut namnen i omvänd bokstavsordning så kan du sortera listan som vanligt och sedan gå igenom listan bakifrån, d.v.s. börja på det sista indexet med en for-loop och räkna nedåt.

Lösningsförslag 10.5

Uppgift 10.6

Skapa ett konsollprogram där användaren ska få skriva in sina senaste månadslöner. Användaren ska inte behöva ange hur många månadslöner som ska skrivas in när programmet börjar. Användaren ska fortsätta att skriva in månadslöner till hen skriver in lönen 0, talet 0 avslutar endast inmatningen och ska inte räknas som en månadslön. Beräkna och skriv ut användarens medel- och medianlön.

Lösningstips 10.6

Sortera listan innan du ska ta reda på medianlönen. Glöm inte att först undersöka om det finns ett jämnt eller udda antal löner i listan, detta påverkar hur du ska beräkna medianen.

Lösningsförslag 10.6

Uppgift 10.7

Skapa ett MonoGameprogram som inte ritar ut några bilder. Varje gång användaren klickar med musen så ska en bild skapas på denna plats och ritas ut där, den ska ritas ut på denna position så länge som programmet körs.

Lösningstips 10.7

Använd två MouseState för att ta reda på om användaren klickat med musen.

Lösningsförslag 10.7

Uppgift 10.8

Skapa ett konsollprogram som innehåller en lista som du själv har skapat i programmets kod. Listan ska innehålla minst 10 ord. Skapa en metod som har en lista med strängar som parameter och som returnerar det vanligast förekommande ordet i listan.

Lösningstips 10.8

Gå igenom listan ord för ord, för varje ord så ska du räkna hur många gånger just detta ord finns i listan med en till loop.

Lösningsförslag 10.8

Uppgift 10.9

Skapa ett MonoGameprogram som innehåller samma bild på 20 olika platser, bilden bör vara ganska så liten. Bilderna ska åka runt med en hastighet som slumpas när programmet startar och de ska studsa mot fönstrets kanter. När två bilder krockar med varandra ska båda bilderna tas bort från programmet.

Lösningstips 10.9

Använd rektanglar och metoden Intersects för att undersöka om två bilder krockar med varandra. När du går igenom listan med rektanglar med en loop så är det bra att låta den räkna nedåt istället för uppåt, annars riskerar man att "hoppa över" några av rektanglarna när loopen körs. Detta sker p.g.a. att när man tar bort ett element från en lista så minskar indexen med 1 för alla element som kommer senare i listan.

Lösningsförslag 10.9

Kommentarer