他の人が答えやコメントで言ったように、これに答えるのが最も難しいのはコードを理解することです。それは良い説明とコメントが欠けています。
コードを理解するためにまずやったのは、タイプシグニチャを追加してから、printfn
ステートメントをコードに追加して、その処理を確認することでした。
それ以降は、質問のコードを理解するのがずっと簡単です。
コードを再設計するときに、小さな部品を一度に変更しようとせず、printfn
出力とタイプシグネチャから学んだことに基づいてコア機能を構築しました。私は躊躇せずに、ref
を使って変更可能なコードから、各関数の最初から再構築された不変のグラフに切り替えました。これは、既存のデータ構造を投げ捨て、毎回新しい構造を構築するのは無駄に思えるかもしれませんが、このように考えることができます。各エッジで決定を下さなければならない関数は、各エッジにアクセスする必要があります。新しいグラフに追加するかしないかのどちらかを選択すると、それを簡単にコーディングしたり、他の人が理解しやすくなるようになります。
また、タイプシグネチャをより意味深くし、コードが何をしているかを明確にするためにタイプを追加しました。少し仕事のための大きなボーナス。
次に、関数を見て、できるだけ簡潔にコードを分かりやすくすることに重点を置いて、読みやすさと保守性に重点を置き、関数をより顕著にするようにしました。
明らかに、この回答は他の2つよりも長くなりますが、元のコードよりも機能的であり、ミューテックスはなく、最初の読み方で分かりやすく、各機能の説明をコメントしています。
これがライブラリの一部である場合は、タイプシグネチャを削除するコードと、これがオプションではない一般的なものである場合は、コードを修正する必要があります。また、別々の関数を内部関数にしてリファクタリングして、組み込みのF#コア関数を使用するようにリファクタリングし、そのときに明瞭さの損失を補うためにコメントを追加します。私はそれがKeyNotFoundException
例外をスロー可能性があり、私は私の機能を好きなので、例外を回避するためにそれを変更したときに可能totalをするList.pickを使用したが実現し、以前のバージョンで
。
私の答えを見ると、私は満足していませんでしたif not (nodeUsed graph node) then
;それは単純さの真っ只中のいぼだった。だから私は機能プログラミングの古いスイス軍ナイフに頼ることにした:factoring。 Pure functionalプログラミングは、基本的に数式のように因数分解できる式で、より理論的にはterm rewritingです。 not
でラインを除外できるかどうか分かりました。私はそれをもっと美しくでき、これを理解しやすくなりました。したがって、not
を除外する方法はlet rec
の外に移動することでした。 pathToNodes
であり、これは遷移のリストの代わりにノードのリストを渡すことによって行うことができる。 reduceGraph2
。それが完了したら、コードは簡単になりました。
私は確信していますが、コードをもっとダウンさせることができますが、私は一般的に理解しやすいので、新しいF#人々のためにこのような答えを残します。
namespace Workspace
module main =
type Node = int
type Transition = bool
type Edge = Node * Transition * Node
type Path = Transition list
type Graph = Edge list
[<EntryPoint>]
let main argv =
let edge1 : Edge = (0, false, 1)
let edge2 : Edge = (0, true , 2)
let edge3 : Edge = (1, true , 3)
let edge4 : Edge = (1, false, 4)
let edge5 : Edge = (2, true , 4)
let edge6 : Edge = (4, false, 4)
let g : Graph = [edge1; edge2; edge3; edge4; edge5; edge6]
// Given a Node, are there any Edges to that node
let nodeUsed (graph : Graph) (checkNode : Node) : bool =
List.exists (fun (_, _, toNode) -> checkNode = toNode) graph
// Given a Node and a transition, what is the next node
// This assumes that Transition is binary
// so only a value will be returned instead of a list.
let nextNode (graph : Graph) (fromNode : Node) (transition : Transition) : Node option =
let rec pick (graph : Graph) (fromNode : Node) (transition : Transition) : Node option =
match graph with
| (f, c, t)::tl when (f = fromNode) && (c = transition) -> Some t
| hd::tl -> pick tl fromNode transition
| _ -> None
pick graph fromNode transition
// Given a graph and a node, remove all edges that start from that node.
// This builds a new graph each time, thus the graph is immutable.
let removeNode (graph : Graph) (node : Node) : Graph =
let rec removeEdges graph node newGraph =
match graph with
| hd::tl ->
let (f,c,t) = hd
if (f = node)
// Don't add current node to new graph
then removeEdges tl node newGraph
// Add current node to new graph
else removeEdges tl node ((f,c,t) :: newGraph)
| [] -> newGraph
removeEdges graph node []
// or
// let removeNode (graph : Graph) (node : Node) : Graph =
// let choiceFunction elem =
// match elem with
// | (f,c,t) when f = node -> None
// | _ -> Some(elem)
// List.choose choiceFunction graph
// Given a graph, a starting node, and a list of transitions (path),
// return a new reduced graph based on the example code in the SO question.
let reduceGraph (graph : Graph) (start : Node) (path : Path) : Graph =
let rec processTransistion graph node transitions =
if not (nodeUsed graph node) then
match transitions with
| (transistion :: transitions) ->
// Get next node before removing nodes used to get next node
let nextNodeResult = nextNode graph node transistion
match nextNodeResult with
| Some(nextNode) ->
let newGraph = removeNode graph node
processTransistion newGraph nextNode transitions
| None -> graph
| [] -> graph
else graph
processTransistion graph start path
let result = reduceGraph g 0 [false; true]
printfn "reduceGraph - result: %A" result
printf "Press any key to exit: "
System.Console.ReadKey() |> ignore
printfn ""
0 // return an integer exit code
。
// Give an graph, a node and a path,
// convert the transition list (path) to a node list
let pathToNodes (graph : Graph) (start : Node) (path : Path) : (Node List) =
let rec visit graph node transistions acc =
match transistions with
| (transition::rest) ->
match (nextNode graph node transition) with
| Some(nextNode) -> visit graph nextNode rest (nextNode :: acc)
| None -> List.rev acc
| [] -> List.rev acc
visit graph start path [start]
// Given a graph, a starting node, and a list of transitions (path),
// return a new reduced graph based on the example code in the SO question.
// This variation does so internally by a node list instead of a transition list
let reduceGraph2 (graph : Graph) (start : Node) (path : Path) : Graph =
let rec processNodes graph nodes =
match nodes with
| (currentNode :: rest) -> processNodes (removeNode graph currentNode) rest
| [] -> graph
let nodes = pathToNodes graph start path
processNodes graph nodes
おそらく、(個人的な)再帰的実装関数の引数にすることで、ミューテータを取り除くことができます。一度それをやったら、あなたはそれをすべて左または右に減らすことができることに気づくかもしれません。このプロセスの詳細については、[この記事](http://blog.ploeh.dk/2015/12/01/recurse)を参照してください。問題を折りたたみにすることができない場合は、[実装が末尾再帰的であることを確認してください](http://blog.ploeh.dk/2015/12/22/tail-recurse)。 –
私はこの特定の問題を手伝ってうれしいですが、私は 'clearEdges'が何をしているのか理解していません。あなたは 'input'が何であるかを詳しく調べることができますし、その値を無視してSeq.takeWhileをなぜ行うのでしょうか? –
機能を有効にしたい場合は、新しいグラフオブジェクトを作成する必要のある突然変異/副作用があってはなりません。また、構造を使ってグラフを記述することをお勧めします:type Node = {id:int;左:ノード。 right:Node} –