2015-10-31 31 views
12

文字列をトークンに分割します。文字列を複数の区切り文字で分割します。

私は別のスタックオーバーフローの質問 - Equivalent to StringTokenizer with multiple characters delimitersをリッピングしましたが、これは文字列メソッド(.equals()、.startsWith()などでのみ実行できるかどうかを知りたいです。私は、RegEx、StringTokenizerクラス、Patterns、Matchers、またはString以外のものは使用しません。

例えば、これは私が法に

String[] delimiters = {" ", "==", "=", "+", "+=", "++", "-", "-=", "--", "/", "/=", "*", "*=", "(", ")", ";", "/**", "*/", "\t", "\n"}; 
     String splitString[] = tokenizer(contents, delimiters); 

を呼び出し、これは私が他の質問(私はこれを行うにはしたくない)のリッピングコードでする方法です。

private String[] tokenizer(String string, String[] delimiters) { 
     // First, create a regular expression that matches the union of the 
     // delimiters 
     // Be aware that, in case of delimiters containing others (example && 
     // and &), 
     // the longer may be before the shorter (&& should be before &) or the 
     // regexpr 
     // parser will recognize && as two &. 
     Arrays.sort(delimiters, new Comparator<String>() { 
      @Override 
      public int compare(String o1, String o2) { 
       return -o1.compareTo(o2); 
      } 
     }); 
     // Build a string that will contain the regular expression 
     StringBuilder regexpr = new StringBuilder(); 
     regexpr.append('('); 
     for (String delim : delimiters) { // For each delimiter 
      if (regexpr.length() != 1) 
       regexpr.append('|'); // Add union separator if needed 
      for (int i = 0; i < delim.length(); i++) { 
       // Add an escape character if the character is a regexp reserved 
       // char 
       regexpr.append('\\'); 
       regexpr.append(delim.charAt(i)); 
      } 
     } 
     regexpr.append(')'); // Close the union 
     Pattern p = Pattern.compile(regexpr.toString()); 

     // Now, search for the tokens 
     List<String> res = new ArrayList<String>(); 
     Matcher m = p.matcher(string); 
     int pos = 0; 
     while (m.find()) { // While there's a delimiter in the string 
      if (pos != m.start()) { 
       // If there's something between the current and the previous 
       // delimiter 
       // Add it to the tokens list 
       res.add(string.substring(pos, m.start())); 
      } 
      res.add(m.group()); // add the delimiter 
      pos = m.end(); // Remember end of delimiter 
     } 
     if (pos != string.length()) { 
      // If it remains some characters in the string after last delimiter 
      // Add this to the token list 
      res.add(string.substring(pos)); 
     } 
     // Return the result 
     return res.toArray(new String[res.size()]); 
    } 
    public static String[] clean(final String[] v) { 
     List<String> list = new ArrayList<String>(Arrays.asList(v)); 
     list.removeAll(Collections.singleton(" ")); 
     return list.toArray(new String[list.size()]); 
    } 

編集:私はONLYのcharAt、等しく、equalsIgnoreCase、のindexOf、長さの文字列メソッドを使用すると、サブ

+0

うわー、それは複雑です。私の答えを見てください。 https://en.wikipedia.org/wiki/KISS_principle – NickJ

+0

@NickJ Haha、私はもっと楽にしたいと思う。しかし、これは私がしなければならないプロジェクトのためのものです... –

+0

スレートハンマーで飛んで –

答えて

8

EDIT: 私のオリジナルの答えは非常にトリックをしなかった、それはしませんでした結果の配列にデリミタを含め、String.split()メソッドを使用しましたが、これは許されませんでした。私はあなたが唯一の文字列メソッドを使用したいと言ったときにPatternを使用している気づく

/** 
* Splits the string at all specified literal delimiters, and includes the delimiters in the resulting array 
*/ 
private static String[] tokenizer(String subject, String[] delimiters) { 

    //Sort delimiters into length order, starting with longest 
    Arrays.sort(delimiters, new Comparator<String>() { 
     @Override 
     public int compare(String s1, String s2) { 
      return s2.length()-s1.length(); 
     } 
     }); 

    //start with a list with only one string - the whole thing 
    List<String> tokens = new ArrayList<String>(); 
    tokens.add(subject); 

    //loop through the delimiters, splitting on each one 
    for (int i=0; i<delimiters.length; i++) { 
     tokens = splitStrings(tokens, delimiters, i); 
    } 

    return tokens.toArray(new String[] {}); 
} 

/** 
* Splits each String in the subject at the delimiter 
*/ 
private static List<String> splitStrings(List<String> subject, String[] delimiters, int delimiterIndex) { 

    List<String> result = new ArrayList<String>(); 
    String delimiter = delimiters[delimiterIndex]; 

    //for each input string 
    for (String part : subject) { 

     int start = 0; 

     //if this part equals one of the delimiters, don't split it up any more 
     boolean alreadySplit = false; 
     for (String testDelimiter : delimiters) { 
      if (testDelimiter.equals(part)) { 
       alreadySplit = true; 
       break; 
      } 
     } 

     if (!alreadySplit) { 
      for (int index=0; index<part.length(); index++) { 
       String subPart = part.substring(index); 
       if (subPart.indexOf(delimiter)==0) { 
        result.add(part.substring(start, index)); // part before delimiter 
        result.add(delimiter);      // delimiter 
        start = index+delimiter.length();   // next parts starts after delimiter 
       } 
      } 
     } 
     result.add(part.substring(start));      // rest of string after last delimiter   
    } 
    return result; 
} 

オリジナル回答

はここで2つの方法に分割され、私の新しいソリューション、です。

私が取るアプローチは、可能な限り簡単な方法を考えることです。私はまず、すべての可能な区切り文字をただ一つの区切り文字に置き換えてから、区切りを行うことだと思います。ここで

はコードです:

private String[] tokenizer(String string, String[] delimiters) {  

    //replace all specified delimiters with one 
    for (String delimiter : delimiters) { 
     while (string.indexOf(delimiter)!=-1) { 
      string = string.replace(delimiter, "{split}"); 
     } 
    } 

    //now split at the new delimiter 
    return string.split("\\{split\\}"); 

} 

replace()はリテラルテキストを取り、replaceAll()は、正規表現の引数を取り、供給区切り文字がリテラル文字であるので、私はString.replace()なくString.replaceAll()を使用する必要があります。

だからこそ、各区切り文字のすべてのインスタンスを置き換えるためにwhileループも必要です。 mainメソッドの呼び出しこの機能では今

public Object[] tokenizer(String value, String[] delimeters){ 
    List<String> list= new ArrayList<String>(); 
    for(String s:delimeters){ 
     if(value.contains(s)){ 
      String[] strArr=value.split("\\"+s); 
      for(String str:strArr){ 
       list.add(str); 
       if(!list.contains(s)){ 
        list.add(s); 
       } 
      } 
     } 
    } 
    Object[] newValues=list.toArray(); 
    return newValues; 
} 

- - 限り私は、あなたがこのような何かを行うことができ、あなたの問題を理解して

+0

素晴らしい!これは最高です。しかし、区切り文字自体をどのように保存するのですか?私はそれを取り除きたいとは思わない。 –

+0

呼び出しメソッドに区切り文字の配列がまだあります – NickJ

+0

いいえ、返された結果を意味します。たとえば、区切り文字が '{'で、文字列が 'ge {ab'ならば、' ge '、' {'と' ab'を使って配列が欲しいです。 –

1

のみ非正規表現文字列のメソッドを使用して

String[] delimeters = {" ", "{", "==", "=", "+", "+=", "++", "-", "-=", "--", "/", "/=", "*", "*=", "(", ")", ";", "/**", "*/", "\t", "\n"}; 
    Object[] obj=st.tokenizer("ge{ab", delimeters); //st is the reference of the other class. Edit this of your own. 
    for(Object o:obj){ 
     System.out.println(o.toString()); 
    } 
+0

私が前に述べたように、私は私たちに含まれているか分割したくない... –

+0

私はあなたが文字列メソッドだけを使いたいと思った。したがって、split()とcontains()はどちらもStringメソッドです。 (ここではListとStringのcontains()メソッドを使用しています) –

+0

あなたの行 'String [] strArr = value.split(" \\ "+ s);'は機能しないかもしれません。 "+ s"は有効な正規表現になります。これはsに依存します。それは簡単に失敗する可能性があります。 – NickJ

3

.. 私はstartsWith(...)メソッドを使用しました。これは正規表現の比較ではなく文字列の比較を行うため、リストされたメソッドの排他的リストには含まれていませんでした。

次のimpl:

public static void main(String ... params) { 
    String haystack = "abcdefghijklmnopqrstuvwxyz"; 
    String [] needles = new String [] { "def", "tuv" }; 
    String [] tokens = splitIntoTokensUsingNeedlesFoundInHaystack(haystack, needles); 
    for (String string : tokens) { 
     System.out.println(string); 
    } 
} 

private static String[] splitIntoTokensUsingNeedlesFoundInHaystack(String haystack, String[] needles) { 
    List<String> list = new LinkedList<String>(); 
    StringBuilder builder = new StringBuilder(); 
    for(int haystackIndex = 0; haystackIndex < haystack.length(); haystackIndex++) { 
     boolean foundAnyNeedle = false; 
     String substring = haystack.substring(haystackIndex); 
     for(int needleIndex = 0; (!foundAnyNeedle) && needleIndex < needles.length; needleIndex ++) { 
      String needle = needles[needleIndex]; 
      if(substring.startsWith(needle)) { 
       if(builder.length() > 0) { 
        list.add(builder.toString()); 
        builder = new StringBuilder(); 
       } 
       foundAnyNeedle = true; 
       list.add(needle); 
       haystackIndex += (needle.length() - 1); 
      } 
     } 
     if(! foundAnyNeedle) { 
      builder.append(substring.charAt(0)); 
     } 
    } 
    if(builder.length() > 0) { 
     list.add(builder.toString()); 
    } 
    return list.toArray(new String[]{}); 
} 

出力

abc 
def 
ghijklmnopqrs 
tuv 
wxyz 

注... このコードはデモのみです。デリミタの1つが空のStringである場合、OutOfMemoryError:多くのCPUを消費した後のJavaヒープ領域で、動作が不十分になり、最終的にクラッシュします。

+0

作品は魅力的です –

1

提案:

private static int INIT_INDEX_MAX_INT = Integer.MAX_VALUE; 

    private static String[] tokenizer(final String string, final String[] delimiters) { 
    final List<String> result = new ArrayList<>(); 

    int currentPosition = 0; 
    while (currentPosition < string.length()) { 
     // plan: search for the nearest delimiter and its position 
     String nextDelimiter = ""; 
     int positionIndex = INIT_INDEX_MAX_INT; 
     for (final String currentDelimiter : delimiters) { 
     final int currentPositionIndex = string.indexOf(currentDelimiter, currentPosition); 
     if (currentPositionIndex < 0) { // current delimiter not found, go to the next 
      continue; 
     } 
     if (currentPositionIndex < positionIndex) { // we found a better one, update 
      positionIndex = currentPositionIndex; 
      nextDelimiter = currentDelimiter; 
     } 
     } 
     if (positionIndex == INIT_INDEX_MAX_INT) { // we found nothing, finish up 
     final String finalPart = string.substring(currentPosition, string.length()); 
     result.add(finalPart); 
     break; 
     } 
     // we have one, add substring + delimiter to result and update current position 
     // System.out.println(positionIndex + ":[" + nextDelimiter + "]"); // to follow the internals 
     final String stringBeforeNextDelimiter = string.substring(currentPosition, positionIndex); 
     result.add(stringBeforeNextDelimiter); 
     result.add(nextDelimiter); 
     currentPosition += stringBeforeNextDelimiter.length() + nextDelimiter.length(); 
    } 

    return result.toArray(new String[] {}); 
    } 

注:

  • 私は必要以上のコメントが追加されました。私はそれがこの場合に役立つと思う。
  • このパフォーマンスは非常に悪いです(ツリー構造とハッシュで改善する可能性があります)。これは仕様の一部ではありませんでした。
  • オペレータの優先順位が指定されていません(質問に私のコメントを参照してください)。これは仕様の一部ではありませんでした。

I ONLY want to use string methods charAt, equals, equalsIgnoreCase, indexOf, length, and substring

チェックしてください。

private static void test() { 
    final String[] delimiters = { "{" }; 
    final String contents = "ge{ab"; 
    final String splitString[] = tokenizer(contents, delimiters); 
    final String joined = String.join("", splitString); 
    System.out.println(Arrays.toString(splitString)); 
    System.out.println(contents.equals(joined) ? "ok" : "wrong: [" + contents + "]#[" + joined + "]"); 
    } 
    // [ge, {, ab] 
    // ok 

最後にひとつの発言:1が持っているしたい場合、私は、特定のコンパイラのフロントエンドでは、コンパイラの構築について読むためにアドバイスすべき機能はindexOf()length()substring()

No, I mean in the returned results. For example, If my delimiter was { , and a string was ge{ab , I would like an array with ge , { and ab

チェックを使用していますこの種の質問のベストプラクティス。

1

おそらく私はこの質問を完全には理解していませんが、Java Stringメソッドsplit()を書き直したいという印象があります。私はあなたにこの機能を見て、それがどのように行われているのかを見てそこから始めることを勧めます。

1

正直、Apache Commons Langとすることができます。ライブラリのソースコードをチェックすると、Regexを使用していないことがわかります。 [StringUtils.split](http://commons.apache.org/proper/commons-lang/javadocs/api-2.6/org/apache/commons/lang/StringUtils.html#split(java.lang.String、java.lang.String)のメソッドでは、Stringと多くのフラグのみが使用されます。

とにかく、Apache Commons Langを使ってコードを見てください。

import org.apache.commons.lang.StringUtils; 
import org.junit.Assert; 
import org.junit.Test; 

public class SimpleTest { 

    @Test 
    public void testSplitWithoutRegex() { 
     String[] delimiters = {"==", "+=", "++", "-=", "--", "/=", "*=", "/**", "*/", 
      " ", "=", "+", "-", "/", "*", "(", ")", ";", "\t", "\n"}; 

     String finalDelimiter = "#"; 

     //check if demiliter can be used 
     boolean canBeUsed = true; 

     for (String delimiter : delimiters) { 
      if (finalDelimiter.equals(delimiter)) { 
       canBeUsed = false; 
       break; 
      } 
     } 

     if (!canBeUsed) { 
      Assert.fail("The selected delimiter can't be used."); 
     } 

     String s = "Assuming that we have /** or /* all these signals like == and;/or * will be replaced."; 
     System.out.println(s); 

     for (String delimiter : delimiters) { 
      while (s.indexOf(delimiter) != -1) { 
       s = s.replace(delimiter, finalDelimiter); 
      } 
     } 

     String[] splitted = StringUtils.split(s, "#"); 

     for (String s1 : splitted) { 
      System.out.println(s1); 
     } 

    } 
} 

私はそれが役に立ちそうです。

1

私はそれを得ることができるのと同じくらい簡単... ​​

public class StringTokenizer { 
    public static String[] split(String s, String[] tokens) { 
     Arrays.sort(tokens, new Comparator<String>() { 
      @Override 
      public int compare(String o1, String o2) { 
       return o2.length()-o1.length(); 
      } 
     }); 

     LinkedList<String> result = new LinkedList<>(); 

     int j=0; 
     for (int i=0; i<s.length(); i++) { 
      String ss = s.substring(i); 

      for (String token : tokens) { 
       if (ss.startsWith(token)) { 
        if (i>j) { 
         result.add(s.substring(j, i)); 
        } 

        result.add(token); 

        j = i+token.length(); 
        i = j-1; 

        break; 
       } 
      } 
     } 

     result.add(s.substring(j)); 

     return result.toArray(new String[result.size()]); 
    } 
} 

それは、新しいオブジェクトの作成の多くを行います - と、文字列の文字で文字を比較しますカスタムstartsWith()実装を書き込むことによって、最適化することができます。

@Test 
public void test() { 
    String[] split = StringTokenizer.split("this==is the most>complext<=string<<ever", new String[] {"=", "<", ">", "==", ">=", "<="}); 

    assertArrayEquals(new String[] {"this", "==", "is the most", ">", "complext", "<=", "string", "<", "<", "ever"}, split); 
} 

は細かい渡し:)

0

あなたはそれがあまり冗長にするために再帰(関数型プログラミングの特徴)を使用することができます。他の回答から

public static void main(String ... params) { 
    String haystack = "abcdefghijklmnopqrstuvwxyz"; 
    String [] needles = new String [] { "def", "tuv" }; 
    String [] tokens = tokenizer(haystack, needles); 
    for (String string : tokens) { 
     System.out.println(string); 
    } 
} 

出力

abc 
def 
ghijklmnopqrs 
tuv 
wxyz 

を同じユニットテストを使用してそれをテストし

public static String[] tokenizer(String text, String[] delims) { 
    for(String delim : delims) { 
     int i = text.indexOf(delim); 

     if(i >= 0) { 

      // recursive call 
      String[] tail = tokenizer(text.substring(i + delim.length()), delims); 

      // return [ head, middle, tail.. ] 
      String[] list = new String[tail.length + 2]; 
      list[0] = text.substring(0,i); 
      list[1] = delim; 
      System.arraycopy(tail, 0, list, 2, tail.length); 
      return list; 
     } 
    } 
    return new String[] { text }; 
} 

Javaが良くネイティブ配列のサポートを持っていた場合、それはもう少しエレガントになります。

関連する問題