函数式编程:列出条件分支/过滤(Javascript)

时间:2017-11-18 10:07:24

标签: 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都是奖励,但不是必需的。感谢。

3 个答案:

答案 0 :(得分:3)

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

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)

这个假设您只想要团队中技能娴熟的球员,即使他们没有足够的球员来填补这些位置。如果你想在这种情况下包括具有MEDIUM技能的玩家,那么你可以用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

上找到

答案 1 :(得分:1)

虽然这个答案并没有真正起作用,但我会将一些逻辑转移到某个变量上,如果有必要,可以重新表示som依赖关系,以便使用统一访问和决策机制的更多组,实际项目必须在该组中进行。< / p>

毕竟逻辑很简单,只需将skillLevel作为起始级别并迭代到下一个较低级别,直到找到一个长度小于该组给定最大值的组。然后将项目推送到该组。

&#13;
&#13;
const
    excluded = [], // LOW skilled
    reserves = [], // only MEDIUM/HIGH skilled
    team = [],     // only HIGH skilled
    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' }],
    maxTeamSize = 2,
    maxReservesSize = 2,
    temp = { HIGH: team, MEDIUM: reserves, LOW: excluded },
    lowerLevel = { HIGH: 'MEDIUM', MEDIUM: 'LOW' },
    max = { HIGH: maxTeamSize, MEDIUM: maxReservesSize, LOW: Infinity };

allPlayers.forEach(p => {
    var level = p.skillLevel;
    while (temp[level].length >= max[level]) {
        level = lowerLevel[level];
    }
    temp[level].push(p);
});

console.log(team);
console.log(reserves);
console.log(excluded);
&#13;
.as-console-wrapper { max-height: 100% !important; top: 0; }
&#13;
&#13;
&#13;

答案 2 :(得分:1)

您正在寻找R.groupBy

&#13;
&#13;
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 skillLevel = R.prop('skillLevel');

console.log(R.groupBy(skillLevel, allPlayers));
&#13;
<script src="//cdn.jsdelivr.net/npm/ramda@0.25/dist/ramda.min.js"></script>
&#13;
&#13;
&#13;