简体   繁体   English

如何使用 Ramda 将组合的 function 应用于列表中的每个 object?

[英]How do I apply a composed function to each object in a list using Ramda?

I'm building a simple app using RamdaJS that aims to take a list of objects that represent US states, and for each state, it should calculate the number of electoral votes and add that value as a new property to each object, called electoralVotes .我正在使用 RamdaJS 构建一个简单的应用程序,该应用程序旨在获取代表美国各州的对象列表,并且对于每个 state,它应该计算选举人票的数量并将该值作为新属性添加到每个 object,称为electoralVotes票。 The basic gist of the calculation itself (as inaccurate as it may be) is to divide the population by 600000 , round that number down, and if the rounded-down number is 0 , round it up to 1 .计算本身的基本要点(尽管可能不准确)是将总体除以600000 ,然后将该数字向下舍入,如果向下舍入的数字为0 ,则将其向上舍入1

For simplicity, the array of states only includes a state name and population for each state:为简单起见, states数组仅包含 state 名称和每个 state 的人口:

const states = [
  { state: 'Alabama', population: 4833722 },
  { state: 'Alaska', population: 735132 },
  { state: 'Arizona', population: 6626624 },
  // ... etc.
];

I created a function called getElectoralVotesForState that is created with nested levels of composition (A composed function that is built using another composed function).我创建了一个名为getElectoralVotesForState的 function,它是使用嵌套的组合级别创建的(使用另一个组合函数构建的组合 function)。 This function takes a state object, examines its population property, then calculates and returns the corresponding number of electoral votes.这个 function 取一个 state object,检查它的population属性,然后计算并返回相应的选举人票数。

const R = require('ramda');

// This might not be factually accurate, but it's a ballpark anyway
const POP_PER_ELECTORAL_VOTE = 600000;

const populationLens = R.lensProp("population");

// Take a number (population) and calculate the number of electoral votes
// If the rounded-down calculation is 0, round it up to 1
const getElectoralVotes = R.pipe(
  R.divide(R.__, POP_PER_ELECTORAL_VOTE),
  Math.floor,
  R.when(R.equals(0), R.always(1))
);

// Take a state object and return the electoral votes
const getElectoralVotesForState = R.pipe(
  R.view(populationLens),
  getElectoralVotes
);

If I want to pass in a single state to the getElectoralVotesForState function, it works fine:如果我想将单个 state 传递给getElectoralVotesForState function,它可以正常工作:

const alabama = { state: 'Alabama', population: 4833722 };
const alabamaElectoralVotes = getElectoralVotesForState(alabama); // Resolves to 8

While this seems to work for a single object, I can't seem to get this to apply to an array of objects.虽然这似乎适用于单个 object,但我似乎无法将其应用于对象数组 My guess is that the solution might look something like this:我的猜测是解决方案可能看起来像这样:

const statesWithElectoralVotes = R.map(
  R.assoc("electoralVotes", getElectoralVotesForState)
)(states);

This does add an electoralVotes property to each state, but it's a function and not a resolved value.确实为每个 state 添加了一个electoralVotes属性,但它是一个 function 而不是解析值。 I'm sure it's just a silly thing I'm getting wrong here, but I can't figure it out.我确定这只是一件愚蠢的事情,我在这里弄错了,但我无法弄清楚。

What am I missing?我错过了什么?

To apply a function to to an array of items use R.map .要将 function 应用于项目数组,请使用R.map Since you want the value you don't need to R.assoc :由于您需要不需要R.assoc的值:

 const POP_PER_ELECTORAL_VOTE = 600000; const populationLens = R.lensProp("population"); const getElectoralVotes = R.pipe( R.divide(R.__, POP_PER_ELECTORAL_VOTE), Math.floor, R.when(R.equals(0), R.always(1)) ); const getElectoralVotesForState = R.pipe( R.view(populationLens), getElectoralVotes ); const mapStates = R.map(getElectoralVotesForState); const states = [{"state":"Alabama","population":4833722},{"state":"Alaska","population":735132},{"state":"Arizona","population":6626624}]; const result = mapStates(states); console.log(result);
 <script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js" integrity="sha512-rZHvUXcc1zWKsxm7rJ8lVQuIr1oOmm7cShlvpV0gWf0RvbcJN6x96al/Rp2L2BI4a4ZkT2/YfVe/8YvB2UHzQw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

In addition, the lens is a bit redundant here, take the value of population using R.prop .另外,这里的镜头有点多余,取人口的值使用R.prop I would also replace R.when with R.max .我还将用R.when替换R.max

 const POP_PER_ELECTORAL_VOTE = 600000; const getElectoralVotesForState = R.pipe( R.prop('population'), R.divide(R.__, POP_PER_ELECTORAL_VOTE), Math.floor, R.max(1) ); const mapStates = R.map(getElectoralVotesForState); const states = [{"state":"Alabama","population":4833722},{"state":"Alaska","population":735132},{"state":"Arizona","population":6626624}]; const result = mapStates(states); console.log(result);
 <script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js" integrity="sha512-rZHvUXcc1zWKsxm7rJ8lVQuIr1oOmm7cShlvpV0gWf0RvbcJN6x96al/Rp2L2BI4a4ZkT2/YfVe/8YvB2UHzQw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

And if you want to add the property to each object, you can use R.chain in conjunction with R.assoc :如果您想将属性添加到每个 object,您可以将R.chainR.assoc结合使用:

 const POP_PER_ELECTORAL_VOTE = 600000; const getElectoralVotesForState = R.pipe( R.prop('population'), R.divide(R.__, POP_PER_ELECTORAL_VOTE), Math.floor, R.max(1) ); const mapStates = R.map( R.chain(R.assoc("electoralVotes"), getElectoralVotesForState) ); const states = [{"state":"Alabama","population":4833722},{"state":"Alaska","population":735132},{"state":"Arizona","population":6626624}]; const result = mapStates(states); console.log(result);
 <script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js" integrity="sha512-rZHvUXcc1zWKsxm7rJ8lVQuIr1oOmm7cShlvpV0gWf0RvbcJN6x96al/Rp2L2BI4a4ZkT2/YfVe/8YvB2UHzQw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

I think Ori Drori answers your question well.我认为 Ori Drori 很好地回答了你的问题。 I have no suggested improvements.我没有建议的改进。 But I want to show that it's not too hard to code the current apportionment method used for the US Congress, the Huntington-Hill Method :但我想表明,编写当前用于美国国会的分配方法Huntington-Hill Method并不难:

 // Huntington-Hill apportionment method const apportion = (total) => (pops) => huntingtonHill (total - pops.length, pops.map (pop => ({...pop, seats: 1}))) // method of equal proportions const huntingtonHill = (toFill, seats, state = nextSeat (seats)) => toFill <= 0? seats: huntingtonHill (toFill - 1, seats.map (s => s.state == state? {...s, seats: s.seats + 1}: s)) // find state to assign the next seat const nextSeat = (seats) => seats.map (({state, population, seats}) => [state, population * Math.sqrt(1 / (seats * (seats + 1)))]).sort (([_, a], [_1, b]) => b - a) [0] [0] // ideally, use a better max implementation that sort/head, but low priority // convert census data to expected object format const restructure = results => results.slice (1) // remove header.map (([population, state]) => ({state, population})) // make objects.filter (({state}) =>, ['District of Columbia'. 'Puerto Rico'].includes (state)) // remove non-states:sort (({state, s1}: {state? s2}) => s1 < s2: -1? s1 > s2: 1: 0) // alphabetize fetch ('https.//api.census?gov/data/2021/pep/population,get=POP_2021:NAME&for=state.*').then (res => res.json()).then (restructure).then (apportion (435)).then (console.log).catch (console .warn)
 .as-console-wrapper {max-height: 100%;important: top: 0}

Here we call the US Census API to fetch the populations of each state, remove Washington DC and Puerto Rico, reformat these results to your {state, population} input format, and then call apportion (435) with the array of values.在这里,我们调用美国人口普查 API 来获取每个 state 的人口,删除华盛顿特区和波多黎各,将这些结果重新格式化为您的{state, population}输入格式,然后使用值数组调用apportion (435) (If you have the data already in that format, you can just call apportion (435) ), and it will assign one seat to each state and then use the Huntington-Hill method to assign the remaining seats. (如果您已经有该格式的数据,您可以调用apportion (435) ),它会为每个 state 分配一个席位,然后使用 Huntington-Hill 方法分配剩余的席位。

It does this by continually calling nextSeat , which divides each state's population by the geometric mean of its current number of seats and the next higher number, then choosing the state with the largest value.它通过不断调用nextSeat来做到这一点,它将每个州的人口除以其当前席位数的几何平均值和下一个更高的数字,然后选择具有最大值的 state。

This does not use Ramda for anything.这不会将 Ramda 用于任何事情。 Perhaps we would clean this up slightly with some Ramda functions (for example, replacing pop => ({...pop, seats: 1}) with assoc('seat', 1) ), but it would not likely be a large gain.也许我们会用一些 Ramda 函数稍微清理一下(例如,用assoc('seat', 1)替换pop => ({...pop, seats: 1}) ),但它可能不会很大获得。 I saw this question because I pay attention to the Ramda tag.我看到这个问题是因为我关注 Ramda 标签。 But the point here is that the actual current method of apportionment is not that difficult to implement, if you happen to be interested.但这里的重点是,如果您碰巧感兴趣,实际的当前分配方法并不难实现。

You can see how this technique is used to compare different sized houses in an old gist of mine.你可以看到这种技术是如何用于比较不同大小的房子的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM