我正在尝试实现一个大致相当于SQL LEFT OUTER JOIN
的函数。这个和大多数实现之间的区别在于它有一个函数参数来决定哪些值用于没有正确匹配的左值,而不是自动使用null
。我尝试了很多变化来使F#编译器满意,但它们都没有工作。
let public LeftOuterJoin<'Left, 'Right, 'Key, 'Result>(
left : seq<'Left>,
right : seq<'Right>,
getLeftKey : Func<'Left, 'Key>,
getRightKey : Func<'Right, 'Key>,
getResult : Func<'Left, 'Right, 'Result>,
getDefaultResult : Func<'Left, 'Result>) =
let getGroups = Func<'Left, seq<'Right>, seq<'Result>>(fun (l, rs) ->
let rsList = rs.ToList();
if rsList.Any()
then rsList |> Seq.map(fun r -> getResult.Invoke(l, r))
else Enumerable.Repeat(getDefaultResult.Invoke(l), 1))
left.GroupJoin(right, getLeftKey, getRightKey, getGroups)
.SelectMany(id)
现在我收到错误这个表达式应该有类型&#39;&#39;左&#39;但是,getGroups
定义(l, rs)
中的lambda表达式的参数中有类型&#39; a *&#39; b&#39; 。对于获得所需结果的任何实现,无论是使用Func
还是标准F#函数作为参数,我都可以。我更喜欢F#中的惯用语,但我认为使用GroupJoin
和SelectMany
会很容易。
这是C#中的工作版本:
public static IEnumerable<TResult> LeftOuterJoin<TLeft, TRight, TKey, TResult>(
this IEnumerable<TLeft> first,
IEnumerable<TRight> second,
Func<TLeft, TKey> getLeftKey,
Func<TRight, TKey> getRightKey,
Func<TLeft, TRight, TResult> getResult,
Func<TLeft, TResult> getDefaultResult)
{
IEnumerable<TResult> getGroupResults (TLeft left, IEnumerable<TRight> rights)
{
rights = rights.ToList();
return rights.Any()
? rights.Select(r => getResult(left, r))
: Enumerable.Repeat(getDefaultResult(left), 1);
}
return first
.GroupJoin(second, getLeftKey, getRightKey, getGroupResults)
.SelectMany(seq => seq);
}
更新
这会编译并看起来更加惯用
let public LeftOuterJoin<'L, 'R, 'Key, 'Result>(
left : seq<'L>,
right : seq<'R>,
getLeftKey : 'L -> 'Key,
getRightKey : 'R -> 'Key,
getResult : 'L -> 'R -> 'Result,
getDefaultResult : 'L -> 'Result) : seq<'Result> =
let rLookup = right.ToLookup(fun r -> getRightKey(r));
seq {
for l in left do
let lKey = getLeftKey(l);
if rLookup.Contains(lKey)
then yield! rLookup.[lKey] |> Seq.map(getResult(l))
else yield getDefaultResult(l)
}