myCSharp.de - DIE C# und .NET Community
Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 | Suche | FAQ

» Hauptmenü
myCSharp.de
» Startseite
» Forum
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Suche
   » Plugin für Firefox
   » Plugin für IE7
   » Gadget für Vista
» Regeln
» Wie poste ich richtig?
» Datenschutzerklärung
» wbb-FAQ

Mitglieder
» Liste / Suche
» Stadt / Anleitung dazu
» Wer ist wo online?

Angebote
» ASP.NET Webspace
» Bücher
» Zeitschriften
   » dot.net magazin
» Accessoires

Ressourcen
» .NET-Glossar
» guide to C#
» openbook: Visual C#
» openbook: OO
» .NET BlogBook
» MSDN Webcasts
» dotnetjob.de
» Search.Net

Team
» Kontakt
» Übersicht
» Wir über uns
» Bankverbindung
» Impressum

» Unsere MiniCity
MiniCity
» myCSharp.de Diskussionsforum
Du befindest Dich hier: Community-Index » Diskussionsforum » Gemeinschaft » Smalltalk » Das Programmier-Spiel: nette Übungsaufgaben für zwischendurch
Letzter Beitrag | Erster ungelesener Beitrag Druckvorschau | An Freund senden | Thema zu Favoriten hinzufügen

Seiten (17): « vorherige 1 2 [3] 4 5 6 7 nächste » ... letzte » Antwort erstellen
Zum Ende der Seite springen  

Das Programmier-Spiel: nette Übungsaufgaben für zwischendurch

 
Autor
Beitrag « Vorheriges Thema | Nächstes Thema »
Corpsegrinder
myCSharp.de-Mitglied

Dabei seit: 30.01.2007
Beiträge: 401
Entwicklungsumgebung: VS 2008 , .Net 3.5


Corpsegrinder ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Okay, dann hätte ich gerne eine einfache Listenklasse, die als binärer Differenzbaum implementiert wird. Es sollen Elemente vorne und hinten angefügt werden können und ein Zugriff auf die Elemente über den Index soll möglich sein. Natürlich soll auch eine hübsche ToString() Methode vorhanden sein ;-)

zum binären Differenzbaum:

mal angenommen wir haben 7 Elemente, dann sieht er so aus:

        4
      /   \
    -2    +2
   /  \   /  \
 -1   +1  -1  +1

Das erste Element ist das mittlere der Liste und hat somit auch den Index dieses Elements in der Liste. Sämtliche andere Knoten enthalten immer als Index einen relativen Wert zum vorherigen Knoten. Also der linke Knoten vom Root-Element enthält -2, also ist der wahre Index 4 - 2 = 2.

nach außen soll es eine Liste sein. Nur INTERN soll es ein Baum sein.

ToString sieht bei einer Liste z.B. so aus, dass für alle Elemente ToString ausgegeben wird.

C#-Code:
interface IList<T> {
  int Count { get; }
  IList<T> Add(T item); // fügt das Item am Ende der Liste an
  IList<T> AddToHead(T item); // fügt das Item am Anfang der Liste an
  IList<T> RemoveFirst(); // entfernt das erste Element
  IList<T> RemoveLast(); // entfernt das letzte Element
  T Get(int index); // liefert das entsprechende Element zum Index
  String ToString(); // gibt für jedes Element ToString aus
}

So, habe gleich die Gelegenheit genutzt und wie aus dem Interface hervorgeht soll die Liste Immutable sein ;-)

Also, da die Liste Immutable sein soll muss bei einem "Mutator" immer eine neue Liste, sprich IList<T> zurückgegeben werden.
16.03.2010 20:12 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
herbivore
myCSharp.de-Team (Admin)

images/avatars/avatar-2627.gif


Dabei seit: 11.01.2005
Beiträge: 47.492
Entwicklungsumgebung: csc/nmake (nothing is faster)
Herkunft: Berlin


herbivore ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo zusammen,

nach Möglichkeit sollten alle Aufgabenstellungen von Anfang an so ausführlich und gleichzeitig so präzise sein, dass jeder sofort ohne Nachfragen loslegen kann. Da bei der letzten Aufgabestellung viele Nachfragen nötig waren und es daher sehr unübersichtlich geworden war, habe ich alle Antworten/Angaben von Corpsegrinder in einem Post zusammengefasst und die zugehörigen Fragen ganz entfernt.

Damit sollte die Aufgabenstellung jetzt klar sein.

Eine solche Liste immutable zu implementieren, macht in meinen Augen zwar für den praktischen Einsatz nicht viel Sinn, weil es den Aufwand der Hinzufüge- und Löschoperationen unnötig auf die Aufwandsklasse O(n) erhöht, aber das soll für das Spiel mal egal sein. Der Aufgabensteller ist ja frei in dem, was er vorgibt, solange die Vorgaben eindeutig sind und das sind sie ja. Nehmt die Aufgabenstellung also so wie sie ist. Ich wünsche viel Erfolg bei der Umsetzung.

herbivore
18.03.2010 07:17 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Corpsegrinder
myCSharp.de-Mitglied

Dabei seit: 30.01.2007
Beiträge: 401
Entwicklungsumgebung: VS 2008 , .Net 3.5


Corpsegrinder ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

So, ich hol den Thread mal wieder hoch, damit er nicht verloren geht ;-)

Zitat von herbivore:
Hallo zusammen,
Eine solche Liste immutable zu implementieren, macht in meinen Augen zwar für den praktischen Einsatz nicht viel Sinn, weil es den Aufwand der Hinzufüge- und Löschoperationen unnötig auf die Aufwandsklasse O(n) erhöht, aber das soll für das Spiel mal egal sein.

Das macht Sinn, wenn man auf Multicore Systemen arbeitet, da ansonsten nach jeder Änderung jede CPU ihren Cache aus dem Arbeitsspeicher aktualisieren muss, was ggf. mehr Zeit verbraucht als bei immutable Listen.


So, da scheinbar keiner Lust hat die Aufgabe zu lösen stell ich hier mal eine Lösung in Scala rein. Wer Lust hat kann auf Basis dessen gerne noch eine Lösung in C# erstellen.

Code:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
// EmptyNode.scala
import java.lang.IndexOutOfBoundsException

case class EmptyNode[A] extends Tree[A] {
  override def isEmptyNode = true
  def toList[B >: A]: List[B] = List()
  def get[B >: A](i: Int): B = {
    throw new IndexOutOfBoundsException
  }

  def get[B >: A](i: Int, parentIndex: Int): B = {
    throw new IndexOutOfBoundsException
  }
}


//Leaf.scala

case class Leaf[A](data: A, index:Int) extends Tree[A] {
  def toList[B >: A]: List[B] = List(data)
  def get[B >: A](i: Int): B = {
    if(i == index)
      data
    else
      throw new IndexOutOfBoundsException
  }

  def get[B >: A](i: Int, parentIndex: Int): B = {
    val realIndex = parentIndex + index
    if(realIndex == i)
      data
    else
      throw new IndexOutOfBoundsException
  }
}


// MyList.scala

object MyList {
  def create[A](objects: A*): Tree[A] = {
    def creator(data: List[A], index: Int): Tree[A] = {
     data.size match {
       case 0 => EmptyNode[A]
       case 1 => Leaf(data.head, index)
       case 2 => Node(data(1), Leaf(data.head, -1), EmptyNode[A], index)
       case _ => {
        val split = data.splitAt(data.size / 2)
        Node(split._2.head, creator(split._1, ((split._1.size+1) / 2) * -1 ), creator(split._2.tail, (split._2.tail.size+1) / 2), index)
       }
     }
    }
    creator(objects.toList, objects.size / 2)
  }
}


// Node.scala


case class Node[A](data: A, left: Tree[A], right: Tree[A], index: Int) extends Tree[A] {
  def toList[B >: A]: List[B] = left.toList ++ List(data) ++ right.toList
  def get[B >: A](i: Int): B = {
    if(index == i) {
      data
    } else if(index > i) {
      left.get(i, index)
    } else {
      right.get(i, index)
    }
  }

  def get[B >: A](i: Int, parentIndex: Int): B = {
    val realIndex = parentIndex + index
    if(realIndex == i) {
      data
    } else if(realIndex > i) {
      left.get(i, realIndex)
    } else {
      right.get(i, realIndex)
    }
  }
}


// Tree.scala


trait Tree[A] {
  def toList[B >: A]: List[B]
  def isEmptyNode = false
  def addToHead[B >: A](o: B): Tree[B] = MyList.create[B]((List(o) ++ toList):_*)
  def addToTail[B >: A](o: B): Tree[B] = MyList.create[B]((toList ++ List(o)):_*)
  def removeFirst[B >: A]: Tree[B] = MyList.create[B](toList.tail:_*)
  def removeLast[B >: A]: Tree[B] = MyList.create[B](toList.dropRight(1):_*)
  override def toString = {
    val str = toList.toString
    str.substring(5, str.length -1)
  }

  def get[B >: A](i: Int): B
  def get[B >: A](i: Int, parentIndex: Int): B
}

Gruß

Dario
07.04.2010 10:36 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
herbivore
myCSharp.de-Team (Admin)

images/avatars/avatar-2627.gif


Dabei seit: 11.01.2005
Beiträge: 47.492
Entwicklungsumgebung: csc/nmake (nothing is faster)
Herkunft: Berlin


herbivore ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo Corpsegrinder,

um den Trauerspiel :-) mal ein Ende zu machen, habe ich deine Aufgabe gelöst.

Dazu folgende Anmerkungen:
  • Dein Interface habe ich in IImmutableList <T> umbenannt, um einen Konflikt mit dem bestehenden Interface IList <T> aus System.Collections.Generic zu vermeiden.
  • Wenn ich die Liste aus deinem Beispiel erzeuge, bekommt die Wurzel den Index/Offset 3 und nicht 4. Ich denke, so ist es auch auch gemeint, denn in dem auf das Beispiel folgenden Text steht ja auch, dass sie "den Index dieses Elements in der Liste" bekommen soll. Im Kontext von C# würde ich davon ausgehen, dass damit der nullbasierte Index gemeint ist und der wäre ja im Beispiel 3.
  • Die Fehlerbehandlung in RemoveFirst und RemoveLast beruht darauf, dass das benutze RemoveAt fehlschlägt, wenn die Liste leer ist. Das könnte man sicher schöner machen. Auch die Fehlerbehandlung in Get ist sehr spartanisch.
  • Was die Multicore Systeme anbelangt: Wenn eine Liste immutable ist, also bei jeder Operation immer eine neue Liste erzeugt wird, die die Änderungen enthält, kann man gleiche eine normale, arraybasierte Liste nehmen. Man gewinnt durch einen binären Differenzbaum nichts, zumal ich die ändernden Operationen - genauso wie du - der Einfachheit halber so implementiert habe, dass aus dem Baum zuerst eine normale Liste erzeugt wird, die geändert wird und dann wieder in einen neuen Baum umgewandelt wird.
Aber das soll nun egal sein. Damit gibt es nun eine Lösung der Aufgabe. Und dass der Testcode im Main ohne Probleme durchgelaufen wird, spricht dafür, dass die Implementierung auch tatsächlich korrekt ist. (Ok, die ändernden Methoden habe ich nicht getestet, aber deren Implementierungen sind ja trivial).

Wenn kein Veto kommt, werde ich morgen eine neue Aufgabe stellen.

Hier noch der Code:

C#-Code:
using System;
using System.Text;
using System.Collections.Generic;
using System.Diagnostics;

public interface IImmutableList <T> {
   int Count { get; }
   IImmutableList <T> Add (T item); // fügt das Item am Ende der Liste an
   IImmutableList <T> AddToHead (T item); // fügt das Item am Anfang der Liste an
   IImmutableList <T> RemoveFirst (); // entfernt das erste Element
   IImmutableList <T> RemoveLast (); // entfernt das letzte Element
   T Get (int index); // liefert das entsprechende Element zum Index
   String ToString (); // gibt für jedes Element ToString aus
}

public class BinaryDifferenceTreeList <T> : IImmutableList <T>
{
   #region Private Members

   private class Node
   {
      private T    _tValue;
      private int  _iOffset;
      private Node _nodeLeft;
      private Node _nodeRight;

      public Node (T tValue, int iOffset, Node nodeLeft, Node nodeRight)
      {
          _tValue    = tValue;
          _iOffset   = iOffset;
          _nodeLeft  = nodeLeft;
          _nodeRight = nodeRight;
      }

      public int Offset
      {
         get { return _iOffset; }
      }

      public T Value
      {
         get { return _tValue; }
      }

      public Node Left
      {
         get { return _nodeLeft; }
      }

      public Node Right
      {
         get { return _nodeRight; }
      }
   }

   private Node _root;

   private static Node ToTree (List <T> list, int iStart, int iLength, int iParentIndex) {
      if (iLength == 0) {
         return null;
      }
      int iCurrIndex = iStart + iLength / 2;
      return new Node (
         list [iCurrIndex],
         iCurrIndex - iParentIndex,
         ToTree (list, iStart,                   iLength       / 2, iCurrIndex),
         ToTree (list, iStart + 1 + iLength / 2, (iLength - 1) / 2, iCurrIndex)
      );
   }

   private List <T> ToList ()
   {
      List <T> list = new List <T> (_root == null ? 0 : _root.Offset * 2);
      ToList (_root, list);
      return list;
   }

   private static void ToList (Node node, List <T> list)
   {
      if (node == null) {
         return;
      }
      ToList (node.Left,  list);
      list.Add (node.Value);
      ToList (node.Right, list);
   }

   #endregion Private Members

   #region Public Members

   public BinaryDifferenceTreeList (List <T> list)
   {
      _root = ToTree (list, 0, list.Count, 0);
   }

   public int Count {
      get {
         if (_root == null) { return 0; }
         int iCount = 1;
         for (Node nodeCurr = _root; nodeCurr != null; nodeCurr = nodeCurr.Right) {
            iCount += nodeCurr.Offset;
         }
         return iCount;
      }
   }

   public T Get (int iIndex)
   {
      if (iIndex < 0) {
         throw new ArgumentOutOfRangeException ();
      }
      Node nodeCurr = _root;
      while (nodeCurr != null) {
         if (iIndex == nodeCurr.Offset) {
            return nodeCurr.Value;
         }
         iIndex -= nodeCurr.Offset;
         if (iIndex < 0) {
            nodeCurr = nodeCurr.Left;
         } else {
            nodeCurr = nodeCurr.Right;
         }
      }
      throw new ArgumentOutOfRangeException ();
   }

   public IImmutableList <T> Add (T tItem)
   {
      List <T> list = ToList ();
      list.Add (tItem);
      return new BinaryDifferenceTreeList <T> (list);
   }

   public IImmutableList <T> AddToHead (T tItem)
   {
      List <T> list = ToList ();
      list.Insert (0, tItem);
      return new BinaryDifferenceTreeList <T> (list);
   }

   public IImmutableList <T> RemoveFirst ()
   {
      List <T> list = ToList ();
      list.RemoveAt (0);
      return new BinaryDifferenceTreeList <T> (list);
   }

   public IImmutableList <T> RemoveLast ()
   {
      List <T> list = ToList ();
      list.RemoveAt (list.Count - 1);
      return new BinaryDifferenceTreeList <T> (list);
   }

   public override String ToString ()
   {
      List <T> list = ToList ();
      if (list.Count <= 0) { return ""; }

      StringBuilder sb = new StringBuilder ();
      String strDelimiter = ", ";

      foreach (T tValue in list) {
         sb.Append (tValue);
         sb.Append (strDelimiter);
      }
      sb.Length -= strDelimiter.Length;
      return sb.ToString ();
   }

   #endregion Public Members
}

static class App
{
   public static void Main (string [] astrArg)
   {
      List <int> list = new List <int> ();

      for (int i = 0 ; i <= 20; ++i) {
         BinaryDifferenceTreeList <int> tree = new BinaryDifferenceTreeList <int> (list);
         Console.WriteLine ("{0,2}: {1}", i, tree);

         Debug.Assert (tree.Count == i);

         for (int i2 = 0; i2 < i; ++i2) {
            Debug.Assert (tree.Get (i2) == i2 + 1);
         }

         try {
            tree.Get (-1);
            Debug.Assert (false);
         }
         catch (ArgumentOutOfRangeException) {
            Debug.Assert (true);
         }
         catch (Exception) {
            Debug.Assert (false);
         }

         try {
            tree.Get (i);
            Debug.Assert (false);
         }
         catch (ArgumentOutOfRangeException) {
            Debug.Assert (true);
         }
         catch (Exception) {
            Debug.Assert (false);
         }

         list.Add (i+1);
      }
   }
}

herbivore
15.04.2010 13:08 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Corpsegrinder
myCSharp.de-Mitglied

Dabei seit: 30.01.2007
Beiträge: 401
Entwicklungsumgebung: VS 2008 , .Net 3.5


Corpsegrinder ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hi herbivore,

keine Einwände.


Gruß

Dario
15.04.2010 16:18 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
herbivore
myCSharp.de-Team (Admin)

images/avatars/avatar-2627.gif


Dabei seit: 11.01.2005
Beiträge: 47.492
Entwicklungsumgebung: csc/nmake (nothing is faster)
Herkunft: Berlin


herbivore ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo Corpsegrinder, hallo zusammen,

ok, dann wird es jetzt ein bisschen kniffelig. :-)

Bei den zurückliegenden Aufgaben schien es mir meistens einen Wettlauf darum zu geben, wer am schnellsten eine Lösung postet. Das ging dann mitunter zulasten der Korrektheit. Bei der nun folgenden Aufgabe steht die Korrektheit im Vordergrund. Postet also bitte nicht vorschnell, sondern überlegt genau und in Ruhe, ob eure Lösung wirklich korrekt ist.

Die Aufgabe ist die Klasse  SyncQueue <T> - Eine praktische Job-Queue so zu erweitern, dass nicht nur Dequeue blockiert, wenn die Queue leer ist, sondern auch Enqueue, wenn die Queue voll ist. Um bestimmen zu können, wann die Queue voll ist, soll eine änderbare Property MaxSize eingeführt werden. MaxSize == 0 soll bedeuten, dass es keine Beschränkung gibt. In diesem Fall soll sich die neue SyncQueue genauso verhalten wie die jetzige.

Obwohl eine vollständige Lösung in 50 Zeilen möglich ist, liegen ziemlich viele Fallstricke in der Aufgabe. Mehr als man denkt! Der Reiz der Aufgabe liegt darin, keinen der Fallstricke zu übersehen.

Ich will nicht in die Details gehen, aber natürlich dürfen keine Deadlocks vorkommen und anderseits muss die Synchronisation in allen Fällen 100%ig wasserdicht erfolgen. Um ein Gefühl zu bekommen, was für Konstellationen man dabei durchdenken muss, schaut euch mal diesen Beitrag an  SyncQueue <T> - Eine praktische Job-Queue.

Um nur ein Beispiel für einen Fallstrick zu geben: Mit einer Abfrage wie if (_q.Count == MaxSize) kann man leicht auf die Nase fallen, wenn MaxSize nachträglich auf einen Wert gesetzt wird, der kleiner ist als die Anzahl der schon in der Queue enthaltenen Elemente. Und das soll durchaus erlaubt sein. Die Queue gilt als voll, wenn die Anzahl der Elemente größer oder gleich der durch MaxSize angegebenen Schranke ist. Achtung: Die letzte Formulierung ist etwas gespreizt, weil hier schon der nächste Fallstrick lauert.

Um es nochmal zu sagen, es geht darum, eine robuste, threadsichere und blockierende Klasse mit den zwei Methoden Enqueue und Dequeue sowie der änderbaren Property MaxSize zu schreiben, die in allen denkbaren Anwendungsfällen und Szenarien korrekt funktioniert. Nicht einfach aber machbar!

Mit meinem Drängen auf Korrektheit möchte ich euch fordern, nicht abschrecken. Man kann viel daraus lernen, wenn man hier wirklich versucht, gründlich zu arbeiten. Wenn ihr euch nicht 100%ig sicher seid, könnt ihr mir eure Lösung auch gerne vorab per PM schicken.

herbivore
15.04.2010 17:33 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Corpsegrinder
myCSharp.de-Mitglied

Dabei seit: 30.01.2007
Beiträge: 401
Entwicklungsumgebung: VS 2008 , .Net 3.5


Corpsegrinder ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hi herbivore,

ich habe mal eine Lösung erstellt. Ich hoffe sie entspricht deinen Anforderungen.

C#-Code:
using System;
using System.Collections.Generic;
using System.Threading;
using System.Diagnostics;

namespace SyncQueueTest
{

    //*****************************************************************************
    public class SyncQueue <T>
    {
           //--------------------------------------------------------------------------
        private Queue <T> _q   = new Queue <T> ();
        public int MaxSize { get; set; }
        public SyncQueue (int maxSize)
        {
            MaxSize = maxSize;
        }

        //==========================================================================
        // <summary>
        // Trägt einen Eintrag und wartet dabei nötigenfalls
      // solange bis wieder Platz vorhanden ist.
        // </summary>
        public void Enqueue (T tItem)
        {
            lock (this) {
                while (_q.Count >= MaxSize && MaxSize != 0) {
                    Debug.WriteLine (Thread.CurrentThread.Name + " Wait");
                    Monitor.Wait (this);
                }
                _q.Enqueue (tItem);
                Monitor.Pulse (this);
                Debug.WriteLine (Thread.CurrentThread.Name + " Enqueue ==> " + _q.Count);
            }
        }

       //==========================================================================
       // <summary>
       //    Holt einen Eintrag aus der Queue heraus und wartet dabei nötigenfalls
       //    solange bis wieder ein Eintrag vorhanden ist.
       // </summary>
       public T Dequeue ()
       {
          lock (this) {
             while (_q.Count == 0) {
                Debug.WriteLine (Thread.CurrentThread.Name + " Wait");
                Monitor.Wait (this);
             }
             Monitor.Pulse(this);
             Debug.WriteLine (Thread.CurrentThread.Name + " Dequeue ==> " + (_q.Count - 1));
             return _q.Dequeue ();
          }
       }
    }
}

Habe das ganze getestet, indem ich vorab die Liste gefüllt habe, danach die MaxSize drastisch verringert habe und dann einfach deinen ursprünglichen Test habe laufen lassen. Lief bei mir ohne Probleme durch.


Gruß

Dario

Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von Corpsegrinder am 15.04.2010 19:11.

15.04.2010 19:06 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
herbivore
myCSharp.de-Team (Admin)

images/avatars/avatar-2627.gif


Dabei seit: 11.01.2005
Beiträge: 47.492
Entwicklungsumgebung: csc/nmake (nothing is faster)
Herkunft: Berlin


herbivore ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo Corpsegrinder,

sorry, nein, ich finde auf Anhieb (wobei ich mich allerdings schon länger mit dem Problem an sich beschäftigt habe) vier Stellen, an denen es nicht passt, was klemmt oder schief geht. Wie gesagt, es ist nicht ganz einfach und mit Testen kann man hier ganz, ganz schlecht die Abwesenheit von Fehlern feststellen.

Ich möchte noch nicht verraten, wo die Probleme liegen, weil der Reiz dieser Aufgabe in meinen Augen in gerade im Knobeln und im Kniffligen liegt. Man muss das Ganze wirklich tief durchdenken. Man muss, wie in dem o.g. Link exemplarisch beschrieben, versuchen, einen möglichst bösartigen Benutzer und zusätzlich einen möglichst fiesen Scheduler zu spielen. Der Benutzer führt die Aufrufe in möglichst böser Reihenfolge in einer vertrackten Umgebung von Threads aus und der Scheduler zerhackt das Ganze noch in möglichst ungünstige Häppchen und wechselt zum jeweils unpassendsten Thread. Und dann muss immer noch alles stimmen.

Den bösen Benutzer kann man natürlich auch in Form von gemeinen Testprogrammen "spielen". Aber den bösen Scheduler kann man bei einem Test, bei dem man das Programm einfach laufen lässt, gar nicht oder nur sehr, sehr schwer simulieren. Das geht weit besser im Kopf.

Das Angebot mit der PM steht weiterhin.

herbivore
15.04.2010 19:38 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Floste
myCSharp.de-Mitglied

images/avatars/avatar-2376.jpg


Dabei seit: 13.06.2007
Beiträge: 1.131
Entwicklungsumgebung: VS 2008
Herkunft: Norddeutschland


Floste ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Jetzt weiß ich, wozu es semaphores gibt.
Dashier ist der 2. Versuch, dank an herbivore, der mich davon abgehalten hat waithandles zu verwenden, dashier ist auchnoch deutlich schneller als der erste versuch.
Dass weder monitor noch lock vorkommt hat damit zutun, dass es auf meiner  Lockfreie threadsichere Queue beruht

C#-Code:
    class SyncQueue<T>
    {
        public SyncQueue(int capacity)
        {
            first = new Node();
            last = first;
            deQueueableS = new Semaphore(0, capacity);
            enQueueableS = new Semaphore(capacity, capacity);
        }

        Semaphore deQueueableS;
        Semaphore enQueueableS;
        Node first;
        Node last;

        public void Enqueue(T item)
        {
            enQueueableS.WaitOne();
lock(this)
{
            Node newNode = new Node();
            newNode.item = item;
            Node old = first;
            first= newNode;
            old.next = newNode;
            deQueueableS.Release();
}
        }

        public T Dequeue()
        {
            deQueueableS.WaitOne();
            Node current;
            do
            {
                current = last;
            }
            while (current != Interlocked.CompareExchange(ref last, current.next, current));
            enQueueableS.Release();
            return current.next.item;
        }

        class Node
        {
            public T item;
            public Node next;
        }
    }

EDIT:Solange man nur einen geberthread hat war alles soweit ok.
sobald ein zweiter thread gibt, kann es passieren, dass ein element noch garnicht zur verfügung steht. hab das mit nem lock gefixt (unschön in meinen augen aber naja...)

Dieser Beitrag wurde 3 mal editiert, zum letzten Mal von Floste am 15.04.2010 21:41.

15.04.2010 21:23 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
zommi zommi ist männlich
myCSharp.de-Poweruser/ Experte

images/avatars/avatar-2617.png


Dabei seit: 14.11.2007
Beiträge: 1.229
Entwicklungsumgebung: VS 2005+2010
Herkunft: Berlin


zommi ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hi Floste,

Zitat von herbivore:
änderbare Property MaxSize

;-)

Ich hab mich ja auch daran probiert, und genau dieses änderbar ist der wunde Punkt.

beste Grüße
zommi
15.04.2010 21:40 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
herbivore
myCSharp.de-Team (Admin)

images/avatars/avatar-2627.gif


Dabei seit: 11.01.2005
Beiträge: 47.492
Entwicklungsumgebung: csc/nmake (nothing is faster)
Herkunft: Berlin


herbivore ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo Floste,

leider ist schon die Abwesenheit der MaxSize-Property ein KO-Kriterium. :-(

Ansonsten ist es ein sehr interessanter Ansatz, eine lockfreie Queue und zwei Semaphoren zu einer blockierenden Queue zu verbinden. :-)

herbivore
15.04.2010 21:45 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Floste
myCSharp.de-Mitglied

images/avatars/avatar-2376.jpg


Dabei seit: 13.06.2007
Beiträge: 1.131
Entwicklungsumgebung: VS 2008
Herkunft: Norddeutschland


Floste ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

C#-Code:
    class SyncQueue<T>
    {
        public SyncQueue(int capacity)
        {
            this.capacity=capacity;
            first = new Node();
            last = first;
            deQueueableS = new Semaphore(0, int.MaxValue);
            enQueueableS = new Semaphore(capacity, int.MaxValue);
        }

        int capacity;
        public int MaxSize
        {
            get
            {
                return capacity;
            }
            set
            {
                ModifyCapacity(value-this.capacity);
                this.capacity=value;
            }
        }

        Semaphore deQueueableS;
        Semaphore enQueueableS;
        Node first;
        Node last;

        public void Enqueue(T item)
        {
            enQueueableS.WaitOne();
lock(this)
{
            Node newNode = new Node();
            newNode.item = item;
            Node old = first;
            first= newNode;
            old.next = newNode;
            deQueueableS.Release();
}
        }

        public T Dequeue()
        {
            deQueueableS.WaitOne();
            Node current;
            do
            {
                current = last;
            }
            while (current != Interlocked.CompareExchange(ref last, current.next, current));
            enQueueableS.Release();
            return current.next.item;
        }

        class Node
        {
            public T item;
            public Node next;
        }

void ModifyCapacity(int change)
{
    if(change>0) enQueueableS.Release(change);
    else
    {
        for(int i=-change;i != 0; i--) enQueueableS.WaitOne();
    }
}
    }
15.04.2010 21:51 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
herbivore
myCSharp.de-Team (Admin)

images/avatars/avatar-2627.gif


Dabei seit: 11.01.2005
Beiträge: 47.492
Entwicklungsumgebung: csc/nmake (nothing is faster)
Herkunft: Berlin


herbivore ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo Floste,

hehe, jetzt aber bitte nicht Schlag auf Schlag posten, bis es stimmt. Momentan ist zumindest noch nicht der Fall berücksichtigt, dass mehrere Threads die MaxSize-Property gleichzeitig ändern. Nach möglichen weiteren Problemen hab ich noch nicht geschaut.

herbivore
15.04.2010 22:00 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Floste
myCSharp.de-Mitglied

images/avatars/avatar-2376.jpg


Dabei seit: 13.06.2007
Beiträge: 1.131
Entwicklungsumgebung: VS 2008
Herkunft: Norddeutschland


Floste ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

So: MaxSize Threadsicher gemacht und das Warten, bis die entfernten Plätze wieder frei sind ins Enqueue verlegt.

C#-Code:
    public class SyncQueue<T>
    {
        public SyncQueue(int capacity)
        {
            this.capacity = capacity;
            first = new Node();
            last = first;
            deQueueableS = new Semaphore(0, int.MaxValue);
            enQueueableS = new Semaphore(capacity, int.MaxValue);
        }

        int capacity;
        public int MaxSize
        {
            get
            {
                return capacity;
            }
            set
            {
                int change = value - Interlocked.Exchange(ref this.capacity,value);
                if (change > 0) enQueueableS.Release(change);
                else Interlocked.Add(ref oversize, -change);
            }
        }

        int oversize = 0;

        Semaphore deQueueableS;
        Semaphore enQueueableS;
        Node first;
        Node last;

        public void Enqueue(T item)
        {
            enQueueableS.WaitOne();
            while (true)
            {
                int cachedOversize = oversize;
                if (cachedOversize <= 0) break;
                if (Interlocked.CompareExchange(ref oversize, cachedOversize - 1, cachedOversize) == cachedOversize)
                    enQueueableS.WaitOne();//Platz wurde dauerhaft entfernt. Da aber immernoch das Item eingefügt werden soll, muss eine neuer reserviert werden.
            }
            lock (this)//hinzufügen
            {
                Node newNode = new Node();
                newNode.item = item;
                Node old = first;
                first = newNode;
                old.next = newNode;
                deQueueableS.Release();
            }
        }

        public T Dequeue()
        {
            deQueueableS.WaitOne();
            Node current;
            do current = last;
            while (current != Interlocked.CompareExchange(ref last, current.next, current));
            enQueueableS.Release();
            return current.next.item;
        }

        class Node
        {
            public T item;
            public Node next;
        }
    }

Zitat:
hehe, jetzt aber bitte nicht Schlag auf Schlag posten, bis es stimmt.

Das mit der änderbaren maxsize hatte ich übersehen. Dann hatte ich erstmal was tzusammengehackt, um es änderbar zu machen. Jetzt habe ich es threadsicher versucht zu machen. Aber warum soll ich den code nicht verbessern, bis er stimmt?

Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von Floste am 17.04.2010 13:00.

17.04.2010 12:58 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
herbivore
myCSharp.de-Team (Admin)

images/avatars/avatar-2627.gif


Dabei seit: 11.01.2005
Beiträge: 47.492
Entwicklungsumgebung: csc/nmake (nothing is faster)
Herkunft: Berlin


herbivore ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo Floste,

Zitat:
Aber warum soll ich den code nicht verbessern, bis er stimmt?

das sollst du ja. Es ging nur darum, dass du bitte nicht jeden Zwischenstand postetet. Aus meiner Sicht hätten die Schritte ...

Zitat:
Dann hatte ich erstmal was tzusammengehackt, um es änderbar zu machen. Jetzt habe ich es threadsicher versucht zu machen.

... zusammengehört, weil ja von vornherein klar war, dass es nicht darum ging, was "zusammenzuhacken", sondern was wirklich threadsicheres, korrektes und robustes abzuliefern. Aber sei es drum. :-)


Das threadsichere hast du nachgereicht (zumindest habe ich auf die Schnelle nichts offensichtliches schiefes gefunden). Die Korrektheit und Robustheit fehlt in meinen Augen noch an zwei Stellen.

Korrektheit: So weit ich das sehe, ist folgender Fall noch nicht berücksichtigt:

Zitat:
MaxSize == 0 soll bedeuten, dass es keine Beschränkung gibt.

Robustheit: Darunter fällt für mich, dass Eingabewerte darauf geprüft werden, ob sie in einem sinnvollen Bereich liegen, bzw. dass mit Werten außerhalb des sinnvollen Bereichs sinnvoll umgegangen wird.


Ich finde es interessant, dass der zunächst so elegante Ansatz durch die zusätzlichen Anforderungen ziemlich "ausgebeult" wird. Ich hatte, als ich die Aufgabe gestellt habe, eine Erweiterung im Sinn, die näher am Original liegt. Und bei der lassen sich alle genannten Anforderung viel leichter "unterbringen".


Hallo zusammen,

vielleicht versucht sich ja noch jemand an einer Lösung, die in die Richtung geht, die Corpsegrinder eingeschlagen hat.

herbivore
17.04.2010 15:05 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
herbivore
myCSharp.de-Team (Admin)

images/avatars/avatar-2627.gif


Dabei seit: 11.01.2005
Beiträge: 47.492
Entwicklungsumgebung: csc/nmake (nothing is faster)
Herkunft: Berlin


herbivore ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo zusammen,

Zitat von herbivore:
vielleicht versucht sich ja noch jemand an einer Lösung, die in die Richtung geht, die Corpsegrinder eingeschlagen hat.

Zitat:
Ich möchte noch nicht verraten, wo die Probleme liegen, weil der Reiz dieser Aufgabe in meinen Augen in gerade im Knobeln und im Kniffligen liegt.

nachdem ihr nun eine Menge Zeit zum Knobeln "im eigenen Saft" hattet, will ich jetzt die zweite Stufe zünden und die Fallstricke, die ich (bei dem Lösungsansatz, den Corpsegrinder gewählt hat und der auch mir vorschwebt) sehe, doch verraten. Die Reihenfolge der Fallstricke ist willkürlich. Sie dient nur dazu, die einzelnen Punkte eindeutig identifizieren zu können:


Fallstrick 1:

Wertebereich von MaxSize: Zu einem robusten Programm gehört- wie schon im vorigen Beitrag gesagt, "dass Eingabewerte darauf geprüft werden, ob sie in einem sinnvollen Bereich liegen, bzw. dass mit Werten außerhalb des sinnvollen Bereichs sinnvoll umgegangen wird."


Fallstrick 2:

Zusätzlich zur allgemeinen Berücksichtigung des Wertebereichs, muss man den Speziallfall, dass MaxSize == 0 keine Beschränkung bedeutet, überall berücksichtigen.


Fallstrick 3: (den ich ja schon weiter oben genannt habe)

Wenn MaxSize verringert wird, kann es sein, dass die Queue "übervoll" ist. Das muss man bei der Formulierung der Abfrage, ob die Queue voll ist, berücksichtigen.


Fallstrick 4:

Man muss den Fall berücksichtige, dass möglicherweise wartende Enqueues weiterarbeiten können bzw. müssen, wenn MaxSize "vergrößert" wird (hier auch an Fallstrick 2 denken). Unabhängig davon, ob die Queue nur voll oder übervoll ist.


Fallstrick 5:

Man muss verhindern, dass durch Caching MaxSize in verschiedene Threads verschiedenen Werte hat.


Fallstrick 6:

Nicht nur die Zugriffe Enqueue und Dequeue müssen threadsafe sein, sondern auch die auf MaxSize (und dabei reicht es nicht unbedingt, dass MaxSize sich als int atomar ändern lässt).


Fallstricke 7 und 8: (besonders tricky)

Bei einem Enqueue muss dafür gesorgt werden, dass ein evtl. schon wartendes Dequeue weiterlauft. Und umgekehrt. Man könnte denke, dass das Pulse im Enqueue und im Dequeue genau das tut. Aber schaut euch mal diesen Fall an:
  1. Stand: Zwei Consumer, zwei Producer, Queue leer, keiner wartet auf die Sperre, keiner im Wait-Status, MaxSize == 1
  2. Consumer1 Dequeue: betritt die Sperre (bei lock) und läuft - da die Queue leer ist - aufs Wait (wodurch er in den Wait-Status geht und die Sperre wieder frei ist)
  3. Consumer2 Dequeue: betritt die Sperre (bei lock) und läuft - da die Queue leer ist - aufs Wait (wodurch er in den Wait-Status geht und die Sperre wieder frei ist)
  4. ProducerA Enqueue: betritt die Sperre (bei lock), aber dann erfolgt zufällig ein Threadwechsel, bevor das Pulse erreicht wurde
  5. ProducerB Enqueue: versucht die Sperre zu betreten, aber da ist ja ProducerA drin, also wartet ProducerB auf die Sperre (bei lock)
  6. ProducerA läuft weiter und führt das Pulse aus
  7. Dadurch wartet Consumer1 wieder auf die Sperre (bei Wait), stellt sich also hinter dem schon wartenden ProducerB an.
  8. ProducerA verlässt die Sperre
  9. Stand: Ein Item in der Queue, ProducerB und Consumer1 warten in dieser Reihenfolge auf die Sperre, Consumer2 wartet im Wait-Status
  10. ProducerB betritt die Sperre (bei lock), und läuft - da die Queue voll ist - aufs Wait (wodurch er in den Wait-Status geht und die Sperre wieder frei ist)
  11. Stand: Ein Item in der Queue, Consumer1 wartet auf die Sperre, Consumer2 und ProducerB warten in dieser Reihenfolge im Wait-Status
  12. Consumer1 betritt die Sperre (bei Wait), holt das von ProducerA eingestellt Element ab und und führt das Pulse aus
  13. Dadurch wartet Consumer2 wieder auf die Sperre (bei Wait). Vor ihm steht keiner.
  14. Consumer1 verlässt die Sperre
  15. Stand: Queue leer, Consumer2 wartet auf die Sperre, ProducerB wartet im Wait-Status
  16. Consumer2 betritt die Sperre (bei Wait) und läuft - da die Queue leer ist - aufs Wait (wodurch er in den Wait-Status geht und die Sperre wieder frei ist)
  17. Stand: Queue leer, keiner wartet auf die Sperre, ProducerB und Consumer2 warten in dieser Reihenfolge (für immer) im Wait-Staus, obwohl ProducerB und danach auch Consumer2 arbeiten könnte
Das Problem ist, dass in Schritt 12 durch das Pulse von Consumer1 fälschlich Consumer2 aufgeweckt wird und nicht wie beabsichtigt ProducerB. Und da der aufgeweckte Consumer2 auf eine leere Queue trifft, schläft er gleich wieder ein, wodurch das Pulse effektiv verloren ist.

Solche Fälle sind es, die die Aufgabe in meinen Augen so spannend machen. Solche Fälle muss man im Blick haben, wenn man sich überlegt, ob die eigene Lösung korrekt ist.


Fallstrick 9 (auch schon bekannt):

Statt einer if-Abfrage um die Waits, muss eine while-Schleife verwendet werden. Siehe den Fall in  SyncQueue <T> - Eine praktische Job-Queue.


Damit habe ich die Fallstricke genannt, die man beachten muss. Ich habe absichtlich nicht gesagt, was man tun kann oder tun muss, um die Fallstricke zu umgehen.


Wem gelingt es mit dem Gesagten, eine fehlerfreie und robuste Implementierung hinzubekommen?

herbivore
18.04.2010 13:25 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Corpsegrinder
myCSharp.de-Mitglied

Dabei seit: 30.01.2007
Beiträge: 401
Entwicklungsumgebung: VS 2008 , .Net 3.5


Corpsegrinder ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

So, hier nun die von herbivore abgesegnete Lösung ;-)

C#-Code:
using System;
using System.Collections.Generic;
using System.Threading;
using System.Diagnostics;

namespace SyncQueueTest
{

    //*****************************************************************************
    public class SyncQueue <T>
    {
           //--------------------------------------------------------------------------
        private Queue <T> _q   = new Queue <T> ();
        private volatile int _maxSize;
        public int MaxSize {
            get {
                return _maxSize;
            }
            set {
                lock(this) {
                    if(value < 0) value = 0;
                    int oldSize = _maxSize;
                    _maxSize = value;
                    if(_maxSize > oldSize || _maxSize == 0)
                        Monitor.PulseAll(this);
                }
            }
        }

        public SyncQueue (int maxSize)
        {
            MaxSize = maxSize;
        }

        //==========================================================================
        // <summary>
        // Trägt einen Eintrag und wartet dabei nötigenfalls
      // solange bis wieder Platz vorhanden ist.
        // </summary>
        public void Enqueue (T tItem)
        {
            lock (this) {
                while (_q.Count >= MaxSize && MaxSize != 0) {
                    Debug.WriteLine (Thread.CurrentThread.Name + " Wait");
                    Monitor.Wait (this);
                }
                _q.Enqueue (tItem);
                Monitor.PulseAll(this);
                Debug.WriteLine (Thread.CurrentThread.Name + " Enqueue ==> " + _q.Count);
            }
        }

       //==========================================================================
       // <summary>
       //    Holt einen Eintrag aus der Queue heraus und wartet dabei nötigenfalls
       //    solange bis wieder ein Eintrag vorhanden ist.
       // </summary>
       public T Dequeue ()
       {
          lock (this) {
             while (_q.Count == 0) {
                Debug.WriteLine (Thread.CurrentThread.Name + " Wait");
                Monitor.Wait (this);
             }
             Monitor.PulseAll(this);
             Debug.WriteLine (Thread.CurrentThread.Name + " Dequeue ==> " + (_q.Count - 1));
             return _q.Dequeue ();
          }
       }
    }
}
20.04.2010 16:58 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
herbivore
myCSharp.de-Team (Admin)

images/avatars/avatar-2627.gif


Dabei seit: 11.01.2005
Beiträge: 47.492
Entwicklungsumgebung: csc/nmake (nothing is faster)
Herkunft: Berlin


herbivore ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo Corpsegrinder,

Zitat:
So, hier nun die von herbivore abgesegnete Lösung ;-)

richtig, ich hatte dir schon per PM bestätigt, dass ich die Lösung als korrekt ansehe.

Ich hoffe du bist mir nicht böse, wenn ich noch zwei winzige Schönheitfehler nenne, die mir aufgefallen sind. :-) Zum einen die unnötige Zwischenvariable oldSize und zum anderen das unnötige PulseAll, wenn MaxSize von 0 auf 0 gesetzt wird. Aber wie ich selbst vorgegeben habe:

Zitat von herbivore:
Eine Aufgabe gilt als gelöst, wenn der Code das vorgegebene Problem löst, egal wie wie gut oder schlecht der Programmierstil ist.

Und der Programmierstil ist insgesamt gar nicht übel. Egal wie gut man etwas macht, es geht fast immer noch ein bisschen besser. :-)

Du bist mit der nächsten Aufgabe dran!

herbivore
20.04.2010 17:16 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Corpsegrinder
myCSharp.de-Mitglied

Dabei seit: 30.01.2007
Beiträge: 401
Entwicklungsumgebung: VS 2008 , .Net 3.5


Corpsegrinder ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Zitat von herbivore:
Und der Programmierstil ist insgesamt gar nicht übel. Egal wie gut man etwas macht, es geht fast immer noch ein bisschen besser. :-)

Danke ;-), muss auch sagen, dass ich aus C# voll raus bin, da ich momentan fast nur noch mit Scala oder Objective-C arbeite. Und Concurrency unter C# habe ich auch noch nicht gemacht. Naja.. nun mal zu meiner nächsten Aufgabe:


Es soll eine möglichst performante Matrix-Klasse erstellt werden. Es sollen die normalen Matrix-Operationen (+, -, * und ^) implementiert werden. Wichtig ist, dass die Matrix absolut Threadsafe und alle Methoden pure sind (d.h. egal wie oft man sie aufruft, es kommt immer das gleiche Ergebnis heraus). Achso... eine schicke ToString() ist natürlich auch gerne gesehen ;-)
20.04.2010 18:00 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
dr4g0n76
myCSharp.de-Poweruser/ Experte

images/avatars/avatar-1768.jpg


Dabei seit: 07.07.2005
Beiträge: 2.705
Entwicklungsumgebung: SharpDevelop/VS.NET
Herkunft: Deutschland


dr4g0n76 ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Dazu habe ich noch eine paar Fragen @Corpsegrinder

Ich stelle die Fragen mal hier öffentlich, weil ich denke dass mehrere Leute hier die gleichen Fragen haben könnten.

Zitat:
Es soll eine möglichst performante Matrix-Klasse erstellt werden.

Heißt möglichst performant elegant mathematisch mit möglichst wenigen Rechenoperationen oder heißt das zusätzlich noch parallele Berechnungen per Threads wenn mehrere benutzt werden?

Zitat:
Es sollen die normalen Matrix-Operationen (+, -, * und ^) implementiert werden.

Heißt hier ^ potenzieren oder meinst Du damit etwas anderes?

Zitat:
Wichtig ist, dass die Matrix absolut Threadsafe und alle Methoden pure sind (d.h. egal wie oft man sie aufruft, es kommt immer das gleiche Ergebnis heraus).

Heißt das wirklich "pure"? Ich kenne das als idempotent (kann mich auch irren). ;-)

Zitat:
Achso... eine schicke ToString() ist natürlich auch gerne gesehen ;-)

Wie soll ausgegeben werden? Ich meine in welchem Format.

Stellst Du Dir da so was vor:

3 5 2
4 3 1
3 4 2

oder etwas anderes?

Oder meinst Du gar eine Matrix aus der Prädikatenlogik? Das wäre dann etwas ganz anderes IMHO als die mathematische Variante.
22.04.2010 12:52 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Corpsegrinder
myCSharp.de-Mitglied

Dabei seit: 30.01.2007
Beiträge: 401
Entwicklungsumgebung: VS 2008 , .Net 3.5


Corpsegrinder ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

zu 1: Wie du du die Performance erlangst ist deine Sache. Wenn du eine vernünftige Lösung mit Threads erstellt ist das natürlich super.

zu 2: Ja, das soll die Potenz sein

zu 3: pure bedeutet, dass die Methode keine Seiteneffekte hat, somit das eigentliche Objekt nicht verändert. Außerdem darf die Methode nicht auf irgendwelchen Werten basieren, die sich zur Laufzeit de Programms verändern können. Nur so kann sichergestellt werden, dass z.B. bei

C#-Code:
int[] args = {1,2,3,4};
Matrix m = new Matrix(2, args);

m*5
m*5
m*5
m*5

immer das gleiche Ergebnis herauskommt. Siehe auch  http://en.wikipedia.org/wiki/Pure_function

zu 4: ja, so zum Beispiel

Achso... der Einfachheit halber: Die Matrix ist quadratisch und fehlende Werte sind mit 0 aufzufüllen. z.B. sähe dann eine Matrix der größe 2, die nur den Wert 1 bekommt so aus:

1 0
0 0

Wie in meinem Beispiel schon zu sehen ist der erste Parameter die Größe der Matrix, der zweite ein Array mit den Werten.

Dieser Beitrag wurde 2 mal editiert, zum letzten Mal von Corpsegrinder am 22.04.2010 13:52.

22.04.2010 13:49 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Floste
myCSharp.de-Mitglied

images/avatars/avatar-2376.jpg


Dabei seit: 13.06.2007
Beiträge: 1.131
Entwicklungsumgebung: VS 2008
Herkunft: Norddeutschland


Floste ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Zitat:
Wie in meinem Beispiel schon zu sehen ist der erste Parameter die Größe der Matrix, der zweite ein Array mit den Werten.

warum keine mehrdimensionalen arrays?

Der code ist ziemlich lang, unter anderem, weil man für negateive exponenten eine invertierte matrix braucht.
Threadsafe ist es, da immuteable. Die Potenzierung ist etwas optimiert, aber weiter opßtimierungen würden den code noch deutlich verlängern.

C#-Code:
    public class MaTrix
    {
        public MaTrix(float[,] array)
        {
            this.array=(float[,])array.Clone();
            width=array.GetUpperBound(0)+1;
            height=array.GetUpperBound(1)+1;
        }

        public MaTrix Clone()
        {
            return new MaTrix((float[,])array.Clone());
        }

        int width;
        public int Width
        {
            get { return width; }
        }

        int height;
        public int Height
        {
            get { return height; }
        }

        private float[,] array;
        public float this[int x, int y]
        {
            get
            {
                return array[x, y];
            }
        }

        public static MaTrix CreateIdentity(int num)
        {
            MaTrix C=new MaTrix(new float[num,num]);
            for(int i=0;i<num;i++)
                C.array[i,i]=1;
            return C;
        }

        public static MaTrix operator +(MaTrix A, MaTrix B)
        {
            if (A.width != B.width || A.height != B.height)
                throw new Exception("Dimensions not Equal");
            MaTrix C = new MaTrix(new float[A.width, B.height]);
            for (int y = 0; y < C.height; y++)
                for (int x = 0; x < C.width; x++)
                {
                    C.array[x, y] = A.array[x, y] + B.array[x, y];
                }
            return C;
        }

        public static MaTrix operator -(MaTrix A, MaTrix B)
        {
            if (A.width != B.width || A.height != B.height)
                throw new Exception("Dimensions not Equal");
            MaTrix C = new MaTrix(new float[A.width, B.height]);
            for (int y = 0; y < C.height; y++)
                for (int x = 0; x < C.width; x++)
                {
                    C.array[x, y] = A.array[x, y] - B.array[x, y];
                }
            return C;
        }


        public static MaTrix operator *(MaTrix A, MaTrix B)
        {
            if (A.width != B.height)
                throw new Exception("Dimensions");
            MaTrix C = new MaTrix(new float[B.width, A.height]);
            for (int y = 0; y < C.height; y++)
                for (int x = 0; x < C.width; x++)
                {
                    float temp=0;
                    for (int i = 0; i < C.height; i++)
                    {
                        temp += A.array[i, y] * B.array[x, i];
                    }
                    C.array[x, y]=temp;
                }
            return C;
        }


        public static MaTrix operator *(float scalar, MaTrix B)
        {
            MaTrix C = new MaTrix(new float[B.width, B.height]);
            for (int y = 0; y < C.height; y++)
                for (int x = 0; x < C.width; x++)
                {
                    C.array[x, y] = scalar * B.array[x, y];
                }
            return C;
        }

        public static MaTrix operator ^(MaTrix B, int exp)
        {
            if(B.width!=B.height)
                throw new Exception("Matrix has to be quadratic");
            if(exp==0)return CreateIdentity(B.width);
            if(exp<0)
            {
                B=MaTrix.Invert(B);
                exp=-exp;
            }
            int pCount=0;
            for(int p=exp;p!=0;p/=2)
                pCount++;
            MaTrix[] pows=new MaTrix[pCount];
            pows[0]=B;//pCount ist nur 0, wenn exp das auch ist, was schon abgefragt wurde
            for(int i=1;i<pCount;i++)
            {
                pows[i]=pows[i-1]*pows[i-1];
            }
            MaTrix C=null;
            bool first = true;
            for(int p=exp,i=0;p!=0;p/=2,i++)
            {
                if ((p & 1) != 0)
                {
                    if (first)
                    {
                        C = pows[i];
                        first = false;
                    }
                    else
                        C *= pows[i];
                }
            }
            return C;
        }

        public static MaTrix Invert(MaTrix A)
        {
            if(A.width!=A.height)
                throw new Exception("Matrix has to be quadratic");
            return new MaTrix(MatrixInversion(A.array, A.width));
        }

        public float[,] ToArray()
        {
            return (float[,])array.Clone();
        }

        static float[,] MatrixInversion(float[,] A, int size)
        {
            double det = 1.0 / CalcDeterminant(A, size);
            float[,] minor = new float[size - 1, size - 1];
            float[,] C=new float[size,size];
            for (int j = 0; j < size; j++)
            {
                for (int i = 0; i < size; i++)
                {
                    GetMinor(A, minor, j, i, size);
                    C[i, j] = (float)(det * CalcDeterminant(minor, size - 1));
                    if ((i + j) % 2 == 1)
                        C[i, j] = -C[i, j];
                }
            }
            return C;
        }

        static int GetMinor(float[,] src, float[,] dest, int row, int col, int size)
        {
            int colCount = 0, rowCount = 0;
            for (int i = 0; i < size; i++)
            {
                if (i != row)
                {
                    colCount = 0;
                    for (int j = 0; j < size; j++)
                    {
                        if (j != col)
                        {
                            dest[rowCount,colCount] = src[i,j];
                            colCount++;
                        }
                    }
                    rowCount++;
                }
            }
            return 1;
        }

        static double CalcDeterminant(float[,] mat, int size)
        {
            if (size == 1)
                return mat[0, 0];
            double det = 0;
            float[,] minor = new float[size - 1, size - 1];
            for (int i = 0; i < size; i++)
            {
                GetMinor(mat, minor, 0, i, size);
                det += /*pow( -1.0, i )*/((i & 1) == 0 ? 1 : -1) * mat[0, i] * CalcDeterminant(minor, size - 1);
            }
            return det;
        }


        public override string ToString()
        {
            List<string> list = new List<string>(2*width+height);
            for (int y = 0; y < height; y++)
            {
                for (int x = 0; x < width; x++)
                {
                    list.Add(array[x, y].ToString("#0.0000").PadRight(10));
                    list.Add("  ");
                }
                list.Add(Environment.NewLine);
            }
            return string.Concat(list.ToArray());
        }
    }
24.04.2010 10:19 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Corpsegrinder
myCSharp.de-Mitglied

Dabei seit: 30.01.2007
Beiträge: 401
Entwicklungsumgebung: VS 2008 , .Net 3.5


Corpsegrinder ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hi floste,

es sind zwar noch ein paar kleine Schönheitsfehler drin (2.0F * matrix möglich matrix * 2.0F nicht) und wenn eine Matrix keine Inverse besitzt liefert sie eine Matrix mit NaN gefüllt zurück, aber die Inverse war eh nicht gefordert und im groben läuft es ja. Also sehe ich die Aufgabe als erfüllt an.

Dann hau ma die nächste raus.
24.04.2010 10:44 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Floste
myCSharp.de-Mitglied

images/avatars/avatar-2376.jpg


Dabei seit: 13.06.2007
Beiträge: 1.131
Entwicklungsumgebung: VS 2008
Herkunft: Norddeutschland


Floste ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Mal was kurtzes:

Die tcp/ip checksumme über ein array berechnen. Kümmert euch dabei weder um pseudoherder, headerstruktur oder sonstwas tcp-spezifisches, noch um little oder big-endian ,denn es kommt unabhängig vom der endianness sowieso die gleiche bytefolge raus.

Die funktion soll also folgende Signatur haben:

Zitat:
public ushort Checksum(byte[] data)//data kann auch eine ungrade länge haben!
{
...

Beschreibung des Algorithmus:
 http://wwwse.inf.tu-dresden.de/data/cour...cp_checksum.pdf
24.04.2010 11:21 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
markus111 markus111 ist männlich
myCSharp.de-Mitglied

images/avatars/avatar-3108.png


Dabei seit: 01.10.2008
Beiträge: 479
Entwicklungsumgebung: Visual Studio 2010 Pro
Herkunft: Henstedt-Ulzburg


markus111 ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

C#-Code:
public ushort Checksum(byte[] data)//data kann auch eine ungrade länge haben!
{
    int checksum=0;
    for (int i=0; i < data.Length; i++)
    {
        if((i & 1) != 0)
            checksum += data[i] * 256;
        else
            checksum += data[i];
    }

    while ((checksum & (~(int)0xFFFF)) != 0)
        checksum =(checksum & 0xFFFF) + (checksum >> 16);

    return (ushort)((~checksum)&0xFFFF);
}

müsste klappen...

mfg.
markus111

Dieser Beitrag wurde 3 mal editiert, zum letzten Mal von markus111 am 29.04.2010 17:27.

28.04.2010 16:36 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Floste
myCSharp.de-Mitglied

images/avatars/avatar-2376.jpg


Dabei seit: 13.06.2007
Beiträge: 1.131
Entwicklungsumgebung: VS 2008
Herkunft: Norddeutschland


Floste ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Ein kleiner Schreibfehler ist drin:checksum und nicht sum.

Aber also irgendwie habe ich da so eine Ahnung, dass der Code nicht wirklich von dir stammt *hust*.

Wie dem auch sei: Der Code ist korrekt (vom Schreibfehler abgesehen) und erfüllt die Aufgabe und es wäre witzlos, wenn eine korrekt6e und vollständige Lösung hier steht und ein anderer eine weitere schreiben müsste: stell die nächste aufgabe....

Dieser Beitrag wurde 3 mal editiert, zum letzten Mal von Floste am 28.04.2010 17:06.

28.04.2010 17:01 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
markus111 markus111 ist männlich
myCSharp.de-Mitglied

images/avatars/avatar-3108.png


Dabei seit: 01.10.2008
Beiträge: 479
Entwicklungsumgebung: Visual Studio 2010 Pro
Herkunft: Henstedt-Ulzburg


markus111 ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Ich weiß dass du mir den Code dafür irgendwann mal gegeben hast, aber ich hatte keine Lust ihn rauszusuchen. Der Schreibfehler kommt daher, dass ich den Code nicth getestet hab großes Grinsen

Gut, nächste Aufgabe (bestimmt zu einfach):
Auf der Konsole soll eine Sinus-Welle ausgegeben werden:

Code:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
                                                      ############
                                                    ###          ###
                                                   ##              ##
                                                 ##                  ##
                                                ##                    ##
                                               ##                      ##
                                             ##                          ##
                                            ##                            ##
                                           ##                              ##
                                          ##                                ##
                                         ##                                  ##
                                        ##                                    ##
#                                      ##                                      #
##                                    ##
 ##                                  ##
  ##                                ##
   ##                              ##
    ##                            ##
     ##                          ##
       ##                      ##
        ##                    ##
         ##                  ##
           ##              ##
            ###          ###
              ###########

So in etwa sollte das Ergebniss aussehen.

mfg.
markus111
29.04.2010 17:53 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
m0rius
myCSharp.de-Mitglied

images/avatars/avatar-3125.png


Dabei seit: 28.08.2007
Beiträge: 994
Entwicklungsumgebung: Visual Studio 2010 Professional


m0rius ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo markus111,

hier meine Lösung. Dauert zwar ein paar Sekunden, aber Effizienz war ja auch nicht Teil der Anforderung.

C#-Code:
using System;

namespace SinusConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            int xWidth = 63;
            int yHeight = 10;

            for (double i = 0; i < xWidth; i += 0.0005)
            {
                double y = (Math.Sin(i / xWidth * 2 * Math.PI) + 1) * yHeight;

                Console.SetCursorPosition(
                    (int)Math.Round(i),
                    (int)Math.Round(y)
                );
                Console.Write('#');
            }

            Console.Read();
        }
    }
}

m0rius

m0rius hat dieses Bild (verkleinerte Version) angehängt:
Sinus.png
Volle Bildgröße

29.04.2010 20:23 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
markus111 markus111 ist männlich
myCSharp.de-Mitglied

images/avatars/avatar-3108.png


Dabei seit: 01.10.2008
Beiträge: 479
Entwicklungsumgebung: Visual Studio 2010 Pro
Herkunft: Henstedt-Ulzburg


markus111 ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Jup, richtig. Ich hattes es so, dass es über die komplette Breite geht, aber das ist schon okay. Du bist dran smile

EDIT: Zu deiner Effizenz: den i Wert nur um das wirklich benötigte erhöhen, also 1 / xWidth.

mfg.
markus111

Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von markus111 am 29.04.2010 21:00.

29.04.2010 20:58 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
m0rius
myCSharp.de-Mitglied

images/avatars/avatar-3125.png


Dabei seit: 28.08.2007
Beiträge: 994
Entwicklungsumgebung: Visual Studio 2010 Professional


m0rius ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo markus111,

kannst das ja gerne mal ausprobieren ... Es kommt nicht die gleich Ausgabe bei raus ;).

m0rius
30.04.2010 10:27 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
m0rius
myCSharp.de-Mitglied

images/avatars/avatar-3125.png


Dabei seit: 28.08.2007
Beiträge: 994
Entwicklungsumgebung: Visual Studio 2010 Professional


m0rius ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo zusammen,

nächste Aufgabe:

Aufgabenstellung
Es soll eine Methode geschrieben werden, die alle glücklichen Zahlen bis zu einer festlegbaren Obergrenze upperLimit zurückgibt. Es soll dabei die folgende Signatur verwendet werden:

C#-Code (Methodensignatur):
public int[] GetLuckyNumbers(int upperLimit)

Alles Weitere findet ihr im entsprechenden  Wikipedia-Artikel.

Ich wünsche euch viel Spaß!

m0rius

Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von m0rius am 30.04.2010 10:37.

30.04.2010 10:36 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Kaji Kaji ist männlich
myCSharp.de-Mitglied

Dabei seit: 10.12.2007
Beiträge: 593
Entwicklungsumgebung: VS2008 Standard
Herkunft: Clausthal-Zellerfeld


Kaji ist offline Füge Kaji Deiner Kontaktliste hinzu

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo m0rius,

ich hab da mal was gebastelt was wirklich ziemlich hässlich ist aber es funktioniert ^^; Ich glaube ich habe da ziemlich quer gedacht.. das ganze kann sicherlich deutlich einfacher gemacht werden aber ich habe heute nen Wurm im Kopf. Naja meine Lösung:

C#-Code:
        static void Main(string[] args)
        {
            int upperLimit = Convert.ToInt32(Console.ReadLine());
            int[] LuckyNumbers = GetLuckyNumbers(upperLimit);
            for (int i = 1; i <= LuckyNumbers.Length; i++)
            {
                Console.WriteLine(LuckyNumbers[i-1].ToString());
            }
            Console.ReadLine();
        }

        public static int[] GetLuckyNumbers(int upperLimit)
        {
            int[] LuckyNumbers = new int[upperLimit];
            int erase = 2;
            int eraseCounter = 1;
            for (int i = 1; i <= upperLimit; i++)
            {
                LuckyNumbers[i - 1] = i;
            }
            while (erase < LuckyNumbers.Length)
            {
                int[] newLuckyNumbers = new int[LuckyNumbers.Length];
                int digit = 1;
                for (int i = 0; i < LuckyNumbers.Length; i++)
                {
                    if (digit == erase)
                    {
                        digit = 0;
                    }
                    else
                    {
                        newLuckyNumbers[i] = LuckyNumbers[i];
                    }
                    digit++;
                }
                LuckyNumbers = new int[LuckyNumbers.Length - (LuckyNumbers.Length / erase)];
                int x = 0;
                foreach (int number in newLuckyNumbers)
                {
                    if (number > 0)
                    {
                        LuckyNumbers[x] = number;
                        x++;
                    }
                }
                erase = LuckyNumbers[eraseCounter];
                eraseCounter++;
            }
            return LuckyNumbers;
        }
30.04.2010 11:55 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
TheBrainiac TheBrainiac ist männlich
myCSharp.de-Mitglied

images/avatars/avatar-3152.png


Dabei seit: 17.12.2006
Beiträge: 767
Entwicklungsumgebung: VS 2010 Prof.
Herkunft: /dev/null


TheBrainiac ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

AHHHH!

Vier Minuten zu spät.....

Ich poste mal trotzdem meine Lösung, aber Kaji war leider schneller... Zunge raus Teufel

C#-Code:
public static Int32[] GetLuckyNumbers(Int32 upperLimit)
{
    var luckyNumbers = new List<Int32>(new Int32[upperLimit]);

    for (var i = 1; i <= upperLimit; i++)
    {
        luckyNumbers[i - 1] = i;
    }

    var step = 2;
    var stepIndex = 1;

    while (luckyNumbers.Count >= step)
    {
        var numsToRemove = new List<Int32>();

        for (var i = step - 1; i < luckyNumbers.Count; i += step)
        {
            numsToRemove.Add(i);
        }

        while (numsToRemove.Count > 0)
        {
            luckyNumbers.RemoveAt(numsToRemove[numsToRemove.Count - 1]);
            numsToRemove.RemoveAt(numsToRemove.Count - 1);
        }

        step = luckyNumbers[stepIndex++];
    }

    return luckyNumbers.ToArray();
}

Der Code zum Testen war:

C#-Code:
public static void Main()
{
    Test(10, new Int32[] { 1, 3, 7, 9 });
    Test(20, new Int32[] { 1, 3, 7, 9, 13, 15 });
    Test(30, new Int32[] { 1, 3, 7, 9, 13, 15, 21, 25 });
    Test(50, new Int32[] { 1, 3, 7, 9, 13, 15, 21, 25, 31, 33, 37, 43, 49 });
    Test(63, new Int32[] { 1, 3, 7, 9, 13, 15, 21, 25, 31, 33, 37, 43, 49, 51, 63 });
    Test(77, new Int32[] { 1, 3, 7, 9, 13, 15, 21, 25, 31, 33, 37, 43, 49, 51, 63, 67, 69, 73, 75 });
    Test(100, new Int32[] { 1, 3, 7, 9, 13, 15, 21, 25, 31, 33, 37, 43, 49, 51, 63, 67, 69, 73, 75, 79, 87, 93, 99 });
    Test(120, new Int32[] { 1, 3, 7, 9, 13, 15, 21, 25, 31, 33, 37, 43, 49, 51, 63, 67, 69, 73, 75, 79, 87, 93, 99, 105, 111, 115 });

    Console.ReadKey();
}

private static void Test(Int32 num, Int32[] expected)
{
    var numbers = GetLuckyNumbers(num);

    var success = numbers.Length == expected.Length;

    if (success)
    {
        for (var i = 0; i < numbers.Length; i++)
        {
            if (numbers[i] != expected[i])
            {
                success = false;
                break;
            }
        }
    }

    Console.WriteLine(success ? "Success!" : "Failed!");

    Console.Write("Expected: ");
    for (var i = 0; i < expected.Length; i++)
    {
        Console.Write(expected[i]);
        Console.Write(" ");
    }

    Console.WriteLine();

    Console.Write("Computed: ");
    for (var i = 0; i < numbers.Length; i++)
    {
        Console.Write(numbers[i]);
        Console.Write(" ");
    }

    Console.WriteLine();
    Console.WriteLine();
}

Gruß, Christian.

Dieser Beitrag wurde 3 mal editiert, zum letzten Mal von TheBrainiac am 30.04.2010 12:03.

30.04.2010 11:59 Beiträge des Benutzers | zu Buddylist hinzufügen
m0rius
myCSharp.de-Mitglied

images/avatars/avatar-3125.png


Dabei seit: 28.08.2007
Beiträge: 994
Entwicklungsumgebung: Visual Studio 2010 Professional


m0rius ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo Kaji,

da du zuerst abgegeben hast und deine Lösung funktioniert, bist du mit der nächsten Aufgabe dran!


Hallo Schamese,

vielleicht tröstet es dich, wenn ich dir sage, dass deine Lösung die schönere gewesen wäre :).

m0rius

Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von m0rius am 30.04.2010 16:19.

30.04.2010 16:18 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Kaji Kaji ist männlich
myCSharp.de-Mitglied

Dabei seit: 10.12.2007
Beiträge: 593
Entwicklungsumgebung: VS2008 Standard
Herkunft: Clausthal-Zellerfeld


Kaji ist offline Füge Kaji Deiner Kontaktliste hinzu

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo Leute,

da ich nicht wirklich fit bin und mir nichts so recht kurzes kleines einfällt wäre es schön wenn jemand anderes eine Aufgabe stellen könnte :)


Gruß Kaji
03.05.2010 09:28 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
herbivore
myCSharp.de-Team (Admin)

images/avatars/avatar-2627.gif


Dabei seit: 11.01.2005
Beiträge: 47.492
Entwicklungsumgebung: csc/nmake (nothing is faster)
Herkunft: Berlin


herbivore ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo Kaji,

ich hätte da was. :-)


Hallo zusammen,

ist eine eher kleine Spielerei ohne praktischen Nutzen, aber dafür tricky zu programmieren.

Die Aufgabe:

Schreibt ein Windows Forms Programm, das unter Verwendung von Array.Sort (T [], IComparer<T>) oder Array.Sort (T [], Comparison<T>) eine vorgegebene Liste von Strings sortiert und am Ende in einer ListBox anzeigt, ohne dass dabei je das GUI blockiert und ohne dass es dabei "ungültige threadübergreifende Zugriffe" gibt.

Wo ist der Witz, werdet ihr euch fragen. Man kann doch einfach das Array.Sort in einem Thread starten und schon blockiert das GUI nicht. Und zum Anzeigen der sortierten Liste verwendet man Control.Invoke/BeginInvoke und schon hat man auch die "ungültigen threadübergreifenden Zugriffe" vermieden.

 [FAQ] Warum blockiert mein GUI?
 [FAQ] Controls von Thread aktualisieren lassen (Control.Invoke/Dispatcher.Invoke)

Ok, stellt euch vor, die Liste von Strings enthält die sieben Eissorten Vanille, Schokolade, Stracciatella, Erdbeer, Zitrone, Kirsch und Schlumpf. Und die Liste soll nach dem Geschmack des Benutzers aufsteigend sortiert werden, die sortierte Liste fängt mit dem am wenigsten gemochten Eis an und hört mit dem leckersten auf.

Der Comparer muss also immer irgendwie den Benutzer fragen, welche von zwei Eissorten er leckerer findet. Dazu sollen die beiden gerade zu vergleichenden Eissorten in zwei Textboxen angezeigt werden und der Benutzer klickt auf die TextBox, in der das Eis steht, das er leckerer findet als das andere. Er muss sich entscheiden, "gleich lecker" ist nicht möglich.

Die Kommunikation zwischen dem Comparer und den GUI hinzubekommen ohne, dass das GUI blockiert, ist also die eigentliche Herausforderung der Aufgabe. Nicht ganz einfach, aber machbar. Viel Spaß dabei.

herbivore

PS: Damit die Länge des Codes nicht unnötig aufgebläht wird, muss sich der Benutzer kooperativ verhalten. Er muss also konsistent antworten. Was andersherum gesagt heißt, er darf keine widersprüchlichen Eingaben machen: Eine widersprüchliche Eingabe wäre, wenn er sagt: Vanille ist leckerer als Schokolade, Schokolade ist lecker Stracciatella und Stracciatella ist lecker als Vanille. In einem robusten Programm müsste man solche widersprüchlichen Eingaben verhindern oder abfangen, aber das würde wie gesagt den Code sehr aufblähen und soll daher nicht Teil der Aufgabe sein.

PPS: Bitte gebt minimalen, aber vollständigen Code an. Wenn man den Designer benutzt, erfordert das vermutlich etwas Nacharbeit, die ich aber als Teil der Aufgabe ansehe. Nur wenn man den geposteten Code 1:1 in eine Datei kopieren und (mit csc) übersetzen kann, gilt die Lösung. :-)
03.05.2010 11:22 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
edsplash edsplash ist männlich
myCSharp.de-Mitglied

images/avatars/avatar-3111.jpg


Dabei seit: 19.04.2008
Beiträge: 390
Entwicklungsumgebung: VS2010


edsplash ist offline Füge edsplash Deiner Kontaktliste hinzu

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo herbivore

Hier meine Lösung!

C#-Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Windows.Forms;

namespace Mcs
{
  class Program
  {
    [STAThread]
    static void Main(string[] args)
    {
      Application.EnableVisualStyles();

      Application.Run(new SortForm());
    }
  }

  class SortForm : Form
  {
    private ListBox sortedListListBox = new ListBox();
    private TextBox firstCompareTextBox = new TextBox();
    private TextBox secondCompareTextBox = new TextBox();
    private Button startSortingButton = new Button();
    private GroupBox compareGroupBox = new GroupBox();

    public SortForm()
    {
      InitializeComponent();
      sortedListListBox.Items.AddRange("Vanille, Schokolade, Stracciatella, Erdbeer, Zitrone, Kirsch und Schlumpf".Split(new string[] { ", ", " und " }, StringSplitOptions.RemoveEmptyEntries));
    }

    private void InitializeComponent()
    {
      //SortForm
      Height = 280;
      Width = 229;
      FormBorderStyle = FormBorderStyle.Fixed3D;
      Text = "UISort";
      MaximizeBox = false;

      //sortedListComboBox
      sortedListListBox.Top = 10;
      sortedListListBox.Left = 10;
      sortedListListBox.Width = 200;
      sortedListListBox.Height = 200;

      Controls.Add(sortedListListBox);

      //startSortingButton
      startSortingButton.Top = 215;
      startSortingButton.Width = 75;
      startSortingButton.Height = 23;
      startSortingButton.Left = 10;
      startSortingButton.Text = "Sort !1elf";
      startSortingButton.Click += StartSortingButton_Click;

      Controls.Add(startSortingButton);
    }

    private void PrepareComparisonControls()
    {
      //SortForm
      Width += 160;

      //compareGroupBox
      compareGroupBox.Top = 10;
      compareGroupBox.Left = 220;
      compareGroupBox.Width = 150;
      compareGroupBox.Height = 200;
      compareGroupBox.Text = "Compare";

      Controls.Add(compareGroupBox);

      //firstCompareTextBox
      firstCompareTextBox.Top = 15;
      firstCompareTextBox.Left = 10;
      firstCompareTextBox.Width = 100;
      firstCompareTextBox.Height = 23;
      firstCompareTextBox.ReadOnly = true;
      compareGroupBox.Controls.Add(firstCompareTextBox);

      //secondCompareTextBox
      secondCompareTextBox.Top = 43;
      secondCompareTextBox.Left = 10;
      secondCompareTextBox.Width = 100;
      secondCompareTextBox.Height = 23;
      secondCompareTextBox.ReadOnly = true;

      compareGroupBox.Controls.Add(secondCompareTextBox);
    }

    private void RemoveComparisonControls()
    {
      if (InvokeRequired)
      {
        Invoke(new Action(RemoveComparisonControls));
      }
      else
      {
        //Reset
        Width -= 160;
        Controls.Remove(compareGroupBox);
        compareGroupBox.Controls.Remove(firstCompareTextBox);
        compareGroupBox.Controls.Remove(secondCompareTextBox);
      }
    }

    private void StartSorting()
    {
      string[] elements = sortedListListBox.Items.Cast<String>().ToArray();
      UIComparer comparer = new UIComparer();

      comparer.ComparisonRequired += ComparisonRequired;

      ThreadPool.QueueUserWorkItem(
        new WaitCallback(o =>
      {
        Array.Sort<String>(elements, comparer);
        DisplaySortedResult(elements);
        RemoveComparisonControls();
      }), null);
    }

    private void DisplayComparison(CompareEventArgs e)
    {
      if (InvokeRequired)
      {
        Invoke(new Action<CompareEventArgs>(DisplayComparison), e);
      }
      else
      {
        firstCompareTextBox.Text = e.X;
        secondCompareTextBox.Text = e.Y;

        firstCompareTextBox.Click += (o, ea) => e.Chosen = e.X;
        secondCompareTextBox.Click += (o, ea) => e.Chosen = e.Y;
      }
    }

    private void DisplaySortedResult(String[] elements)
    {
      if (InvokeRequired)
      {
        Invoke(new Action<String[]>(DisplaySortedResult), ((Object)elements));
      }
      else
      {
        sortedListListBox.Items.Clear();
        sortedListListBox.Items.AddRange(elements);
      }
    }

    #region Events

    private void StartSortingButton_Click(Object sender, EventArgs e)
    {
      PrepareComparisonControls();
      StartSorting();
    }

    private void ComparisonRequired(Object sender, CompareEventArgs e)
    {
      DisplayComparison(e);
    }

    #endregion
  }

  class UIComparer : IComparer<String>
  {

    #region IComparer<string> Member

    public int Compare(String x, String y)
    {
      if (x == y)
        return 0;

      CompareEventArgs e = new CompareEventArgs(x, y);

      if (ComparisonRequired != null)
        ComparisonRequired.Invoke(this, e);

      e.WaitForUserInput();

      if (e.Chosen == x)
      {
        return 1337;
      }
      else
      {
        return -1337;
      }
    }

    #endregion

    public event EventHandler<CompareEventArgs> ComparisonRequired;
  }

  class CompareEventArgs : EventArgs
  {
    private String chosen = string.Empty;
    private AutoResetEvent waiter;

    public CompareEventArgs(String x, String y)
    {
      X = x;
      Y = y;
      waiter = new AutoResetEvent(false);
    }

    public void WaitForUserInput()
    {
      waiter.WaitOne();
    }

    public String X { get; set; }
    public String Y { get; set; }

    public String Chosen
    {
      get
      {
        return chosen;
      }
      set
      {
        chosen = value;
        waiter.Set();
      }
    }
  }
}

Dieser Beitrag wurde 2 mal editiert, zum letzten Mal von edsplash am 03.05.2010 14:56.

03.05.2010 14:37 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
herbivore
myCSharp.de-Team (Admin)

images/avatars/avatar-2627.gif


Dabei seit: 11.01.2005
Beiträge: 47.492
Entwicklungsumgebung: csc/nmake (nothing is faster)
Herkunft: Berlin


herbivore ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo edsplash,

zwei Kleinigkeiten habe(hatte) ich gefunden:

Die eine, nämlich dass dem Benutzer Fragen der Art "Kirsch" vs "Kirsch" gestellt, hast du mittlerweile selber bemerkt und behoben.

Die andere ist, dass für die TextBoxen immer neue EventHandler registriert werden, ohne dass die alten deregistriert werden. Das führt mal abgesehen von dem bei steigender Listenlänge quadratischen Aufwand inbesondere dazu, dass die EventArgs die ganze Zeit nicht freigegeben werden.

Ansonsten funktioniert die Lösung genau so, wie ich mir das vorgestellt hatte, also inbesondere mit dem AutoResetEvent, durch den der Sort-Thread wartet, bis der Benutzer sich entschieden hat.

Du bist mit der nächsten Aufgabe dran.

herbivore
03.05.2010 15:40 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
edsplash edsplash ist männlich
myCSharp.de-Mitglied

images/avatars/avatar-3111.jpg


Dabei seit: 19.04.2008
Beiträge: 390
Entwicklungsumgebung: VS2010


edsplash ist offline Füge edsplash Deiner Kontaktliste hinzu

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo herbivore

[offtopic]
Das mit dem Kirsch vs Kirsch halte ich nach wie vor für einen Schönheitsfehler, da der Quicksort Algorithmus ja sowieso nicht weiss, was er machen soll wenn zwei Werte gleich gross sind. Es ändert sich da Funktional also nichts. Habs aber trotzdem nachgebessert. ;)

Die Click Events hätte man allerdings generischer behandeln können, aber da hier gerne Quick&Dirty Lösungen präsentiert werden, bzw. das im ersten Beitrag erwähnt wird, ist das wohl auch nicht so Schlimm. ;)
[/offtopic]


Meine Aufgabe kommt dann Morgen. :)

Gruss
03.05.2010 22:41 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
edsplash edsplash ist männlich
myCSharp.de-Mitglied

images/avatars/avatar-3111.jpg


Dabei seit: 19.04.2008
Beiträge: 390
Entwicklungsumgebung: VS2010


edsplash ist offline Füge edsplash Deiner Kontaktliste hinzu

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo Zusammen

Meine Aufgabe:

Ziel ist es ein Interface IMap<TLeft, TRight> zu implementieren: ein IMap Objekt stellt quasi eine 1:1 Verknüpfung, bzw. eine eindeutige Zuordnung von Objekten verschiedenen Typs dar. Sowohl auf der linken, wie auf der rechten Seite darf nicht zweimal dasselbe Objekt vorkommen. Elemente der rechten Seite können dabei mit dem entsprechenden Element der linken Seite aus dem IMap Objekt geladen/beschrieben werden und umgekehrt.

Man sollte dabei sofern möglich vermeiden auf .NET Klassen wie Dictionary<T>, HashSet<T> zurück zu greifen. (Wäre ja langweilig :))

C#-Code:
  interface IMap<TLeft, TRight>
  {
    /// <summary>
    /// Fügt der Map eine Zuordnung hinzu
    /// </summary>
    void Add(TLeft leftItem, TRight rightItem);

    /// <summary>
    /// Liest ein Element auf der rechten Seite der Map aus oder setzt dieses
    /// </summary>
    /// <param name="item">Ein Element auf der linken Seite, welches als Schlüssel dient</param>
    TRight this[TLeft item] { get; set; }

    /// <summary>
    /// Liest ein Element auf der linken Seite der Map aus oder setzt dieses
    /// </summary>
    /// <param name="item">Ein Element auf der rechten Seite, welches als Schlüssel dient</param>
    TLeft this[TRight item] { get; set; }

    /// <summary>
    /// Gibt an, ob auf der linken Seite ein spezifisches Element vorhanden ist
    /// </summary>
    bool Contains(TLeft item);

    /// <summary>
    /// Gibt an, ob auf der rechten Seite ein spezifisches Element vorhanden ist
    /// </summary>
    bool Contains(TRight item);

    /// <summary>
    /// Löscht eine Zuordnung
    /// </summary>
    /// <param name="item">Ein Element auf der linken Seite, welches als Schlüssel dient</param>
    bool Remove(TLeft item);

    /// <summary>
    /// Löscht eine Zuordnung
    /// </summary>
    /// <param name="item">Ein Element auf der rechten Seite, welches als Schlüssel dient</param>
    bool Remove(TRight item);

    /// <summary>
    /// Löscht alle Zuordnungen
    /// </summary>
    void Clear();

    /// <summary>
    /// Gibt die Anzahl der vorhandenen Zuordnungen zurück
    /// </summary>
    int Count { get; }
  }

Gruss

Dieser Beitrag wurde 2 mal editiert, zum letzten Mal von edsplash am 04.05.2010 17:36.

04.05.2010 13:59 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Seiten (17): « vorherige 1 2 [3] 4 5 6 7 nächste » ... letzte » Baumstruktur | Brettstruktur       | Top 
myCSharp.de | Forum Der Startbeitrag ist älter als 3 Jahre.
Der letzte Beitrag ist älter als ein Monat.
Antwort erstellen


© Copyright 2003-2013 myCSharp.de-Team. Alle Rechte vorbehalten. 23.05.2013 06:08