#f# #functional-programming #grouping #aggregate
#f# #функциональное программирование #группировка #агрегировать
Вопрос:
Я довольно новичок в функциональном программировании, и поэтому у нас с F # возникли серьезные проблемы с поиском правильного решения этой проблемы.
У меня есть последовательность типов записей, скажем, как:
type Invoice = {
GrpText : string
GrpRef : int
Article : int
Wkz : int
Text : string
Price : decimal
InvoiceRef : int
}
Теперь я хочу сгруппировать или агрегировать последовательность Invoices
по заданным критериям и, т.Е. суммировать их цены. Invoices
то, что не соответствует критериям, не должно группироваться и просто возвращаться как есть.
Функция критериев может выглядеть следующим образом:
/// determines whether to group the two given Invoice items or not
let criteria item toCompareWith =
(item.GrpRef > 0 amp;amp; item.Article = toCompareWith.Article
amp;amp; item.InvoiceRef = toCompareWith.InvoiceRef) ||
(item.Wkz <> 0 amp;amp; item.Text = toCompareWith.Text)
Агрегирование или группировка могут выглядеть следующим образом:
/// aggregate the given Invoice items
let combineInvoices item1 item2 =
{item1 with Price = item1.Price item2.Price; Wkz = 0}
Проблема выглядит довольно простой, но в настоящее время я недостаточно разбираюсь в функциональном программировании, чтобы соединить точки.
Редактировать:
Я просто изменил criteria
функцию, чтобы лучше показать, что она может быть немного сложнее, чем группировка по одному или нескольким полям.
Ответ №1:
Если я чего-то не упустил, есть два шага: группировать и сокращать. Самый простой способ сгруппировать Seq.groupBy
. Поскольку вы хотите использовать пользовательское равенство, вам нужно либо применить [<CustomEquality>]
атрибут к вашему типу и переопределить Equals
и GetHashCode
, либо создать собственную функцию генерации ключей, которая использует вашу концепцию равенства. Вот пример последнего.
//custom key generator
let genKeyWith compare =
let lookup = ResizeArray()
fun item ->
match Seq.tryFindIndex (compare item) lookup with
| Some idx -> idx
| None ->
lookup.Add(item)
lookup.Count - 1
Использование
let getKey = genKeyWith criteria
let invoices = Seq.init 10 (fun _ -> Unchecked.defaultof<Invoice>)
invoices
|> Seq.groupBy getKey
|> Seq.map (fun (_, items) -> Seq.reduce combineInvoices items)
Ответ №2:
Что-то вроде этого, где Defaultinvoice — это своего рода счет-фактура «0»
input
|> Seq.groupBy (fun t -> t.Article)
|> Seq.map (fun (a,b) -> a, (b |> List.fold (fun (c,d) -> combineInvoices c d) Defaultinvoice)
РЕДАКТИРОВАТЬ — для более сложной функции объединения.
Поэтому, если ваша функция объединения сложнее, лучшим подходом, вероятно, является использование рекурсии, и я думаю, что будет трудно избежать решения O (n ^ 2). Я бы выбрал что-то вроде
let rec overallfunc input =
let rec func input (valid:ResizeArray<_>) =
match input with
|[] -> valid //return list
|h::t ->
match valid.tryfindIndex (fun elem -> criteria h elem) with //see if we can combine with something
|Some(index) -> valid.[index] <- combineInvoices (valid.[index]) h //very non-functional here
|None -> valid.Add(h)
func t valid //recurse here
func input (ResizeArray<_>())
Это решение является крайне нефункциональным и, вероятно, очень медленным, но оно должно работать для произвольно сложных комбинированных функций
Комментарии:
1. Я думаю, что это идет в правильном направлении, но я думаю, что осталась одна проблема. Вы просто группируете по
Article
, и я не совсем уверен, где и как я могу интегрировать этуcriteria
функцию или еще более сложную.2. @kongo2002 что, если вы сделаете это
type comparison = {InvoiceRef:int;Article:int}
, а затем добавитеmember x.Comp() = {InvoiceRef=x.InvoceRef;Article=x.Article
в свой элемент счета-фактуры — тогда вы можете сделатьgroupBy (fun t -> t.Comp())
— хотя вы не можете получить сравнение groupref — возможно, для этого используйте фильтр?3. Хорошо, тогда это будет основной вопрос, как применить фильтр к вашей описанной конструкции? 🙂 Кстати, я только что отредактировал свой первоначальный пост, чтобы лучше показать возможный характер / сложность
criteria
функции.