I was wondering what would be the best way to split an array into two different arrays using JavaScript, but to keep it in the realms of functional programming.
我想知道用JavaScript将数组分成两个不同的数组的最佳方法是什么,但是要将它保存在函数编程的领域中。
Let's say that the two arrays should be created depending on some logic. For instance splitting one array should only contain strings with less than four characters and the other the rest.
假设这两个数组应该根据某种逻辑创建。例如,分割一个数组应该只包含少于四个字符的字符串,而另一个数组应该包含其余字符。
const arr = ['horse', 'elephant', 'dog', 'crocodile', 'cat'];
I have thought about different methods:
我想过不同的方法:
Filter:
过滤器:
const lessThanFour = arr.filter((animal) => {
return animal.length < 4;
});
const fourAndMore = arr.filter((animal) => {
return animal.length >= 4;
});
The problem with this for me is that you have to go through your data twice, but it is very readable. Would there be a massive impact doing this twice if you have a rather large array?
对我来说,这个问题的关键在于,你必须对你的数据进行两次检查,但它是非常可读的。如果你有一个相当大的阵列,会有两次巨大的冲击吗?
Reduce:
减少:
const threeFourArr = arr.reduce((animArr, animal) => {
if (animal.length < 4) {
return [[...animArr[0], animal], animArr[1]];
} else {
return [animArr[0], [...animArr[1], animal]];
}
}, [[], []]);
Where the array's 0 index contains the array of less than four and the 1 index contains the array of more than three.
数组的0索引包含小于4的数组,而1索引包含3个以上的数组。
I don't like this too much, because it seems that the data structure is going to give a bit of problems, seeing that it is an array of arrays. I've thought about building an object with the reduce, but I can't imagine that it would be better than the array within an array solution.
我不太喜欢这个,因为看起来数据结构会有一些问题,因为它是数组的数组。我想过用reduce来构建一个对象,但是我无法想象它会比数组中的数组更好。
I've managed to look at similar questions online as well as Stack Overflow, but many of these break the idea of immutability by using push()
or they have very unreadable implementations, which in my opinion breaks the expressiveness of functional programming.
我已经设法在网上查看了类似的问题以及堆栈溢出,但是其中许多都通过使用push()或它们具有非常不可读的实现打破了不可变性的概念,在我看来,这破坏了函数式编程的表现力。
Are there any other ways of doing this? (functional of course)
还有别的办法吗?(当然功能)
5 个解决方案
#1
5
collateBy
I just shared a similar answer here
我只是分享了一个相似的答案
I like this solution better because it abstracts away the collation but allows you to control how items are collated using a higher-order function.
我更喜欢这个解决方案,因为它抽象了排序规则,但允许您使用高阶函数控制项的排序规则。
Notice how we don't say anything about animal.length
or < 4
or animals[0].push
inside collateBy
. This procedure has no knowledge of the kind of data you might be collating.
注意我们怎么不说动物。长度或小于4或动物[0]。collateBy内推。此过程不了解您可能正在整理的数据类型。
// generic collation procedure
const collateBy = f => g => xs => {
return xs.reduce((m,x) => {
let v = f(x)
return m.set(v, g(m.get(v), x))
}, new Map())
}
// custom collator
const collateByStrLen4 =
// collate by length > 4 using array concatenation for like elements
// note i'm using `[]` as the "seed" value for the empty collation
collateBy (x=> x.length > 4) ((a=[],b)=> [...a,b])
// sample data
const arr = ['horse','elephant','dog','crocodile','cat']
// get collation
let collation = collateByStrLen4 (arr)
// output specific collation keys
console.log('greater than 4', collation.get(true))
console.log('not greater than 4', collation.get(false))
// output entire collation
console.log('all entries', Array.from(collation.entries()))
Check out that other answer I posted to see other usage varieties. It's a pretty handy procedure.
看看我发布的另一个答案,看看其他的用法。这是一个非常方便的程序。
bifilter
This is another solution that captures both out outputs of a filter function, instead of throwing away filtered values like Array.prototype.filter
does.
这是另一种解决方案,它捕获过滤器函数的输出,而不是像Array.prototype那样丢弃经过过滤的值。过滤器。
This is basically what your reduce
implementation does but it is abstracted into a generic, parameterized procedure. It does not use Array.prototype.push
but in the body of a closure, localized mutation is generally accepted as OK.
这基本上就是reduce实现所做的,但是它被抽象为一个泛型的、参数化的过程。它不使用Array.prototype。但在闭合体中,局部突变被普遍接受为OK。
const bifilter = (f,xs) => {
return xs.reduce(([T,F], x, i, arr)=> {
if (f(x, i, arr) === false)
return [T, [...F,x]]
else
return [[...T,x] ,F]
}, [[],[]])
}
const arr = ['horse','elephant','dog','crocodile','cat']
let [truthy,falsy] = bifilter(x=> x.length > 4, arr)
console.log('greater than 4', truthy)
console.log('not greater than 4', falsy)
Though it might be a little more straightforward, it's not nearly as powerful as collateBy
. Either way, pick whichever one you like, adapt it to meet your needs if necessary, and have fun !
虽然它可能更简单一点,但它远没有collateBy强大。无论哪种方式,选择你喜欢的,调整它以满足你的需要,如果有必要的话,玩得开心!
If this is your own app, go nuts and add it to Array.prototype
如果这是你自己的应用,那就疯狂地把它添加到Array.prototype中
// attach to Array.prototype if this is your own app
// do NOT do this if this is part of a lib that others will inherit
Array.prototype.bifilter = function(f) {
return bifilter(f,this)
}
#2
8
The function you are trying to build is usually known as partition
and can be found under that name in many libraries, such as underscore.js. (As far as I know its not a builtin method)
您正在尝试构建的函数通常称为分区,在许多库中都可以找到这个名称,比如underscore.js。(据我所知,这不是一种构建方法)
var threeFourArr = _.partition(animals, function(x){ return x.length < 4 });
I don't like this too much, because it seems that the data structure is going to give a bit of problems, seeing that it is an array of arrays
我不太喜欢这个,因为看起来数据结构会有一些问题,因为它是数组的数组
Well, that is the only way to have a function in Javascript that returns two different values. It looks a bit better if you can use destructuring assignment (an ES6 feature):
这是Javascript中返回两个不同值的函数的唯一方法。如果可以使用析构赋值(ES6特性),效果会更好一些:
var [smalls, bigs] = _.partition(animals, function(x){ return x.length < 4 });
Look at it as returning a pair of arrays instead of returning an array of arrays. "Array of arrays" suggests that you may have a variable number of arrays.
把它看作返回一对数组而不是返回数组。“数组数组”提示您可能有一个可变数量的数组。
I've managed to look at similar questions online as well as Stack Overflow, but many of these break the idea of immutability by using push() or they have very unreadable implementations, which in my opinion breaks the expressiveness of functional programming.
我已经设法在网上查看了类似的问题以及堆栈溢出,但是其中许多都通过使用push()或它们具有非常不可读的实现打破了不可变性的概念,在我看来,这破坏了函数式编程的表现力。
Mutability is not a problem if you localize it inside a single function. From the outside its just as immutable as before and sometimes using some mutability will be more idiomatic than trying to do everything in a purely functional manner. If I had to code a partition function from scratch I would write something along these lines:
如果在单个函数中本地化,可变性就不是问题。从表面上看,它和以前一样不可变,有时使用一些可变性将比试图以一种纯粹的功能方式去做每一件事更符合习惯。如果我必须从头开始编写一个分区函数,我会按照下面的思路写一些东西:
function partition(xs, pred){
var trues = [];
var falses = [];
xs.forEach(function(x){
if(pred(x)){
trues.push(x);
}else{
falses.push(x);
}
});
return [trues, falses];
}
#3
2
If you are not opposed to using underscore there is a neat little function called groupBy that does exactly what you are looking for:
如果你不反对使用下划线,那么有一个整洁的小函数叫做groupBy,它可以精确地显示你想要的东西:
const arr = ['horse', 'elephant', 'dog', 'crocodile', 'cat'];
var results = _.groupBy(arr, function(cur) {
return cur.length > 4;
});
const greaterThanFour = results.true;
const lessThanFour = results.false;
console.log(greaterThanFour); // ["horse", "elephant", "crocodile"]
console.log(lessThanFour); // ["dog", "cat"]
#4
2
A shorter .reduce()
version would be:
更短的.reduce()版本将是:
const split = arr.reduce((animArr, animal) => {
animArr[animal.length < 4 ? 0 : 1].push(animal);
return animArr
}, [ [], [] ]);
Which might be combined with destructuring:
可能与破坏性结合:
const [ lessThanFour, fourAndMore ] = arr.reduce(...)
#5
-1
I don't think there could be another solution than returning an array of arrays or an object containing arrays. How else is a javascript function return multiple arrays after splitting them?
我认为除了返回数组或包含数组的对象之外,没有其他解决方案了。javascript函数如何在分割多个数组后返回多个数组?
Write a function containing your push logic for readability.
编写一个函数,其中包含了可读性的push逻辑。
var myArr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var x = split(myArr, v => (v <= 5));
console.log(x);
function split(array, tester) {
const result = [
[],
[]
];
array.forEach((v, i, a) => {
if (tester(v, i, a)) result[0].push(v);
else result[1].push(v);
});
return result;
}
#1
5
collateBy
I just shared a similar answer here
我只是分享了一个相似的答案
I like this solution better because it abstracts away the collation but allows you to control how items are collated using a higher-order function.
我更喜欢这个解决方案,因为它抽象了排序规则,但允许您使用高阶函数控制项的排序规则。
Notice how we don't say anything about animal.length
or < 4
or animals[0].push
inside collateBy
. This procedure has no knowledge of the kind of data you might be collating.
注意我们怎么不说动物。长度或小于4或动物[0]。collateBy内推。此过程不了解您可能正在整理的数据类型。
// generic collation procedure
const collateBy = f => g => xs => {
return xs.reduce((m,x) => {
let v = f(x)
return m.set(v, g(m.get(v), x))
}, new Map())
}
// custom collator
const collateByStrLen4 =
// collate by length > 4 using array concatenation for like elements
// note i'm using `[]` as the "seed" value for the empty collation
collateBy (x=> x.length > 4) ((a=[],b)=> [...a,b])
// sample data
const arr = ['horse','elephant','dog','crocodile','cat']
// get collation
let collation = collateByStrLen4 (arr)
// output specific collation keys
console.log('greater than 4', collation.get(true))
console.log('not greater than 4', collation.get(false))
// output entire collation
console.log('all entries', Array.from(collation.entries()))
Check out that other answer I posted to see other usage varieties. It's a pretty handy procedure.
看看我发布的另一个答案,看看其他的用法。这是一个非常方便的程序。
bifilter
This is another solution that captures both out outputs of a filter function, instead of throwing away filtered values like Array.prototype.filter
does.
这是另一种解决方案,它捕获过滤器函数的输出,而不是像Array.prototype那样丢弃经过过滤的值。过滤器。
This is basically what your reduce
implementation does but it is abstracted into a generic, parameterized procedure. It does not use Array.prototype.push
but in the body of a closure, localized mutation is generally accepted as OK.
这基本上就是reduce实现所做的,但是它被抽象为一个泛型的、参数化的过程。它不使用Array.prototype。但在闭合体中,局部突变被普遍接受为OK。
const bifilter = (f,xs) => {
return xs.reduce(([T,F], x, i, arr)=> {
if (f(x, i, arr) === false)
return [T, [...F,x]]
else
return [[...T,x] ,F]
}, [[],[]])
}
const arr = ['horse','elephant','dog','crocodile','cat']
let [truthy,falsy] = bifilter(x=> x.length > 4, arr)
console.log('greater than 4', truthy)
console.log('not greater than 4', falsy)
Though it might be a little more straightforward, it's not nearly as powerful as collateBy
. Either way, pick whichever one you like, adapt it to meet your needs if necessary, and have fun !
虽然它可能更简单一点,但它远没有collateBy强大。无论哪种方式,选择你喜欢的,调整它以满足你的需要,如果有必要的话,玩得开心!
If this is your own app, go nuts and add it to Array.prototype
如果这是你自己的应用,那就疯狂地把它添加到Array.prototype中
// attach to Array.prototype if this is your own app
// do NOT do this if this is part of a lib that others will inherit
Array.prototype.bifilter = function(f) {
return bifilter(f,this)
}
#2
8
The function you are trying to build is usually known as partition
and can be found under that name in many libraries, such as underscore.js. (As far as I know its not a builtin method)
您正在尝试构建的函数通常称为分区,在许多库中都可以找到这个名称,比如underscore.js。(据我所知,这不是一种构建方法)
var threeFourArr = _.partition(animals, function(x){ return x.length < 4 });
I don't like this too much, because it seems that the data structure is going to give a bit of problems, seeing that it is an array of arrays
我不太喜欢这个,因为看起来数据结构会有一些问题,因为它是数组的数组
Well, that is the only way to have a function in Javascript that returns two different values. It looks a bit better if you can use destructuring assignment (an ES6 feature):
这是Javascript中返回两个不同值的函数的唯一方法。如果可以使用析构赋值(ES6特性),效果会更好一些:
var [smalls, bigs] = _.partition(animals, function(x){ return x.length < 4 });
Look at it as returning a pair of arrays instead of returning an array of arrays. "Array of arrays" suggests that you may have a variable number of arrays.
把它看作返回一对数组而不是返回数组。“数组数组”提示您可能有一个可变数量的数组。
I've managed to look at similar questions online as well as Stack Overflow, but many of these break the idea of immutability by using push() or they have very unreadable implementations, which in my opinion breaks the expressiveness of functional programming.
我已经设法在网上查看了类似的问题以及堆栈溢出,但是其中许多都通过使用push()或它们具有非常不可读的实现打破了不可变性的概念,在我看来,这破坏了函数式编程的表现力。
Mutability is not a problem if you localize it inside a single function. From the outside its just as immutable as before and sometimes using some mutability will be more idiomatic than trying to do everything in a purely functional manner. If I had to code a partition function from scratch I would write something along these lines:
如果在单个函数中本地化,可变性就不是问题。从表面上看,它和以前一样不可变,有时使用一些可变性将比试图以一种纯粹的功能方式去做每一件事更符合习惯。如果我必须从头开始编写一个分区函数,我会按照下面的思路写一些东西:
function partition(xs, pred){
var trues = [];
var falses = [];
xs.forEach(function(x){
if(pred(x)){
trues.push(x);
}else{
falses.push(x);
}
});
return [trues, falses];
}
#3
2
If you are not opposed to using underscore there is a neat little function called groupBy that does exactly what you are looking for:
如果你不反对使用下划线,那么有一个整洁的小函数叫做groupBy,它可以精确地显示你想要的东西:
const arr = ['horse', 'elephant', 'dog', 'crocodile', 'cat'];
var results = _.groupBy(arr, function(cur) {
return cur.length > 4;
});
const greaterThanFour = results.true;
const lessThanFour = results.false;
console.log(greaterThanFour); // ["horse", "elephant", "crocodile"]
console.log(lessThanFour); // ["dog", "cat"]
#4
2
A shorter .reduce()
version would be:
更短的.reduce()版本将是:
const split = arr.reduce((animArr, animal) => {
animArr[animal.length < 4 ? 0 : 1].push(animal);
return animArr
}, [ [], [] ]);
Which might be combined with destructuring:
可能与破坏性结合:
const [ lessThanFour, fourAndMore ] = arr.reduce(...)
#5
-1
I don't think there could be another solution than returning an array of arrays or an object containing arrays. How else is a javascript function return multiple arrays after splitting them?
我认为除了返回数组或包含数组的对象之外,没有其他解决方案了。javascript函数如何在分割多个数组后返回多个数组?
Write a function containing your push logic for readability.
编写一个函数,其中包含了可读性的push逻辑。
var myArr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var x = split(myArr, v => (v <= 5));
console.log(x);
function split(array, tester) {
const result = [
[],
[]
];
array.forEach((v, i, a) => {
if (tester(v, i, a)) result[0].push(v);
else result[1].push(v);
});
return result;
}