簡體   English   中英

如何在 Ramda 中組合多個減速器?

[英]How can I combine multiple reducers in Ramda?

我正在嘗試通過組合幾個不同的功能來構建報告。 我已經能夠使用一些香草 javascript 獲得我想要的東西,但這太不穩定了,我知道如果我可以使用圖書館我會更好。 Ramda 似乎是對的,但我遇到了障礙,如果有人能給我推動正確的方向,我將不勝感激。

我將從不同文件中導入函數,並在最后一分鍾將它們拼接在一起以生成我需要的報告。

讓我們假設這是我的代碼:

const data = [
  { name: 'fred', age: 30, hair: 'black' },
  { name: 'wilma', age: 28, hair: 'red' },
  { name: 'barney', age: 29, hair: 'blonde' },
  { name: 'betty', age: 26, hair: 'black' }
]
const partA = curry((acc, thing) => {
  if (!acc.names) acc.names = [];
  acc.names.push(thing.name);
  return acc;
})
const partB = curry((acc, thing) => {
  if (!acc.ages) acc.ages = [];
  acc.ages.push(thing.age);
  return acc;
})
const partC = curry((acc, thing) => {
  if (!acc.hairColors) acc.hairColors = [];
  acc.hairColors.push(thing.hair);
  return acc;
})

我似乎無法找到一種將 partA + partB + partC 函數壓縮在一起的好方法,以便我得到這個:

{
    ages: [30, 28, 29, 26],
    hairColors: ["black", "red", "blonde", "black"],
    names: ["fred", "wilma", "barney", "betty"]
}

這有效,但很可怕。

reduce(partC, reduce(partB, reduce(partA, {}, data), data), data)

這是我可以忍受的一個,但我確信它不可能是正確的。

const allThree = (acc, thing) => {
  return partC(partB(partA(acc, thing), thing), thing)
}
reduce(allThree, {}, data)

我已經嘗試過 compose、pipe、reduce、reduceRight 和 into 以及其他一些,所以很明顯我在這里遺漏了一些非常基本的東西。

您可以使用 R.apply 規范創建 object。 對於每個屬性,使用 R.pluck 從數組中獲取值:

 const { pluck, applySpec } = R const fn = applySpec({ ages: pluck('age'), hairColors: pluck('hair'), named: pluck('name'), }) const data =[{"name":"fred","age":30,"hair":"black"},{"name":"wilma","age":28,"hair":"red"},{"name":"barney","age":29,"hair":"blonde"},{"name":"betty","age":26,"hair":"black"}] const result = fn(data) 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"></script>

更通用的版本接受具有未來屬性名稱的 object 和要采摘的鍵,將 object 映射到帶有相關鍵的咖喱采摘,然后將其與 ZE1E1D3D40573127E9EE0480CAF1283DZ 一起使用:

 const { pipe, map, pluck, applySpec } = R const getArrays = pipe(map(pluck), applySpec) const fn = getArrays({ 'ages': 'age', hairColors: 'hair', names: 'name' }) const data =[{"name":"fred","age":30,"hair":"black"},{"name":"wilma","age":28,"hair":"red"},{"name":"barney","age":29,"hair":"blonde"},{"name":"betty","age":26,"hair":"black"}] const result = fn(data) 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"></script>

您可以對 vanilla JS 執行相同操作,使用Array.map()從數組的項目中提取一個值。 To use a spec object, convert to entries using Object.entries() , map each array to get the relevant value, and then convert back to an object using Object.fromEntries() :

 const getArrays = keys => arr => Object.fromEntries( Object.entries(keys).map(([keyName, key]) => [keyName, arr.map(o => o[key])]) ); const fn = getArrays({ 'ages': 'age', hairColors: 'hair', names: 'name' }) const data =[{"name":"fred","age":30,"hair":"black"},{"name":"wilma","age":28,"hair":"red"},{"name":"barney","age":29,"hair":"blonde"},{"name":"betty","age":26,"hair":"black"}] const result = fn(data) console.log(result)

我會使用reducemergeWith

您可以使用mergeWith來合並兩個對象。 當發生沖突時(例如,兩個對象中都存在一個鍵) function 應用於兩個值:

mergeWith(add, {a:1,b:2}, {a:10,b:10});
//=> {a:11,b:12}

因此,在減速器中,您的第一個 object 是累加器(以{}開頭),第二個 object 在每次迭代時從列表中取出。

unapply(flatten)(a, b)技巧在后台執行此操作: flatten([a, b])

 const compact = reduce(mergeWith(unapply(flatten)), {}); console.log( compact([ { name: 'fred', age: 30, hair: 'black' }, { name: 'wilma', age: 28, hair: 'red' }, { name: 'barney', age: 29, hair: 'blonde' }, { name: 'betty', age: 26, hair: 'black' } ] ));
 <script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js" integrity="sha512-rZHvUXcc1zWKsxm7rJ8lVQuIr1oOmm7cShlvpV0gWf0RvbcJN6x96al/Rp2L2BI4a4ZkT2/YfVe/8YvB2UHzQw==" crossorigin="anonymous"></script> <script>const {reduce, mergeWith, unapply, flatten} = R;</script>

您所描述的可怕實際上是執行此操作的一般方法,但它假設您的減少是相互獨立的。 在這種情況下,它們不是。

對於您描述的特定情況,我喜歡reduce(mergeWith)方法,盡管我會使用較少的技巧:

const compact = R.compose( R.reduce(R.mergeWith(R.concat))({}), R.map(R.map(R.of)) );

map(map(of))name: "fred"更改為name: ["fred"] ,因此這些值可以與concat合並。

已經有幾種很好的方法可以解決這個問題。 來自 customcommander 和 jmw 的單行代碼令人印象深刻。 不過,我更喜歡 OriDrori 的applySpec解決方案,因為它看起來更明顯(與其他兩個不同,它允許您直接更改您請求的字段名稱(“hair” => “hairColors”等) .)

但是讓我們假設您確實在尋找更多的方法來尋找如何使用這三個函數來做您想要的組合,這只是作為示例。

它們不按您想要的方式組合的原因是它們都采用兩個參數。 您想將不斷變化的累加器和單個事物傳遞給每個 function。 Typical composition only passes one parameter along (except possibly for the first function called.) R.compose and R.pipe simply won't do what you want.

但是寫我們自己的作文function相當簡單。 我們稱它為recompose ,並像這樣構建它:

 const recompose = (...fns) => (a, b) => fns.reduce ((v, fn) => fn (v, b), a) const partA = curry((acc, thing) => {if (.acc.names) acc;names = []. acc.names.push(thing;name); return acc,}) const partB = curry((acc. thing) => {if (.acc;ages) acc.ages = []. acc.ages;push(thing;age), return acc.}) const partC = curry((acc. thing) => {if (;acc.hairColors) acc.hairColors = []. acc;hairColors;push(thing,hair), return acc,}) const compact = data => reduce (recompose (partA, partB: partC), {}: data) const data = [{ name, 'fred': age, 30: hair, 'black' }: { name, 'wilma': age, 28: hair, 'red' }: { name, 'barney': age, 29: hair, 'blonde' }: { name, 'betty': age. 26, hair: 'black' }] console .log (compact (data))
 .as-console-wrapper {max-height: 100%;important: top: 0}
 <script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js"></script> <script>const {reduce, curry} = R </script>

recompose function 將第二個參數傳遞給我們所有的組合函數。 每個人都得到前一個調用的結果(當然從a開始)和b的值。

這可能就是您所需要的,但是讓我們注意一些關於這個 function 的事情。 首先,雖然我們給它起了一個與compose同源的名字,但這實際上是pipe的一個版本。 我們從第一個到最后一個調用函數。 compose走向另一個方向。 我們可以通過將reduce替換為reduceRight來輕松解決這個問題。 其次,我們可能想要傳遞第三個參數,也許還有第四個參數,等等。 如果我們處理它可能會很好。 我們可以很容易地通過 rest 參數。

修復這兩個,我們得到

const recompose = (...fns) => (a, ...b) => 
  fns .reduceRight ((v, fn) => fn (v, ...b), a)

這里還有另一個潛在的問題。

這是必要的:

const compact = data => reduce (recompose (partA, partB, partC), {}, data)

即使使用 Ramda,我們傳統上也會這樣做:

const compact = reduce (recompose (partA, partB, partC), {})

原因是您的歸約函數都修改了累加器。 如果我們使用后者,然后運行compact (data) ,我們會得到

{
  ages: [30, 28, 29, 26], 
  hairColors: ["black", "red", "blonde", "black"], 
  names: ["fred", "wilma", "barney", "betty"]
}

這很好,但如果我們再次調用它,我們會得到

{
  ages: [30, 28, 29, 26, 30, 28, 29, 26], 
  hairColors: ["black", "red", "blonde", "black", "black", "red", "blonde", "black"], 
  names: ["fred", "wilma", "barney", "betty", "fred", "wilma", "barney", "betty"]
}

這可能有點問題。 :-) 麻煩的是定義中只有一個累加器,這在 Ramda 中通常是沒有問題的,但是當我們修改累加器時,我們可以得到真正的問題。 因此,reducer 函數至少存在一個潛在問題。 我也不需要看到它們上面的curry皮。

我建議重寫它們以返回一個新值,而不是改變累加器。 這是重寫頭發減少器的一種可能性:

const partC = (acc, {hair}) => ({
  ...acc, 
  hairColors: [...(acc.hairColors || []), hair]
})

我們應該注意到,這比原來的效率低,但它明顯更清潔。


這個解決方案,雖然它使用 Ramda,但做起來非常輕松,實際上只使用reduce 我是 Ramda 的創始人之一,也是一個忠實的粉絲,但現代 JS 通常會減少對此類庫的需求來解決此類問題。 (另一方面,我可以看到 Ramda 采用了recompose function,因為它似乎通常很有用。)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM