RoundRobin功能方法 - 为什么我的功能有副作用?

时间:2017-11-15 13:28:06

标签: javascript functional-programming

目的

我正在尝试以纯粹的功能方式创建循环算法(https://en.wikipedia.org/wiki/Round-robin_scheduling)。

此函数应该接收如下数组:

[
    [ 1, 2 ],
    [ 3, 4 ]
]

并产生以下输出:

[ 1, 3, 2, 4 ]

代码

为实现这一目标,我决定以递归方式实现循环,如下所示:

const roundRobin = (arr, results) => {
    if (arr.length === 0) return results;

    const newResults = arr.reduce((acc, current) => {

        if (current.length > 0) {
            acc.results.push(current.shift());
            acc.arr.push(current);
        }
        return acc;

    }, { arr: [], results });

    return roundRobin(newResults.arr, newResults.results);
};

在这里,我感觉到一系列结果,当我没有任何东西可以添加时,我就完成了。可以使用以下代码:

const array =     [
        [ 1, 2 ],
        [ 3, 4 ]
    ];

const result = roundRobin( array, [] );

问题

在我的代码中,我在reduce参数中使用了arr,以确保我不会修改原始内容。但是,如果我在使用roundRobin之前打印数组,之后变量就变了!我以某种方式改变它!

问题:

  1. 如果我使用的是reduce,那是纯粹的,我如何改变我的参数?
  2. 是否有另一种实现roundRobin的纯/功能方式?

3 个答案:

答案 0 :(得分:4)

  
      
  1. 如果我使用的是reduce,那是纯粹的,我如何改变我的参数?
  2.   

功能参数无法真正发生变异;一个奇怪的想法 - 但我确定你的意思是提供给你的函数的参数正在变异。是的,与其他人.shift指出的那样

除非用户提供的lambda是纯粹的,否则.reduce不是纯粹的

  
      
  1. 是否有另一种实现roundRobin的纯/功能方式?
  2.   

是的



const isEmpty = xs =>
  xs.length === 0
  
const head = ( [ x , ...xs ] ) =>
  x
  
const tail = ( [ x , ...xs ] ) =>
  xs

const append = ( xs , x ) =>
  xs.concat ( [ x ] )
  
const roundRobin = ( [ x , ...xs ] , acc = [] ) =>
  x === undefined
    ? acc
    : isEmpty ( x )
      ? roundRobin ( xs , acc )
      : roundRobin ( append ( xs , tail ( x ) )
                   , append ( acc , head ( x ) )
                   )

const data =
  [ [ 1 , 4 , 7 , 9 ]
  , [ 2 , 5 ]
  , [ 3 , 6 , 8 , 10 , 11 , 12 ]
  ]
                   
console.log ( roundRobin ( data ) )
// => [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 ]

console.log ( roundRobin ( [ [ 1 , 2 , 3 ] ] ) )
// => [ 1 , 2 , 3 ]

console.log ( roundRobin ( [] ) )
// => []




答案 1 :(得分:2)

Array#shift正在进行变异。

var array = [0, 1, 2, 3, 4];
array.shift(); // -> 0
array; // -> [1, 2, 3, 4];

最简单的方法是克隆数组。通常可以使用Array#concat来完成,但由于您的数组是嵌套的(虽然很简单),您可以这样做:

const roundRobin = (arr, results) => {
    arr = JSON.parse(JSON.stringify(arr));
    if (arr.length === 0) return results;
    // ...

如果您担心全局JSON会使函数不纯,那么您可以将其抽象出来。

const deepClone = (obj) => JSON.parse(JSON.stringify(obj));
roundRobin(deepClone(array), []);

答案 2 :(得分:1)

在JS中创建不可变对象的简单方法是使用Object.freeze

我创建了你的输入数组:

const array1 = Object.freeze([
    Object.freeze([ 1, 2 ]),
    Object.freeze([ 3, 4 ])
])

然后,当我试图调用你的函数时,我得到了:

Uncaught TypeError: Cannot add/remove sealed array elements
at Array.shift (<anonymous>)
at arr.reduce (<anonymous>:7:38)
at Array.reduce (<anonymous>)
at roundRobin (<anonymous>:4:28)
at <anonymous>:6:17

你正在使用shift,它正在改变orignal数组。

将其替换为Array.slice(0,1)[0],它将开始工作。