2017-11-18 129 views
3

我从命令式编程背景(JAVA),并开始尝试理解FP概念更好地很。特别是条件分支/过滤以及它如何应用于流/数据列表。函数式编程:列表条件分支/过滤(JavaScript)的

这里是一个傻人为的例子......我们的球员名单,并想将它们分为基于他们的技能水平不同的名单。一个基本的必要方法可以是这样的:

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中实现类似的更好(更优雅/陈述式)的方式?在任何答案中使用拉姆达将是一个奖金,但不是必需的。谢谢。

+2

如果您不介意队伍中的“MEDIUM”球员和储备中的“LOW”球员在没有足够的球员的情况下,您可以使用'R.sortBy'技能水平,然后选择'R.slice'。 – GingerPlusPlus

回答

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) 

这一个假设,你只需要在球队高技能的球员,即使没有足够的人来填补斑点。如果你想包括在这种情况下,中等技能的玩家,那么你可以用reject(propEq('skillLevel', 'LOW')取代filter(propEq('skillLevel', 'HIGH')。如果你想填写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

这个回答是不是真正发挥作用,我想了一些逻辑移动到某个变量,这replresent SOM依赖使用,如果需要的话,更多的群体有一个统一的访问,并在集团的实际项目已去决策机制。

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

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);
.as-console-wrapper { max-height: 100% !important; top: 0; }

+0

谢谢。我故意让逻辑简单来表示我感兴趣的更一般的问题。我想我真正了解的是如何更好地理解如何处理涉及过滤不同数据集的问题,其中过滤条件是相互依赖的数据集本身和一些外部状态的变化状态......然后对每个结果应用一些操作。我很好奇看看其他人如何更好地在功能上/声明上构建代码,以及他们将使用哪些概念(地图,分组,过滤,撰写等) –

1

您正在寻找R.groupBy

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));
<script src="//cdn.jsdelivr.net/npm/[email protected]/dist/ramda.min.js"></script>