使用函数JavaScript

时间:2016-08-09 21:20:43

标签: javascript arrays functional-programming reduce

我想知道使用JavaScript将数组拆分成两个不同数组的最佳方法是什么,但要将其保留在函数式编程领域。

让我们说应该根据某些逻辑创建两个数组。例如,拆分一个数组应该只包含少于四个字符的字符串,另一个包含剩下的字符串。

const arr = ['horse', 'elephant', 'dog', 'crocodile', 'cat'];

我考虑过不同的方法:

过滤

const lessThanFour = arr.filter((animal) => {
    return animal.length < 4;
});
const fourAndMore = arr.filter((animal) => {
    return animal.length >= 4;
});

对我来说,问题在于你必须两次检查数据,但它非常易读。如果你有一个相当大的阵列,会有两次这样的巨大影响吗?

减少

const threeFourArr = arr.reduce((animArr, animal) => {
  if (animal.length < 4) {
    return [[...animArr[0], animal], animArr[1]];
  } else {
    return  [animArr[0], [...animArr[1], animal]];
  }
}, [[], []]);

数组的0索引包含少于4的数组,1索引包含超过3的数组。

我不太喜欢这个,因为看起来数据结构会产生一些问题,因为它是一个数组数组。我已经考虑过使用reduce构建一个对象,但是我无法想象它会比数组解决方案中的数组更好。

我设法在线查看类似的问题以及Stack Overflow,但其中许多都使用push()打破了不可变性的想法,或者他们有非常难以理解的实现,我认为这打破了函数式编程的表现力。

还有其他方法吗? (功能当然)

6 个答案:

答案 0 :(得分:8)

您尝试构建的功能通常称为partition,可以在许多库中以该名称找到,例如underscore.js。 (据我所知,它不是内置方法)

var threeFourArr = _.partition(animals, function(x){ return x.length < 4 });
  

我不太喜欢这个,因为看起来数据结构会产生一些问题,看到它是一个数组数组

嗯,这是在Javascript中使用函数返回两个不同值的唯一方法。如果您可以使用destructuring assignment(ES6功能),它看起来会更好一点:

var [smalls, bigs] = _.partition(animals, function(x){ return x.length < 4 });

将其视为返回一对数组而不是返回数组数组。 &#34;阵列阵列&#34;建议你可能有不同数量的数组。

  

我设法在线查看类似的问题以及Stack Overflow,但是其中许多通过使用push()打破了不可变性的想法,或者他们有非常难以理解的实现,在我看来这打破了表达性函数式编程。

如果在单个函数中对其进行本地化,则可变性不是问题。从外面看它和以前一样不变,有时使用一些可变性比试图以纯函数方式做所有事情更加惯用。如果我必须从头开始编写分区函数,我会按照以下几行写一些东西:

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];
}

答案 1 :(得分:7)

collateBy

我刚刚分享了similar answer here

我更喜欢这个解决方案,因为它抽象了整理,但允许您使用高阶函数控制如何整理项目。

请注意我们如何对animal.length内的< 4animals[0].pushcollateBy进行任何说明。此过程不了解您可能正在整理的数据。

// 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()))

查看other answer I posted以查看其他使用类型。这是一个非常方便的程序。

bifilter

这是另一种捕获过滤函数输出的解决方案,而不是像Array.prototype.filter那样丢弃过滤后的值。

这基本上是您的reduce实现所做的,但它被抽象为一个通用的参数化过程。 使用Array.prototype.push但是在闭包体中,通常认为局部变异为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)

虽然它可能稍微简单一点,但它并不像collateBy那么强大。无论哪种方式,选择你喜欢的任何一个,根据需要调整它以满足你的需求,并且玩得开心!

如果这是您自己的应用,请坚持并将其添加到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 :(得分:3)

较短的.reduce()版本将是:

const split = arr.reduce((animArr, animal) => {
  animArr[animal.length < 4 ? 0 : 1].push(animal);
  return animArr
}, [ [], [] ]);

可能与解构相结合:

const [ lessThanFour,  fourAndMore ] = arr.reduce(...)

答案 3 :(得分:2)

如果您不反对使用下划线,那么有一个简洁的小功能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 :(得分:0)

感谢用户的漂亮回复,谢谢,这里有一个使用递归的替代方法,

const arr = ['horse', 'elephant', 'dog', 'crocodile', 'cat'];


const splitBy = predicate => {
  return x = (input, a, b) => {
    if (input.length > 0) {
      const value = input[0];
      const [z, y] = predicate(value) ? [[...a, value], b] : [a, [...b, value]];
      return x(input.slice(1), z, y);
    } else {
      return [a, b];
    }
  }
}

const  splitAt4 = splitBy(x => x.length < 4);
const [lessThan4, fourAndMore ] = splitAt4(arr, [], []);
console.log(lessThan4, fourAndMore);

答案 5 :(得分:-1)

我认为除了返回数组数组或包含数组的对象之外,还有其他解决方案。 javascript函数在拆分后如何返回多个数组?

编写一个包含推送逻辑的函数以便于阅读。

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;
}