Как мы можем создать алгоритм, чтобы проверить, является ли строка результатом двух объединенных строк? - PullRequest
2 голосов
/ 22 февраля 2020

Я выполняю следующее упражнение по программированию: Проверка объединенной строки

1) Я попробовал следующий код:

import java.util.*;

public class StringMerger {
  public static boolean isMerge(String s, String part1, String part2) {
    System.out.println("\n\ns: "+s);
    System.out.println("part1: "+part1);
    System.out.println("part2: "+part2);
    if(!s.isEmpty() && part1.isEmpty() && part2.isEmpty()) return false;
    if( ( part1==null || part1.isEmpty() && part2.equals(s) ) || part2==null || part2.isEmpty() && part1.equals(s) ){
      return true;
    }

    /*Check if complete words from s are in part1 or part2*/

    List<String> sList = new ArrayList(Arrays.asList(s.split(" ")));
    List<String> part1List = new ArrayList(Arrays.asList(part1.split(" ")));
    List<String> part2List = new ArrayList(Arrays.asList(part2.split(" ")));
    System.out.println("sList: "+Arrays.toString(sList.toArray()));
    System.out.println("part1List: "+Arrays.toString(part1List.toArray()));
    System.out.println("part2List: "+Arrays.toString(part2List.toArray()));

    for(Iterator<String> it = sList.iterator(); it.hasNext(); ){
      String word = it.next();
      if(word.equals(part1List.get(0))){
        it.remove();
        part1List.remove(0);
        System.out.println("sList: "+Arrays.toString(sList.toArray()));
        System.out.println("part1List: "+Arrays.toString(part1List.toArray()));
      }else if(word.equals(part2List.get(0))){
        it.remove();
        part2List.remove(0);
        System.out.println("sList: "+Arrays.toString(sList.toArray()));
        System.out.println("part2List: "+Arrays.toString(part2List.toArray()));        
      }
    }

    s=String.join(" ",sList);
    part1=String.join(" ",part1List);
    part2=String.join(" ",part2List);
    System.out.println("\n\ns: "+s);
    System.out.println("part1: "+part1);
    System.out.println("part2: "+part2);

    /*Check if s first character is part1 or part2 initial character*/

    for(char letter : s.toCharArray()){
      System.out.println("letter: "+letter);
      System.out.println("part1: "+part1);
      System.out.println("part2: "+part2);

      if(!part1.isEmpty() && letter == part1.charAt(0)){
        part1 = part1.substring(1);
        System.out.println("part1: "+part1);
        s = s.substring(1);
      }else if(!part2.isEmpty() && letter==part2.charAt(0)){
        part2 = part2.substring(1);
        System.out.println("part2: "+part2);
        s = s.substring(1);
      }
      System.out.println("s: "+s);

      System.out.println("s.substring(0,part1.length()): "+s.substring(0,part1.length()));

      if(s.substring(0,part1.length()).equals(part1)){
        s=s.substring(part1.length());
        part1="";
        System.out.println("are equal, s: "+s);
      }else if(s.substring(0,part2.length()).equals(part2)){
        s=s.substring(part2.length());
        part2="";
        System.out.println("are equal, s: "+s);
      }

      if(s.isEmpty() || (part1.length()==0 && part2.length()==0) ) break;
    }
    System.out.println("\n\ns: "+s);
    System.out.println("part1: "+part1);
    System.out.println("part2: "+part2);
    return s.isEmpty() && part1.isEmpty() && part2.isEmpty();
  }
}

И я бы хотел, чтобы вы объясните: почему он не проходит следующий тест?

import org.junit.Test;
import static org.junit.Assert.*;

public class StringMergerTest {

  @Test
  public void suffledPartsLetters(){
    assertTrue("",StringMerger.isMerge("Can we merge it? Yes, we can!","nwe me?s, e cn","Ca erg it Yewa!"));
  }

}

Я обнаружил в следе, где он ведет себя неожиданно:

letter: **r**
part1: ?s, e cn
part2: e**r**g it Yewa!
s: rge it? Yes, we can!
s.substring(0,part1.length()): rge it? 

letter: **g**
part1: ?s, e cn
part2: er**g** it Yewa!
s: rge it? Yes, we can!
s.substring(0,part1.length()): rge it? 

Я понимаю, что буквы r и g не обнаруживаются потому что код просто проверяет, является ли он первым символом в части 1 или части 2.

Однако я не до конца понимаю, как мы можем исправить предыдущий код, чтобы он мог обрабатывать этот случай, не могли бы вы помочь мне, пожалуйста?

Кроме того, я также исследовал и нашел этот пост, в котором описаны некоторые упражнения 'javascript решения:

CodeWars / Merged String Checker

Я пытался написать рекурсивный, не глядя на решение, и я пришел к выводу:

public class StringMerger {
  public static boolean isMerge(String s, String part1, String part2) {
    System.out.println("\n\ns: "+s);
    System.out.println("part1: "+part1);
    System.out.println("part2: "+part2);

    if(s.length()!= (part1.length()+part2.length()) ){
      System.out.println("lengths are different");
      return false;
    }
    if(s.length()==0) {
      System.out.println("s.length is 0");
      return true;
    }
    if(part1.length()>0 && s.charAt(0)==part1.charAt(0)){
      System.out.println("s first char is part1 first char");
      isMerge(s.substring(1),part1.substring(1),part2);
    }
    if(part2.length()>0 && s.charAt(0)==part2.charAt(0)){
      System.out.println("s first char is part2 first char");
      isMerge(s.substring(1),part1,part2.substring(1));
    }
    return false;
  }
}

Почему предыдущий провалил следующие тесты?

import org.junit.Test;
import static org.junit.Assert.*;

public class StringMergerTest {

  @Test
  public void normalHappyFlow() {
    assertTrue("codewars can be created from code and wars", StringMerger.isMerge("codewars", "code", "wars"));
    assertTrue("codewars can be created from cdwr and oeas", StringMerger.isMerge("codewars", "cdwr", "oeas"));
    assertFalse("Codewars are not codwars", StringMerger.isMerge("codewars", "cod", "wars"));
  }

  @Test
  public void suffledPartsLetters(){
    assertTrue("",StringMerger.isMerge("Can we merge it? Yes, we can!","nwe me?s, e cn","Ca erg it Yewa!"));
  }

}

Я ожидал, что когда все буквы подобраны с буквами part1 или part2, и s пусто с длиной 0, он вывел бы true.

Однако он выдает false, даже когда обнаруживает s.length = 0.

Трассировка:

s: codewars
part1: code
part2: wars
s first char is part1 first char


s: odewars
part1: ode
part2: wars
s first char is part1 first char


s: dewars
part1: de
part2: wars
s first char is part1 first char


s: ewars
part1: e
part2: wars
s first char is part1 first char


s: wars
part1: 
part2: wars
s first char is part2 first char


s: ars
part1: 
part2: ars
s first char is part2 first char


s: rs
part1: 
part2: rs
s first char is part2 first char


s: s
part1: 
part2: s
s first char is part2 first char


s: 
part1: 
part2: 
s.length is 0

Как мы могли бы исправить предыдущий код? И почему он не проходит тесты?

Я также прочитал:

Лучший способ преобразовать ArrayList в строку ConcurrentModificationException для ArrayList java: удалить слова из ArrayList Удаление элементов из списка Преобразование массива в список в Java Проверка наличия строки пусто или равно нулю в Java

Ответы [ 6 ]

4 голосов
/ 22 февраля 2020

Рассмотрим случай ниже:

S = eefe
    ^

с A = e и B = eef Вы не можете взять первое e с A, потому что результирующая подстрока будет efe и B не может соответствовать efe.

Так что в случае двусмысленности вы должны исследовать два условия: A взять или B взять?

рекурсия быть:

// return true if A and B can match S, false otherwise
bool AOrB(s, iS, iA, iB) {
  if (iS > s.length) {
    // we consumed all chars in S: SUCCESS
    return true
  }

  a = A[iA]
  b = B[iB]
  s = S[iS]

  // consider all possibilities...
  if (a == s) {
    if (b == s) {
      // we need to explore both cases
      return AOrB(s, iS+1, iA+1, iB) || AOrB(s, iS+1, iA, iB+1)
    } else {
      // only A is candidate!
      return AOrB(s, iS+1, iA+1, iB)
    }
  } else {
    if (b == s) {
      // only B is candidate
      return AOrB(s, iS+1, iA, iB+1)
    } else {
      // no one matches s
      return false
    }
  }
}

Это можно упростить до

AOrB(s, iS, iA, iB) {
  if (iS > s.length) {
    return true
  }

  a = A[iA]
  b = B[iB]
  s = S[iS]

  // consider all possibilities...
  bool hasSolution = false
  if (a == s) {
    hasSolution ||= AOrB(s, iS+1, iA+1, iB)
  }
  if (b == s) {
    hasSolution ||= AOrB(s, iS+1, iA, iB+1)
  }
  return hasSolution
}

, что эквивалентно

AOrB(s, iS, iA, iB) {
  if (iS > s.length) {
    return true
  }

  a = A[iA]
  b = B[iB]
  s = S[iS]

  return a == s && AOrB(s, iS+1, iA+1, iB) || b == s && AOrB(s, iS+1, iA, iB+1)
}

Наконец, вы можете выбрать динамический c маршрут подхода :

  • Вы строите кандидатов, начиная с S[0] (таким образом, 0 кандидатов, если ни A, ни B не соответствуют S [0], 1, если только A или B соответствуют, или 2 кандидата, если оба совпадают)
  • Затем вы используете каждого из этих кандидатов в качестве отправной точки для s [1] и т. Д.
dpAOrB (S) {
  // a candidate is a couple { iA, iB } where iA is the next char to be matched by A
  // initially you only have one candidate: the couple { iA: 0, iB: 0 }
  candidates = new Set({ iA: 0, iB: 0 })
  foreach(s of S) {

    nextCandidates = new Set()
    foreach (candidate of candidates) {

      if(A[candidate.iA] == s) {
        nextCandidates.push({
          iA: iA + 1, // A can take, that's a candidate
          iB: candidate.iB
        })
      }

      if(B[candidate.iB] == s) {
        nextCandidates.push({
          iA: iA,
          iB: candidate.iB + 1
        })
      }
    }
    // if we could not generate any candidate, we can't match S
    if (nextCandidates.empty () {
      return false
    }
    candidates = nextCandidates
  }
  // we consumed all chars of S!
  return true
}

Ниже приведена лишь небольшая демонстрация, чтобы показать, что "это работает"

function dpAOrB (S, A, B) {
  let candidates = [{ iA: 0, iB: 0 }]
  return S.split('').every(s => {

    const nextCandidates = []
    candidates.forEach(({ iA, iB }) => {
      A[iA] === s && nextCandidates.push({ iA: iA + 1, iB })
      B[iB] === s && nextCandidates.push({ iA, iB: iB + 1 })
    })
    candidates = nextCandidates
    return nextCandidates.length
  })
}
console.log(dpAOrB('Can we merge it? Yes, we can!', 'nwe me?s, e cn', 'Ca erg it Yewa!'))
console.log(dpAOrB("codewars", "code", "wars"))
console.log(dpAOrB("codewars", "cdwr", "oeas"))
console.log(dpAOrB("codewars", "cod", "wars"))
console.log(dpAOrB("a ttest", "a tt", "tes")) // thx Turo

Улучшение: без дублирования

Наконец, как показывает код Туро

Мы можем заметить, что у нас могут быть дубликаты кандидатов .

Рассмотрим S = 'aaaabc', A='aab', B='aac'. После употребления 'aa':

  candidates [
    { iA: 2, iB: 0 },
    { iA: 1, iB: 1 },
    { iA: 1, iB: 1 },
    { iA: 0, iB: 2 }
  ]

Здесь мы приняли в порядке AA, AB, BA, BB. Однако AB и BA оба приводят к кандидату { iA: 1, iB: 1 }

Таким образом, мы можем уменьшить исследуемое состояние пространства, рассматривая ключ ha sh iA+''+iB и избегая дублирования.

function dpAOrB (S, A, B) {
  let candidates = new Map([[0+'_'+0, { iA: 0, iB: 0 }]])
  return S.split('').every(s => {

    const nextCandidates = new Map()
    ;[...candidates].forEach(([_, { iA, iB }]) => {
      A[iA] === s && nextCandidates.set([iA+1, iB].join('_'), { iA: iA + 1, iB })
      B[iB] === s && nextCandidates.set([iA, iB+1].join('_'), { iA, iB: iB + 1 })
    })
    candidates = nextCandidates
    // notice only one { iA: 1, iB: 1 }
    console.log('candidates', [...candidates.values()])
    return nextCandidates.size
  })
}
console.log(dpAOrB("aaaa", "aab", "aac"))
1 голос
/ 22 февраля 2020

Вы забыли некоторые возвраты при рекурсивных вызовах isMerge-вызовов, поэтому вы в конечном итоге возвращаете false в нижней части.

if (isMerge(...)) {
     return true;
}

РЕДАКТИРОВАТЬ: забыли проверить другой способ, если первый отказывает

И, для забавы, здесь классический (может быть, уже исторический c) подход сделать это без рекурсии (если в ваших штатах могут быть циклы бей, вам понадобится Set<State> closed, чтобы проверить это ):

public class StringMerger2 {

    private class State {
        String current;
        String left;
        String right;

        public State(String current, String left, String right) {
            super();
            this.current = current;
            this.left = left;
            this.right = right;
        }

    }

    private Queue<State> open = new LinkedList<>();

    private String target;

    public StringMerger2(String target, String part1, String part2) {
        super();
        this.target = target;

        open.add(new State("", part1, part2));
    }

    public boolean isMerge() {
        while (!open.isEmpty()) {
            State state = open.poll();
            System.out.println(state.current + ":" + state.left + ":" + state.right);
            if (state.current.equals(target)) {
                return true;
            }
            int pos = state.current.length();
            if (pos == target.length()) { // for safety reasons, one should never end here
                return false;
            }
            if (state.left.startsWith(target.substring(pos, pos + 1))) {
                open.add(new State(state.current + state.left.charAt(0), state.left.substring(1), state.right));
            }
            if (state.right.startsWith(target.substring(pos, pos + 1))) {
                open.add(new State(state.current + state.right.charAt(0), state.left, state.right.substring(1)));
            }

        }
        return false;
    }

    public static void main(String[] args) {
        System.out.println(new StringMerger2("a ttest", "a tt", "tes").isMerge());
        System.out.println(
                new StringMerger2("Can we merge it? Yes, we can!", "nwe me?s, e cn", "Ca erg it Yewa!").isMerge());
        System.out.println(new StringMerger2("codewars", "code", "wars").isMerge());
        System.out.println(new StringMerger2("codewars", "cdwr", "oeas").isMerge());
        System.out.println(new StringMerger2("codewars", "cod", "wars").isMerge());
        System.out.println(new StringMerger2("a ttest", "a tt", "tes").isMerge());
        System.out.println(new StringMerger2("a ttest", " tta", "tes").isMerge());

    }
}
0 голосов
/ 22 февраля 2020

Это сложно, когда part1 и part2 имеют одинаковые символы с определенным индексом. Мы не можем гарантировать, какой из них будет соответствовать позже. Таким образом, это похоже на двоичное дерево, где у нас есть 2 варианта на каждом этапе в случае с cla sh.

. Единственный способ выяснить это - изучить оба варианта. Таким образом, вы создаете очередь, которая содержит целочисленный массив размером 2. Первый индекс перемещает указатель part1, а второй индекс перемещает указатель part2 в случае совпадения. Если мы достигнем стадии, когда оба полностью достигли своей длины, и если текущий символ итерации в строке также является последним, мы возвращаем true, иначе мы возвращаем false.

Обратите внимание, что также могут быть случаи, когда текущий символ в итерации не соответствует никому из очереди. В этом случае мы также возвращаем false, так как мы ищем полное совпадение . В следующем коде это учитывается переменной took.

Фрагмент:

import java.util.*;
public class StringMerger {
    public static boolean isMerge(String s, String part1, String part2) {  
      if(s.length() == 0){
        if(part1.length() == 0 && part2.length() == 0) return true;
        return false;
      }
      Queue<int[]> q = new LinkedList<int[]>();
      q.offer(new int[]{0,0});
      for(int i=0;i<s.length();++i){
          int size = q.size();  
          boolean took = false;
          for(int j=0;j<size;++j){
            int[] t = q.poll();            
            if(t[0] < part1.length() && s.charAt(i) == part1.charAt(t[0])){
              if(t[0] + 1 == part1.length() && t[1] == part2.length() && i == s.length() - 1) return true;
              took = true;
              q.offer(new int[]{t[0] + 1,t[1]});
            }

            if(t[1] < part2.length() && s.charAt(i) == part2.charAt(t[1])){
                if(t[1] + 1 == part2.length() && t[0] == part1.length() && i == s.length() - 1) return true;
                took = true;
                q.offer(new int[]{t[0],t[1] + 1});
            }            
          }

          if(took == false) return false;
      }

      return false;
    }
}
0 голосов
/ 22 февраля 2020

вдохновленный ответом Оливье, тесты Гродзи и Туро
(не уважает порядок)

public class StringMerger {

    final static class MergePart {

        private String str;

        public MergePart(String str) {
            this.str = str;
        }
        private void removeCharFromStr(int index) {
            str = new StringBuilder(str).deleteCharAt(index).toString();
        }
        public boolean isComplete() {
            return str.length() == 0;
        }
        public boolean compare(char c) {
            if (isComplete()) return false;
            int index = str.indexOf(c);
            if (index < 0) return false;
            removeCharFromStr(index);
            return true;
        }
    }

    static boolean isMerge(String s, String part1, String part2) {
        MergePart m1 = new MergePart(part1);
        MergePart m2 = new MergePart(part2);
        for(char c : s.toCharArray())
        {
            if (m1.compare(c)) continue;
            if (m2.compare(c)) continue;
            return false;
        }
        return m1.isComplete() && m2.isComplete();
    }

    public static void main(String []args) {
        System.out.println(isMerge("Can we merge it? Yes, we can!", "nwe me?s, e cn", "Ca erg it Yewa!")); // true
        System.out.println(isMerge("codewars", "code", "wars")); // true
        System.out.println(isMerge("codewars", "cdwr", "oeas")); // true
        System.out.println(isMerge("codewars", "cod", "wars")); // false
        System.out.println(isMerge("a ttest", "a tt", "tes")); // true
    }
}
0 голосов
/ 22 февраля 2020

Основываясь на ответе @Olivier и не глядя на него во время повторения упражнения с codewars, мы могли бы также написать:

public class StringMerger {
    public static boolean isMerge/*??*/(String s, String part1, String part2) {
      System.out.println("\n\ns: "+s);
      System.out.println("part1: "+part1);
      System.out.println("part2: "+part2);
      if(s.isEmpty() && part1.isEmpty() && part2.isEmpty()) return true;
      if(part1.equals(part2)) return false;
      int pointer1 = 0, pointer2 = 0;
      for(char letter : s.toCharArray()){
        if(pointer1 < part1.length() && part1.charAt(pointer1)==letter){
          pointer1++;
        }
        if(pointer2 < part2.length() && part2.charAt(pointer2)==letter){
          pointer2++;
        }
      }
      System.out.println("pointer1: "+pointer1);
      System.out.println("pointer2: "+pointer2);
      return s.length()==pointer1+pointer2 && pointer1==part1.length() && pointer2==part2.length();
    }
}

Где мы просто посчитаем буквы в исходной строке s, которую можно найти в part1 или part2, и затем мы проверяем, равно ли это число длине s.

След может быть:

s: codewars
part1: code
part2: code


s: More progress
part1: More ess
part2: pro
pointer1: 8
pointer2: 3
0 голосов
/ 22 февраля 2020

Ваш код слишком сложен. Вот способ сделать это:

public class StringMerger
{
    static boolean isMerge(String s, String part1, String part2)
    {
        int len1 = part1.length();
        int len2 = part2.length();
        int i1 = 0;
        int i2 = 0;
        for(char c : s.toCharArray())
        {
            if(i1<len1 && c==part1.charAt(i1))
                i1++;
            else if(i2<len2 && c==part2.charAt(i2))
                i2++;
            else
                return false;
        }
        return i1==len1 && i2==len2;
     }

     public static void main(String []args)
     {
         System.out.println(isMerge("codewars", "cdw", "oears"));
     }
}

РЕДАКТИРОВАТЬ: как указывал Туро, он работает только при условии, что part1 и part2 не имеют общих букв.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...