F#用有趣的东西替换ref变量
我有下面的F#函数,它利用一个ref变量来播种和跟踪一个运行的总数,有些东西告诉我这不符合fp的精神,甚至它本身也不是特别清晰的。我想要一些关于最清晰(可能的fp,但如果命令式方法更清晰,我愿意接受)的方向,用F#来表达这一点。请注意,selectItem实现了随机加权选择算法F#用有趣的东西替换ref变量,f#,refactoring,functional-programming,F#,Refactoring,Functional Programming,我有下面的F#函数,它利用一个ref变量来播种和跟踪一个运行的总数,有些东西告诉我这不符合fp的精神,甚至它本身也不是特别清晰的。我想要一些关于最清晰(可能的fp,但如果命令式方法更清晰,我愿意接受)的方向,用F#来表达这一点。请注意,selectItem实现了随机加权选择算法 type WeightedItem(id: int, weight: int) = member self.id = id member self.weight = weight let selectI
type WeightedItem(id: int, weight: int) =
member self.id = id
member self.weight = weight
let selectItem (items: WeightedItem list) (rand:System.Random) =
let totalWeight = List.sumBy (fun (item: WeightedItem) -> item.weight) items
let selection = rand.Next(totalWeight) + 1
let runningWeight = ref 0
List.find
(fun (item: WeightedItem) ->
runningWeight := !runningWeight + item.weight
!runningWeight >= selection)
items
let items = [new WeightedItem(1,100); new WeightedItem(2,50); new WeightedItem(3,25)]
let selection = selectItem items (new System.Random())
嗯,这里有一种方法是使用
折叠
,但它感觉不雅,而且总是遍历整个列表
type WeightedItem(id: int, weight: int) =
member self.id = id
member self.weight = weight
let selectItem (items: WeightedItem list) (rand:System.Random) =
let totalWeight = List.sumBy (fun (item: WeightedItem) -> item.weight) items
let selection = rand.Next(totalWeight) + 1
List.fold
(fun (runningWeight,found) (item: WeightedItem) ->
if not found then
let newRunningWeight = runningWeight + item.weight
newRunningWeight, newRunningWeight >= selection
else
(runningWeight,found))
(0,false)
items
|> fst
let items = [new WeightedItem(1,100)
new WeightedItem(2,50)
new WeightedItem(3,25)]
let selection = selectItem items (new System.Random())
嗯,这是一个带有Seq.scan的,但感觉也很难看
type WeightedItem(id: int, weight: int) =
member self.id = id
member self.weight = weight
let selectItem (items: WeightedItem list) (rand:System.Random) =
let totalWeight = List.sumBy (fun (item: WeightedItem) -> item.weight) items
let selection = rand.Next(totalWeight) + 1
Seq.scan
(fun (runningWeight,found,itemO) (item: WeightedItem) ->
if not found then
let newRunningWeight = runningWeight + item.weight
newRunningWeight, newRunningWeight >= selection, Some(item)
else
(runningWeight,found,itemO))
(0,false,None)
items
|> Seq.find (fun (rw,f,i) -> f)
|> (fun (rw,f,i) -> i.Value)
let items = [new WeightedItem(1,100)
new WeightedItem(2,50)
new WeightedItem(3,25)]
let selection = selectItem items (new System.Random())
嗯,这里有一些可变项和一个循环;但是仍然遍历整个列表
type WeightedItem(id: int, weight: int) =
member self.id = id
member self.weight = weight
let selectItem (items: WeightedItem list) (rand:System.Random) =
let totalWeight = List.sumBy (fun (item: WeightedItem) -> item.weight) items
let selection = rand.Next(totalWeight) + 1
let mutable runningWeight = 0
let mutable found = None
for item in items do
match found with
| None ->
runningWeight <- runningWeight + item.weight
if runningWeight >= selection then
found <- Some(item)
| _ -> ()
found.Value
let items = [new WeightedItem(1,100)
new WeightedItem(2,50)
new WeightedItem(3,25)]
let selection = selectItem items (new System.Random())
键入WeightedItem(id:int,weight:int)=
成员self.id=id
构件自重=重量
让我们选择项(项:WeightedItem列表)(rand:System.Random)=
让totalWeight=List.sumBy(乐趣(项目:WeightedItem)->item.weight)项目
让所选内容=随机数下一个(总重量)+1
设可变运行权重=0
让mutable找到=无
对于项目中的项目,请执行以下操作:
匹配
|无->
运行重量=选择
找到()
发现。价值
let items=[new WeightedItem(1100)
新权重编辑项(2,50)
新权重editem(3,25)]
let selection=selectItems项目(new System.Random())
这是三个中我最喜欢的。我期待着F#添加
break
的那一天。当然,您可以调用GetEnumerator
并获得完全控制权,但这也很难看。这里是一个使用递归函数的搜索算法版本。我的F#已经生锈了,当我们找不到任何东西时,我不知道该归还什么:
let rec find list item total =
match list with
| h::t -> if h > total then h else find t item total+h
| [] -> 0 //<-- return some sort of default to say can't find the item
就效率而言,Igor的答案可能是存储在列表中的项目的最佳答案,但由于Brian的扫描方法代表了一种重复序列操作模式,因此我建议使用更紧凑的变体:
let selectItem (items: WeightedItem list) (rand:System.Random) =
let totalWeight = List.sumBy (fun (item: WeightedItem) -> item.weight) items
let selection = rand.Next(totalWeight) + 1
items
|> Seq.scan (fun acc (item : WeightedItem) -> acc + item.weight) 0
|> Seq.skip 1 |> Seq.zip items
|> Seq.find (fun (i, rw) -> rw >= selection) |> fst
使用
Seq.unfold
构建累积运行重量的按需序列,然后使用Seq.pick
搜索具有足够大的运行重量的第一个元素:
let gen = function
| _, [] -> None
| runningWeight, item::items ->
let runningWeight = runningWeight + item.weight
Some((if runningWeight >= selection then Some item else None), (runningWeight, items))
Seq.unfold gen (0, xs) |> Seq.pick id
谢谢,我想折叠可能也很合适(播种和积累!),但唉,我不能忍受浏览整个列表。哇!是的,那有点多。但我非常感激看到一个替代方法,因为它本身!是的,我在玩for/in,但没有找到休息的地方。我开始认为我的原始实现离基础不远。事实上,一个局部尾部递归函数在这里可以很好地工作。很好。这正是我想要的。谢谢Jon,我学到了很多新东西来解决这个问题。但是为了让类型推断工作并满足编译器的要求,我需要做(0,items)|>Seq.unfold((gen body在此))|>Seq.pick idAh,这是因为您将weightedItem
定义为一个类,因此F#无法推断项的类型。weight
。在我的测试中,我将其定义为记录类型typeweightedItem={id:int;weight:int}
。
let gen = function
| _, [] -> None
| runningWeight, item::items ->
let runningWeight = runningWeight + item.weight
Some((if runningWeight >= selection then Some item else None), (runningWeight, items))
Seq.unfold gen (0, xs) |> Seq.pick id