|
|
Autor
 |
|
Corpsegrinder
myCSharp.de-Mitglied
Dabei seit: 30.01.2007
Beiträge: 401
Entwicklungsumgebung: VS 2008 , .Net 3.5
|
|
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);
IList<T> AddToHead(T item);
IList<T> RemoveFirst();
IList<T> RemoveLast();
T Get(int index);
String ToString();
}
|
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)
Dabei seit: 11.01.2005
Beiträge: 47.568
Entwicklungsumgebung: csc/nmake (nothing is faster) Herkunft: Berlin
|
|
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
|
|
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)
Dabei seit: 11.01.2005
Beiträge: 47.568
Entwicklungsumgebung: csc/nmake (nothing is faster) Herkunft: Berlin
|
|
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);
IImmutableList <T> AddToHead (T item);
IImmutableList <T> RemoveFirst ();
IImmutableList <T> RemoveLast ();
T Get (int index);
String ToString ();
}
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
|
|
herbivore
myCSharp.de-Team (Admin)
Dabei seit: 11.01.2005
Beiträge: 47.568
Entwicklungsumgebung: csc/nmake (nothing is faster) Herkunft: Berlin
|
|
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
|
|
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;
}
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);
}
}
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)
Dabei seit: 11.01.2005
Beiträge: 47.568
Entwicklungsumgebung: csc/nmake (nothing is faster) Herkunft: Berlin
|
|
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
Dabei seit: 13.06.2007
Beiträge: 1.130
Entwicklungsumgebung: VS 2008 Herkunft: Norddeutschland
|
|
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
myCSharp.de-Poweruser/ Experte
Dabei seit: 14.11.2007
Beiträge: 1.231
Entwicklungsumgebung: VS 2005+2010 Herkunft: Berlin
|
|
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)
Dabei seit: 11.01.2005
Beiträge: 47.568
Entwicklungsumgebung: csc/nmake (nothing is faster) Herkunft: Berlin
|
|
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
|
|
herbivore
myCSharp.de-Team (Admin)
Dabei seit: 11.01.2005
Beiträge: 47.568
Entwicklungsumgebung: csc/nmake (nothing is faster) Herkunft: Berlin
|
|
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
Dabei seit: 13.06.2007
Beiträge: 1.130
Entwicklungsumgebung: VS 2008 Herkunft: Norddeutschland
|
|
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();
}
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;
}
}
|
| 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)
Dabei seit: 11.01.2005
Beiträge: 47.568
Entwicklungsumgebung: csc/nmake (nothing is faster) Herkunft: Berlin
|
|
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)
Dabei seit: 11.01.2005
Beiträge: 47.568
Entwicklungsumgebung: csc/nmake (nothing is faster) Herkunft: Berlin
|
|
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: - Stand: Zwei Consumer, zwei Producer, Queue leer, keiner wartet auf die Sperre, keiner im Wait-Status, MaxSize == 1
- 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)
- 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)
- ProducerA Enqueue: betritt die Sperre (bei lock), aber dann erfolgt zufällig ein Threadwechsel, bevor das Pulse erreicht wurde
- ProducerB Enqueue: versucht die Sperre zu betreten, aber da ist ja ProducerA drin, also wartet ProducerB auf die Sperre (bei lock)
- ProducerA läuft weiter und führt das Pulse aus
- Dadurch wartet Consumer1 wieder auf die Sperre (bei Wait), stellt sich also hinter dem schon wartenden ProducerB an.
- ProducerA verlässt die Sperre
- Stand: Ein Item in der Queue, ProducerB und Consumer1 warten in dieser Reihenfolge auf die Sperre, Consumer2 wartet im Wait-Status
- 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)
- Stand: Ein Item in der Queue, Consumer1 wartet auf die Sperre, Consumer2 und ProducerB warten in dieser Reihenfolge im Wait-Status
- Consumer1 betritt die Sperre (bei Wait), holt das von ProducerA eingestellt Element ab und und führt das Pulse aus
- Dadurch wartet Consumer2 wieder auf die Sperre (bei Wait). Vor ihm steht keiner.
- Consumer1 verlässt die Sperre
- Stand: Queue leer, Consumer2 wartet auf die Sperre, ProducerB wartet im Wait-Status
- 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)
- 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
|
|
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;
}
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);
}
}
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)
Dabei seit: 11.01.2005
Beiträge: 47.568
Entwicklungsumgebung: csc/nmake (nothing is faster) Herkunft: Berlin
|
|
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
|
|
| 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
Dabei seit: 07.07.2005
Beiträge: 2.708
Entwicklungsumgebung: SharpDevelop/VS.NET Herkunft: Deutschland
|
|
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
|
|
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
Dabei seit: 13.06.2007
Beiträge: 1.130
Entwicklungsumgebung: VS 2008 Herkunft: Norddeutschland
|
|
| 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;
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 += ((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
|
|
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
Dabei seit: 13.06.2007
Beiträge: 1.130
Entwicklungsumgebung: VS 2008 Herkunft: Norddeutschland
|
|
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
myCSharp.de-Mitglied
Dabei seit: 01.10.2008
Beiträge: 479
Entwicklungsumgebung: Visual Studio 2010 Pro Herkunft: Henstedt-Ulzburg
|
|
C#-Code: |
public ushort Checksum(byte[] data)
{
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
Dabei seit: 13.06.2007
Beiträge: 1.130
Entwicklungsumgebung: VS 2008 Herkunft: Norddeutschland
|
|
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
myCSharp.de-Mitglied
Dabei seit: 01.10.2008
Beiträge: 479
Entwicklungsumgebung: Visual Studio 2010 Pro Herkunft: Henstedt-Ulzburg
|
|
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
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
Dabei seit: 28.08.2007
Beiträge: 994
Entwicklungsumgebung: Visual Studio 2010 Professional
|
|
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:
 Volle Bildgröße
|
|
29.04.2010 20:23
|
E-Mail |
Beiträge des Benutzers |
zu Buddylist hinzufügen
|
|
markus111
myCSharp.de-Mitglied
Dabei seit: 01.10.2008
Beiträge: 479
Entwicklungsumgebung: Visual Studio 2010 Pro Herkunft: Henstedt-Ulzburg
|
|
Jup, richtig. Ich hattes es so, dass es über die komplette Breite geht, aber das ist schon okay. Du bist dran
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
Dabei seit: 28.08.2007
Beiträge: 994
Entwicklungsumgebung: Visual Studio 2010 Professional
|
|
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
Dabei seit: 28.08.2007
Beiträge: 994
Entwicklungsumgebung: Visual Studio 2010 Professional
|
|
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
myCSharp.de-Mitglied
Dabei seit: 10.12.2007
Beiträge: 593
Entwicklungsumgebung: VS2008 Standard Herkunft: Clausthal-Zellerfeld
|
|
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
myCSharp.de-Mitglied
Dabei seit: 17.12.2006
Beiträge: 769
Entwicklungsumgebung: VS 2010 Prof. Herkunft: /dev/null
|
|
AHHHH!
Vier Minuten zu spät.....
Ich poste mal trotzdem meine Lösung, aber Kaji war leider schneller...
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
Dabei seit: 28.08.2007
Beiträge: 994
Entwicklungsumgebung: Visual Studio 2010 Professional
|
|
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
|
|
herbivore
myCSharp.de-Team (Admin)
Dabei seit: 11.01.2005
Beiträge: 47.568
Entwicklungsumgebung: csc/nmake (nothing is faster) Herkunft: Berlin
|
|
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
myCSharp.de-Mitglied
Dabei seit: 19.04.2008
Beiträge: 390
Entwicklungsumgebung: VS2010
|
|
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()
{
Height = 280;
Width = 229;
FormBorderStyle = FormBorderStyle.Fixed3D;
Text = "UISort";
MaximizeBox = false;
sortedListListBox.Top = 10;
sortedListListBox.Left = 10;
sortedListListBox.Width = 200;
sortedListListBox.Height = 200;
Controls.Add(sortedListListBox);
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()
{
Width += 160;
compareGroupBox.Top = 10;
compareGroupBox.Left = 220;
compareGroupBox.Width = 150;
compareGroupBox.Height = 200;
compareGroupBox.Text = "Compare";
Controls.Add(compareGroupBox);
firstCompareTextBox.Top = 15;
firstCompareTextBox.Left = 10;
firstCompareTextBox.Width = 100;
firstCompareTextBox.Height = 23;
firstCompareTextBox.ReadOnly = true;
compareGroupBox.Controls.Add(firstCompareTextBox);
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
{
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)
Dabei seit: 11.01.2005
Beiträge: 47.568
Entwicklungsumgebung: csc/nmake (nothing is faster) Herkunft: Berlin
|
|
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
myCSharp.de-Mitglied
Dabei seit: 19.04.2008
Beiträge: 390
Entwicklungsumgebung: VS2010
|
|
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>
{
void Add(TLeft leftItem, TRight rightItem);
TRight this[TLeft item] { get; set; }
TLeft this[TRight item] { get; set; }
bool Contains(TLeft item);
bool Contains(TRight item);
bool Remove(TLeft item);
bool Remove(TRight item);
void Clear();
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
|
|
|