Круглый функциональный подход - почему у моей функции есть побочные эффекты?
Цель
Я пытаюсь создать алгоритм Round Robin (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 и после, переменная изменяется! Я как-то мутирую его!
Вопросы:
- Если я использую сокращение, которое является чистым, как я изменяю свои параметры?
- Есть ли еще один чистый/функциональный способ реализации roundRobin?
Ответы
Ответ 1
- Если я использую сокращение, которое является чистым, как я изменяю свои параметры?
Параметры функции не могут быть мутированы; странная мысль - но я уверен, что вы имели в виду, что аргументы, предоставленные вашей функции, мутируются. И да, это с .shift
, как указывали другие.
И для того, что стоит, .reduce
не является чистым, если только предоставленная пользователем лямбда не является чистой
- Есть ли еще один чистый/функциональный способ реализации roundRobin?
Да
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 ( [] ) )
// => []
Ответ 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), []);
Ответ 3
Простым способом создания неизменяемого объекта в 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]
и он начнет работать.