« Swift3のコーディング規約 | トップページ | ◆◆ hiClass:C++基礎クラスライブラリ ◆◆ »

Swift3で文字列のsubstring

臨時のメモ

 Swiftで文字列のsubstringを得る

Objective-Cも文字列の取り扱いは大変でしたが、SwiftのStringの大変さも相当なものです。

部分文字列を取るという単純なことが簡単にはできません。

そこでこんなクラスを用意します。文字位置指定でのsubstring()と部分文字列検索find()を実装しました。
一般的なSwift風でないのは◆Swift3のコーディング規約のコーディング規約に沿っているからです。

// hiU.swift
import UIKit
public class hiU {
   /// 文字列の長さを得る
   /// - parameter str_:文字列
   /// - return        :文字列の長さ
   static public func length(str_:String)
                      -> Int {
      return str_.characters.count;
      }
   /// 部分文字列を得る
   /// - parameter str_  :文字列
   /// - parameter start_:開始文字位置(0オリジン)
   /// - parameter end_  :終了文字位置の後ろ(この位置の文字は含まない)
   ///                    元の文字列を超える場合は元の文字列の範囲が得られる
   /// - return          :部分文字位置
   static public func substring(str_:String, start_:Int, end_:Int)
                      -> String{
      let _start:Int = start_ < 0 ? 0 :start_;
      let _last:Int  = str_.characters.count;
      let _end:Int   = end_   > _last ? _last : end_;
      return str_.substringWithRange( // これだけと言えばこれだけだが
         str_.startIndex.advancedBy(_start)
          ..<
         str_.startIndex.advancedBy(_end) );
      }
   /// 部分文字列を得る
   /// - parameter str_    :文字列
   /// - parameter start_  :開始文字位置(0オリジン)
   /// - parameter length_ :文字列長
   ///                    元の文字列を超える場合は元の文字列の範囲が得られる
   /// - return            :部分文字位置
   static public func substring(str_:String,start_:Int,length_:Int)
                      -> String{
      return hiU.substring(str_,start_:start_,end_:start_ + length_);
      }
   /// 部分文字列を得る
   /// - parameter str_    :文字列
   /// - parameter start_  :開始文字位置(0オリジン)
   /// - parameter length_ :文字列長
   ///                    元の文字列を超える場合は元の文字列の範囲が得られる
   /// - return            :部分文字位置
   static public func substring(str_:String,start_:Int)
                      -> String{
      return hiU.substring(str_,start_:start_
                               ,length_:str_.characters.count-start_);
      }
   /// 部分文字列を得る
   /// - parameter str_    :文字列
   /// - parameter start_  :開始文字位置(0オリジン)
   /// - parameter length_ :文字列長
   ///                    元の文字列を超える場合は元の文字列の範囲が得られる
   /// - return            :部分文字位置
   static public func substring(str_:String,range_:Range<Int>?)
                      -> String{
      guard let _range:Range<Int> = range_
      else { return ""; }
      return hiU.substring(str_,start_:_range.startIndex
                               ,end_:_range.endIndex);
      }
   /// 部分文字列を検索する(先出優先)
   /// - parameter in_  : この文字列の中を検索
   /// - parameter find_: この文字列を見つける
   /// - return         : 発見した文字列の範囲
   ///                    見つからない場合はnil
   static public func find(in_:String,find_:String)
                      -> Range<Int>? {
      guard let _range:Range<String.Index> = in_.rangeOfString(find_)
      else { return nil; }
      return (in_.startIndex.distanceTo(_range.startIndex)
               ..<
              in_.startIndex.distanceTo(_range.endIndex))
      }
   /// 指定位置以降で部分文字列を検索する(先出優先)
   /// - parameter in_   : この文字列の中を検索
   /// - parameter find_ : この文字列を見つける
   /// - parameter after_: この文字位置以降を探す。この文字位置を含む
   /// - return          : 発見した文字列の範囲
   ///                     見つからない場合はnil
   static public func find(in_:String,find_:String,after_:Int)
                      -> Range<Int>? {
      let _in_sub:String
           = in_.substringFromIndex(in_.startIndex.advancedBy(after_));
      guard let _range:Range<Int> = hiU.find(_in_sub,find_: find_)
      else { return nil; }
      return (_range.startIndex+after_
               ..<
              _range.endIndex+after_);
      }
   /// 指定位置以降で部分文字列を検索する(先出優先)
   /// - parameter in_   : この文字列の中を検索
   /// - parameter find_ : この文字列を見つける
   /// - parameter after_: この範囲以降を探す。この範囲は含まない
   /// - return          : 発見した文字列の範囲
   ///                     見つからない場合はnil
   static public func find(in_:String,find_:String,after_:Range<Int>?)
                      -> Range<Int>? {
      let _after:Int = ( after_ != nil ) ? after_!.endIndex : 0;
      return hiU.find(in_,find_:find_,after_:_after);
      }
   /// 指定位置以前で部分文字列を検索する(後出優先)
   /// - parameter in_   : この文字列の中を検索
   /// - parameter find_ : この文字列を見つける
   /// - parameter before_: この文字より前を探す。この文字位置は含まない
   /// - return          : 発見した文字列の範囲
   ///                     見つからない場合はnil
   static public func find(in_:String,find_:String,before_:Int)
                      -> Range<Int>? {
      var _found:Range<Int>? = nil;
      var _start:Int         = 0;
      var _in_sub:String     = hiU.substring(in_,start_:_start,end_:before_);
      var _found_temp:Range<Int>? = hiU.find(_in_sub,find_:find_);
      while( _found_temp != nil ){
         _found      =  (_found_temp!.startIndex+_start 
                         ..<
                         _found_temp!.endIndex+_start);
         _start      += _found!.startIndex;
         _in_sub     =  hiU.substring(in_,start_:_start,end_:before_);
         _found_temp =  hiU.find(_in_sub,find_:find_);
         }
      return _found;
      }
   /// 指定位置以前で部分文字列を検索する(後出優先)
   /// - parameter in_   : この文字列の中を検索
   /// - parameter find_ : この文字列を見つける
   /// - parameter after_: この文字位置以降を探す。この文字位置を含む
   /// - return          : 発見した文字列の範囲
   ///                     見つからない場合はnil
   static public func find(in_:String,find_:String,before_:Range<Int>?)
                      -> Range<Int>? {
      let _before:Int = ( before_ != nil ) ? before_!.startIndex
                                           : in_.characters.count;
      return hiU.find(in_,find_:find_,before_:_before);
      }
   // ...
   }

Range<String.Index>ではなくRange≤Int>を戻り値にしたのは当初(swift2)取扱いが面倒だったためです。
日本語などでも問題なく動きました。

簡単な試験はしてあります。

   func tests(){
      //                      0123456789
      let _text:String     = "ababababXY";
      print("text:0123456789");
      print("text="+_text);
      var _text_sub:String = hiU.substring(_text,start_:1,end_:3);
      print("substring(start_:1,end_:3)=\(_text_sub    )\t期待値:ba");
      _text_sub = hiU.substring(_text,start_:1,length_:3);
      print("substring(start_:1,length_:3)=\(_text_sub )\t期待値:bab");
      _text_sub = hiU.substring(_text,start_:1,end_:50);
      print("substring(start_:1,end_:50)=\(_text_sub   )\t期待値:b〜Y");
      _text_sub = hiU.substring(_text,start_:1,length_:50);
      print("substring(start_:1,length_:50)=\(_text_sub)\t期待値:b〜Y");
      var _index:Range<Int>? = hiU.find(_text,find_:"bab");
      print("find(abc)=\(_index                        )\t期待値:1..<4");
      _index = hiU.find(_text,find_:"baB");
      print("find(baB)=\(_index                        )\t期待値:nil");
      _index = hiU.find(_text,find_:"bab",after_:_index);
      print("find(bab after baB)=\(_index              )\t期待値:1..<4");
      _index = hiU.find(_text,find_:"bab",after_:_index);
      print("find(bab after bab)=\(_index              )\t期待値:5..<8");
      _index = hiU.find(_text,find_:"bab",after_:3);
      print("find(bab after 3)=\(_index                )\t期待値:3..<6");
      _index = hiU.find(_text,find_:"bab",after_:4);
      print("find(bab after 4)=\(_index                )\t期待値:5..<8");
      _index = hiU.find(_text,find_:"bab",before_:5);
      print("find(bab befor 5)=\(_index                )\t期待値:1..<4");
      _index = hiU.find(_text,find_:"bab",before_:6);
      print("find(bab befor 6)=\(_index                )\t期待値:3..<6");
      _index = hiU.find(_text,find_:"bab",before_:_index);
      print("find(bab befor bab)=\(_index              )\t期待値:nil");
      _index = hiU.find(_text,find_:"bab",before_:_index);
      print("find(bab befor nil)=\(_index              )\t期待値:5..<8");
      _index = hiU.find(_text,find_:"bab",before_:_index);
      print("find(bab befor bab)=\(_index              )\t期待値:1..<4");
      //...
      }

結果は次のようになります。

text:0123456789
text=ababababXY
substring(start_:1,end_:3)=ba	期待値:ba
substring(start_:1,length_:3)=bab	期待値:bab
substring(start_:1,end_:50)=babababXY	期待値:b〜Y
substring(start_:1,length_:50)=babababXY	期待値:b〜Y
find(abc)=Optional(Range(1..<4))	期待値:1..<4
find(baB)=nil	期待値:nil
find(bab after baB)=Optional(Range(1..<4))	期待値:1..<4
find(bab after bab)=Optional(Range(5..<8))	期待値:5..<8
find(bab after 3)=Optional(Range(3..<6))	期待値:3..<6
find(bab after 4)=Optional(Range(5..<8))	期待値:5..<8
find(bab befor 5)=Optional(Range(1..<4))	期待値:1..<4
find(bab befor 6)=Optional(Range(3..<6))	期待値:3..<6
find(bab befor bab)=nil	期待値:nil
find(bab befor nil)=Optional(Range(5..<8))	期待値:5..<8
find(bab befor bab)=Optional(Range(1..<4))	期待値:1..<4

 Stringに機能を加える

extension機能を使えば、Stringに機能を加えることができます。

// StringExtension.swift
import Foundation
extension String {
   // 部分文字列を得る
   func substring(start_:Int,length_:Int)
        -> String {
      return hiU.substring(self,start_:start_,length_:length_);
      }
   func substring(start_:Int,end_:Int)
        -> String {
      return hiU.substring(self,start_:start_,end_:end_);
      }
   func substring(start_:Int)
        -> String {
      return hiU.substring(self,start_:start_);
      }
   func substring(range_:Range<Int>?)
        -> String {
      return hiU.substring(self,range_:range_);
      }
   // 部分文字列の検索
   func find(find_:String)
        -> Range<Int>? {
      return hiU.find(self,find_:find_);
      }
   func find(find_:String,after_:Int)
        -> Range<Int>? {
      return hiU.find(self,find_:find_,after_:after_);
      }
   func find(find_:String,after_:Range<Int>?)
        -> Range<Int>? {
      return hiU.find(self,find_:find_,after_:after_);
      }
   func find(find_:String,before_:Int)
        -> Range<Int>? {
      return hiU.find(self,find_:find_,before_:before_);
      }
   func find(find_:String,before_:Range<Int>?)
        -> Range<Int>? {
      return hiU.find(self,find_:find_,before_:before_);
      }
   //
   func length()
        -> Int {
      return self.characters.count;
      }
   //
   }

簡単な試験はしてあります。
hiUの試験と同等の試験をStringで行っています。

   func tests(){
      //                      0123456789
      let _text:String     = "ababababXY";
      //...
      var _index:Range? = ...;
      //...
      // Stringのextension
      print("text:0123456789");
      print("text="+_text);
      _text_sub = _text.substring(1,end_:3);
      print("substring(1,end_:3)=\(_text_sub    )\t期待値:ba");
      _text_sub = _text.substring(1,length_:3);
      print("substring(1,length_:3)=\(_text_sub )\t期待値:bab");
      _text_sub = _text.substring(1,end_:50);
      print("substring(1,end_:50)=\(_text_sub   )\t期待値:b〜Y");
      _text_sub = _text.substring(1,length_:50);
      print("substring(1,length_:50)=\(_text_sub)\t期待値:b〜Y");
      _index = _text.find("bab");
      print("find(abc)=\(_index                 )\t期待値:1..<4");
      _index = _text.find("baB");
      print("find(baB)=\(_index                 )\t期待値:nil");
      _index = _text.find("bab",after_:_index);
      print("find(bab after baB)=\(_index       )\t期待値:1..<4");
      _index = _text.find("bab",after_:_index);
      print("find(bab after bab)=\(_index       )\t期待値:5..<8");
      _index = _text.find("bab",after_:3);
      print("find(bab after 3)=\(_index         )\t期待値:3..<6");
      _index = _text.find("bab",after_:4);
      print("find(bab after 4)=\(_index         )\t期待値:5..<8");
      _index = _text.find("bab",before_:5);
      print("find(bab befor 5)=\(_index         )\t期待値:1..<4");
      _index = _text.find("bab",before_:6);
      print("find(bab befor 6)=\(_index         )\t期待値:3..<6");
      _index = _text.find("bab",before_:_index);
      print("find(bab befor bab)=\(_index       )\t期待値:nil");
      _index = _text.find("bab",before_:_index);
      print("find(bab befor nil)=\(_index       )\t期待値:5..<8");
      _index = _text.find("bab",before_:_index);
      print("find(bab befor bab)=\(_index      )\t期待値:1..<4");
      }

結果は次のようになります。

text:0123456789
text=ababababXY
substring(1,end_:3)=ba	期待値:ba
substring(1,length_:3)=bab	期待値:bab
substring(1,end_:50)=babababXY	期待値:b〜Y
substring(1,length_:50)=babababXY	期待値:b〜Y
find(abc)=Optional(Range(1..<4))	期待値:1..<4
find(baB)=nil	期待値:nil
find(bab after baB)=Optional(Range(1..<4))	期待値:1..<4
find(bab after bab)=Optional(Range(5..<8))	期待値:5..<8
find(bab after 3)=Optional(Range(3..<6))	期待値:3..<6
find(bab after 4)=Optional(Range(5..<8))	期待値:5..<8
find(bab befor 5)=Optional(Range(1..<4))	期待値:1..<4
find(bab befor 6)=Optional(Range(3..<6))	期待値:3..<6
find(bab befor bab)=nil	期待値:nil
find(bab befor nil)=Optional(Range(5..<8))	期待値:5..<8
find(bab befor bab)=Optional(Range(1..<4))	期待値:1..<4

とても楽になります。
ただし、大規模な開発においてJacascriptのprototype汚染のようにextension汚染が発生しないか心配はあります。

|

« Swift3のコーディング規約 | トップページ | ◆◆ hiClass:C++基礎クラスライブラリ ◆◆ »

トラックバック


この記事へのトラックバック一覧です: Swift3で文字列のsubstring:

« Swift3のコーディング規約 | トップページ | ◆◆ hiClass:C++基礎クラスライブラリ ◆◆ »