如何以功能方式实现数组连接?

时间:2021-08-07 22:04:16

I have a function that joins an array of objects with a conditional separator.

我有一个函数,它使用条件分隔符连接一个对象数组。

function getSegmentsLabel(segments) {
    var separator = '-';

    var segmentsLabel = '';
    var nextSeparator = '';
    _.forEach(segments, function(segment) {
        segmentsLabel += nextSeparator + segment.label;
        nextSeparator = segment.separatorUsed ? separator : ' ';
    });
    return segmentsLabel;
}

Usages:

var segments = [
    {label: 'First', separatorUsed: true},
    {label: 'Second', separatorUsed: false},
    {label: 'Third', separatorUsed: true},
    {label: 'Forth', separatorUsed: true}
];

getSegmentsLabel(segments); // Result: "First-Second Third-Forth"

How can the above getSegmentsLabel function be written in a purely functional way without mutating variables? We can use lodash functions.

如何在不改变变量的情况下以纯函数方式编写上述getSegmentsLabel函数?我们可以使用lodash函数。

6 个解决方案

#1


3  

You can use map() method that will return new array and then join() to get string form that array.

您可以使用将返回新数组的map()方法,然后使用join()来获取该数组的字符串形式。

var segments = [
    {label: 'First', separatorUsed: true},
    {label: 'Second', separatorUsed: false},
    {label: 'Third', separatorUsed: true},
    {label: 'Forth', separatorUsed: true}
];

function getSegmentsLabel(segments) {
  return segments.map(function(e, i) {
    return e.label + (i != segments.length - 1 ? (e.separatorUsed ? '-' : ' ') : '')
  }).join('')
}

console.log(getSegmentsLabel(segments));

#2


4  

recursion

or instead of map/reduce/join, you can use direct recursion – the benefit here is that we don't iterate thru the collection multiple times to compute the result – oh and the program is really small so it's easy to digest

或者代替map / reduce / join,你可以使用直接递归 - 这里的好处是我们不会多次迭代通过集合来计算结果 - 哦,程序非常小,所以很容易消化

be careful of stack overflows in javascript tho; relevant: How do I replace while loops with a functional programming alternative without tail call optimization?

小心javascript中的堆栈溢出;相关:如何在没有尾调优化的情况下使用函数式编程替换while循环?

var segments = [
  {label: 'First', separatorUsed: true},
  {label: 'Second', separatorUsed: false},
  {label: 'Third', separatorUsed: true},
  {label: 'Forth', separatorUsed: true}
];

const main = ([x,...xs]) =>
  x === undefined
    ? ''
    : xs.length === 0
      ? x.label
      : x.label + (x.separatorUsed ? '-' : ' ') + main (xs)
      
console.log (main (segments))
// First-Second Third-Forth


functional programming

that last implementation of our function is awfully specific - functional programming isn't just about using map and reduce, it's about making meaningful abstractions and writing generic procedures that can easily be reused

我们函数的最后一个实现是非常具体的 - 函数式编程不只是使用map和reduce,而是关于进行有意义的抽象和编写可以轻松重用的泛型过程

this example is intentionally very different from your original code with the hope that it will get you to think about programs in a different way – if this stuff interests you, as a follow up to this post, you could start reading about monoids.

这个例子有意与原始代码有很大的不同,希望它能让你以不同的方式思考程序 - 如果这些东西让你感兴趣,作为这篇文章的后续内容,你可以开始阅读关于幺半群的内容。

by writing our program this way, we've represented this idea of "joinable pieces of text with conditional separators" in a generic text module that could be used in any other program – writers can create units of text using Text.make and combine them using Text.concat

通过这种方式编写我们的程序,我们在一个通用文本模块中表示了“带条件分隔符的可连接文本”的概念,可以在任何其他程序中使用 - 编写者可以使用Text.make创建文本单元并将它们组合起来使用Text.concat

another advantage in this program is the separator is parameter-controlled

该程序的另一个优点是分离器是参数控制的

// type Text :: { text :: String, separator :: String }
const Text =
  {
    // Text.make :: (String × String?) -> Text
    make: (text, separator = '') =>
      ({ type: 'text', text, separator }),
      
    // Text.empty :: Text
    empty: () =>
      Text.make (''),
      
    // Text.isEmpty :: Text -> Boolean
    isEmpty: l =>
      l.text === '',
      
    // Text.concat :: (Text × Text) -> Text
    concat: (x,y) =>
      Text.isEmpty (y)
        ? x
        : Text.make (x.text + x.separator + y.text, y.separator),
    
    // Text.concatAll :: [Text] -> Text
    concatAll: ts =>
      ts.reduce (Text.concat, Text.empty ())  
  }

// main :: [Text] -> String
const main = xs =>
  Text.concatAll (xs) .text
  
// data :: [Text]
const data =
  [ Text.make ('First', '-'), Text.make ('Second', ' '), Text.make ('Third', '-'), Text.make ('Fourth', '-') ]
  
console.log (main (data))
// First-Second Third-Fourth

#3


1  

It's better to use reduceRight instead of map in this case:

在这种情况下,最好使用reduceRight而不是map:

const segments = [
    {label: 'First',  separatorUsed: true},
    {label: 'Second', separatorUsed: false},
    {label: 'Third',  separatorUsed: true},
    {label: 'Forth',  separatorUsed: true}
];

const getSegmentsLabel = segments =>
    segments.slice(0, -1).reduceRight((segmentsLabel, {label, separatorUsed}) =>
        label + (separatorUsed ? "-" : " ") + segmentsLabel,
    segments[segments.length - 1].label);

console.log(JSON.stringify(getSegmentsLabel(segments)));

As you can see, it's better to iterate through the array from right to left.

如您所见,最好从右到左遍历数组。


Here's a more efficient version of the program, although it uses mutation:

这是一个更高效的程序版本,虽然它使用了变异:

const segments = [
    {label: 'First',  separatorUsed: true},
    {label: 'Second', separatorUsed: false},
    {label: 'Third',  separatorUsed: true},
    {label: 'Forth',  separatorUsed: true}
];

const reduceRight = (xs, step, base) => {
    const x = xs.pop(), result = xs.reduceRight(step, base(x));
    return xs.push(x), result;
};

const getSegmentsLabel = segments =>
    reduceRight(segments, (segmentsLabel, {label, separatorUsed}) =>
        label + (separatorUsed ? "-" : " ") + segmentsLabel,
    ({label}) => label);

console.log(JSON.stringify(getSegmentsLabel(segments)));

It's not purely functional but if we treat reduceRight as a black box then you can define getSegmentsLabel in a purely functional way.

它不是纯粹的功能,但如果我们将reduceRight视为一个黑盒子,那么你可以用纯粹的功能方式定义getSegmentsLabel。

#4


1  

You could use an array for the separators and decide, if a spacer, a dash or no separator for strings at the end.

你可以使用一个数组作为分隔符,并决定,如果是间隔符,破折号或没有字符串的分隔符。

const separators = [' ', '', '-'];
var getSegmentsLabel = array => array
        .map(({ label, separatorUsed }, i, a) =>
            label + separators[2 * separatorUsed - (i + 1 === a.length)])
        .join('');

var segments = [{ label: 'First', separatorUsed: true }, { label: 'Second', separatorUsed: false }, { label: 'Third', separatorUsed: true }, { label: 'Forth', separatorUsed: true }];

console.log(getSegmentsLabel(segments));

#5


0  

Here I separate out the functions:

在这里,我将功能分开:

// buildSeparatedStr returns a function that can be used
// in the reducer, employing a template literal as the returned value
const buildSeparatedStr = (sep) => (p, c, i, a) => {
  const separator = !c.separatorUsed || i === a.length - 1 ? ' ' : sep;
  return `${p}${c.label}${separator}`;
}

// Accept an array and the buildSeparatedStr function
const getSegmentsLabel = (arr, fn) => arr.reduce(fn, '');

// Pass in the array, and the buildSeparatedStr function with
// the separator
const str = getSegmentsLabel(segments, buildSeparatedStr('-'));

DEMO

#6


0  

const segments = [
    {label: 'First',  separatorUsed: true},
    {label: 'Second', separatorUsed: false},
    {label: 'Third',  separatorUsed: true},
    {label: 'Forth',  separatorUsed: true}
];

const segmentsLabel = segments.reduce((label, segment, i, arr) => {
    const separator = (i === arr.length - 1) ? '' : (segment.separatorUsed) ? '-' : ' ';
    return label + segment.label + separator;
 }, '');

 console.log(segmentsLabel);

#1


3  

You can use map() method that will return new array and then join() to get string form that array.

您可以使用将返回新数组的map()方法,然后使用join()来获取该数组的字符串形式。

var segments = [
    {label: 'First', separatorUsed: true},
    {label: 'Second', separatorUsed: false},
    {label: 'Third', separatorUsed: true},
    {label: 'Forth', separatorUsed: true}
];

function getSegmentsLabel(segments) {
  return segments.map(function(e, i) {
    return e.label + (i != segments.length - 1 ? (e.separatorUsed ? '-' : ' ') : '')
  }).join('')
}

console.log(getSegmentsLabel(segments));

#2


4  

recursion

or instead of map/reduce/join, you can use direct recursion – the benefit here is that we don't iterate thru the collection multiple times to compute the result – oh and the program is really small so it's easy to digest

或者代替map / reduce / join,你可以使用直接递归 - 这里的好处是我们不会多次迭代通过集合来计算结果 - 哦,程序非常小,所以很容易消化

be careful of stack overflows in javascript tho; relevant: How do I replace while loops with a functional programming alternative without tail call optimization?

小心javascript中的堆栈溢出;相关:如何在没有尾调优化的情况下使用函数式编程替换while循环?

var segments = [
  {label: 'First', separatorUsed: true},
  {label: 'Second', separatorUsed: false},
  {label: 'Third', separatorUsed: true},
  {label: 'Forth', separatorUsed: true}
];

const main = ([x,...xs]) =>
  x === undefined
    ? ''
    : xs.length === 0
      ? x.label
      : x.label + (x.separatorUsed ? '-' : ' ') + main (xs)
      
console.log (main (segments))
// First-Second Third-Forth


functional programming

that last implementation of our function is awfully specific - functional programming isn't just about using map and reduce, it's about making meaningful abstractions and writing generic procedures that can easily be reused

我们函数的最后一个实现是非常具体的 - 函数式编程不只是使用map和reduce,而是关于进行有意义的抽象和编写可以轻松重用的泛型过程

this example is intentionally very different from your original code with the hope that it will get you to think about programs in a different way – if this stuff interests you, as a follow up to this post, you could start reading about monoids.

这个例子有意与原始代码有很大的不同,希望它能让你以不同的方式思考程序 - 如果这些东西让你感兴趣,作为这篇文章的后续内容,你可以开始阅读关于幺半群的内容。

by writing our program this way, we've represented this idea of "joinable pieces of text with conditional separators" in a generic text module that could be used in any other program – writers can create units of text using Text.make and combine them using Text.concat

通过这种方式编写我们的程序,我们在一个通用文本模块中表示了“带条件分隔符的可连接文本”的概念,可以在任何其他程序中使用 - 编写者可以使用Text.make创建文本单元并将它们组合起来使用Text.concat

another advantage in this program is the separator is parameter-controlled

该程序的另一个优点是分离器是参数控制的

// type Text :: { text :: String, separator :: String }
const Text =
  {
    // Text.make :: (String × String?) -> Text
    make: (text, separator = '') =>
      ({ type: 'text', text, separator }),
      
    // Text.empty :: Text
    empty: () =>
      Text.make (''),
      
    // Text.isEmpty :: Text -> Boolean
    isEmpty: l =>
      l.text === '',
      
    // Text.concat :: (Text × Text) -> Text
    concat: (x,y) =>
      Text.isEmpty (y)
        ? x
        : Text.make (x.text + x.separator + y.text, y.separator),
    
    // Text.concatAll :: [Text] -> Text
    concatAll: ts =>
      ts.reduce (Text.concat, Text.empty ())  
  }

// main :: [Text] -> String
const main = xs =>
  Text.concatAll (xs) .text
  
// data :: [Text]
const data =
  [ Text.make ('First', '-'), Text.make ('Second', ' '), Text.make ('Third', '-'), Text.make ('Fourth', '-') ]
  
console.log (main (data))
// First-Second Third-Fourth

#3


1  

It's better to use reduceRight instead of map in this case:

在这种情况下,最好使用reduceRight而不是map:

const segments = [
    {label: 'First',  separatorUsed: true},
    {label: 'Second', separatorUsed: false},
    {label: 'Third',  separatorUsed: true},
    {label: 'Forth',  separatorUsed: true}
];

const getSegmentsLabel = segments =>
    segments.slice(0, -1).reduceRight((segmentsLabel, {label, separatorUsed}) =>
        label + (separatorUsed ? "-" : " ") + segmentsLabel,
    segments[segments.length - 1].label);

console.log(JSON.stringify(getSegmentsLabel(segments)));

As you can see, it's better to iterate through the array from right to left.

如您所见,最好从右到左遍历数组。


Here's a more efficient version of the program, although it uses mutation:

这是一个更高效的程序版本,虽然它使用了变异:

const segments = [
    {label: 'First',  separatorUsed: true},
    {label: 'Second', separatorUsed: false},
    {label: 'Third',  separatorUsed: true},
    {label: 'Forth',  separatorUsed: true}
];

const reduceRight = (xs, step, base) => {
    const x = xs.pop(), result = xs.reduceRight(step, base(x));
    return xs.push(x), result;
};

const getSegmentsLabel = segments =>
    reduceRight(segments, (segmentsLabel, {label, separatorUsed}) =>
        label + (separatorUsed ? "-" : " ") + segmentsLabel,
    ({label}) => label);

console.log(JSON.stringify(getSegmentsLabel(segments)));

It's not purely functional but if we treat reduceRight as a black box then you can define getSegmentsLabel in a purely functional way.

它不是纯粹的功能,但如果我们将reduceRight视为一个黑盒子,那么你可以用纯粹的功能方式定义getSegmentsLabel。

#4


1  

You could use an array for the separators and decide, if a spacer, a dash or no separator for strings at the end.

你可以使用一个数组作为分隔符,并决定,如果是间隔符,破折号或没有字符串的分隔符。

const separators = [' ', '', '-'];
var getSegmentsLabel = array => array
        .map(({ label, separatorUsed }, i, a) =>
            label + separators[2 * separatorUsed - (i + 1 === a.length)])
        .join('');

var segments = [{ label: 'First', separatorUsed: true }, { label: 'Second', separatorUsed: false }, { label: 'Third', separatorUsed: true }, { label: 'Forth', separatorUsed: true }];

console.log(getSegmentsLabel(segments));

#5


0  

Here I separate out the functions:

在这里,我将功能分开:

// buildSeparatedStr returns a function that can be used
// in the reducer, employing a template literal as the returned value
const buildSeparatedStr = (sep) => (p, c, i, a) => {
  const separator = !c.separatorUsed || i === a.length - 1 ? ' ' : sep;
  return `${p}${c.label}${separator}`;
}

// Accept an array and the buildSeparatedStr function
const getSegmentsLabel = (arr, fn) => arr.reduce(fn, '');

// Pass in the array, and the buildSeparatedStr function with
// the separator
const str = getSegmentsLabel(segments, buildSeparatedStr('-'));

DEMO

#6


0  

const segments = [
    {label: 'First',  separatorUsed: true},
    {label: 'Second', separatorUsed: false},
    {label: 'Third',  separatorUsed: true},
    {label: 'Forth',  separatorUsed: true}
];

const segmentsLabel = segments.reduce((label, segment, i, arr) => {
    const separator = (i === arr.length - 1) ? '' : (segment.separatorUsed) ? '-' : ' ';
    return label + segment.label + separator;
 }, '');

 console.log(segmentsLabel);