如何使用 Ramda.js 在 Javascript 中动态插入二维数组 [英] How to use Ramda.js to dynamically insert into a 2d Array in Javascript

查看:29
本文介绍了如何使用 Ramda.js 在 Javascript 中动态插入二维数组的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有以下状态

{大批": [[名称",电话",电子邮件"]],索引":{"名称": 0,电话":1,电子邮件":2},"tempInput": ["test@test.com","test2@test.com"],"tempDestination": "电子邮件"}

现在我想创建一个函数,获取对象并将输入值作为新行动态插入到指定目的地的二维数组中,最终返回

{大批": [[名称",电话",电子邮件"],["","",test@test.com"],["","",test2@test.com"]],索引":{"名称": 0,电话":1,电子邮件":2}}

为了解决这个问题,我一直在查看文档并发现

R.lensProp 和 R.view.这种组合为我提供了起点(即获得所提供目的地的正确索引,但我从那里开始卡住了.

const addInputToArray = ({ array, index, tempDestination, tempInput, }) =>{//使用 Lense 和 R.view 获取正确的索引const xLens = R.lensProp(tempDestination);R.view(xLens, index),//包含索引//将 2 行插入 newArray - 我在这部分迷路了.const newArray = array.push(//newRows )返回 {数组:新数组,索引:索引}}

我知道我必须以某种方式循环输入,例如使用 map 函数.但是我不知道 map 函数应该执行什么才能获得正确的数组结果.

如果你能在这里帮我一下就好了?

解决方案

更新

评论提出了额外的要求(我确实期望这些要求).这需要一种稍微不同的方法.这是我的看法:

const addInputToArray = ({ 数组、索引、tempDestination、tempInput、...rest}、索引 = 索引 [tempDestination]) =>({数组:tempInput .reduce ((a, v, i) =>(i + 1) 在一个?更新 ( (i + 1), 更新 (index, v, a [i + 1] ), a): concat (a, [update (index, v, map (always(''), array[0]) )] ),大批),索引,...休息})const state = {array: [["Name", "Phone", "Email"]], 索引: {Name: 0,电话:1,电子邮件:2},临时输入:["test@test.com","test2@test.com"],临时目的地:电子邮件"}const state2 = addInputToArray(状态)控制台 .log (状态2)const state3 = addInputToArray({...状态2,tempInput: ['Wilma', 'Fred', 'Betty'],临时目的地:'名称'})控制台 .log (状态 3)const state4 = addInputToArray({...状态3,tempInput: [123, , 456],//^------------- 注意这里的间隙tempDestination: '电话'})控制台 .log (状态 4)

<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></脚本><脚本>const {更新,连接,映射,总是} = R;</script>

请注意,在原始版本(如下)中,我发现不需要 Ramda 函数.在这里,update 使它更干净,如果我使用 Ramda,我不妨在它简化事情的任何地方使用它,所以我也使用 concat 代替 Array.prototype.concat 并使用 map (总是(''), array[0]) 而不是 Array (array [0] .length) .fill ('').我发现这些让代码更容易阅读.

您可以很容易地删除最后那些,但是如果您要在没有库的情况下编写它,我建议您编写类似于 update 的内容,因为调用会使代码比它更干净可能是内联的.

替代 API

我在这里可能有点离题,但我确实怀疑您在这里尝试编写的 API 仍然比您的基本要求所暗示的要复杂.这个索引列表让我觉得是一种代码味道,一种解决方法而不是解决方案.(事实上​​,它很容易从数组的第一行推导出来.)

例如,我可能更喜欢这样的 API:

const addInputToArray = ({ array, changes, ...rest}) =>({数组:对象 .entries (changes) .reduce ((a, [k, vs], _, __, index = array [0] .indexOf (k)) =>与减少((a, v, i) =>(i + 1) 在一个?更新 ((i + 1), 更新 (index, v, a [i + 1] ), a): concat (a, [update (index, v, map (always (''), array [0]) )] ),一种),大批),...休息})常量状态 = {数组:[[姓名",电话",电子邮件"]],更改:{电子邮件:["test@test.com","test2@test.com"],姓名:['Wilma', 'Fred', 'Betty']}}const state2 = addInputToArray(状态)控制台 .log (状态2)

<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></脚本><脚本>const {更新,连接,映射,总是} = R;</script>

但不管怎样,它仍然导致了一个有趣的问题,所以谢谢!

说明

一个评论询问了这个版本中reduce 的参数.为了解释,我先退一步.我是函数式编程的忠实粉丝.这有很多含义,也有很多含义,但与此相关的是,我更喜欢尽可能多地用表达式而不是陈述来写作.诸如 foo = 1if (a) {doB()} 之类的语句不容易分析,因为它们将时序和顺序引入到分析中,否则可以以数学方式进行.

为了支持这一点,我尽可能编写函数体,其主体由单个表达式组成,即使它相当复杂.我不能总是以可读的方式做到这一点,在这些情况下,我选择可读性.不过,我经常可以,正如我在这里设法做到的那样,但为了支持这一点,我可能会向函数添加默认参数,以支持原本应该是赋值语句的内容.纯函数式语言 Haskell 为此类临时赋值提供了方便的语法:

让二 = 2;三 = 3在二 * 三 - 6

Javascript 不提供这样的语法.(或者真的那个语法有这样的问题,它已被弃用.)在参数中添加具有默认值的变量是一种合理的解决方法.它允许我做相当于定义一个局部变量以避免重复表达式.

如果我们有这个:

const foo = (x) =>(x + 1) * (x + 1)

我们在这里重复计算(x + 1).显然这里是次要的,但在其他情况下,它们可能很昂贵,所以我们可能会写这个替换:

const foo = (x) =>{常量下一个 = x + 1返回下一个 * 下一个}

但是现在我们有多个语句,我希望尽可能避免这种情况.如果相反,我们这样写:

const foo = (x, next = x + 1) =>下一个 * 下一个

我们仍然保存重复计算,但代码更容易进行更直接的分析.(我知道在这些简单的情况下,分析仍然很简单,但很容易想象这会变得更加复杂.)

回到实际问题.我写的代码是这样的:

.reduce ((a, [k, vs], _, __, index = array [0] .indexOf (k)) => <expression2>

正如您所指出的,Array.prototype.reduce 最多接受四个参数,累加器、当前值、当前索引和初始数组.我将 index 添加为新的默认参数,以避免多次计算它或添加临时变量.但我不关心当前索引或初始数组.我可以把它写成 ((a, [k, vs], ci, ia, index = <expression>)(ci"代表当前索引",ia"代表初始数组")或任何类似的东西.如果我想添加 index 作为第五个参数,我必须提供这些,但我不关心它们.我不会使用这些变量.

一些具有模式匹配语法的语言在此处提供下划线作为有用的占位符,表示由调用者提供但未使用的变量.虽然 JS 在语法上不支持,但下划线 (_) 是一个合法的变量名称,它们中的一对 (__) 也是如此.进行函数式编程的人经常像在模式匹配语言中一样使用它们.他们只是宣布将在这里通过某些事情,但我不再关心它.有一些提议1为 JS 添加一个类似的语法特性,如果实现了,我可能会改用它.

因此,如果您将 _ 视为参数或 __ 或(很少)_1_2_3 等,它们通常是 JS 中缺少占位符的简单解决方法._ 还有其他用途:正如您所注意到的,有使用它作为私有对象属性前缀的约定.它也是库 Underscore 及其克隆的默认变量名,lodash.但它们之间几乎没有混淆的余地.虽然您可以想象将 Underscore 作为参数传递给函数,但您随后会将其用作函数体中的变量,并且应该清楚其含义是什么.

(而且还以为我原本打算在评论中写下这个解释!)

<小时>

1 有兴趣的可以看讨论各种提案

原答案

如果没有关于基本要求的更多信息,我会从简单开始.这个功能似乎做你想做的:

const addInputToArray = ({ 数组、索引、tempDestination、tempInput、...rest}、索引 = 索引 [tempDestination]) =>({大批: [数组 [0],...tempInput .map (v => array [0] .map ((s, i) => i == index ? v : ''))],索引,...休息})const state = {array: [["Name", "Phone", "Email"]], 索引: {Name: 0, Phone: 1, Email: 2}, tempInput: ["test@test.com","test2@test.com"], tempDestination: "Email"}控制台 .log (addInputToArray(state))

但我不会惊讶地发现还有更多的要求尚未表达.此版本从头开始构建附加元素,但您似乎可能希望使用不同的 tempInputtempDestination 再次调用它,然后附加到这些元素.如果是这样,那么这将不起作用.但它可能是一个很好的起点.

I have the following state

{
  "array": [
    [
      "Name",
      "Phone",
      "Email"
    ]
  ],
  "indexes": {
    "Name": 0,
    "Phone": 1,
    "Email": 2
  },
  "tempInput": ["test@test.com","test2@test.com"],
  "tempDestination": "Email"
}

Now I want to create a function taking the object and dynamically inserting the input values as new rows into the 2d array with at the designated destination, ultimately returning

{
  "array": [
    [
      "Name",
      "Phone",
      "Email"
    ],
    [
      "",
      "",
      "test@test.com"
    ],
    [ 
      "",
      "",
      "test2@test.com"
    ]
  ],
  "indexes": {
    "Name": 0,
    "Phone": 1,
    "Email": 2
  }
}

To solve this I've been taking a look at the docs and discovered

R.lensProp and R.view. This combination provides me with the starting ground (i.e. getting the correct index for the provided destination, but I'm stuck from there onwards.

const addInputToArray = ({ array, indexes, tempDestination, tempInput, }) => {
  // Use Lense and R.view to get the correct index
  const xLens = R.lensProp(tempDestination);
  R.view(xLens, indexes), // contains index

  // Insert the 2 Rows into newArray - I'm lost on this part.
  const newArray = array.push( // newRows )

  return {
    array: newArray,
    indexes: indexes
  }
}

I know that I somehow have to loop over the input, for example with a map function. But I'm lost on what the map function should execute to get to the correct array result.

It'd be awesome if you could help me out here?

解决方案

Update

Comments have asked for additional requirements (ones I did expect.) That requires a slightly different approach. Here's my take:

const addInputToArray = (
  { array, indexes, tempDestination, tempInput, ...rest},
  index = indexes[tempDestination]
) => ({
  array: tempInput .reduce (
    (a, v, i) =>
      (i + 1) in a
        ? update ( (i + 1), update (index, v, a [i + 1] ), a)
        : concat (a, [update (index, v, map (always(''), array[0]) )] ),
    array
  ),
  indexes,
  ...rest
})

const state = {array: [["Name", "Phone", "Email"]], indexes: {Name: 0,
Phone: 1, Email: 2}, tempInput: ["test@test.com","test2@test.com"],
tempDestination: "Email"}

const state2 = addInputToArray (state)

console .log (
  state2
)

const state3 = addInputToArray({
  ...state2,
  tempInput: ['Wilma', 'Fred', 'Betty'],
  tempDestination: 'Name'
})

console .log (
  state3
)

const state4 = addInputToArray({
  ...state3,
  tempInput: [123, , 456],
  //              ^------------- Note the gap here
  tempDestination: 'Phone'
})

console .log (
  state4
)

<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
<script> const {update, concat, map, always} = R;                    </script>

Note that in the original version (below), I found no need for Ramda functions. Here, update makes it cleaner, and if I'm using Ramda, I might as well use it wherever it simplifies things, so I also use concat in place of Array.prototype.concat and use map (always(''), array[0]) instead of something like Array (array [0] .length) .fill (''). I find those make the code somewhat easier to read.

You could very easily remove those last ones, but if you were to write this without a library, I would suggest that you write something similar to update, as calling that makes the code cleaner than it likely would be with this inlined.

Alternative API

I could be way off-base here, but I do suspect that the API you're attempting to write here is still more complex than your basic requirements would imply. That list of indices strikes me as a code smell, a workaround more than a solution. (And in fact, it's easily derivable from the first row of the array.)

For instance, I might prefer an API like this:

const addInputToArray = ({ array, changes, ...rest}) => ({
  array: Object .entries (changes) .reduce ((a, [k, vs], _, __, index = array [0] .indexOf (k)) =>
    vs.reduce(
      (a, v, i) =>
        (i + 1) in a
          ? update ((i + 1), update (index, v, a [i + 1] ), a)
          : concat (a, [update (index, v, map (always (''), array [0]) )] ),
      a),
    array
  ),
  ...rest
})

const state = {
  array: [["Name", "Phone", "Email"]], 
  changes: {Email: ["test@test.com","test2@test.com"], Name: ['Wilma', 'Fred', 'Betty']}
}

const state2 = addInputToArray (state)

console .log (
  state2
)

<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
<script> const {update, concat, map, always} = R;                    </script>

But regardless, it still led to an interesting problem, so thanks!

Explanation

A comment asked about the parameters to reduce in this version. To explain, I'll first take one step back. I'm a big fan of functional programming. That has a lot of meanings, and a lot of implications, but the relevant one here is that I prefer to write as much as possible with expressions rather than statements. Statements, such as foo = 1, or if (a) {doB()} are not susceptible to easy analysis, since they introduce timing and sequencing to an analysis that otherwise could proceed in a mathematical fashion.

To support this, when I can, I write functions whose bodies consist of a single expression, even if it's fairly complex. I can't always do this in a readable manner, and in those cases, I choose readability instead. Often I can, though, as I manage to do here, but in order to support that, I might add default parameters to functions to support what would otherwise be assignment statements. The purely functional language Haskell, has a convenient syntax for such temporary assignments:

let two = 2; three = 3 
    in two * three  -- 6

Javascript does not offer such a syntax. (Or really that syntax had such problems that it's been deprecated.) Adding a variable with a default value in a parameter is a reasonable work-around. It allows me to do the equivalent of defining a local variable to avoid repeated expressions.

If we had this:

const foo = (x) =>
  (x + 1) * (x + 1) 

We do a repeated calculation here (x + 1). Obviously here that's minor, but in other cases, they could be expensive, so we might write this replacement:

const foo = (x) => {
  const next = x + 1
  return next * next
}

But now we have multiple statements, something I like to avoid when possible. If instead, we write this:

const foo = (x, next = x + 1) =>
  next * next

we still save the repeated calculation, but have code more susceptible to more straightforward analysis. (I know that in these simple cases, the analysis is still straightforward, but it's easy to envision how this can grow more complex.)

Back to the actual problem. I wrote code like this:

<expression1> .reduce ((a, [k, vs], _, __, index = array [0] .indexOf (k)) => <expression2>

As you point out, Array.prototype.reduce takes up to four parameters, the accumulator, the current value, the current index, and the initial array. I add index as a new default parameter to avoid either calculating it several times or adding a temporary variable. But I don't care about the current index or the initial array. I could have written this as ((a, [k, vs], ci, ia, index = <expression>) (with "ci" for "current index" and "ia" for "initial array") or anything similar. I have to supply these if I want to add index as a fifth parameter, but I don't care about them. I won't use those variables.

Some languages with pattern-matching syntaxes offer the underscore as a useful placeholder here, representing a variable that is supplied by the caller but not used. While JS doesn't support that syntactically, the underscore (_) is a legal variable name, as is a pair of them (__). People doing functional programming often use them as they would in pattern-matching languages. They simply announce that something will be passed here but I don't care any more about it. There are proposals1 to add a similar syntactic feature to JS, and if that comes to pass I will likely use that instead.

So if you see _ as a parameter or __ or (rarely) _1, _2, _3, etc., they are often a simple workaround for the lack of a placeholder in JS. There are other uses of the _: as you note there is a convention of using it to prefix private object properties. It is also the default variable name for the library Underscore as well as for its clone-that's-grown, lodash. But there is little room for confusion between them. While you might conceivably pass Underscore as an argument to a function, you will then use it as a variable in the body, and it should be clear what the meaning is.

(And to think I was originally going to write this explanation in a comment!)


1 If you're interested, you can see a discussion of the various proposals

Original Answer

Without more information on the underlying requirements, I would start simple. This function seems to do what you want:

const addInputToArray = (
  { array, indexes, tempDestination, tempInput, ...rest}, 
  index = indexes[tempDestination]
) => ({
  array: [
    array [0], 
    ...tempInput .map (v => array [0] .map ((s, i) => i == index ? v : ''))
  ],
  indexes,
  ...rest
})

const state = {array: [["Name", "Phone", "Email"]], indexes: {Name: 0, Phone: 1, Email: 2}, tempInput: ["test@test.com","test2@test.com"], tempDestination: "Email"}

console .log (
  addInputToArray(state)
)

But I wouldn't be surprised to find that there are more requirements not yet expressed. This version builds up the additional elements from scratch, but it seems likely that you might want to call it again with a different tempInput and tempDestination and then append to those. If that's the case, then this won't do. But it might serve as a good starting place.

这篇关于如何使用 Ramda.js 在 Javascript 中动态插入二维数组的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆