質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
ソート

複数のデータを、順序性に従って並べ替えること。 データ処理を行う際に頻繁に用いられ、多くのアルゴリズムが存在します。速度、容量、複雑さなどに違いがあり、高速性に特化したものにクイックソートがあります。

Rust

Rustは、MoFoが支援するプログラミング言語。高速性を維持しつつも、メモリ管理を安全に行うことが可能な言語です。同じコンパイル言語であるC言語やC++では困難だったマルチスレッドを実装しやすく、並行性という点においても優れています。

Q&A

2回答

6982閲覧

Rustで挿入ソート 配列を受け取り、配列を返す関数を作りたい

yakiniku_obake

総合スコア7

ソート

複数のデータを、順序性に従って並べ替えること。 データ処理を行う際に頻繁に用いられ、多くのアルゴリズムが存在します。速度、容量、複雑さなどに違いがあり、高速性に特化したものにクイックソートがあります。

Rust

Rustは、MoFoが支援するプログラミング言語。高速性を維持しつつも、メモリ管理を安全に行うことが可能な言語です。同じコンパイル言語であるC言語やC++では困難だったマルチスレッドを実装しやすく、並行性という点においても優れています。

0グッド

0クリップ

投稿2018/11/04 17:19

Rust初心者です。基本的なアルゴリズムを実装してみようと考え、ソートアルゴリズム(挿入ソート)の実装にトライしました。しかし、タイトル通り、配列を受け取って配列を返す関数が上手く作れません。ライフタイムの関係でエラーが発生しているのは分かっているのですが、インターネットで調べたりドキュメントを読んだりしても全く解決しません。

Rust

1fn insert<'a, T: Ord+Clone>(x: T, v: &'a [T]) -> &'a [T] { 2 if v.len() == 0 { 3 &[x] 4 }else { 5 if x < v[0] { 6 let res = [&[x], v].concat(); 7 &res 8 }else { 9 let res = [&[v[0].clone()], insert(x, &v[1..])].concat(); 10 &res 11 } 12 } 13} 14 15fn isort<T: Ord+Clone>(v: &[T]) -> &[T] { 16 if v.len() == 0 { 17 &[] 18 }else { 19 let x = v[0].clone(); 20 insert(x, isort(&v[1..])) 21 } 22}

&[x]の部分でtemporary value does not live long enough&resの部分でborrowed value does not live long enoughというエラーが発生しています。insert関数の中で有効でも、返り値として使用すれば、関数のスコープを抜けた際にアクセス出来ないようになるためエラーを出しているということでしょうか?

また、この問題はVecを使うことで解決することが出来たのですが、出来るだけVecではなく配列を使うべきだという話をよく聞きます。なので、配列ではどのような実装をすれば良いのか教えていただきたいです。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

最初に知っておいてほしいのは、&[T]はスライス型であって、配列型ではないことです。配列型は[T; 10]のように、要素の型と要素数が入った型になります。

insert()関数は任意の長さ(Nとします)の配列を引数に取り、長さN + 1の配列を返しますが、Rustではそのような関数は定義できません。

rust

1// コンパイルエラーになる 2fn insert()<T: Ord+Clone>(x: T, v: [T; N]) -> [T; N + 1] { 3 4// これならコンパイルできるが、引数に長さ1の配列しか取れない 5fn insert()<T: Ord+Clone>(x: T, v: [T; 1]) -> [T; 2] {

次にご質問のコードでなぜスライス&[T]を返せないかですが、

insert関数の中で有効でも、返り値として使用すれば、関数のスコープを抜けた際にアクセス出来ないようになるためエラーを出しているということでしょうか?

スライスは型に参照&が現れているように、それ自体は値の実体を持たず、別の場所にある実体(配列、Vecなど)を参照します。スライスは実体なしには存在できません。つまりスライスのライフタイム(生存期間)は実体のライフタイムよりも短くなければなりません。

ではご質問のコードでそれらの実体はどこにあるでしょうか? 以下のようになっています。

  • &[x]では[x]が実体。[T; 1]型の配列
  • [&[x], v].concat()では[&[x], v]が実体。[&[T]; 2]型の配列

insert()関数のスコープを抜けると、これらの実体のライフタイムが尽きます(メモリ領域が解放されます)。それらの実体を参照するスライスを返そうとしているので、スライスの方がライフタイムが長くなってしまいエラーになるわけです。

また、この問題はVecを使うことで解決することが出来たのですが、出来るだけVecではなく配列を使うべきだという話をよく聞きます。なので、配列ではどのような実装をすれば良いのか教えていただきたいです。

残念ながら、このプログラムですと配列またはスライスを返すことはできません。

  • 最初の理由により、任意の長さの配列を返す関数が書けない
  • 2番目の理由により、関数内で作った実体を参照するスライスは戻り値として返せない

すでにされているようにVecを返すことならできます。

rust

1fn insert<'a, T: Ord + Clone>(x: T, v: &[T]) -> Vec<T> { 2 if v.is_empty() { 3 vec![x] 4 } else { 5 if x < v[0] { 6 let mut res = vec![x]; 7 res.extend_from_slice(v); 8 res 9 } else { 10 let mut res = vec![v[0].clone()]; 11 res.append(&mut insert(x, &v[1..])); 12 res 13 } 14 } 15} 16 17fn isort<T: Ord + Clone>(v: &[T]) -> Vec<T> { 18 if v.is_empty() { 19 Vec::new() 20 } else { 21 let x = v[0].clone(); 22 insert(x, &isort(&v[1..])) 23 } 24}

なお上の実装では、insert()関数が呼ばれるたびに新しいベクタを作るため効率はよくありません。これはもし配列が使えたとしても効率の面では同じです。

もしisort()insert()が引数にとったスライス(&mut [T])をその場で変更してもよいのなら、効率の良い実装もできます。(その場合はかなり違ったプログラムになるので例は割愛します)

投稿2018/11/05 01:43

編集2018/11/06 02:52
tatsuya6502

総合スコア2035

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

insert関数の中で有効でも、返り値として使用すれば、関数のスコープを抜けた際にアクセス出来ないようになるためエラーを出しているということでしょうか

このエラー自体の理解はそれで合っています。

より根本的な原因として、関数シグネチャからこれがほぼ無理なことがわかります。どちらの関数も入力スライスと出力スライスに同じライフタイムを使っています(というか、ほぼそうするしかないんですが)。後者の関数の場合はlifetime elisionという規則で、同じライフタイムが使われると解釈されます。また、スライス型 [T] は連続したメモリ領域を指しますから、これらの関数の戻り値は、入力されたスライスの連続した一部分しか(ほとんど)ありえないということになります。

また、この問題はVecを使うことで解決することが出来たのですが、出来るだけVecではなく配列を使うべきだという話をよく聞きます。なので、配列ではどのような実装をすれば良いのか教えていただきたいです。

Rust用語でいう配列は長さが決まってしまっているので、スライスのこととして説明します。確かにスライスはVecより低コストなので「できるだけ」スライスを使うべきですが、できないときはあります。(スライスで何でもできるわけではないからこそ、Vecがあります。)Vecのコストは他の多くの言語では常に支払っているようなコストなので、慣れないうちはあまり気にせずVecを使ってしまっていいと思います。

さて、最初のほうで説明したように、スライスを受け取ってスライスを返す方法では部分列しか返せませんから、この場合はスライスを受け取ってVecを返すのが妥当です。しかし、Vecを使わない方法もあります。可変スライスを受け取り、それ自体をその場でソートするインターフェースにすればよいです。標準ライブラリのsortメソッドはそのようなインターフェースになっています。

投稿2018/11/05 01:03

qnighy

総合スコア210

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問