Laden...

Forenbeiträge von Traumzauberbaum Ingesamt 512 Beiträge

27.11.2006 - 23:48 Uhr

Speicheroptimierung in C# fußt immer auf Wissen über den Garbage Collector.

Ich schlag vor du machst dich dahingehend schlau. Wie arbeitet der Garbage Collector, wann sammelt er, was sammelt er, und wie macht man seine Arbeit schwerer.

Ansonsten wäre auch das Thema Stack vs Heap nicht ganz uninteressant.

Aber gleich vorweg, um da was rauszuquetschen muss man sich schon ziemlich reinknien, und erhält am Ende nicht viel Nutzen. .NET Programme belegen Speicher, wenn er gebraucht wird, und geben ihn erst frei, wenn er von jemand anderem gebraucht wird. Wenns nur um pure Zahlen geht und nicht um messbare Effektivität, wirst du es da eher schwer haben.

27.11.2006 - 21:43 Uhr

NDoc 2.0 gibt es nicht, ist aber soweit ich das höre in Arbeit aber mit niedriger Priorität.

Als Alternative kenne ich jetzt auch nur Doxygen

27.11.2006 - 12:10 Uhr

Wenn es um nicht-doppelte Zufallszahlen geht, empfiehlt sich ein Array mit allen möglichen Zahlen zu nehmen (wenns nicht gar so viele sind... bei Bingo hält sich das ja noch in Grenzen), dieses dann zu Mischen, und dann die ersten 18 Zahlen zu nehmen.

Für Mischen gibt es viele Varianten. Eine einfache aber effektive

for( int i = 0; i < array.Length; ++i )
    Swap( i, rand.Next(0,array.Length) );

Swap ist dabei das Austauschen der Elemente.
rand ist das Random Objekt.

27.11.2006 - 11:53 Uhr

Sorry dass ich das nochmal hochhole. Ist herbivores Schuld, der hat auf den Thread verlinkt 😁

Im Grunde stimmt es, dass Rekursion und Iteration gleichmächtig sind. Aber passt auf, wenn ihr das in einer Prüfung sagt. Es gibt nämlich auch Definitionen von Rekursion, die nicht so mächtig sind wie das, was wir hier auf dem Computer haben.
Bzw. geht es in der Theoretischen Informatik nicht um "unsere" Rekursion, sondern um die primitive Rekursion. Die ist nur LOOP-Berechenbar und nicht Turing-berechenbar.

Der Unterschied ist, dass man mit LOOP nur Schleifen mit einer vorher festgelegten Anzahl an Iterationen machen kann. Das heißt ich kann nicht in der Schleife auswerten, ob ich abbreche. Ich muss schon bevor die Schleife betreten wird bestimmen, wie oft sie durchlaufen wird. In manchen Programmiersprachen ist die FOR-Schleife so ausgelegt (nicht in C#, ich glaube in Pascal und in Python).

Das hat jetzt nicht wirklich praktische Relevanz. Aber wie gesagt in der Prüfung könnte man da leicht auf die Nase fallen, wenn man Rekursion und Primitiv-Rekursive Funktionen durcheinander haut.

27.11.2006 - 11:31 Uhr

Also ich hab meine Bemühungen aus zwei Gründen gestartet:

Erstens hatte ich die zündende Idee, was ich überhaupt da rein machen könnte (mit Herbivores Denkanstoß 😄)
Und zweitens, weil ich gesehen habe, dass die Resonanz nicht so übermäßig ist. Da dachte ich, ich kann euch ja nicht einfach so verhungern lassen 😁

Meine Zeit ist eigentlich im Moment auch relativ knapp bemessen. Aber den Beitrag hatte ich jetzt ungefähr innerhalb einer Woche intensiver Arbeit tatsächlich fertiggebracht.

Und ich hab viele schöne Dinge gelernt und probiert, die ich schon immer mal probieren wollte. Nur was verrate ich frühestens, wenn das entsprechende Türchen auf ist 😉

Ich würde mich auf jeden Fall freuen, wenn es sowas wieder gibt. Ich denke die Preise waren auch für niemanden der Auslöser was zu tun 🙂

27.11.2006 - 00:41 Uhr

Das was dr4g0n76 beschreibt ist auch eher die Idee hinter Backtracking.

Backtracking und Rekursion liegen ja relativ dicht beieinander. Mit Backtracking braucht man erst garnicht anfangen, wenn man Rekursion noch nicht so richtig versteht.

Paradebeispiele für Rekursionen findest du, wenn du dich mal mit binären Bäumen befasst. Speziell die verschiedenen Möglichkeiten durch einen Baum zu gehen.

Hier mal ein Beispiel eines sortierten Baumes.


      D
    /   \
   B      F
 /  \    / \
A    C  E   G

Die Frage: Wie bekommt man eine Sortierte Liste der Knoten aus dem Baum, wenn man als Start nur den Zeiger auf D hat?

Wie würdest du vorgehen? Du weißt, dass links alle Elemente kleiner sind als rechts. Also wenn du erstmal alle Elemente links richtig sortiert aufzählst, dann das aktuelle Element nennst, und dann alle Elemente rechts richtig sortiert aufzählst, ist das Problem erledigt. Und weil du weißt, dass diese Eigenschaft für alle Knoten gilt, kannst du das Prinzip überall anwenden. Also schreibst du erstmal diese Idee auf:

void Expandieren()
{
    linkerSohn.Expandieren();
    WertSchreiben();
    rechterSohn.Expandieren();
}

Beispiele in dieser Art gibt es in der Graphentheorie viele. Ich denke das ist leicht genug um einen guten Einstieg in die Denkweise Rekursion zu finden.

Das Beispiel hab ich auch gebracht, weil es der Rekursion von "hanoi" ziemlich ähnlich ist. Das Muster ist offensichtlich das gleiche, man muss nur erkennen warum.
Das Grundprinzip ist ja bei beiden: verschieb das Problem erstmal zu einem anderen (dem linken Sohn), wenn der das gelöst hat, weiß ich wies weitergeht.

In dem Baumbeispiel ist es relativ einfach. Wenn alle aufgezählt wurden, die kleiner als mein aktueller Wert sind, dann brauch ich bloß noch den aktuellen Wert schreiben und dann an den nächstbesten übergeben.

Bei der hanoi Rekursion sieht das dann so aus:
Also ich starte mit n Schiben auf dem ersten Stapel (a) und will alle auf einen anderen (c) über einen Hilfsstapel (b) kriegen, und darf nur kleinere Scheiben auf größere Scheiben legen.
Wenn ich alle außer die größte Scheibe von a nach b Schieben würde, könnte ich die größte (letzte) Scheibe einfach auf c legen. Dann müsste ich bloß noch den ganzen Stapel von b wieder nach c bewegen, mit dem Hilfsstapel a. Dass bei c schon die größte Scheibe liegt stört dabei ja nicht. Und so ist der Algorithmus zu verstehen:

void hanoi(int hoehe, char von, char nach, char ueber)
        {
            // Ok ich hab vieleicht keine Ahnung wie ich von "von" nach "nach" komme
            // aber wenn ich alle außer die letzte nach "über" kriegen würde...
            // also lass ich einfach nen anderen Deppen das erledigen
            hanoi(hoehe - 1, von, ueber, nach);
            
            // Jetzt weiß ich wies weitergeht. Muss nur noch die letzte Scheibe rüberlegen
            Console.WriteLine("Scheibe '" + von + "' nach '" + nach);
            
            // so, jetzt hab ich meine unterste Scheibe auf "nach" *schweißabwisch*
            // und meine restlichen Scheiben sind bei über
            // da muss ja nur noch irgendwer die restlichen Scheibe von "über"
            // nach "nach" bringen und ich bin fertig
            hanoi(hoehe - 1, ueber, nach, von);
            }
        }

So, was fehlt noch? Richtig, eine Abbruchbedingung. Das ganze würde sich endlos im Kreis drehen, und da sind wir bei dem wichtigsten, das eine Rekursion erst ermöglicht: Der Trivialfall. Meistens erkennt man den als allererstes. Es ist die Menge an Problemen, für die man sofort eine Lösung hinschreiben könnte. Bei Hanoi ist das ganz einfach dann, wenn der Turm nur aus einer einzigen Scheibe besteht. Dann ist der Fall trivial, ich leg halt die Scheibe hin und muss sonst nichts mehr tun. Ich muss nur noch den Code so auslegen, dass der Trivialfall erkannt und behandelt wird. Also frage ich hab, ob "höhe" überhaupt größer als 1 ist. Und wenn nicht, ist der Trivialfall eingetreten.

26.11.2006 - 18:40 Uhr

Vieleicht eine Angewohnheit aus Firmennetzwerken.

Wir haben z.B. einen Kunden, der uns .piz Dateien schickt, bzw. wir ihm schicken müssen, weil irgendein Depp meint die Mitarbeiter dürfen keine zip Dateien empfangen und man kann da auch keine Ausnahmen machen...

26.11.2006 - 16:23 Uhr

So fertig, gleich wirds abgeschickt 🙂 Wenns nicht da ist bitte mal melden.

Und wehe das geht bei irgendwem nicht... X(

Zumindest der Quellcode sollte für einige interessant sein und so manche FAQ beantworten. Ich hoff aber mal der wird erst nach dem Wettbewerb freigegeben, sonst ist ja die ganze Überaschung dahin 😜

Immerhin 90% von dem geschaft, was ich mit vorgestellt habe. Und für die restlichen 10% braucht man ja bekanntlich nochmal so lange.

25.11.2006 - 11:27 Uhr

Also ich beantworte Fragen, die mir zu doof sind, einfach nicht.

Ausnahme sind Fragen wie "Mit [beliebige Programmiersprache] konnte ich das so machen. Warum kann das C# nicht?". Besonders wenn die Programmiersprache C/C++ oder (Visual) Basic heißt... Da kann ich dann schon etwas angefressen reagieren.

24.11.2006 - 01:06 Uhr

Ich muss noch 12 Türchen füllen und für 6 davon brauch ich noch ne Idee 😁

Also vor Sonntag sieht das schlecht aus, aber ich denke ich kriegs fertig.

22.11.2006 - 16:01 Uhr

Schonmal probiert was auf die Art zu starten?

Probier mal über Ausführen "cmd /k echo test" mit Anführungszeichen...
Oder probiers in nem Command Prompt einzugeben...

Da kommt nur Mist raus.

22.11.2006 - 13:51 Uhr

Also der Algorithmus scheint mir eine Annäherung zu sein.
In deinem Code iterierst du über eine feste Anzahl an Schritten. Der Algorithmus auf Wikipedia bricht erst dann ab, wenn die Änderung im aktuellen Schritt unter eine bestimmte Grenze fällt.

Außerdem stört mich auch diese Zeile:
tmpArr = resArr;

In der Schleife über i berechnest du doch sowieso nur die i-te Komponente des Vektors. Das Array zu kopieren ist also völlig unnötig, es kann sich nur an der Stelle i verändert haben.

Ich würde also folgendes daran ändern:

  1. iSteps musst du uminterpretieren als eine maximale Anzahl an Schritten. Sprich im schlechtesten Falle macht er iSteps Iterationen, aber er kann auch schon vorher aufhören wenn eine Konvergenz erreicht ist. Dementsprechend hoch müsstest du iSteps anlegen um ein gutes Ergebniss zu bekommen.

  2. tmpArr = resArr; kopiert nicht den Inhalt, sondern nur die Referenzen. Änderungen an tmpArray ändern also auch resArray. Deswegen musst du das kopieren. Ich würde dazu resArr.CopyTo( tmpArr, 0 ); benutzen.

  3. Kopiere das Array erst hinter der Zählschleife über i, nicht darin. Und bevor du kopierst musst du die Differenz zum vorhergehenden Vektor tmpArray ausrechnen. Wikipedia schweigt sich darüber aus. Ich würde da aber spontan das Betragsquadrat des Differenzvektors nehmen. Also die Summe über (tmpArr_-resArr_)*(tmpArr_-resArr_)

20.11.2006 - 23:19 Uhr

Also für Bitfelder gibt es das BitArray. Dort kann man komfortabel einzelne Bits setzen und abfragen, und das ganze auf beliebiger Größe.

Das mit den 5 Bit wundert mich in C aber doch sehr. Man könnte sich ja einreden, dass man damit den Wertebereich festlegen kann, aber C ist ja nicht sonderlich typsicher.
Denn C rundet garantiert auch auf Byte. Das geht einfach nicht anders. Eventuell gewinnt man etwas, wenn man viele solcher Variablen in eine Struktur packt. Aber das bezweifle ich auch. Standardeinstellung ist ja sogar Runden auf 32 Bit, und unter 8 Bit kann man das auch nicht stellen.
Auf der Assembler Ebene kann man mit den meisten, um nicht zu sagen allen gängigen, CPUs nicht unter Byteebene arbeiten. Ich hab mal mit ner Architektur gespielt, da konnte man einzelne Bits mit einem Befehl setzen oder Laden. Aber auch dort war die kleinste Verarbeitungseinheit ein Byte.

20.11.2006 - 23:09 Uhr

Also seit deinem Tipp im Mathethread bin ich echt am Arbeiten.
Hoffentlich schaff ichs noch rechtzeitig.

Benutzeroberfläche ist im Moment mein größter Feind. Das alles was ich einbaue, muss ja auch irgendwie bedienbar sein. Im Moment kompilier ich immer neu wenn ich ne Option teste 😁

Eigentlich müsste ichs schaffen... Ungefähr die Hälfte der Türchen hab ich in den letzten 5 Tagen gefüllt. Die nächsten 2 Tage hab ich noch Userinterface auf dem Plan. Und dann wird der Rest aufgefüllt. Wenn die Zeit knapp wird bau ich einfach ein paar Nacktfotos ein 😁

16.11.2006 - 00:36 Uhr

Mit der Performance kann man bei Regex auch bisschen tricksen, indem man je nach Situation den Regex kompiliert. (Wenn man den Regex mehrmals braucht)

Ich benutze Regex immer, wenn die normalen string-Funktionen nicht mehr ausreichen. Das sind dann beispielsweise:

  • einen String auf korrektes Format testen
  • in einem String nach speziell formatierte Sequenzen suchen/ersetzen
  • einen formatierten String in seine Bestandteile zerlegen um diese weiterzuverarbeiten (Stichwort Gruppen)

Es gibt bessere Methoden um seine Zeit totzuschlagen als Regex-Funktionalität nachzuprogrammieren. Gerade mit der Begründung Performance... einen Regex für die meisten Probleme hat man innerhalb von Minuten aufgesetzt und getestet. Für eine spezielle Funktion braucht man für nichttriviale Probleme (die, die nicht mit Stringbefehlen lösbar sind) schon leicht mehrere Tage.
Es kostet einen also quasi nichts erstmal mit nem Regex anzufangen, und sich um Performanceprobleme zu kümmern, wenn sie auftreten. Meist werden sie nicht auftreten.

15.11.2006 - 11:22 Uhr

Ich würde ja teilnehmen aber ich hab nicht die geringste Idee 😉

14.11.2006 - 12:11 Uhr

Schau mal in die MSDN bei der Exception Klasse. Dort findest du auch eine Liste aller abgeleiteten Klassen im Framework. Ansonsten eigene anlegen.

EDIT: Achso im catch...
Naja bei ner halbwegs brauchbaren Doku sollten die Exceptions aufgelistet sein. Dann könnte nur noch schlampige Programmierung der Bibliothek dafür sorgen, dass undokumentierte Exceptions geworfen werden, wie z.B. NullReferenceException eigentlich nie geworfen werden sollte.

13.11.2006 - 20:38 Uhr

Ich hab mit Managed C++ noch nicht sonderlich viel gemacht und bin etwas darüber verwundert wie das wohl läuft.

Hast du die andere AppDomain selbst erstellt, oder baut es die automatisch als Trennwand zwischen managed und unmanaged?
Letzteres ist dann doch recht kompliziert zu handhaben, da wirst du wohl Remoting benutzen müssen. Performancemäßig müsste da der IPC Channel mit BinaryFormatter noch am Besten sein.

Wenn du die AppDomain aber selbst erstellst, was ja bei Plugins auch üblich ist, gibt es leichtere Wege.

Ganz prinzipiell brauchst du ja nur ein MarshalByRefObject auf der "anderen" Seite, was dir die Instanz von dem "Singleton" (was ebenfalls MarshalByRefObject erben muss) liefert. Bei selbsterstellter AppDomain ist es ja ganz leicht soetwas einzuschleusen.

13.11.2006 - 16:07 Uhr

Das war lediglich ein Hinweis, wie du die Chance erhöhst, das jemand überhaupt deinen Beitrag ließt, und dir dann entsprechend helfen kann. Ich persönlich hab auch keine Lust mich da durchzuarbeiten. Wenn das Lesen der Frage einfach etwas weniger quälend ist, bekommst du auch mehr, schneller und bessere Antworten.

Aber wenn dich das nicht interessiert, zwingt dich auch keiner die Hinweise zu befolgen.

12.11.2006 - 11:54 Uhr

Bisschen Code wäre nicht schlecht. Also wo du die Verbindung herstellst.

10.11.2006 - 22:42 Uhr

Sehr nett.

Nur will sie für meinen Geschmack etwas zu viel Aufmerksamkeit (wie wohl viele Frauen...)

Immerhin hat sie auch nen Mute Knopf

09.11.2006 - 20:23 Uhr

Müsste nicht der Trick klappen?

SELECT Vorname,Nachname, Sum(Floor(1/Position)) AS AnzahlSiege
FROM t_Positionierung GROUP BY Vorname, Nachname

Wenn du schon ein UNION machst, warum nicht einfach 0 statt Sum(0) 😁

09.11.2006 - 17:24 Uhr

Verstehe das Problem nicht. Du hast x Elemente und y Schubladen. Also kommen in jede Schublade einfach x/y Elemente.
Brauchst doch nur mitzählen wieviele schon drin sind.

Und was meinst du mit dem "Wert der Schublade"? Ich meine eine Differenz zu bestimmen ist ja wirklich leicht. Einen Durchschnitt auszurechnen auch...

Eigentlich alles leicht. Deine Beschreibung ist schon so ausführlich, dass du sie 1:1 in nen Algorithmus übersetzen kannst. Ich weiß nicht was du noch brauchst.

06.11.2006 - 23:42 Uhr

Original von Schakal
Hmm, außer mir waren in unserem Ausbildungsbetrieb noch neun.
Und ich würde, die endlosen Projekte deutschlandweit, die vielen selbständigen Arbeiten und so weiter, mit einem Studium definitiv nicht besser hinbekommen...

Ok glück muß man haben, und gott sei Dank habe jetzt meinen Festvertrag bei einer Inovativen kleinen Firma im Aufstieg.
Vor den Entlassungen zur Zeit (SBS, etc. )bist du auch mit dipl. und wie Ich mit nem Zeugnis von 1.0 nicht sicher.
Wo wir dann schon wieder bei der Selbstständigkeit wären.
Da zählt glaub ich auch Erfahrung😉

Ich hab das Gefühl du bist so sehr damit beschäftigt dir selbst auf die Schulter zu klopfen, dass dir das wesentliche entgeht.
Den Festvertrag mit der Inovativen kleinen Firma im Aufstieg hast du wegen der Erfahrung in dem Ausbildungsbetrieb und den Projekten dort bekommen.
Diese Stelle wiederum hast du durch Glück bekommen, das ist nichts worauf man planen kann.
Trotzdem stellst du deinen Werdegang als beispielhaft hin. Das ist er nicht, das ist nicht einfach so wiederholbar und deswegen ein schlechter Rat. Klar kann man es auch so schaffen, nur gibt es Wege auf denen die Chancen besser aussehen. Darum gehts doch. Nicht mit Diplom ist man der King im Ring und ohne ist man nur Kabeläffchen. Wer so pauschalisiert kann nur falschliegen. Du pauschalisierst in die andere Richtung, und liegst damit nicht weniger falsch.
Die Arbeitslosenrate bei Akademikern ist halb so hoch. Aber egal, du bist immerhin der mit den dicken Eiern, deswegen muss ne Statistik die millionen von Fällen betrachtet dem einfach nachstehen.

_Original von Schakal_Und schöön, endlich mal einer der mir erklärt das die Praxis auf Theorie aufsetzt.8)

Es scheint mir so als ob Du keine Ahnung hast, was eigentlich außer dem puren Tippen oder Netzwerk noch so alles zum Berufsbild des Fachinformatikers zählt.
Man nehme nur mal das Projektmanagement.
Und deine schöne Formulierung könnte auch durchaus jemanden beleidigen, zumindest wenn er sich angesprochen fühlt.
Nur da ich außer C# noch Perl, PhP, COBOL, REXX, C-Scr., Assembler, Javascr., VB (darf man die Unix-Shell-Dialekte hier eigentlich aufführen??)... spreche und mich ne menge Kumpels(übrigens Inf.Studies) um Raht fragen...

Erklär mir mal was du willst.
Du sagst Praxis > Theorie. Dann tust du so als hätte ich was völlig offensichtliches gesagt, als ich davon schrieb, dass das praktischste eine gute Theorie ist.
Und dann? Dann kommt nurnoch heiße Luft. Werf ruhig weiter Akronyme in den Raum. Nur beeindruckst du damit wirklich keinen mit nem Hauch von Ahnung. Was hat denn die tolle Aufzählung bitte damit zu tun, dass du deine praktischen Erfahrungen unter Garantie auch zu einer Theorie abstrahierst, die du dann auf neue Probleme anwendest?

Das was du hier ablieferst ist genau das Bild, was ich gerne Computerfuzi nenne. Du bildest dir was auf deine Kentnisse ein. Oh ja du must ja ein echter Mann sein bei den vielen Programmiersprachen die du kannst...

Wach auf, das ist nichts Besonderes. Nichts was du hier aufzählst kann mich sonderlich beeindrucken. Füg mal noch ne funktionale Programmiersprache hinzu, dann wird die Liste immerhin zweidimensional. Oder wenigstens eine die funktionale Konstrukte enthält wie Python. Aber so? Sorry, trivial. Behalts lieber für dich und produzier weniger heiße Luft.

06.11.2006 - 19:46 Uhr

Original von Schakal
@Traumzauberbaum:
folgendes, wenn Du versuchst mir die Wörter in der Hand umzudrehen:

Nenn mir einen Grund warum du das, was du ohne Studium gelernt hast, nicht mit Studium lernen kannst.
Dieses habe ich NIE behauptet!!!

Es wird dich auch nicht daran hindern es zu Lernen, nur Fakt ist:

alles was du dir selbst durch Praxis beibringen kannst, hättest du dir genauso selbst beibringen können, wenn du Informatik studiert hättest.

!!! Nein, ist nicht richtig. !!! Denn dann fehlt dir der tägliche Erfahrungsaustausch mit Kollegen😉. Was man nicht so alles in der Kantine gelehrnt hat😉

Nur weil du es unbedingt in deinen Kontext setzen willst. Das ist zum Beispiel auch etwas, was man im Studium schon in den frühen Semestern lernt: Daten (mein Satz) müssen in den richtigen Kontext gesetzt werden, erst dann wird eine Nachricht draus.
Es ist einfach mal nichts falsches an dem Satz, du willst es nur so verstehen, dass ich gesagt hätte, dass man alles was du durch Praxis gelernt hast im Studium lernen würde. Das steht aber nicht dort.
Natürlich musst du dich dann noch etwas über das Studium hinaus bemühen. In der Uni rennen übrigens auch haufenweise Leute rum die Ahnung von ihrem Fachgebiet haben. Mit denen kann man auch reden und Erfahrungen austauschen. Und das sind praktische Erfahrungen, denn jede Professur hat mehrere Forschungsthemen, bei denen es um echte Lösungen für echte Probleme geht.

Außerdem solltest du dir echt mal bewusst machen, dass du nur einen wirklich kleinen Ausschnitt der Informatik kennst.
Ich meine du konntest alle Algorithmen nachschlagen, die du bisher brauchtest. Es gibt aber ganze Klassen von Problemen, für die es keinen niedergeschriebenen Algorithmus gibt. Dafür gibt es eine Theorie, wie man einen Algorithmus dafür aufstellen kann. Dafür müsstest du aber erstens wissen, dass es sowas überhaupt gibt, und zweitens, was für eine Klasse von Problemen du damit erfasst.

Nur weil du etwas nicht brauchst, heißt das nicht, dass es nie oder auch nur selten gebraucht wird. Du ziehst von dir die Verallgemeinerung zu allem. Schonmal nachgedacht, dass du einfach nur Glück hattest an diese eine Stelle zu kommen? Dass du ohne diese Stelle dir die Praxis gefehlt hätte, um erstens überhaupt an deine weiteren Stellen zu kommen (bewerbungstechnisch) und zweitens sie bewältigen zu können? Ist es ein guter Rat auf soetwas zu bauen?

Und wo dir vieleicht auch einfach das Verständniss fehlt: Auch du benutzt Theorie. Du hast die Theorie durch Praxis gelernt, aber ohne die Theorie würdest du jedesmal von vorne anfangen müssen ein Problem zu lösen. Aber du weißt wonach du suchen musst, wie du an das Problem rangehen musst, genau das ist Theorie. Was also bringt dir die Theorie? Eine Lösung zu Problemen, die du noch nicht hattest. Was bringt dir Praxis? Eine Lösung zu einem Problem, das du schonmal hattest.
Wenn du nicht aus deiner Praxis zu einer Theorie abstrahieren kannst, bist du einer von denen, die sich an ihrem Office '98 festklammern, weil sie Office 2000 schon nichtmehr verstehen.
Du siehst es vieleicht nicht als Theorie, aber genau das ist was, was einem eine Theorie vermittelt.

06.11.2006 - 17:49 Uhr

Du willst es einfach nicht verstehen oder?

Nenn mir einen Grund warum du das, was du ohne Studium gelernt hast, nicht mit Studium lernen kannst.

Ich seh ein, dass man es nicht im Studium lernt. Das brauchst du nicht betonen, das ist ja nichtmal das Ziel des Studiums. Nur warum hindert mich ein Studium daran es zu lernen?

06.11.2006 - 16:07 Uhr

@Schakal

Bei der Informatik gehts genauso um Computer wie in der Astronomie um Teleskope.

Aber geh mal davon aus: alles was du dir selbst durch Praxis beibringen kannst, hättest du dir genauso selbst beibringen können, wenn du Informatik studiert hättest.

03.11.2006 - 20:29 Uhr

Der Code der hier steht wird wunderbar funktionieren. Der Code der bei dir steht wird noch ein "public" vor dem "class ClassB" haben.

Ist auch logisch. Wenn du ClassB public definierst, können andere Assemblies sie verwenden. Andere Assemblies müssen dann auf alle Properties und Methoden zugreifen können. Um das zu können, müssen die Assemblies aber auch alle Typen der Parameter und Properties kennen. Wenn ClassA nicht public definiert ist, kennen die anderen Assemblies ClassA auch nicht.

Entweder du machst beide public, oder beide internal (= visibility Schlüsselwort weglassen)

03.11.2006 - 20:16 Uhr

Ich würde vorschlagen wenn du beim Einfügen nichts findest, schau mal beim Ausgeben nach. Vieleicht liegt der Fehler dort. Beim Enumerator oder beim Indexer. Prüf bei der Ausgabe auch mal, ob dort die Frames nicht nur vom Inhalt gleich sind, oder vieleicht sogar die gleiche Referenz haben ( == Operator, oder object.ReferenceEquals )

03.11.2006 - 20:10 Uhr

Das Problem ist, dass dein Client die echte Klasse nicht kennt, und GetType() da logischerweise nichts sinnvolles liefern kann. Woher soll er denn die Typinformationen für Reflection nehmen?

Es wäre sowieso logischer wenn die Informationen die der Client braucht alle über das Interface geholt werden können. Also du fügst dem Interface z.B. eine Funktion GetProperties() hinzu. In der Implementation des Interfaces solltest du problemlos this.GetType().GetProperties() verwenden können.

02.11.2006 - 23:27 Uhr

Das Ding mit dem Studium ist ja nicht, dass man da Dinge lernt die man sonst nie rausfinden würde. Es ist vorallem auch ein Qualifikationsnachweiß.
Ich meine in einer Bewerbung kann man ja allen möglichen Käse schreiben.

Kleines Beispiel von thedailywtf.com

Ein Diplom ist einfach ein Leistungsnachweis. Um das zu erreichen muss man ein bestimmtes Maß an Verständniss und Wissen besitzen. Zumindest ist da die Wahrscheinlichkeit ne absolute Gurke zu erwischen deutlich kleiner.

Das hat nix mit verknöcherten Strukturen, sondern vorallem purem Selbsterhaltungstrieb. Das Business besteht zur Hälfte aus Lügen und die andere Hälfte sind Übertreibungen. Es ist einfach verflucht schwer einen wirklich qualifizierten Mitarbeiter zu finden.

Eigentlich ist fürs pure Programmieren auch kein Studium nötig. Das ist im Grunde eine relative leichte Aufgabe, wenn man den Geist dazu hat. Schwer wird es wenn die Projekte eine gewisse größe erreichen das alles zu koordinieren, aufzuteilen und das so zu machen, dass es am Ende zusammenpasst, überhaupt fertig wird, und am Ende noch wartbar ist. Das ist die eigentliche Ingenieurskunst, bei der erst ein Studium schon wirklich vorausgesetzt werden sollte.

31.10.2006 - 16:07 Uhr

Der Dijkstra Algorithmus berechnet eher einen Graphen der kürzesten Wege von einem Startknoten aus. Der A* Algorithmus zielt eher darauf ab nur den kürzesten Weg zwischen zwei Knoten zu ermitteln.

Der Unterschied ist eigentlich sehr klein. Wenn du den Dijkstra Algorithmus anschaust, wählt er ja immer als nächste Kante die mit dem geringsten Gewicht.
Der A* Algorithmus macht das etwas allgemeiner, dort wird die "günstigste" Kante ausgewählt.

Was günstig bedeutet, wird durch eine Heuristik definiert, die bestimmte Eigenschaften erfüllen muss, damit ein brauchbares Ergebnis garantiert ist.
Beispielsweise wenn die Knoten Koordinaten haben ist eine geläufige Heuristik die Luftlinie zum Zielknoten.
Und wenn du einfach festlegst, dass die günstigste Kante die mit dem geringsten Gewicht ist, kommst du offensichtlich wieder bei Dijkstra raus.

30.10.2006 - 18:41 Uhr

Sorry, das regt mich schon wieder etwas auf. Wie Dijkstra so schön sagte:

It is practically impossible to teach good programming to students that have had a prior exposure to BASIC: as potential programmers they are mentally mutilated beyond hope of regeneration.

30.10.2006 - 15:59 Uhr

Danke für die Klarstellung, das war der Teil den ich übersehen habe. Das macht es natürlich umständlicher und eine eigene Queue notwendig.

30.10.2006 - 00:31 Uhr

Also wenn ich das richtig überschaue, erreicht man den selben Effekt auch deutlich einfacher.

Man braucht nur ein AutoResetEvent Objekt und die ThreadPool Funktion RegisterWaitForSingleObject.

Alle Operationen werden dann über das AutoResetEvent synchronisiert. Das erreicht man, indem man die Funktionen dann über RegisterWaitForSingleObject statt mit QueueUserWorkItem im Threadpool registriert.

Das würde schon ausreichen, um den Threadpool während einer Schreiboperation nicht mit wartenden Threads vollzumüllen. Die Queue würde dann vom ThreadPool übernommen. Das würde z.B. so aussehen:

AutoResetEvent waitObject = new AutoResetEvent(true);
// true damit erste Operation sofort ausgeführt wird

void RuftSchreibenAuf()
{
    ThreadPool.RegisterWaitForSingleObject( waitObject, WriteCallback, null, Timeout.Infinite, true );
}

void RuftLesenAuf()
{
    ThreadPool.RegisterWaitForSingleObject( waitObject, ReadCallback, null, Timeout.Infinite, true );
}

void WriteCallback( object state, bool timedOut )
{
    try {
        // hier findet das echte Schreiben statt
    }
    finally {
        waitObject.Set(); // gibt für die nächste Operation frei
    }
}

void ReadCallback( object state, bool timedOut )
{
    waitObject.Set(); // gibt die nächste Operation sofort frei

    // Lesen findet dann hier statt
    // weil vor dem eigentlichen Lesen schon freigegeben wird,
    // könnten auch mehrere Leseoperation parallel stattfinden
}

Damit gibt es noch ein Problem mit der anderen Richtung. Wenn gerade ein paar Threads fleißig am Lesen sind, und einer will was Schreiben, dann wird das nicht verhindert. Er kann einfach anfangen obwohl die Leser noch nicht fertig sind.

Das könnte man zum Beispiel mit dem ReaderWriterLock in den Griff bekommen. Im Ganzen hätte man dann maximal einen Schreibethread im Wartezustand, solange noch Leseoperationen laufen, die vorher aufgerufen wurden. Leseoperationen die nach einer Schreibeoperation aufgerufen werden, würden vom ThreadPool gequeued werden und würden keinen Thread belegen, bis der Schreiber fertig ist.

Im Ganzen würde der Code dann zum Beispiel so aussehen:

AutoResetEvent waitObject = new AutoResetEvent(true);
// true damit erste Operation sofort ausgeführt wird
ReadWriterLock readWriterLock = new ReadWriterLock();

void RuftSchreibenAuf()
{
    ThreadPool.RegisterWaitForSingleObject( waitObject, WriteCallback, null, Timeout.Infinite, true );
}

void RuftLesenAuf()
{
    ThreadPool.RegisterWaitForSingleObject( waitObject, ReadCallback, null, Timeout.Infinite, true );
}

void WriteCallback( object state, bool timedOut )
{
    readWriterLock.AcquireWriterLock( Timeout.Infinite );
    try {
        // hier findet das echte Schreiben statt
    }
    finally {
        readWriterLock.ReleaseWriterLock();
        waitObject.Set(); // gibt für die nächste Operation frei
    }
}

void ReadCallback( object state, bool timedOut )
{
    readWriterLock.AcquireReaderLock( Timeout.Infinite );
    try {
        waitObject.Set(); // gibt die nächste Operation sofort frei

        // Lesen findet dann hier statt
    }
    finally {
        readWriterLock.ReleaseReaderLock();
    }
}

Nicht wirklich sonderlich umständlich finde ich. Und den Aufwand kann man sich sparen, wenn man paralleles Lesen nicht braucht, da würde Variante 1 auch reichen, wenn man das Lesen genauso strickt wie das Schreiben (also Set() erst am Ende aufrufen). Dann würde immer nur eine Operation gleichzeitig laufen, der Rest würde über den ThreadPool gequeued werden. Ich glaube sowieso nicht so richtig, dass paralleles Lesen etwas bringen würde. Selbst mit 4 Prozessoren teilen die sich doch immernoch die gleiche Schnittstelle um auf die Festplatte zuzugreifen.

Das ist zumindest was mir in den ersten 10 Minuten dazu einfällt. Mich würde da mal eure Kritik interessieren. Scheint mir vom Ergebniss her aber ziemlich analog zu funktionieren. Man könnte bestimmt noch was verbessern, und vieleicht ists auch völliger Blödsinn.

28.10.2006 - 15:23 Uhr

Ich denke du solltest bei GetProperties die Überladung benutzen, bei der BindingFlags als Parameter genommen werden. Über die kannst du dann ganz exakt einstellen was die Funktion zurückgeben soll.

25.10.2006 - 10:26 Uhr

Es gibt tatsächlich einen Polynomialzeit Algorithmus zur Faktorisierung auf Quantencomputern. Mehr als das weiß ich aber auch nicht, hab die Vorlesung nicht weiter besucht 😁

25.10.2006 - 00:44 Uhr

Original von BerndFfm
Borg : Genau das was ich gesagt hatte, ausser

Stimmt auch nicht. Man kann zwar mit einigen Formeln Kandidaten für Primzahlen ermitteln (z. Bsp. Mersenne-Zahlen), muss diese aber wieder mit anderen Verfahren überprüfen. (erst die schnellen, die eine Wahrscheinlichkeit liefern, dann um sicher zu gehen das langsame Brute-Force)

Es gibt verschiedene Formeln um hohe Primzahlen zu ermitteln OHNE die gefundene Zahl durch Brute Force zu verifizieren, z.B. Mersenne-Zahlen).
Sonst wäre ja die Public Key Verschlüsselung auch ewig langsam.

Es gibt keine Formel mit der man garantiert eine Primzahl ermitteln kann. Das sieht man doch gerade an den Mersenne-Primzahlen. Für die 25 ersten Mersenne-Zahlen (also nach der Formel 2^n-1 mit n ist Primzahl) sind gerademal 10 auch Primzahlen. Der Trick ist, dass man mit ner höheren Wahrscheinlichkeit eine Primzahl findet, als durch pures Raten. Bzw. ist der Vorteil bei den Mersenne-Zahlen, dass man sie leichter auf prim testen kann.
Und das ist auch ein Erkenntniss der Primzahlforschung. Es gibt kein festes Muster, aber es gibt eine Verteilung der Primzahlen. Das heißt es gibt Formeln die öfters eine Primzahl finden als andere. Oder anders: man kann Zahlen eine Wahrscheinlichkeit zuordnen, dass sie eine Primzahl sind und diese Wahrscheinlichkeit ist nicht gleichverteilt. Bzw. glaubt man, dass sie nicht ganz zufällig verteilt sind. Ob es einen Beweis dafür gibt, oder ob man das inzwischen verworfen hat möchte ich jetz nicht beschwören... kann gut sein dass ich mich irre...

Aber es gibt keine Formel die jedesmal eine Primzahl findet. Man muss also sehr wohl die Zahl mit Brute Force (oder bei Mersenne-Zahlen mit einem eigenen Test) prüfen, egal welche Formel man hernimmt.

24.10.2006 - 18:29 Uhr

Naja kann schon verstehen warum sich kein Mathematiker großartig dafür interessiert 😉

Es ist eben kein Muster für Primzahlen, sondern ein Muster für Nichtprimzahlen. Funktioniert eigentlich ziemlich analog zum Sieb des Eratosthenes, nur graphisch etwas schöner. Das passt eben alles zusammen. 2*3 = 6, wodurch sich die Spalten ergeben und zufällig liegen links und rechts von 6 wieder Primzahlen (5 und 7), wodurch die schönen Diagonalen zustande kommen. Aber dann hörts auch schon auf mit Schönheit 😉

23.10.2006 - 22:33 Uhr

Du bestimmst die Potenzen immer neu. Du rechnest x11 aus, ich rechne einfach das x9 was ich schon kenne mal x^2 was ich schon kenne. Wozu da ne Potenzfunktion? Und genau das kannst du bei den anderen auch benutzen.

Fakultät ist ja so definiert: n! = (n-1)! * n
Genau das rechne ich.

Ich hab nur die Bedeutung von n geändert. Der Wert in n ist jetzt direkt der Exponent, statt dass 2*n + 1 der Exponent wäre wie bei deinem Code.

Gehs doch einfach mal durch.
Erster Durchlauf: facn = 1, n = 1 (ok n=1 hab ich vergessen hinzuschreiben, du initialisierst ja noch mit 0)
++n => n = 2
facn *= 2 => facn = 2
++n => n = 3
facn *= 3 => facn = 6 = 3! => richtiger Wert für den 2. Durchlauf.

++n => n = 4
facn *= 4 => facn = 24
++n => n = 5
facn *= 5 => facn = 120 = 5! => richtiger Wert für den 3. Durchlauf.

Wie gesagt ist das was du gemacht hast ja mathematisch ganz hübsch. Aber in der realen Welt braucht jeder Funktsionsaufruf Speicher. Irgendwann ist keiner mehr da und du bekommst diese Fehlermeldung. Lösung: Weniger Funktionsaufrufe. Ich seh auch echt keinen Sinn dahinter einen Funktionsaufruf, hinter dem ein umfangreicher Algorithmus steckt, einem simplen c = a*b vorzuziehen.

Wenn du nur x^n ausrechnen willst ist die Potenzfunktion gut. Wenn dich die Vorhergehenden Ergebnisse auch interessieren nicht. Stell dir einfach vor wie du das per Hand machen würdest. Du würdest mit Sicherheit nicht jede Potenz neu bestimmen. Du würdest das von der vorhergehenden Iteration hernehmen und damit das für die aktuelle Iteration berechnen.

23.10.2006 - 21:38 Uhr

Wärs nicht irgendwie cleverer die Potenzen und die Fakultät gleich mit der Schleife auszurechnen? Damit brauchst du schonmal keine Pow Funktion und die Präzesion sollte darunter nicht leiden.


potx = x;
x = x*x;
facn = 1;
sign = 1;

while(true)
{
    sin1 += sign * x / facn;
    if (n > 12 || System.Math.Abs(sinalt - sin1) < grenze)
        break;

    ++n;
    facn *= n;
    ++n;
    facn *= n;
    potx *= x;
    sign *= -1;

    sinalt = sin1;
}

Und das gleiche System bitte nochmal auf die Exp und Ln Funktion und dein Problem sollte sich erledigen. In der Mathematik Programmiersprache mag das ja ne ganz schöne Formel sein aber bei ner imperativen Programmiersprache sollte man das so nicht bringen 😉

Achja und vermeide es wenn du viel mit festen decimal werten arbeitestn (1m) Variablen kurze mit l beginnende Namen zu geben. 1m und lm sind nicht wirklich unterscheidbar und ln ist auch nicht viel besser g

(EDIT: noch nen Fehler im Code gefunden)

22.10.2006 - 21:04 Uhr

Naja dafür würde sich eigentlich eine Datenbank anbieten.

Alles andere läuft darauf hinaus, dass du etwas Datenbankähnliches nachbaust (z.B. ISAM). Wird imo zu aufwändig.

Aber 5000 oder auch 100000 Dateinamen mit nem Farbwert sind eigentlich auch nicht so massig viele Daten (geschätze 10 MB). Die könntest du auch einfach in ein Dictionary/Hashtable im Speicher halten. Oder du benutzt ein DataSet zum Arbeiten, und lässt das dann serialisieren zum Speichern. Musst ja nicht bei jeder Änderung direkt auf Platte schreiben.

Prinzipiell heißt Arbeiten mit Datein Arbeiten mit Streams. Und das heißt immer alles umwälzen um eines zu ändern.

22.10.2006 - 18:46 Uhr

Original von Seikilos
Ich würde gerne auf Serialisierung verzichten und denke dass txt(xml, plain, usw) Dateien zu langsam sind, welche Arten habe ich noch?

Serialisierung ist genau das was du machen willst. Abbilden von Objekten auf externe Darstellungsformen, oft verwendet zum persistieren (speichern) von Daten.

Die einzige Alternative die mir einfällt ist, dass du nicht die Objekte abbildest sondern nur die Daten. Wie z.B. mit ner relationalen Datenbank. Dann nennt sich das nicht mehr Serialisierung aber irgendwie erschließt sich mir der Vorteil nicht.

20.10.2006 - 19:46 Uhr

Die Nebenobjekte können aber auch einfach so mal nicht mehr gebraucht werden, weil sie ihre Funktion erfüllt haben. Und da würde dann erst gewartet werden, bis die Methode ausgelöst wird, bevor der GC sich die holen kann, weil ja im Hauptobjekt noch Referenzen darauf existieren. Es ist ja eigentlich sogar eher die Ausnahme, dass sich etwas so ändert, dass ich die Nebenobjekte abknipsen muss.

Wie gesagt, die einzige Möglichkeit die ich bei diesem Ansatz sehe ist, die Verantwortung dem Benutzer des Objekts zu übergeben. Wenn er es nicht abmeldet wird es eventuell erst viel später bis garnicht gesammelt.

Die andere Variante wäre eben eine Id mitzuführen und zu Vergleichen und die Verantwortung zum deaktivieren direkt beim Nebenobjekt lassen, statt das Hauptobjekt überhaupt erst damit zu belasten.

Und ich hab mich eigentlich gefragt ob ich vieleicht damit völlig auf dem Holzweg bin, ob es für das Problem vieleicht eine Standardlösung gibt, und wenn nichts davon welche Lösung ihr bevorzugen würdet 😁

20.10.2006 - 19:05 Uhr

Ja das ist mir schon klar. Ich will ja auch nur die Nebenobjekte vom GC sammeln lassen, das Hauptobjekt ist sehr langlebig, um nicht zu sagen es wird mit Programmstart instanziert und beim Programmende beerdigt.

Vieleicht stelle ich mir das auch einfach zu umständlich vor. Vieleicht sollte ich einfach den Zugriff auf die Liste der Nebenobjekte nach außen hin zulassen. Dann ist man einfach selbst verantwortlich sein Objekt dort zu deaktivieren, wenn man es loswerden will. Und falls man das nicht tut ist es immerhin kein Leak, man kommt noch von außen ran. Ein Memory Leak zeichnet sich ja dadurch aus, dass das Objekt im Speicher (bei OOP) nicht mehr zugreifbar ist.

20.10.2006 - 18:30 Uhr

Wie soll das denn durch Events entkoppelt werden?

Ich könnte alle Funktionen die vom Hauptobjekt benötigt werden als Delegate übergeben, aber das ändert ja am Problem nichts.

Oder ich könnte die Nebenobjekte in ein Event vom Hauptobjekt registrieren lassen, aber auch das ändert am Problem nichts, die Nebenobjekte landen trotzdem nicht beim GC.

Der Grund warum die alle ein gemeinsames Objekt kennen ist Synchronisation mehrerer Threads. Das Hauptobjekt war vorher da und hat den Zugriff auf bestimmte Resourcen threadsicher gemacht. Die Nebenobjekte sind neu und enthalten kompilierten Code der den Zugriff auf die Resourcen performanter macht. Nur wenn sich etwas am Hauptobjekt ändert, funktioniert dieser Code nicht mehr. Das ist nicht weiter tragisch, es passiert selten und es wäre kein Problem das dann zu handhaben. Aber ich muss es handhaben können, und dazu muss ich es merken.
Ein statisches Objekt zur Synchronisation ist auch keine Lösung, denn es sollen jeweils nur Gruppen untereinander synchronisiert sein und nicht immer alle.

20.10.2006 - 17:28 Uhr

Ich habe ein Hauptobjekt, dass von vielen Nebenobjekten verwendet wird.
Eigentlich reicht es aus, dass die Nebenobjekte das Hauptobjekt kennen um ihre Funktion zu erfüllen.
Dieses Hauptobjekt hat aber eine Eigenschaft, die bei Änderung alle Nebenobjekte funktionsunfähig macht.

Und damit hätte ich dann den Kreis gebaut. Das Hauptobjekt müsste sich irgendwie eine Liste der Nebenobjekte merken und diese dann benachrichtigen, dass sie nutzlos sind.

Mein Problem dabei ist der GC.
Dadurch, dass das Hauptobjekt die Nebenobjekte irgendwo gespeichert hat, werden diese nicht mehr vom GC gesammelt. Ich hätte quasi trotz GC ne Möglichkeit für Memory Leaks gefunden...
Ich könnte höchstens mit IDisposable die Verantwortung für das Memory Leak an den Benutzer der Nebenobjekte übertragen. Der Anwender muss dann dafür sorgen, dass Dispose auch aufgerufen wird, wenn er speicher freigeben will. Das läuft mir aber irgendwie gegen den Sinn einer Umgebung mit GC. Dispose sollte ja eher dazu da sein Resourcen zu einem kontrollierten Zeitpunkt freizugeben, und nicht um überhaupt Resourcen freizugeben.

WeakReferences wären auch keine Lösung die mir gefallen würde. Entweder ich hab dann den MemoryLeak eben mit WeakReferences statt mit den Nebenobjekten, oder ich müsste irgendwie regelmäßig durch die Liste gehen und schauen ob ich eine WeakReference loswerden kann. Und die Regelmäßigkeit könnte ich nur garantieren, indem ich alle Befehle das ausführen lasse (schlecht) oder einen zusätzlichen Thread beauftrage das zu tun (schlechter).

Eine Alternative wäre eine Art Identifikator (z.B. GUID), der sich immer ändert wenn die Eigenschaft sich ändert. Wenn der Identifikator in den Nebenobjekten nicht mehr mit dem des Hauptobjekts übereinstimmt, wissen diese dann dass sie entwertet wurden. Es interessiert mich ja auch nicht wie sich die Eigenschaft geändert hat, sondern nur dass er sich geändert hat. Der Overhead wäre auf jeden Fall vernachlässigbar.

Was meint ihr denn dazu? Es geht hier auch nicht hauptsächlich um Performance, sondern um ein gutes Design ohne Löcher.

17.10.2006 - 16:40 Uhr

Vieleicht irre ich mich, aber da XNA ja nichts an der CLR ändert, müsste man doch eigentlich nur die richtigen Referenzen setzen. Sprich man kompiliert mit der .NET 2.0 CLR nur eben mit extra Bibliotheken im GAC.

17.10.2006 - 14:57 Uhr

Nur um das mal zu übersetzen:
\p{Ll} "Letter, Lowecase"
\p{Lu} "Letter, Uppercase"
\p{Lt} "Letter, Titlecase"
\p{Lo} "Letter, Other"
\p{Lm} "Letter, Modifier"
\p{Nd} "Number, Decimal Digit"
\p{Pc} "Punctuation, Connector"

Also alle Arten von Buchstaben, Ziffern und "Verbinder"

Also bei normalen Strings das gleiche wie [a-zA-Z_0-9], nur bei Unicode Strings sind es noch mehr. Wird nur fälschlicherweise oft (und wohl auch hier von einigen) als Buchstabe angenommen.

17.10.2006 - 14:42 Uhr

\w ist übrigens das gleiche wie [A-Za-z0-9_]

Sprich alphanumerische Zeichen und Unterstrich