In String, Buchstaben ersetzen durch andere Buchstaben

C.B.

Aktives Mitglied
Hallo,
könnt ihr mir helfen, was ich an meinem Code ändern soll?
Ich möchte, dass die Buchstaben ausgetauscht werden z.B. A sollte mit T ausgetauscht werden, T durch A, G durch C und C durch G. Aber bei der Ausgabe erhalte ich noch die Buchstaben, die ich eingegeben habe.
Ich denke, dass Problem liegt an den typen char und string oder?

Vielen Dank.

Java:
public class ComplementaryDNA {
    
    /*DnaStrand.makeComplement("ATTGC") // return "TAACG"
     *DnaStrand.makeComplement("GTAT") // return "CATA"
     *In DNA strings, symbols "A" and "T" are complements of each other,
     * as "C" and "G". You have function with one side of the DNA
     * (string, except for Haskell); you need to get the
     * other complementary side. DNA strand is never empty or
     * there is no DNA at all (again, except for Haskell).
    /**
     *
     */
              public static String makeComplement(String dna) {     
                for (int i = 0; i< dna.length(); i++){
                 if(dna.indexOf(0) == 'A') {
                   dna.replace((char) i, 'T');
                 }
                 else if (dna.charAt(i) == 'T'){
                 dna.replace((char) i, 'A');
                 }
                 else if (dna.charAt(i) == 'C'){
                     dna.replace((char) i, 'G');
                     }
                
                 else { if (dna.charAt(i) == 'G') {
                      dna.replace((char) i, 'C');
                      }
                 else {
                     return dna;
                 }
                 }
                }
                return dna;
              }
              
                
                
public static void main(String[] args) {
String test = "ATGGC";
String result = makeComplement(test);
System.out.println(result);
    
}
}
 
String sind immutable, lassen sich also nicht ändern.

Die replace Methode ändert daher das dna Objekt nicht, sondern liefert ein neues Objekt zurück.

Außerdem sieht dein cast von i zu char falsch aus.

Bei sowas immer in die Doku schauen
Stringreplace(char oldChar, char newChar)Returns a string resulting from replacing all occurrences of oldChar in this string with newChar.

Du ersetzt den character, der durch i dargestellt wird durch das jeweilige Zeichen - nicht das Zeichen an Position i.
Dein erstes if fragt übrigens auch immer das Zeichen an position 0 ab, nicht wie du vermutlich möchtest an position i.
 
Ich würd hier aber nicht mit replace() arbeiten, du kriegst dann in jedem Schritt einen neuen String erzeugt, das ist schon ziemlicher Overkill.

Probier mal einen StringBuilder zu verwenden, in den du in der Schleife pro Schritt mit append() entweder das ersetzte Zeichen oder - falls nichts zu ersetzen ist - das originale Zeichen anhängst.
Auch kannst du deinen String etwas weniger aufwendig und leicher lesbar auf diese Weise durchlaufen (Index braucht's nicht)
Java:
    StringBuilder strb = new StringBuilder(dna.length); //gleich mal auf die richtige Länge setzen, ist aber nicht wichtig.
    for (char c : dna.toCharArray()) {
      //...
      strb.append(...);
    }
   return strb.toString();
 
Zuletzt bearbeitet:
Habe es ausprobiert aber irgendwas mache ich trotzdem falsch, es kommt immer eine Fehlermeldung. Was sollte ich ändern? Vielen Dank.

Java:
public class ComplementaryDNASwitchCase {
        
         /*DnaStrand.makeComplement("ATTGC") // return "TAACG"
         *DnaStrand.makeComplement("GTAT") // return "CATA"
         *In DNA strings, symbols "A" and "T" are complements of each other,
         * as "C" and "G". You have function with one side of the DNA
         * (string, except for Haskell); you need to get the
         * other complementary side. DNA strand is never empty or
         * there is no DNA at all (again, except for Haskell).
        /**
         *
         */
    
    static StringBuilder strb = new StringBuilder();
    
    public static StringBuilder makeComplement(String dna) {
        for (char c : dna.toCharArray()) {
            if(dna.charAt(c) == 'A') {
                strb.append('T');
            }
            if (dna.charAt(c) == 'T') {
                strb.append('A');
            }
            if (dna.charAt(c) == 'G') {
                strb.append('H');
            }
            if (dna.charAt(c) == 'H') {
                strb.append('G');
            }
        }
        return strb;   
    }
    

public static void main(String[] args) {
    String dnasequence= "ATHGA";
    
    System.out.println(makeComplement(dnasequence));

}

}
 
Kannst Du einmal erläutern, was for (char c : dna.toCharArray()) { macht?
Evtl. gibst Du einmal in der for Schleife nur c aus?

Und was macht String.charAt?

Und generell: Bitte immer die Fehlermeldung angeben, die Du bekommst.

Und Tipp: Sinnvolle Variablennamen verwenden. Das hilft oft auch, Fehler zu erkennen und zu vermeiden.
 
Vielen Dank für die Rückfragen. Habe es auf eine normale for Schleife angepasst und nun funktioniert es.

Java:
    public static StringBuilder makeComplement(String dna) {
        for (int i =0; i<dna.length(); i++) {
            if(dna.charAt(i) == 'A') {
                strb.append('T');
            }
            if (dna.charAt(i) == 'T') {
                strb.append('A');
            }
            if (dna.charAt(i) == 'G') {
                strb.append('H');
            }
            if (dna.charAt(i) == 'H') {
                strb.append('G');
            }
        }
        return strb;   
    }
    

public static void main(String[] args) {
    String dnasequence= "ATHGA";
    
    StringBuilder result = makeComplement(dnasequence);
    System.out.println(result);

}

}
 
Ich würde an deiner Stelle die ursprüngliche Methodensignatur beibehalten, also etwas in dieser Art, das ist wesentlich schöner als dieser statische StringBuilder.
Java:
public String makeComplement(String dna) {
   
    StringBuilder sb = new Stringbuilder();
   
    // ...
   
    return sb.toString(); // Ergebnis als String zurückgeben.
}

Der StringBuillder ist ja nur ein Hilfsmittel, um die Anforderung umzusetzen.
 
Ok, ich denke, dass Du es verstanden hast (Super, toll, dass Du die Lösung selbst gefunden hast mit den Hinweisen!) aber einfach noch ein paar Erläuterungen:

Bei for (char c : dna.toCharArray()) { wird in c Zeichen für Zeichen abgelegt. Ein Zeichen ist aber auch zugleich eine 16Bit Integer Zahl in Java, daher funktioniert der charAt Aufruf. Aber es werden natürlich viel zu hohe Indices aufgerufen, so dass es zu dem Fehler kommt. Da wir das Zeichen schon haben, ist natürlich auch kein Zugriff mehr auf den String möglich, um das Zeichen zu holen. Daher wäre Code wie folgt denkbar:
Java:
    public static StringBuilder makeComplement(String dna) {
        for (char c : dna.toCharArray()) {
            if(c == 'A') {
                strb.append('T');
            }
            if (c == 'T') {
                strb.append('A');
            }
            if (c == 'G') {
                strb.append('H');
            }
            if (c == 'H') {
                strb.append('G');
            }
        }
        return strb;   
    }

Und ich kann auch nur @temi Recht geben - der StringBuilder ist ja nur in der Methode ein Hilfsmittel, daher gehört die Variable als lokale Variable in die Methode und es wird ein String zurück gegeben. (Wenn Du die Methode zwei mal aufrufst, hast Du ja sonst bei dem zweiten Aufruf auch noch das erste Ergebnis mit drin...)

Damit sähe die Lösung dann so aus:
Java:
    public static String makeComplement(String dna) {
        StringBuilder strb = new StringBuilder();
        for (char c : dna.toCharArray()) {
            if(c == 'A') {
                strb.append('T');
            }
            if (c == 'T') {
                strb.append('A');
            }
            if (c == 'G') {
                strb.append('H');
            }
            if (c == 'H') {
                strb.append('G');
            }
        }
        return strb.toString();   
    }
 
Damit sähe die Lösung dann so aus:
Java:
    public static String makeComplement(String dna) {
        StringBuilder strb = new StringBuilder();
        for (char c : dna.toCharArray()) {
            if(c == 'A') {
                strb.append('T');
            }
            if (c == 'T') {
                strb.append('A');
            }
            if (c == 'G') {
                strb.append('H');
            }
            if (c == 'H') {
                strb.append('G');
            }
        }
        return strb.toString(); 
    }
fast
Du hast den Default-Wert nicht berücksichtigt.
Java:
 public static String makeComplement(String dna) {
        StringBuilder strb = new StringBuilder();
        for (char c : dna.toCharArray()) {
            if(c == 'A') {
                strb.append('T');
            } else if (c == 'T') {
                strb.append('A');
            } else if (c == 'G') {
                strb.append('H');
            } else if (c == 'H') {
                strb.append('G');
            } else {
                strb.append(c);
            }
        }
        return strb.toString();
    }
 
@ OP
Eine Alternative zu vielen if-Anweisungen wäre in diesem Fall eine switch/case Anweisung.
Java:
 public static String makeComplement(String dna) {
  StringBuilder strb = new StringBuilder();
  for (char c : dna.toCharArray()) {
    switch (c) {
       case 'A': {
         strb.append('T');
         break;
       }
       case 'T': {
         strb.append('A');
         break;
       }
       case 'G':{
         strb.append('H');
         break;
       }
       case 'H': {
         strb.append('G');
         break;
       }
       default: {
         strb.append(c);
       }
     }
   }
   return strb.toString();
}
Eine flexible Lösung ohne hardcodierte Regel verwendet eine von außen übergebene Map.
Java:
main(...) {
    String text = ...;
    Map<Character, Character> charMap = new HashMap<>();
    charMap.put('T', 'A');
    charMap.put('A', 'T');
    charMap.put('G', 'H');
    charMap.put('H', 'G');
    System.out.println(replaceCharacters(text, charMap));
}

public static String replaceCharacters(String dna, Map<Character,Character> replacementMap) {
    StringBuilder strb = new StringBuilder();
    for (char c : dna.toCharArray()) {
        strb.append(replacementMap.getOrDefault(c, c));
    }
    return strb.toString();
}
 
Zuletzt bearbeitet:
fast
Du hast den Default-Wert nicht berücksichtigt.
Öhm, wozu den default Wert? Bei der DNA ist doch genau das so vorgegeben. Sobald andere Werte vorkommen, wäre eher ein InvalidStateException zu werfen, oder? Da müsste man dann mal einen Biologen fragen ...

Ansonsten ist das natürlich ein typischer Fall für ein Switch oder Map. Ja nach Anforderung kann man sich sogar eine enum überlegen:
(Und woher kommt das H? Cytosin (C) ist das 4te Element ...)
Java:
import java.util.List;

public enum Nukleotid {
    /**
     * Purin: Adenin
     */
    A("T"),

    /**
     * Pyrimidin: Thymin
     */
    T("A"),

    /**
     * Pyrimidin: Cytosin
     */
    C("G"),

    /**
     * Purin: Guanin
     */
    G("C");

    /**
     * Compoisite Element.
     */
    private Nukleotid composite;

    /**
     * Gets the composite nukleotid.
     * @return Composite nukleotid.
     */
    public Nukleotid getComposite() {
        return composite;
    }

    /**
     * Creates a new instance of Nukleotid.
     * @param compositeName Name of composite nukleotid.
     */
    Nukleotid(String compositeName) {
        this.composite = Nukleotid.valueOf(compositeName);
    }

    /**
     * Creates a List of Nukleotid of a String with nukleotid symbols.
     * @param dna String with DNA (nukleotid symbols)
     * @return List of Nukleotids.
     */
    public static List<Nukleotid> createNukleotidListOf(final String dna) {
        return dna.chars()
                .mapToObj(ch -> Nukleotid.valueOf(""+ch))
                .toList();
    }
}
Das hat aus meiner Sicht den Vorteil, dass man da schon einen ersten guten Punkt hat, um Logik zu sammeln (habe da als Beispiel einfach mal ein createNulleotidListOf eingebaut).
Das verhindert dann zum einen eine Sammlung von "Utility" Klassen / Methoden und zum anderen haben wir eine saubere Typisierung statt mit Strings und Zeichen herum zu hantieren.

Edit: @C.B. Das ist jetzt etwas abgedriftet. Das ist nicht mehr als "Musterlösung" oder ähnliches zu verstehen. Der Hinweis mit dem switch von @Neumi5694 solltest Du Dir in Ruhe ansehen. Die Lösung mit der Map von Ihm und die Lösung mit Enum geht da aber schon zu weit. Die Map Lösung wird interessant, wenn Du zu abstrakten Datentypen kommst (List, Set, Map, ...) aber ich vermute, dass Du noch nicht so weit sein dürftest.
 
Öhm, wozu den default Wert? Bei der DNA ist doch genau das so vorgegeben. Sobald andere Werte vorkommen, wäre eher ein InvalidStateException zu werfen, oder? Da müsste man dann mal einen Biologen fragen ...
Gut, ok. DNA spezifisch ergibt das Sinn. Da hab ich die Anwendung aus den Augen verloren. Auch die Map ist in so einem spezifischen Fall wohl etwas übertrieben.

Mir rumort's nur in der Magengegend, wenn bei einer Methode, deren Sinn und Zweck es ist, einzelne Buchstaben in einem String auszutauschen danach ein String diverser Länge rauskommen kann. Die Exception wäre mir auf jeden Fall lieber 🙂
 
Ich versuche mich gerade an einer weiteren Aufgabe und möchte nun wieder den Stringbuilder verwenden.
Leider bekomme ich auch dieses Mal eine Fehlermeldung. Ich möchte einen verschlüsselten Text, entschlüsseln. Es sollen alle Kleinbuchstaben um 3 Stellen weiter nach hinten im Alphabet geschoben werden um den text zu entschlüsseln. Was ist dieses Mal der Fehler?

Java:
public class Ceasar2 {
    
    
    public static String entschlüssle(String text) {
        StringBuilder abc = new StringBuilder("abcdefghijklmnopqrstuvwxyz");
        StringBuilder entschlüsselterText = new StringBuilder();
        
        for (int i =0; i<text.length(); i++) {
            if(text.charAt(i) == abc.charAt(i)) {
                entschlüsselterText.append(abc.charAt(i+3));
            }
        }
        return entschlüsselterText.toString();
        }
    
    
    
    public static void main(String[] args) {
        // TODO Auto-generated method stub

        String intialText= "ihjsdfsdhfjdashfkjsdfjkdshfjdhfdl";
        
        String resultEntschlüsselung = entschlüssle(intialText);
        System.out.println(resultEntschlüsselung);
        
    }

}
 
Es erscheint diese Fehlermeldung:
Exception in thread "main" java.lang.StringIndexOutOfBoundsException: index 26,length 26


at java.base/java.lang.String.checkIndex(String.java:3066)


at java.base/java.lang.AbstractStringBuilder.charAt(AbstractStringBuilder.java:281)


at java.base/java.lang.StringBuilder.charAt(StringBuilder.java:77)


at Ceasar2.entschlüssle(Ceasar2.java:10)


at Ceasar2.main(Ceasar2.java:22)
 
Also der Algorithmus ist schon falsch von der Idee her. Da würde ich Dir raten, Dir das erst einmal richtig mit Stift und Papier vorzustellen.
Und dann musst du die Schritte so beschreiben, dass jemand, der nicht weiss was er da machen soll, zum richtigen Ergebnis kommt.

Und wegen der Exception: zu welchem Buchstaben wird denn das z? Position vom z + 3 ist dann welches Zeichen?
 
Exception in thread "main" java.lang.StringIndexOutOfBoundsException: index 26,length 26
Die Meldung ist ja ziemlich eindeutig (und wird dir so ähnlich vermutlich noch oft begegnen, denn das ist ein häufiger Fehler). Die Länge des Arrays ist 26. Da der Index bei 0 beginnt, ist der höchste gültige Wert 25.
 
Vielen Dank für die Hinweise. Habe es nun anders probiert. Der Code funktioniert aber ich bekomme nicht, dass Ergebnis, das ich gerne hätte.

Die Textangabe lauetet "olhehnxuvwhloqhkphu"
Dieser Text ist verschlüsselt und sollte als Ergebnis liefern "liebekursteilnehmer"

Leider erhalte ich als Ergebnis "lokhkqaxyzkortknskx". Ich denke es liegt an der ASCII Tabelle oder? Ich möchte nur Kleinbuchstaben betrachten, keine Großbuchstaben oder Sonderzeichen. Dann sind die -65 wohl falsch in dem Code?


Java:
public class Ceasar2 {
    
    
    public static String verschlüssle(String text, int verschiebung) {
        StringBuilder verschlüsselterText = new StringBuilder();
            text.toLowerCase();
            for( int i = 0; i < text.length(); ++i ){
                verschlüsselterText.append((char)((text.charAt( i ) - 65 + verschiebung) % 26 + 65 ));
            }
            return verschlüsselterText.toString();
    }
            
    public static String decode(String text, int verschiebung)
    {
            return verschlüssle(text, -verschiebung).toLowerCase();
    }
    
    public static void main(String[] args) {
        // TODO Auto-generated method stub

        String initialText = "Olhehnxuvwhloqhkphu";
        String resultEntschlüsselung = decode(initialText,3);
        System.out.println(resultEntschlüsselung);
                
    }

}
 
Wie wird die Methode aufgerufen? Rufst du verschlüssle oder decode auf? (ps - wenn sollte man sich für eine Schreibweise - deutsch oder englisch entscheiden)
Mit welchem Wert für verschiebung rufst du die auf?
 
Dann schau Dir doch einfach einmal die ASCII-Tabelle an:

Die 65 ist das 'A' (Du solltest es auch lesbar halten - Du kannst da statt der 65 auch direkt 'A' schreiben!) Das kleine a ist die 97.

Wenn Du es alles nur in Kleinbuchstaben haben willst, dann wandel den Ursprünglichen String erst ein kleinbuchstaben und dann dekodiere. Und natürlich 'a' statt 'A' nehmen!
 
Drei Tips:
1. Schreib mal eine Methode, die genau ein Zeichen decodiert und nicht gleich einen ganzen String. Dann teste die als erstes. Schau, ob sie das macht, was du erwartest. Sobald sie ihr Soll erfüllt, verwende diese Methode in der Schleife.
2. Da du sequentiell alle Zeichen des Strings abarbeitest, verwende eine for-each Schleife. Dadurch wird dein Code leichter lesbar, du brauchst hier wirklich keinen Index
3. Verwende niemals Umlaute im Code. Spätestens, wenn du den Code mal in einem anderen Zeichensatz abspeichern musst(verwendest du ASCII, ANSI oder UTF8?), dann wirst du verstehen, warum.
 
Nur um das, was ich als Fehler angerissen habe, an einem Beispiel zu zeigen:
'a' soll verschlüsselt werden:
'a' - 'A' ist 97-65 = 32.

Mit Modulo 26 und + 'A' hast Du daraus also schon - ohne jede Verschlüsselung - 'A'+ 6 ('G') gemacht.
 
Hab noch weiter bisschen rumgespielt. Und bei diesem verschlüsselten Code "uhfkwkhucolfkhqjoxhfnzxqvfkcxuhuirojuhlfkhqhqwvfkoxhvvhoxqj"
erhalte ich diese Ausgabe auf der Konsole "rechther`lichenglueckwunsch`urerfolgreichenentschluesselung"

Die Ausgabe enthält ein Apostroph. Die einzelnen Buchstaben funktionieren aber. Woran kann das liegen?

Vielen Dank.



Java:
public class Ceasar2 {
    
    
    public static String verschlüssle(String text, int verschiebung) {
        StringBuilder verschlüsselterText = new StringBuilder();
            text.toLowerCase();
            for( int i = 0; i < text.length(); ++i ){
                verschlüsselterText.append((char)((text.charAt( i ) - 'a' + verschiebung) % 26 +'a' )); //anstatt klein 'a' kann auch die Zahl 97 genommen werden. Steht in der ASCII Tabelle für klein a. Groß A ist 65
            }
            return verschlüsselterText.toString();
    }
            
    public static String decode(String text, int verschiebung)
    {
            return verschlüssle(text, -verschiebung).toLowerCase();
    }
    
    public static void main(String[] args) {
        // TODO Auto-generated method stub

        String initialText = "uhfkwkhucolfkhqjoxhfnzxqvfkcxuhuirojuhlfkhqhqwvfkoxhvvhoxqj";
        String resultEntschlüsselung = decode(initialText,3);
        System.out.println(resultEntschlüsselung);
 
Was ist denn -1 % 26?
Wo in der ASCII Tabelle ist das `?

Mit den Antworten auf die beiden Fragen solltest du das Problem beheben können.
 

Zurück
Oben