2016-10-23 3 views
7

注:How to get subslicesは近いですが、どちらの制約もありません。どのようにパニックにも安全でもないサブスライスを取得するには?

私はこのシグネチャを持つ関数の可能な最適な実装を探しています:

impl Index<Range<usize>>と同じ機能を持っていますが、 Noneで失敗ケースを交換する代わりに、慌てう
fn sub(slice: &[u8], range: Range<usize>) -> Option<&[u8]>; 

panic

  • 使用unsafeコード
  • してもよいsliceインタフェースの注意深い調査がにもたらした

    • 呼出し機能:

      ねじれは、一見異常な制約この関数はべきではないことです私の注意:

      は、しかし、ほとんどは適していません。

      • split_atIndex<Range<usize>>::indexがパニックすることがあり
      • from_raw_partsはとしてsubを実装するために私を残しunsafe

      ある:一見作業O(N)複雑性を有している

      fn sub(slice: &[u8], range: Range<usize>) -> Option<&[u8]> { 
          if range.start > range.end { return None; } 
      
          let length = range.end - range.start; 
      
          if length > slice.len() { return None; } 
      
          if length == 0 { return Some(b""); } 
      
          let mut slice = slice; 
          for _ in 0..range.start { 
           if let Some((_, s)) = slice.split_first() { 
            slice = s; 
           } 
          } 
      
          for _ in 0..(slice.len() - length) { 
           if let Some((_, s)) = slice.split_last() { 
            slice = s; 
           } 
          } 
      
          Some(slice) 
      } 
      

      。むしろ満足していない。

      もっと良い方法がありますか?

    +3

    これを標準ライブラリに実装すると、 'unsafe'を使用することがほぼ保証されます。どうしてあなたはそれを自分で使うのを躊躇していますか?また、本当にパニックにならないコードと 'パニック 'コールを持つコードの違いはありますか?決して**実行されることはありませんか? – Shepmaster

    +2

    @Shepmaster: 'panic! 'について:静的に'パニック 'が起こらないことを知っていることと'パニック 'が決して起こらないように動的に(テストスイートで)望んでいることには違いがあります。特に、前者の場合、(たとえテストが不安定であったとしても)コード生成がパニックフックを呼び出さないという保証があります。 '安全でない 'に関しては、これは私が現時点で使用しているものですが、私は標準ライブラリに頼って(おそらく純粋に)、より良いテストができ、自分のコードよりも徹底的に監査されていることを好むでしょう。 –

    +0

    @Shepmaster:私は、 'split_first'と' split_last'が存在し、 'Option'を返すと、' split_at'がそのトラックに続くことを期待しています。私はここでインターフェイスの矛盾に驚いています。 –

    答えて

    6

    好きなバージョンから始めます。それはget_sliceであり、境界チェックスライシングを使用しており、コンパイラの最適化された出力を見て、コンパイラがパニックにならないことを確認することができます。 (私は同意、「何のパニックは、」アサーションとしてで作業することができるようにする素晴らしいものになるんでしょう; get_sliceがlibstdする素晴らしい追加となり、実際に計画されています。)

    /// Return the subslice corresponding to `range`, if it is in bounds 
    /// and well formed (start <= end). 
    pub fn get_slice(slice: &[u8], range: Range<usize>) -> Option<&[u8]> { 
        if range.start > range.end || range.end > slice.len() { 
         None 
        } else { 
         Some(&slice[range]) 
        } 
    } 
    

    次に、O(N)アルゴリズムのようにコード化されていますが、オプティマイザによって強度がO(1)に低減された試行されたソリューションです。

    私たちはスライスイテレータと、それをスライスに戻すことができるという事実を使用します。スライスイテレータの.nth()は、一定時間内に前方にジャンプするようにコード化されていますが、逆のバージョンは残念ながらありません。しかし、そのループは最適化されています。

    pub fn sub(slice: &[u8], range: Range<usize>) -> Option<&[u8]> { 
        if range.start > range.end || range.end > slice.len() { 
         return None; 
        } 
    
        let mut iter = slice.iter(); 
        let take_front = range.start; 
        let take_back = slice.len() - range.end; 
        if take_front > 0 { 
         iter.nth(take_front - 1); 
        } 
        if take_back > 0 { 
         (&mut iter).rev().nth(take_back - 1); 
        } 
        Some(iter.as_slice()) 
    } 
    

    playground link

    注:私は残念ながら、我々はここで、任意のルールのビットを作っていると思います。テイク・フロント操作の後に.chunks()を使用すると、直接O(1)の解決策が得られます。しかし、0のサイズのチャンクを要求すると、チャンクがパニックになることがあります。それは私のために境界をチェックされたスライス( "それはパニックになるかもしれません")を使用するだけで、同じ括弧に入れます。

    +0

    ニース。 'nth'がO(1)を保証するために最適化できるかどうか知っていますか? –

    +0

    パニックの場合、オーバーフローチェックがオンになっているときにパニックになる整数演算を使用していることがわかりました。 'slice.len() - range.end'は' wrapping_sub'で書き直す必要があります。私は本当に 'no_panic'リントから利益を得ることができました。 –

    +0

    その方法論ですべてのパニックケースを削除するということは、間違いなく静かなコードを書くことを意味します。それは良くない:それは悪い。 'get_slice'はそれよりはるかに優れています(特にlibstdから来た場合)。 – bluss

    関連する問題