2017-05-11 11 views
6

私は自分自身でList.map関数を書く必要があります。 'for elem in list'とtail/non-tail recursiveです。私はいくつかのヒントのためにGoogleの周りを見てきましたが、それほど多くは見つかりませんでした。私はPythonに慣れていますが、そのメソッドを使用することについて考えるのはかなり難しいですが、もちろん、これらの言語は互いに非常に異なっています。再帰F#で自分自身のList.map関数を書くには

let myMapFun funcx list = 
    for elem in list do 
     funcx elem::[] 

テール::私は何かを始め最初のもののために

let rec myMapFun2 f list = 
    let cons head tail = head :: tail 

しかし、いずれにせよ、私はそれは間違っている知っている、それは間違って感じています。私はまだF#strctureには使用されていないと思う。誰かが私に手を差し伸べることができる?

ありがとうございました。

+0

この宿題はありますか? – rmunn

+0

多かれ少なかれ...私はF#を使った実践的なプログラムを勉強して準備しています。 –

+0

クラスの宿題でなければ、より完全な答えを与えることができます。あなたは多分あなた自身をある程度までそれを働かせることによってもっと利益を得ることができるので、私はあなたにちょっとした概要を与えようとします。 – rmunn

答えて

7

番号参照の異なる態様を参照あなたはF#のリストを使って作業していますが、リストの先頭に何かをする再帰関数を書いて、リストの末尾に自分自身を呼び出したいとします。

// NON-tail-recursive version 
let rec myListFun list = 
    match list with 
    | [] -> valueForEmptyList // Decision point 1 
    | head :: tail -> 
     let newHead = doSomethingWith head // Decision point 2 
     newHead :: (myListFun tail) // Return value might be different, too 

必要な決定は2つあります。リストが空の場合はどうすればよいですか?そしてリストの各項目で私は何をしますか?たとえば、リスト内のアイテムの数を数えたいと思っている場合は、「空リストの値」はおそらく0で、各アイテムで行うことはおそらく1を加算することです長さ。この機能には、リスト内の各項目のスタックへの新しい再帰呼び出しが追加されるため、問題があります。リストが長すぎると、スタックがオーバーフローします。一般的なパターンは、(このような)再帰呼び出しではない再帰呼び出しに直面したときに、再帰関数を変更して、「アキュムレータ」になる余分なパラメータを取ります。したがって、再帰関数から結果を戻してから計算を実行する代わりに、「accumulator」値で計算を実行し、真のテール再帰呼び出しで新しいアキュムレータ値を再帰関数に渡します。ここではそれがmyListLength機能のために次のようになります。

let rec myListLength acc list = 
    match list with 
    | [] -> acc // Empty list means I've finished, so return the accumulated number 
    | head :: tail -> 
     let headLength = 1 // The head is one item, so its "length" is 1 
     myListLength (acc + headLength) tail 

今、あなたはmyListLength 0 listとしてこれを呼び出すと思います。そしてそれはちょっと迷惑なので、アキュムレータを "内部"関数のパラメータにすることでアキュムレータを隠すことができます。その定義はmyListLengthの中に隠されています。このように:

myListLengthはもはや再帰的であり、それは一つのパラメータのみ、その長さはあなたがカウントしたいリストを取る方法
let myListLength list = 
    let rec innerFun acc list = 
     match list with 
     | [] -> acc // Empty list means I've finished, so return the accumulated number 
     | head :: tail -> 
      let headLength = 1 // The head is one item, so its "length" is 1 
      innerFun (acc + headLength) tail 
    innerFun 0 list 

注意してください。

ここまで戻って、私が答えの最初の部分で提示した一般的でないNON-tail-recursive myListFunを見てください。それがmyListLength関数とどのように対応しているのかをご覧ください。あなたがこの方法あなたのmap関数を記述した場合、あなたはそれが実際にを出てくることがわかりますことを除いて...

let myListFun list = 
    let rec innerFun acc list = 
     match list with 
     | [] -> acc // Decision point 1: return accumulated value, or do something more? 
     | head :: tail -> 
      let newHead = doSomethingWith head 
      innerFun (newHead :: acc) tail 
    innerFun [] list 

:まあ、その末尾再帰バージョンもmyListLengthの末尾再帰バージョンにも対応します逆転。解決策は最後の行のinnerFun [] listinnerFun [] list |> List.revに変更することですが、理由はです。なぜならが逆転してくるからです。自分で解決することで利益を得ることができるので、助けを求めない限りは教えません。

そして今、あなたはすべてのリストを持つもののを再帰的に行うための一般的なパターンを持っています。 List.mapと書くのは簡単です。余分な挑戦のために、次にList.filterを書いてみてください:それは同じパターンを使用します。

+0

私は慎重に読んでメモを取っていた。私は勉強と改善を続けます。あなたはたくさんの助けになりました! :) どうもありがとうございました。 –

4
let myMapFun funcx list = 
    [for elem in list -> funcx elem] 

myMapFun ((+)1) [1;2;3] 

let rec myMapFun2 f = function  // [1] 
    | [] -> []      // [2] 
    | h::t -> (f h)::myMapFun f t  // [3] 

myMapFun2 ((+)1) [1;2;3]   // [4] 


let myMapFun3 f xs =    // [6] 
    let rec g f xs=     // [7] 
    match xs with     // [1] 
    | [] -> []      // [2] 
    | h::t -> (f h)::g f t   // [3] 
    g f xs 
myMapFun3 ((+)1) [1;2;3]   // [4] 

            // [5] see 6 for a comment on value Vs variable. 
            // [8] see 8 for a comment on the top down out-of-scopeness of F# 

(* 参考:

規則:私は、B、Cを使用した、等と、一般的なルールとして

[1] roughly function is equivalent to the use of match. It's the way they do it in 
    OCaml. There is no "match" in OCaml. So this is a more compatible way 
    of writing functions. With function, and the style that is used here, we can shave 
    off a whole two lines from our definitions(!) Therefore, readability is increased(!) 
    If you end up writing many functions scrolling less to be on top 
    of the breadth of what is happening is more desirable than the 
    niceties of using match. "Match" can be 
    a more "rounded" form. Sometimes I've found a glitch with function. 
    I tend to change to match, when readability is better served. 
    It's a style thing. 

[1b] when I discovered "function" in the F# compiler source code + it's prevalence in OCaml, 
    I was a little annoyed that it took so long to discover it + that it is deemed such an 
    underground, confusing and divisive tool by our esteemed F# brethren. 

[1c] "function" is arguably more flexible. You can also slot it into pipelines really 
    quickly. Whereas match requires assignment or a variable name (perhaps an argument). 
    If you are into pipelines |> and <| (and cousins such as ||> etc), then you should 
    check it out. 

[1d] on style, typically, (fun x->x) is the standard way, however, if you've ever 
    appreciated the way you can slot in functions from Seq, List, and Module, then it's 
    nice to skip the extra baggage. For me, function falls into this category. 

[2a] "[]" is used in two ways, here. How annoying. Once it grows on you, it's cool. 
    Firstly [] is an empty list. Visually, it's a list without the stuff in it 
    (like [1;2;3], etc). Left of the "->" we're in the "pattern" part of the partern 
    matching expression. So, when the input to the function (lets call it "x" to stay 
    in tune with our earliest memories of maths or "math" classes) is an empty list, 
    follow the arrow and do the statement on the right. 

    Incidentally, sometimes it's really nice to skip the definition of x altogether. 
    Behold, the built in "id" identity function (essentially fun (x)->x -- ie. do nothing). 
    It's more useful than you realise, at first. I digress. 

[2b] "[]" on the right of [] means return an empty list from this code block. Match or 
    function symantics being the expression "block" in this case. Block being the same 
    meaning as you'll have come across in other languages. The difference in F#, being 
    that there's *always* a return from any expression unless you return unit which is 
    defined as(). I digress, again. 

[3a] "::" is the "cons" operator. Its history goes back a long way. F# really only 
    implements two such operators (the other being append @). These operators are 
    list specific. 

[3b] on the lhs of "->" we have a pattern match on a list. So the first element 
    on the lhs of :: goes into the value (h)ead, and the rest of the list, the tail, 
    goes into the (t)ail value. 

[3c] Head/tail use is very specific in F#. Another language that I like a lot, has 
    a nicer terminology for obviously interesting parts of a list, but, you know, it's 
    nice to go with an opinionated simplification, sometimes. 

[3d] on the rhs of the "->", the "::", surprisingly, means join a single element 
    to a list. In this case, the result of the function f or funcx. 

[3e] when we are talking about lists, specifically, we're talking about a linked 
    structure with pointers behind the scenes. All we have the power to do is to 
    follow the cotton thread of pointers from structure to structure. So, with a 
    simple "match" based device, we abstract away from the messy .Value and .Next() 
    operations you may have to use in other languages (or which get hidden inside 
    an enumerator -- it'd be nice to have these operators for Seq, too, but 
    a Sequence could be an infinite sequence, on purpose, so these decisions for 
    List make sense). It's all about increasing readability. 

[3f] A list of "what". What it is is typically encoded into 't (or <T> in C#). 
    or also <T> in F#. Idiomatically, you tend to see 'someLowerCaseLetter in 
    F# a lot more. What can be nice is to pair such definitions (x:'x). 
    i.e. the value x which is of type 'x. 

[4a] move verbosely, ((+)1) is equivilent to (fun x->x+1). We rely on partial 
    composition, here. Although "+" is an operator, it is firstmost, also a 
    function... and functions... you get the picture. 

[4b] partial composition is a topic that is more useful than it sounds, too. 

[5] value Vs variable. As an often stated goal, we aim to have values that 
    never ever change, because, when a value doesn't change, it's easier to 
    think and reason about. There are nice side-effects that flow from that 
    choice, that mean that threading and locking are a lot simpler. Now we 
    get into that "stateless" topic. More often than not, a value is all you 
    need. So, "value" it is for our cannon regarding sensible defaults. 

    A variable, implies, that it can be changed. Not strictly true, but in 
    the programming world this is the additional meaning that has been strapped 
    on to the notion of variable. Upon hearing the word variable, ones mind might 
    start jumping through the different kinds of variable "hoops". It's more stuff 
    that you need to hold in the context of your mind. Apparently, western people 
    are only able to hold about 7 things in their minds at once. Introduce mutability 
    and value in the same context, and there goes two slots. I'm told that more uniform 
    languages like Chinese allow you to hold up to 10 things in your mind at once. 
    I can't verify the latter. I have a language with warlike Saxon and elegant 
    French blended together to use (which I love for other reasons). 

    Anyway, when I hear "value", I feel peace. That can only mean one thing. 

[6] this variation really only achieves hiding of the recursive function. Perhaps 
    it's nice to be a little terser inside the function, and more descriptive to 
    the outside world. Long names lead to bloat. Sometimes, it's just simpler. 

[7a] type inference and recursion. F# is one of the nicest 
    languages that I've come across for elegantly dealing with recursive algorithms. 
    Initially, it's confusing, but once you get past that 

[7b] If you are interested in solving real problems, forget about "tail" 
    recursion, for now. It's a cool compiler trick. When you get performance conscious, 
    or on a rainy day, it 
    might be a useful thing to look up. 
    Look it up by all means if you are curious, though. If you are writing recursive 
    stuff, just be aware that the compiler geeks have you covered (sometimes), and 
    that horrible "recursive" performance hole (that is often associated with 
    recursive techniques -- ie. perhaps avoid at all costs in ancient programming 
    history) may just be turned into a regular loop for you, gratis. This auto-to-loop 
    conversion has always been a compiler geek promise. You can rely on it more though. 
    It's more predictable in F# as to when "tail recursion" kicks in. I digress. 
    Step 1 correctly and elegantly solve useful problems. 
    Step 2 (or 3, etc) work out why the silicon is getting hot. 

    NB. depending on the context, performance may be an equally important thing 
    to think about. Many don't have that problem. Bear in mind that by writing 
    functionally, you are structuring solutions in such a way that they are 
    more easily streamlineable (in the cycling sense). So... it's okay not to 
    get caught in the weeds. Probably best for another discussion. 

[8] on the way the file system is top down and the way code is top down. 
    From day one we are encouraged in an opinionated (some might say coerced) into 
    writing code that has flow + code that is readable and easier to navigate. 
    There are some nice side-effects from this friendly coercion. 
+0

これは、標準ライブラリにない場合に 'map'を実装するための良い、速い方法ですが、コードだけでF#初心者を教えることはありません。おそらく、あなたはあなたの答えを少し拡大して、何が起こっているのかを説明することができますか?たとえば、「function」キーワード(私の答えで慎重に避けた)は実際には**実際には**何ですか? – rmunn

+0

@rmunn:シーケンスはこのようになりました。質問をしました。助けることにしました。それに答えました。その後、あなたの声が上がり、上記のコメントが続きました。それは単純な動機づけ、本当に、目的を果たしたものでした。 – sgtz

+0

ご協力いただきありがとうございます。最初の関数で "[]"が必要な理由は何ですか? :) –

関連する問題