我想对类的项进行排序,并在Collection-Classes中收集它们,List-Member旁边还包含排序过程所需的更多信息。
以下示例是我的问题的一个非常简化的示例。虽然没有意义,但我希望它仍然有助于理解我的问题。
type ItemType = Odd|Even //realworld: more than two types possible
type Item(number) =
member this.number = number
member this.Type = if (this.number % 2) = 0 then Even else Odd
type NumberTypeCollection(numberType:ItemType , ?items:List<Item>) =
member this.ItemType = numberType
member val items:List<Item> = defaultArg items List.empty<Item> with get,set
member this.append(item:Item) = this.items <- item::this.items
let addToCollection (collections:List<NumberTypeCollection>) (item:Item) =
let possibleItem =
collections
|> Seq.where (fun c -> c.ItemType = item.Type) //in my realworld code, several groups may be returned
|> Seq.tryFind(fun _ -> true)
match possibleItem with
|Some(f) -> f.append item
collections
|None -> NumberTypeCollection(item.Type, [item]) :: collections
let rec findTypes (collections:List<NumberTypeCollection>) (items:List<Item>) =
match items with
| [] -> collections
| h::t -> let newCollections = ( h|> addToCollection collections)
findTypes newCollections t
let items = [Item(1);Item(2);Item(3);Item(4)]
let finalCollections = findTypes List.empty<NumberTypeCollection> items
我对 addToCollection 方法不满意,因为它要求 NumberTypeCollection 中的项目是相互的。也许还有其他问题。
什么是解决此问题的适当功能解决方案?
编辑:对不起。可能代码太简单了。这是一个更复杂的例子,应该有希望说明为什么我选择了共同的班级成员(虽然这可能仍然是错误的决定):open System
type Origin = Afrika|Asia|Australia|Europa|NorthAmerika|SouthAmerica
type Person(income, taxrate, origin:Origin) =
member this.income = income
member this.taxrate = taxrate
member this.origin = origin
type PersonGroup(origin:Origin , ?persons:List<Person>) =
member this.origin = origin
member val persons:List<Person> = defaultArg persons List.empty<Person> with get,set
member this.append(person:Person) = this.persons <- person::this.persons
//just some calculations to group people into some subgroups
let isInGroup (person:Person) (personGroup:PersonGroup) =
let avgIncome =
personGroup.persons
|> Seq.map (fun p -> float(p.income * p.taxrate) / 100.0)
|> Seq.average
Math.Abs ( (avgIncome / float person.income) - 1.0 ) < 0.5
let addToGroup (personGroups:List<PersonGroup>) (person:Person) =
let possibleItem =
personGroups
|> Seq.where (fun p -> p.origin = person.origin)
|> Seq.where (isInGroup person)
|> Seq.tryFind(fun _ -> true)
match possibleItem with
|Some(f) -> f.append person
personGroups
|None -> PersonGroup(person.origin, [person]) :: personGroups
let rec findPersonGroups (persons:List<Person>) (personGroups:List<PersonGroup>) =
match persons with
| [] -> personGroups
| h::t -> let newGroup = ( h|> addToGroup personGroups)
findPersonGroups t newGroup
let persons = [Person(1000,20, Afrika);Person(1300,22,Afrika);Person(500,21,Afrika);Person(400,20,Afrika)]
let c = findPersonGroups persons List.empty<PersonGroup>
我可能需要强调的是:可能有几个具有相同来源的不同群体。
答案 0 :(得分:2)
托马斯&#39;使用groupby的解决方案是最佳方法,如果您只想生成一次集合,它简单而简洁。
如果您希望能够针对此类问题以功能性,引用透明的方式添加/删除项目,建议您远离seq
并开始使用Map
。
你有一个基本上类似字典的设置。您有一个唯一的密钥和一个值。与字典等效的函数F#是Map
,它是基于AVL树的不可变数据结构。您可以在O(log n)时间插入,删除和搜索。当您从Map
追加/删除时,会保留旧的Map
并收到新的Map
。
以下是以此风格表示的代码
type ItemType =
|Odd
|Even
type Item (number) =
member this.Number = number
member this.Type = if (this.Number % 2) = 0 then Even else Odd
type NumTypeCollection = {Items : Map<ItemType, Item list>}
/// Functions on NumTypeCollection
module NumberTypeCollection =
/// Create empty collection
let empty = {Items = Map.empty}
/// Append one item to the collection
let append (item : Item) numTypeCollection =
let key = item.Type
match Map.containsKey key numTypeCollection.Items with
|true ->
let value = numTypeCollection.Items |> Map.find key
let newItems =
numTypeCollection.Items
|> Map.remove key
|> Map.add key (item :: value) // append item
{Items = newItems }
|false -> {Items = numTypeCollection.Items |> Map.add key [item]}
/// Append a list of items to the collections
let appendList (item : Item list) numTypeCollection =
item |> List.fold (fun acc it -> append it acc) numTypeCollection
然后使用:
调用它let items = [Item(1);Item(2);Item(3);Item(4)]
let finalCollections = NumberTypeCollection.appendList items (NumberTypeCollection.empty)
答案 1 :(得分:1)
如果我正确理解您的问题,您就会尝试按类型对项目进行分组。最简单的方法是使用标准库函数Seq.groupBy
。以下内容应实现与代码相同的逻辑:
items
|> Seq.groupBy (fun item -> item.Type)
|> Seq.map (fun (key, values) ->
NumberTypeCollection(key, List.ofSeq values))
答案 2 :(得分:0)
也许还有其他问题。
可能。这很难说,因为很难发现OP代码的目的......仍然:
为什么你甚至需要一个Item
课程?相反,你可以只使用itemType
函数:
let itemType i = if i % 2 = 0 then Even else Odd
此功能为referentially transparent,这意味着您可以根据需要将其替换为其值。这使得它与属性getter方法一样好,但是现在你已经节省了自己引入新类型。
为什么定义NumberTypeCollection
类?为什么不是简单记录?
type NumberTypeList = { ItemType : ItemType; Numbers : int list }
您可以像这样实现addToCollection
:
let addToCollection collections i =
let candidate =
collections
|> Seq.filter (fun c -> c.ItemType = (itemType i))
|> Seq.tryHead
match candidate with
| Some x ->
let x' = { x with Numbers = i :: x.Numbers }
collections |> Seq.filter ((<>) x) |> Seq.append [x']
| None ->
collections |> Seq.append [{ ItemType = (itemType i); Numbers = [i] }]
不可变,它不会改变输入collections
,而是返回NumberTypeList
的新序列。
另请注意使用Seq.tryHead
代替Seq.tryFind(fun _ -> true)
。
但是,如果您尝试对商品进行分组,那么Tomas&#39;建议使用Seq.groupBy
更合适。