添加到类成员列表的功能方式

时间:2015-11-24 15:35:57

标签: f# functional-programming

我想对类的项进行排序,并在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>

我可能需要强调的是:可能有几个具有相同来源的不同群体。

3 个答案:

答案 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更合适。