javascript - 函数式编程 : list conditional branching/filtering (Javascript)

标签 javascript functional-programming ramda.js

我拥有命令式编程背景 (Java),并开始尝试更好地理解 FP 概念。特别是条件分支/过滤及其如何应用于数据流/数据列表。

这是一个愚蠢的人为示例...我们有一个玩家列表,并希望根据他们的技能水平将他们分成不同的列表。基本的命令式方法可能类似于:

const excluded = []; // LOW skilled
const reserves = []; // only MEDIUM/HIGH skilled
const team = []; // only HIGH skilled

const allPlayers = [
    {
        name: 'personh1',
        skillLevel: 'HIGH'
    },
    {
        name: 'personh2',
        skillLevel: 'HIGH'
    },
    {
        name: 'personh3',
        skillLevel: 'HIGH'
    },
    {
        name: 'personm1',
        skillLevel: 'MEDIUM'
    },
    {
        name: 'personm2',
        skillLevel: 'MEDIUM'
    },
    {
        name: 'personl1',
        skillLevel: 'LOW'
    },
    {
        name: 'personl2',
        skillLevel: 'LOW'
    }
];

const maxTeamSize = 2;
const maxReservesSize = 2;

allPlayers.forEach(p => {
    if (p.skillLevel === 'HIGH') {
        if (team.length < maxTeamSize) {
            team.push(p);
        } else {
            reserves.push(p);
        }
    } else if (p.skillLevel === 'MEDIUM') {
        if (reserves.length < maxReservesSize) {
            reserves.push(p);
        } else {
            excluded.push(p);
        }
    } else {
        excluded.push(p);
    }
});

// functions defined elsewhere...
notifyOfInclusion(team.concat(reserves));
notifyOfExclusion(excluded);

我可以以更实用的方式完成此操作:(使用 JS 和 Ramda 库):

team = R.slice(0, maxTeamSize, R.filter(p => p.skillLevel === 'HIGH', allPlayers));
reserves = R.slice(0, maxReservesSize, R.filter(p => (p.skillLevel === 'HIGH' || p.skillLevel === 'MEDIUM') && !R.contains(p, team), allPlayers));
excluded = R.filter(p => !R.contains(p, team) && !R.contains(p, reserves), allPlayers);

notifyOfInclusion(team.concat(reserves));
notifyOfExclusion(excluded);

但它看起来很粗糙,重复而且不是很明确。从功能性 POV 实现这样的事情的更好(更优雅/声明性)的方法是什么?在任何答案中使用 Ramda 都是一种奖励,但不是必需的。谢谢。

最佳答案

我的版本比你的更具声明性,但只有一点点:

const skills = groupBy(prop('skillLevel'), allPlayers)
const ordered = flatten([skills['HIGH'], skills['MEDIUM'], skills['LOW']])
const team = filter(propEq('skillLevel', 'HIGH'), take(maxTeamSize, ordered))
const reserves = reject(propEq('skillLevel', 'LOW'), 
                        take(maxReservesSize, drop(length(team), ordered)))
const excluded = drop(length(team) + length(reserves), ordered)

这个假设你只希望团队中有高技能的球员,即使他们没有足够的人来填补空缺。如果您想在这种情况下包括中等技能的玩家,那么您可以将 filter(propEq('skillLevel', 'HIGH') 替换为 reject(propEq('skillLevel', 'LOW ')。如果您想填充到 max[Team/Reserves]Size,即使他们与技能水平不匹配,您也可以删除 filter/reject 调用。(这也会使代码看起来更简洁。)

我最初尝试为所有这些函数使用一个函数是非常糟糕的,它甚至不能像这些函数那样工作:

chain(
  selected => allPlayers => assoc(
    'excluded', 
    difference(allPlayers, concat(selected.team, selected.reserves)), 
    selected
  ),
  pipe(
    groupBy(prop('skillLevel')),
    lift(concat)(prop('HIGH'), prop('MEDIUM')),
    eligible => ({
      team: take(maxTeamSize, eligible),
      reserves: take(maxReservesSize, drop(maxTeamSize, eligible))
    }),
  )
)(allPlayers)

当然,您也可以在排序列表的单个 reduce 中执行此操作,这可能仍然是您解决实际问题的最佳选择,但我怀疑规则与规则之间的交互各种结果列表的长度也将允许在这里编写非常漂亮的代码。

所有这些都可以在 Ramda REPL 上找到.

关于javascript - 函数式编程 : list conditional branching/filtering (Javascript),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47364815/

相关文章:

javascript - Jquery 单击函数不适用于弹出框内的按钮

function - 在 Haskell 中,(+) 是一个函数,((+) 2) 是一个函数,((+) 2 3) 是 5。到底发生了什么?

javascript - `lt` 、 `lte` 、 `gt` 和 `gte` 的翻转版本的好名字?

ramda.js - Ramda 从数组减少到对象?

javascript - 如何传递多个参数以与 Ramda.JS 组合

javascript - 如何在给定的对象结构中使用 Ramda 执行嵌套更新?

javascript - 第一次加载谷歌图表时,垂直轴标签没有出现

javascript - ExtJS:是否可以在网格面板中声明绑定(bind)的 if else 条件?

javascript - 如何滚动到仍未呈现的页脚处的图像?

functional-programming - 如何在 Halogen 5 的 handleAction 函数中使用 logShow