Kirjautuminen

Haku

Tehtävät

Keskustelu: Ohjelmointikysymykset: VB.NET: Sana ryhmät

Sivun loppuun

codemike [11.02.2011 22:20:13]

#

Millaisellaohan ohjelma pätkällä pystyisi vb.net'lla saamaan aikaiseksi että leikkaus pöydältä ottaa esim.tiedot:
leipä 2 euroa
limpsa 1 euroa
kakku 10 euroa
limpsa 2 euroa
että löytää hinnat ja pystyy antamaan niille symbolit ja huomaa että limpsa on siellä kaksi kertaa?
Olen huomaanut että opin ohjelmointia parhaiten tutkimalla esimerkkejä ja siten ite ohjelmoimalla.
Olisin tosi kiitollinen/onnellinen jos joku jaksaisi näyttää koodin?

Grez [11.02.2011 22:41:10]

#

Se on leikepöytä. Leikkauspöytä on tämän näköinen: http://www.vivimedi.fi/surgimax.php

Dim hinnat = New Dictionary(Of String, Decimal)()
Dim rivit = Clipboard.GetText().Split(ControlChars.NewLine)
For Each rivi In rivit
    If (Not String.IsNullOrWhiteSpace(rivi)) Then
        Dim tiedot = rivi.Split(ControlChars.Tab)
        Dim Summa As Decimal
        If tiedot.Length <> 3 OrElse tiedot(2) <> "euroa" OrElse Decimal.TryParse(tiedot(1), Summa) Then
            MessageBox.Show("Rivin tiedot väärässä muodossa: " & rivi)
        ElseIf hinnat.ContainsKey(tiedot(0)) Then
            MessageBox.Show("Tuote " + tiedot(0) + " on moneen kertaan, vain ensimmäinen huomioidaan")
        Else
            hinnat.Add(tiedot(0), Summa)
        End If
    End If
Next

Tai jos tykkää käyttää Regexpejä

Imports System.Text.RegularExpressions
'....
        Dim hinnat = New Dictionary(Of String, Decimal)()
        Dim re = New Regex("^(\S+)\s(\d+)\seuroa")
        For Each m As Match In re.Matches(Clipboard.GetText())
            If hinnat.ContainsKey(m.Groups(1).Value) Then
                MessageBox.Show("Tuote " + m.Groups(1).Value + " on moneen kertaan, vain ensimmäinen huomioidaan")
            Else
                hinnat.Add(m.Groups(1).Value, m.Groups(2).Value)
            End If
        Next

codemike [12.02.2011 22:22:46]

#

Kiitos todella paljon avustasi. Mutta saan paljon virheilmoituksia kun laitan koodit visual basic 2010 expressiin? Ja koska en saanut sitä toimimaan en päässyt yrittämään omia sovelluksia. Siis tarkoituksena on että ohjelma osaisi toimia myös vaikka teksti olisi näin satunnaisesti:
leipä €2
limpsa €1
kakku €10
limpsa €2

(ja ilmoittaisi että siellä on limpsa kaksi kertaa.)

Grez [12.02.2011 23:04:07]

#

codemike kirjoitti:

Kiitos todella paljon avustasi. Mutta saan paljon virheilmoituksia kun laitan koodit visual basic 2010 expressiin?

Harmillista. Minkäköhän tyyppisiä virheilmoituksia sait? Mihin laitoit tuon koodin? Kannattaa tehdä vaikka nappi ja sen klikkaus-eventiin laittaa tuo koodi. Jos laitat alemman koodin tuo Imports tulee tietenkin ihan sinne tiedoston alkuun ja toi loppu taas johonkin eventiin tms subiin/funktioon.

codemike [13.02.2011 21:41:23]

#

Kiitos sain ohjelmat toimimaan mutta minulle jäi hieman epäselväksi niiden toiminta? Tarkoitus siis oli että ohjelma osaisi antaa
xxx1 leipä €2
xxxxxx limpsa €1
xx kakku €10
xxx2 limpsa €2
leikepöydälle
leipä = x, limpsa = y, kakku = e, ja ohjelmasta voisi myöhemmin huomata leipä = y = 2 ja että limpsa mainitaan kaksi kertaa.
Jos tämä olisi liian vaikeata niin sellainenkin riittäisi mikä toimisi vastaavasti mutta etukäteen olisi annettu arvot leipä = x jne. ? (enemmän kyllä kiinnostaisi ensin mainittu. Ja vielä jos ohjelma tietäisi että xxx1 ja leipä on samalla rivillä mutta se taitaa olla jo liikaa vaadittu?)

Grez [13.02.2011 21:54:22]

#

Sori nyt vaan, mutta ei tuossa sepostuksessasi ole päätä eikä häntää. Leikepöydälel voit laittaa tekstiä Clipboard.SetText() -metodilla. Et selittänyt lainkaan mitä noi xxx1, xxxxxx yms. noiden rivien aluissa on. Kerroit että leipä =x, mutta ei tuo nyt näytä hirveän järkevältä jos se tulostaisi

leipäleipäleipä1 leipä €2
leipäleipäleipäleipäleipäleipä limpsa €1

jne.

Ainakin tuo ensimmäinen versioni oli hyvin yksinkertainen. Se vain jakoi leikepöydältä jaetun tekstin riveihin ja sitten käsitteli kunkin rivin erikseen jakamalla sen osiin välilyönin perusteella. Tulokset se tallesti dictionaryyn, joka siis on tietorakenne, joka voi sisältää halutun tyyppisiä arvoja joita voi hakea halutun tyyppisellä avaimella. Esimerkissä avain oli merkkijono (tuotten nimi) ja arvo olisi desimaaliluku (hinta).

Eli varmastikin siitä pystyisit hyvin helposti lähteä kehittämään asiaa eteenpäin, jos vaan kiinnostusta löytyy. Kiinnostus tarkoittaa sitä, että myös itse haluaa nähdä vähän vaivaa asian eteen.

Sitten jos tulee ongemia jossain asiassa, niin yritä kysyä silleen, että kysymys on selkeä ja yksikäsitteinen.

codemike kirjoitti:

mutta se taitaa olla jo liikaa vaadittu

Jos sen ohjelman täytyy osata lukea ajatuksia, niin sitten se on liikaa vaadittu. Muuten ei. Yleisesti minkäänlaisen tietojen käsittelyn tekeminen ei ole mahdotonta, jos vaan ei vaadita että ohjelman pitäisi kehittää informaatiota tyhjästä. Esimerkiksi ensi viikon lottonumeroita on turha yrittää kehittää ohjelmallisesti.

codemike [14.02.2011 13:44:17]

#

Kiitos avusta mietiskelen rauhassa. Mutta ihan perus asioihin mennäkseni niin kun viiimeksi aikoinaan ohjelmoin enemmän se oli aikaa jolloin oli rivinumerot. Nyt innostuin funktioista ja huomasin ne hyväksi ja luulin jo tajunneeni ohjelmoinnin idean että hypätään funktiosta funktioon. Mutta siinähän käy nopeasti niin että Recursion level exceeded koska jos hyppää funktiosta toiseen se jää tavallaan päälle ja kun siihen palataan uudestaan se luo copyn. Onko tälläinen ongelma ratkaistavissa vai mikä on nykyisen ohjelmoinnin idea?

Grez [14.02.2011 13:59:30]

#

codemike kirjoitti:

Nyt innostuin funktioista ja huomasin ne hyväksi ja luulin jo tajunneeni ohjelmoinnin idean että hypätään funktiosta funktioon. Mutta siinähän käy nopeasti niin että Recursion level exceeded

No, aika monta sisäkkäistä funktiokutsua saat tehdä ennen kuin tulee Recursion level exceeded. Ehkä olet ymmärtänyt jotain väärin. Siis idea toki on, että funktiot kutsuu funktioita jotka kutsuu funktioita, mutta niistä on välillä tarkoitus mennä poiskin.

Kokeilin tuossa tehdä funktion joka kutsui itseään ja 14300 rekursiota ehti tulla ennen stack overflowta. Normaalissa ohjelmoinnissa tuskin koskaan mennään yli 100 rekursion (ellei nimemomaan käytetä jotain rekursiivista algoritmia)

Ehkä jos laitat jonkun tekemäsi ohjelman joka saa aikaan recursion level exceededin, niin voisin katsoa jos sen saisi korjattua.

Metabolix [14.02.2011 16:01:24]

#

Funktioilla ei ole tarkoitus hyppiä ohjelman osasta toiseen, vaan ideana on, että jokainen funktio (tai aliohjelma) tekee jonkin asian ja loppuu sitten.

Sub tee_yksi_asia()
  ' Tee jotain pientä
End Sub

Sub tee_toinen_asia()
  ' Tee jotain muuta pientä
End Sub

Sub tee_kolmas_asia()
  ' Tee vielä jotain pientä
End Sub

Sub tee_monta_asiaa()
  tee_yksi_asia()
  tee_toinen_asia()
  tee_kolmas_asia()
End Sub

codemike [14.02.2011 20:52:10]

#

"Siis idea toki on, että funktiot kutsuu funktioita jotka kutsuu funktioita, mutta niistä on välillä tarkoitus mennä poiskin." Miten niistä pääsee pois ettei se tavallaan jää päälle jos sieltä haluaa toiseen funktioon ehtolauseen kautta? tai voiko ne mahdollisesti nollata? Esimerkiksi jos edellisessä pätkässä palataan alkuun eli vaikka halutaan tarkkailla muuttuvia muuttujia loopissa niin eihän siinä kauaa mene kun muisti täyttyy vai ovatko ohjelmointi kielten muistipituus erinlainen? Esim. jos seuraavassa pätkässä jonka sain aikaisemmin tätä kautta haluan then'in tai if'in täyttyessä toiseen funktioon ja vielä palatakkin jossain vaiheessa ja niin edespäin vai miten voisin mahdollisesti muuten tehdä vastaavan? Ja kerran vielä kiitos että jaksatte vastailla.

Module Module1
    Sub Main()
        Console.Clear()
        Console.Write("Kirjoita analysoitava teksti: ")
        Dim Teksti = Console.ReadLine()
        If SisältääKirosanan(Teksti) Then
            Console.WriteLine("Lause sisältää yhden tai useamman kirosanan.")
        Else
            Console.WriteLine("Lauseesta ei löytynyt kirosanoja.")
            Console.WriteLine("Vittu")
        End If
        Threading.Thread.Sleep(1000)
    End Sub
    Private Function SisältääKirosanan(ByVal teksti As String) As Boolean
        Dim Kirosanat = New String() {"vittu", "saatana",
                        "perkele", "paska", "jumalauta", "helvetti",
                        "fuck", "shit", "wtf", "vitu"}
        For Each Kirosana In Kirosanat
            If teksti.ToLower.Contains(Kirosana) Then
                Return True
            End If
        Next
        Return False
    End Function
End Module

Grez [14.02.2011 20:55:33]

#

lainaus:

Miten niistä pääsee pois ettei se tavallaan jää päälle jos sieltä haluaa toiseen funktioon ehtolauseen kautta?

Funktiosta pääsee pois joko Returnilla tai ihan vaan sillä että se funktio loppuu. Ja VB:ssä on vielä Exit Sub / Exit Function. Tosin en kyllä suosittele käyttämään niitä nyt kun Return on tuettu.

Toki funktion sisältä voi kutsua toista funktiota, mutta kun siitä toisesta funktiosta palataan, niin ei se jää mihinkään roikkumaan.

En nyt tiedä millä tavalla tuo sinulle aikaisemmin kirjoittamani koodi liittyy aiheeseen, mutta siinähän tuo SisältääKirosanan funktio nimenomaan poistuu palauttaen joko True tai False, jolloin se ei jää "roikkumaan" ja sitä kutsunut Main-funktio (tai no VB tapauksessa "Sub") osaa toimia saamansa vastauksen mukaisesti.

Eli esimerkiksi tuolla ohjelmalla ei saa aikaiseksi liian syvää rekursiota.

codemike [14.02.2011 23:20:31]

#

Tässä on AutoIt basicin kaltaista selvää helppoa kieltä (visual basic selosteella) mitä tarkoitan ja tässä tapahtuu ylitys lopulta kuten varmaan muillakin ohjelmointi kielillä eli mitä olisi tehtävissä? (Funktiokutsut voisivat tietysti myös olla muihin funktioihin mutta niiden pitäisi lopulta kuitenkin kiertää jatkuvasti.)

Main()
Func Main() ; Sub Main()



$clipboardContents = Clipget()
    $Text = $clipboardContents; Console.Write("Write text: ")
    ; Dim Text = Console.ReadLine()
    If ContainsSuchword($Text) Then ;If ContainsSuchword(Text) Then

         MainB()   ;Console.WriteLine("There is one or more such words")
	 Else
		 MainB()


    EndIF


EndFunc

Func ContainsSuchWord($Text) ;Private Function ContainsSuchword(ByVal text As String) As Boolean
    Dim $Suchwords[3] = ["sana1", "sana2", "sana3"]
    For $i = 0 To UBound($Suchwords)-1 ;For Each Suchword In Suchwords
        If StringInStr($Text, $Suchwords[$i]) Then ;If text.ToLower.Contains(Suchword) Then
            Return True
        EndIf
    Next
    Return False
EndFunc

Func MainB()

$clipboardContentsB = Clipget()
    $TextB = $clipboardContentsB
    If ContainsSuchwordB($TextB) Then ;If ContainsSuchwordB(Text) Then
         Main()
    Else
		Main()


    EndIF


EndFunc

Func ContainsSuchWordB($TextB) ;Private Function ContainsSuchwordB(ByVal text As String) As Boolean
    Dim $SuchwordsB[3] = ["sana1", "sana2", "sana3"]
    For $i = 0 To UBound($SuchwordsB)-1 ;For Each Suchword In Suchwords
        If StringInStr($TextB, $SuchwordsB[$i]) Then ;If text.ToLower.Contains(Suchword) Then
            Return True
        EndIf
    Next
    Return False
EndFunc

Grez [15.02.2011 00:05:14]

#

Gyaah. Ei kooditageja, ja sitten vielä kahta "kieltä" sekaisin.

Noh, tuossa nyt on kuitekin aivan selvä vika se, että kutsut MainB:stä Mainia.

Tässä nyt toiminnallisesti sama kuin yllä oleva koodisi mutta niin, että ei aiheudu rekursioylivuotoa.

Module Module1
    Sub Main()
        Do
            Dim clipboardContents = Clipboard.GetText()
            Dim Text = clipboardContents 'Console.Write("Write text: ")
            'Text = Console.ReadLine()
            If ContainsSuchword(Text) Then
                MainB() 'Console.WriteLine("There is one or more such words")
            Else
                MainB()
            End If
        Loop
    End Sub

    Private Function ContainsSuchword(ByVal text As String) As Boolean
        Dim Suchwords = {"sana1", "sana2", "sana3"}
        For Each Suchword In Suchwords
            If text.ToLower.Contains(Suchword) Then
                Return True
            End If
        Next
        Return False
    End Function

    Private Sub MainB()
        Dim clipboardContentsB = Clipboard.GetText()
        Dim TextB = clipboardContentsB
        If ContainsSuchwordB(TextB) Then
        Else
        End If
    End Sub

    Private Function ContainsSuchwordB(ByVal text As String) As Boolean
        Dim SuchwordsB = {"sana1", "sana2", "sana3"}
        For Each Suchword In SuchwordsB
            If text.ToLower.Contains(Suchword) Then
                Return True
            End If
        Next
        Return False
    End Function
End Module

En edes yritä keksiä mitään järkevää selitystä sille, että tuolla on nuo B-subit/funktiot, jotka kuitenkin on identtisiä.

codemike [15.02.2011 13:09:09]

#

Tämä on vaan testiohjelma periaatteelle. Mutta ongelma ei kuitenkaan korjaantunut kun laitoin vastaavan AutoIt'iin. Siellä ei ole loop käskyä mutta vastaava tilanne tulee jos laitan sinne niinkun alla $i = 0, Do, $i = $i + 1
Until $i = 10. ? Se pitäisi tulla myös visual basicissa vaikka en päässyt sitä kokeilemaan koska tulee virhe ilmoitus ennen ohjelman päälle laittoa 'clipboard' is not declared.

$i = 0
Main()
Func Main() ; Sub Main()
    Do


$clipboardContents = Clipget()
    $Text = $clipboardContents; Console.Write("Write text: ")
    ; Dim Text = Console.ReadLine()
    If ContainsSuchword($Text) Then ;If ContainsSuchword(Text) Then

         MainB()   ;Console.WriteLine("There is one or more such words")
	 Else ; Else
		 MainB()


    EndIF
$i = $i + 1
Until $i = 10

EndFunc

Func ContainsSuchWord($Text) ;Private Function ContainsSuchword(ByVal text As String) As Boolean
    Dim $Suchwords[3] = ["sana1", "sana2", "sana3"]
    For $i = 0 To UBound($Suchwords)-1 ;For Each Suchword In Suchwords
        If StringInStr($Text, $Suchwords[$i]) Then ;If text.ToLower.Contains(Suchword) Then
            Return True
        EndIf
    Next
    Return False
EndFunc

Func MainB()

$clipboardContentsB = Clipget()
    $TextB = $clipboardContentsB
    If ContainsSuchwordB($TextB) Then ;If ContainsSuchword(Text) Then
         Main()
    Else
		Main()


    EndIF


EndFunc

Func ContainsSuchWordB($TextB) ;Private Function ContainsSuchword(ByVal text As String) As Boolean
    Dim $SuchwordsB[3] = ["sana1", "sana2", "sana3"]
    For $i = 0 To UBound($SuchwordsB)-1 ;For Each Suchword In Suchwords
        If StringInStr($TextB, $SuchwordsB[$i]) Then ;If text.ToLower.Contains(Suchword) Then
            Return True
        EndIf
    Next
    Return False
EndFunc

Chiman [15.02.2011 15:05:26]

#

codemike, opettelemalla kooditagien käytön viesteissäsi saat todennäköisemmin neuvoja. Klikkaa Ohjeet viestien kirjoitukseen -linkkiä tältä samalta sivulta.

Grez [15.02.2011 15:15:38]

#

codemike kirjoitti:

Tämä on vaan testiohjelma periaatteelle. Mutta ongelma ei kuitenkaan korjaantunut kun laitoin vastaavan AutoIt'iin.

Et laittanut vastaavaa kuin osittain. Olisi huomattavasti kivempaa vastailla, jos vaivautuisit myös lukemaan mitä kirjoitetaan...

Grez kirjoitti:

Noh, tuossa nyt on kuitekin aivan selvä vika se, että kutsut MainB:stä Mainia.

codemike kirjoitti:

Func MainB()

$clipboardContentsB = Clipget()
    $TextB = $clipboardContentsB
    If ContainsSuchwordB($TextB) Then ;If ContainsSuchword(Text) Then
         Main()
    Else
		Main()
    EndIF
EndFunc

Tuon siis pitäisi olla:

Func MainB()
    $clipboardContentsB = Clipget()
    $TextB = $clipboardContentsB
    If ContainsSuchwordB($TextB) Then ;If ContainsSuchword(Text) Then

    Else

    EndIF
EndFunc

Varsinaiseen virheilmoitukseen en osaa sanoa. Laittamassasi koodissa ei lue kertaakaan Clipboard, joten en tiedä miksi AutoIT siitä valittaisi.

codemike [15.02.2011 17:39:39]

#

Virheilmoitus tuli siis Visual Basic Express'illä. Mutta niinkuin sanon olen nöyrästi kiitollinen kaikesta avusta. Tuota muutostasi en huomannut mutta ongelmana siis on kutsua toista funkiota toisesta funktiosta ilman muistin ylitystä lopulta. Uskon siis Metabolix'in vastaukseen että se ei ole mahdollista ennen kun toisin näytetään. Sillä tuossa sinun esimerkissähän MainB() on kuollut (then ja else'ä ei saa ohjattua mihinkään).

Grez [15.02.2011 17:54:12]

#

codemike kirjoitti:

Tuota muutostasi en huomannut mutta ongelmana siis on kutsua toista funkiota toisesta funktiosta ilman muistin ylitystä lopulta.

Tietenkin muisti loppuu aikanaan, jos tekee ikuisen rekursion. Pointti on, että mitään hyötyä sellaisesta esimerkissäsi ei ole. Kuten kirjoitin: "Tässä nyt toiminnallisesti sama kuin yllä oleva koodisi mutta niin, että ei aiheudu rekursioylivuotoa."

Eli siis, laittamani koodi tekee täsmälleen saman kuin alkuperäinen koodisi, mutta se ei turhaan kutsu Mainia.

codemike kirjoitti:

MainB() on kuollut (then ja else'ä ei saa ohjattua mihinkään).

Sinähän siitä kuolleen teit, kirjoitin vain saman toiminnallisuuden järkevämmin. Omassa koodissasikaan ei ole mitään eroa suoritetaanko if vai else -osio. Toki jos se ilahduttaa sinua, niin voit laittaa molempiin "Return". Lopputulos ei muutu, koska funktion loppuminen aiheuttaa joka tapauksessa returnin.

codemike kirjoitti:

Uskon siis Metabolix'in vastaukseen että se ei ole mahdollista ennen kun toisin näytetään.

Kukaan ei ole väittänytkään että ikuinen rekursio olisi mahdollinen. Totesin vaan että sellaiselle ei myöskään koskaan ole tarvetta:

Grez kirjoitti:

Normaalissa ohjelmoinnissa tuskin koskaan mennään yli 100 rekursion (ellei nimemomaan käytetä jotain rekursiivista algoritmia)

codemike [15.02.2011 20:14:08]

#

Omaa tyhmyyttähän se oli olisin voinut laittaa paremman esimerkin. Elikkä asia on siis niin että funktio voi antaa jonkun arvon mutta siitä ei voi siis siirtyä toiseen funktioon aiheuttamatta lopulta muistin ylitystä.

Hennkka [15.02.2011 20:54:37]

#

Tietenkin voit käyttää vanhan aikaista goto-järjestelmää eli

'Ei testattu, mutta idean varmaan saa
Sub main()
	Alustus:
	Dim n as Integer = 0
	'Hypätään kohtaan Toka
	goto Toka
	Eka:
	'Tokana tapahtuu tää ja sit hypätään alustukseen
	Console.writeline(n)
	goto Alustus
	Toka:
	'Tää taapahtuu ensin ja sitten hypätään kohtaan Eka
	Console.writeline(n)
	n = 100
	goto Eka
End Sub

codemike [15.02.2011 21:17:28]

#

Kiitti mutta AutoIt'ssä ei ole enää goto käsky mukana mutta saan kyllä tehtyä ohjelmani muutenkin.

Chiman [15.02.2011 21:19:13]

#

codemike kirjoitti:

Elikkä asia on siis niin että funktio voi antaa jonkun arvon mutta siitä ei voi siis siirtyä toiseen funktioon aiheuttamatta lopulta muistin ylitystä.

Voi siirtyä toiseen funktioon, josta voi siirtyä kolmanteen, jonka loputtua suoritus palaa toiseen, jonka loputtua palataan ensimmäiseen. Näin esimerkkinä.

Toki jos et koskaan anna funktion suorittaa itseään loppuun asti, vaan kutsut aina seuraavaa joka tasolla, niin jossain vaiheessa suoritus päättyy virheeseen.

Grez [15.02.2011 23:23:18]

#

Tein tällaisen hienon kuvan: http://grez.info/putka/kutsut.png

Siinä on kolmen värisiä nuolia:

Punainen nuoli:
Funktiokutsu
Siirtää suorituksen toiseen funktioon ja laittaa lähtöpaikan muistiin, että siihen voitaisiin palata

Vihreä nuoli:
Funktiosta palaaminen, eli jokin seuraavista
- Return
- Exit Function
- Exit Sub
- End Function
- End Sub

Tämä palauttaa suorituksen paikkaan, josta funktiota kutsuttiin. Tallennettu paikka voidaan poistaa muistista, eli muistia vapautuu.

Sininen nuoli:
Toistorakenne, eli esimerkiksi:
- For
- Loop
- While
- Do

Tämän ei tarvitse muistaa, mistä kohti toistorakennetta palattiin takaisin toiston alkuun, joten se ei kuluta eikä vapauta muistia.

Yhteenveto:
Jos käytät punaisia nuolia, pitää välillä käyttää myös vihreitä nuolia. Jos ohjelman suoritus pyörii ympyrässä, missä vaan tehdään punaista koko ajan, niin resurssit loppuu väkisinkin.

codemike [16.02.2011 16:41:26]

#

Kiitos kuvasta. Ja muuten ohjelmoinnista mainitakseni se on mukavaa kun on projekti joka kiinnostaa ja löytää ratkaisuja siihen. Voi olla kiinnostavampi kirjallisuuteenkin paneutua kun on perus asiat hallinnassa jotka ei tuntunu edes kirjallisuudesta heti avautuvan.


Sivun alkuun

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta