为了账号安全,请及时绑定邮箱和手机立即绑定

使用嵌套 for 循环重构函数 - Javascript

使用嵌套 for 循环重构函数 - Javascript

摇曳的蔷薇 2023-08-18 14:04:15
我需要删除我创建的函数中的嵌套 for 循环。我的函数接收一个关联数组,并根据某些属性返回一个新数组,以便对消息进行分组以供以后使用。例如,我有两所学校,有很多学生。所以我根据性别和年级对他们进行分组。我不知道如何重构这个函数,因为我对算法不太了解。我的逻辑是否需要被彻底抹去或者需要重新做一遍并不重要。我必须删除第二个 for 循环。另外,我可以返回公共数组、关联数组或只是对象。我尝试使用相同的逻辑但不同的数据复制我的函数:var studentsArray = new Array();studentsArray["SCHOOL_1"] = [    // girls    {id: '1', school: 'SCHOOL_1', grade: 'A', message: 'Congratulations!', isMan: false},    {id: '2', school: 'SCHOOL_1', grade: 'A', message: 'Good work!', isMan: false},    {id: '3', school: 'SCHOOL_1', grade: 'A', message: 'Ok', isMan: false},    // boys    {id: '4', school: 'SCHOOL_1', grade: 'A', message: 'Congratulations!', isMan: true},    {id: '5', school: 'SCHOOL_1', grade: 'B', message: 'Good work!', isMan: true},    {id: '6', school: 'SCHOOL_1', grade: 'B', message: 'Good work!', isMan: true},    {id: '7', school: 'SCHOOL_1', grade: 'A', message: 'Congratulations!', isMan: true},    {id: '8', school: 'SCHOOL_1', grade: 'B', message: 'Good work!', isMan: true},];studentsArray["SCHOOL_2"] = [    // girls    {id: '9', school: 'SCHOOL_2', grade: 'A', message: 'Congratulations!', isMan: false},    {id: '10', school: 'SCHOOL_2', grade: 'A', message: 'Congratulations!', isMan: false},    {id: '11', school: 'SCHOOL_2', grade: 'A', message: 'Congratulations!', isMan: false},    {id: '12', school: 'SCHOOL_2', grade: 'B', message: 'Good work!', isMan: false},    {id: '13', school: 'SCHOOL_2', grade: 'B', message: 'Nice!', isMan: false},    // boys    {id: '14', school: 'SCHOOL_2', grade: 'A', message: 'Congratulations!', isMan: true},    {id: '15', school: 'SCHOOL_2', grade: 'A', message: 'Congratulations!', isMan: true},    {id: '16', school: 'SCHOOL_2', grade: 'A', message: 'Congratulations!', isMan: true},    {id: '17', school: 'SCHOOL_2', grade: 'B', message: 'Congratulations!', isMan: true},];
查看完整描述

3 回答

?
慕哥9229398

TA贡献1877条经验 获得超6个赞

可重复使用的模块


这是了解可重用模块的绝佳机会。您的GroupMessages函数几乎超过 100 行,并且与您的数据结构紧密耦合。此答案中的解决方案解决了您的特定问题,无需对之前编写的模块进行任何修改。


我将在这个答案的末尾提供一些代码审查,但现在我们重命名,因为schools数组grades中的每个项目代表特定学校单个学生的单个成绩 -


const grades =

  [ {id: 1, school: 'SCHOOL_1', grade: 'A', message: 'Congratulations!', isMan: false}

  , {id: 2, school: 'SCHOOL_1', grade: 'A', message: 'Good work!', isMan: false}

  , {id: 3, school: 'SCHOOL_1', grade: 'A', message: 'Ok', isMan: false}

  , {id: 4, school: 'SCHOOL_1', grade: 'A', message: 'Congratulations!', isMan: true}

  , {id: 5, school: 'SCHOOL_1', grade: 'B', message: 'Good work!', isMan: true}

  , {id: 6, school: 'SCHOOL_1', grade: 'B', message: 'Good work!', isMan: true}

  , {id: 7, school: 'SCHOOL_1', grade: 'A', message: 'Congratulations!', isMan: true}

  , {id: 8, school: 'SCHOOL_1', grade: 'B', message: 'Good work!', isMan: true}

  , {id: 9, school: 'SCHOOL_2', grade: 'A', message: 'Congratulations!', isMan: false}

  , {id: 10, school: 'SCHOOL_2', grade: 'A', message: 'Congratulations!', isMan: false}

  , {id: 11, school: 'SCHOOL_2', grade: 'A', message: 'Congratulations!', isMan: false}

  , {id: 12, school: 'SCHOOL_2', grade: 'B', message: 'Good work!', isMan: false}

  , {id: 13, school: 'SCHOOL_2', grade: 'B', message: 'Nice!', isMan: false}

  , {id: 14, school: 'SCHOOL_2', grade: 'A', message: 'Congratulations!', isMan: true}

  , {id: 15, school: 'SCHOOL_2', grade: 'A', message: 'Congratulations!', isMan: true}

  , {id: 16, school: 'SCHOOL_2', grade: 'A', message: 'Congratulations!', isMan: true}

  , {id: 17, school: 'SCHOOL_2', grade: 'B', message: 'Congratulations!', isMan: true}

  ]

正如您所知,JavaScript 没有关联数组。它也没有任何支持使用复合键查找(选择具有多个键的值)的本机数据结构。我们将从二叉树模块导入一些函数,btree为您的记录创建一个标识符,myIdentifier并使用它来初始化您的树,myTree-


import { nil, fromArray, inorder } from "./btree.js"


const myIdentifier = record =>

  [ record?.school ?? "noschool" // if school property is blank, group by "noschool"

  , record?.grade ?? "NA"        // if grade property is blank, group by "NA"

  , record?.isMan ?? false       // if isMan property is blank, group by false

  ]


const myTree =

  nil(myIdentifier)

二叉树根据可定制的标识符自动处理分组,并且可以使用任意数量的分组键。我们将使用 basicfilter来选择与查询匹配的所有成绩gender。fromArray选定的成绩数组与处理树更新的合并函数一起传递。inorder用于从树中提取分组值 -


function groupMessages (grades, gender)

{ const t =

    fromArray

      ( myTree

      , grades.filter(x => !x.isMan || gender === "man")

      , ({ messages = [] } = {}, { message = "", ...r }) =>

          ({ ...r, messages: [ ...messages, message ]})

      )

  return Array.from(inorder(t))

}

现在让我们看看输出 -


console.log(groupMessages(grades, 'woman'))

[

  {

    "id": "3",

    "school": "SCHOOL_1",

    "grade": "A",

    "isMan": false,

    "messages": [

      "Congratulations!",

      "Good work!",

      "Ok"

    ]

  },

  {

    "id": "11",

    "school": "SCHOOL_2",

    "grade": "A",

    "isMan": false,

    "messages": [

      "Congratulations!",

      "Congratulations!",

      "Congratulations!"

    ]

  },

  {

    "id": "13",

    "school": "SCHOOL_2",

    "grade": "B",

    "isMan": false,

    "messages": [

      "Good work!",

      "Nice!"

    ]

  }

]

为了完成这篇文章,我们将展示以下的实现btree,


// btree.js


import { memo } from "./func.js"

import * as ordered from "./ordered.js"


const nil =

  memo

    ( compare =>

        ({ nil, compare, cons:btree(compare) })

    )


const btree =

  memo

    ( compare =>

        (value, left = nil(compare), right = nil(compare)) =>

          ({ btree, compare, cons:btree(compare), value, left, right })

    )


const isNil = t =>

  t === nil(t.compare)


const compare = (t, q) =>

  ordered.all

    ( Array.from(t.compare(q))

    , Array.from(t.compare(t.value))

    )


function get (t, q)

{ if (isNil(t))

    return undefined

  else switch (compare(t, q))

  { case ordered.lt:

      return get(t.left, q)

    case ordered.gt:

      return get(t.right, q)

    case ordered.eq:

      return t.value

  }

}


function update (t, q, f)

{ if (isNil(t))

    return t.cons(f(undefined))

  else switch (compare(t, q))

  { case ordered.lt:

      return t.cons(t.value, update(t.left, q, f), t.right)

    case ordered.gt:

      return t.cons(t.value, t.left, update(t.right, q, f))

    case ordered.eq:

      return t.cons(f(t.value), t.left, t.right)

  }

}


const insert = (t, q) =>

  update(t, q, _ => q)


const fromArray = (t, a, merge) =>

  a.reduce

    ( (r, v) =>

       update

          ( r

          , v

          , _ => merge ? merge(_, v) : v

          )

    , t

    )


function* inorder (t)

{ if (isNil(t)) return

  yield* inorder(t.left)

  yield t.value

  yield* inorder(t.right)

}


export { btree, fromArray, get, inorder, insert, isNil, nil, update }

可重用性至关重要。模块可以导入其他模块!上面,从和btree导入- 下面包含部分模块 -funcordered


// func.js


function memo (f)

{ const r = new Map

  return x =>

    r.has(x)

      ? r.get(x)

      : (r.set(x, f(x)), r.get(x))

}


export { memo }

// ordered.js


const lt =

  -1


const gt =

  1


const eq =

  0


const empty =

  eq


const compare = (a, b) =>

  a < b

    ? lt

: a > b

    ? gt

: eq


const all = (a = [], b = []) =>

  a.reduce

    ( (r, e, i) =>

        concat(r, compare(e, b[i]))

    , eq

    )


const concat = (a, b) =>

  a === eq ? b : a


export { all, compare, concat, empty, eq, gt, lt }


查看完整回答
反对 回复 2023-08-18
?
慕森卡

TA贡献1806条经验 获得超8个赞

我想提供一种稍微不同的方法。它同样构建在现有功能之上,但不像该方法那样低级别。


首先,这是我对问题的初步解决方案:


const call = (fn, ...args) => fn (...args)

const groupBy = (fn) => (xs) =>

  xs .reduce ((a, x) => call (key => ((a [key] = [... (a [key] || []), x]), a), fn (x)), {})


const groupMessages = (students, gender) => 

  Object .values (groupBy (x => `${x.school}|${x.grade}`) (Object .values (students) 

    .flat ()

    .filter (({isMan}) => isMan == (gender == 'man'))))

    .map ((students) => ({

      ... students [0],

      message: students .map (s => s.message) .join ('|')

    }))


const students = {SCHOOL_1: [/* girls */ {id: '1', school: 'SCHOOL_1', grade: 'A', message: 'Congratulations!', isMan: false}, {id: '2', school: 'SCHOOL_1', grade: 'A', message: 'Good work!', isMan: false}, {id: '3', school: 'SCHOOL_1', grade: 'A', message: 'Ok', isMan: false}, /* boys */ {id: '4', school: 'SCHOOL_1', grade: 'A', message: 'Congratulations!', isMan: true}, {id: '5', school: 'SCHOOL_1', grade: 'B', message: 'Good work!', isMan: true}, {id: '6', school: 'SCHOOL_1', grade: 'B', message: 'Good work!', isMan: true}, {id: '7', school: 'SCHOOL_1', grade: 'A', message: 'Congratulations!', isMan: true}, {id: '8', school: 'SCHOOL_1', grade: 'B', message: 'Good work!', isMan: true}], SCHOOL_2: [/* girls */ {id: '9', school: 'SCHOOL_2', grade: 'A', message: 'Congratulations!', isMan: false}, {id: '10', school: 'SCHOOL_2', grade: 'A', message: 'Congratulations!', isMan: false}, {id: '11', school: 'SCHOOL_2', grade: 'A', message: 'Congratulations!', isMan: false}, {id: '12', school: 'SCHOOL_2', grade: 'B', message: 'Good work!', isMan: false}, {id: '13', school: 'SCHOOL_2', grade: 'B', message: 'Nice!', isMan: false}, /* boys */ {id: '14', school: 'SCHOOL_2', grade: 'A', message: 'Congratulations!', isMan: true}, {id: '15', school: 'SCHOOL_2', grade: 'A', message: 'Congratulations!', isMan: true}, {id: '16', school: 'SCHOOL_2', grade: 'A', message: 'Congratulations!', isMan: true}, {id: '17', school: 'SCHOOL_2', grade: 'B', message: 'Congratulations!', isMan: true}]}

         

console .log (groupMessages (students, 'woman'))

.as-console-wrapper {max-height: 100% !important; top: 0}

它使用了groupBy我经常使用的一个功能。这又取决于call,它接受一个函数和参数列表,并使用这些参数调用该函数。(我在这里使用它只是为了将局部变量保持在最低限度。)


这是有效的,并且显然比原始代码短得多。但它有一个真正的丑陋之处,这是许多此类 JS 代码所共有的。它很紧凑,但操作顺序很难看出。需要深入理解代码才能看到这一点:


    const groupMessages = (students, gender) => 

      Object .values (groupBy (x => `${x.school}|${x.grade}`) (Object .values (students)

        /* ^-- 5 */   /* ^-- 4 */                                 /* ^-- 1 */ 

        .flat ()  /* <-- 2 */

        .filter (({isMan}) => isMan == (gender == 'man'))))  /* <-- 3 */

        .map ((students) => ({  /* <-- 6 */

          ... students [0],

          message: students .map (s => s.message) .join ('|')

        }))

我倾向于使用一种pipe函数,它将一个函数的结果传递给下一个函数,并将该函数的结果传递给下一个函数,依此类推。这可以清理很多东西。但它确实需要具有具体化数组方法等内容的柯里化函数。


所以,我发现这个细分更清晰:


// reusable utility functions

const pipe = (...fns) => (arg) => fns .reduce ((a, f) => f (a), arg)

const map = (fn) => (xs) => xs .map (x => fn (x))

const filter = (fn) => (xs) => xs .filter (x => fn (x))

const call = (fn, ...args) => fn (...args)

const groupBy = (fn) => (xs) =>

  xs .reduce ((a, x) => call (key => ((a [key] = [... (a [key] || []), x]), a), fn (x)), {})

const flat = (xs) => xs .flat ()


// helper function

const groupStudentMessages = (students) => ({

  ... students [0],

  message: students .map (s => s.message) .join ('|')

})


// main function, as a pipeline

const groupMessages = (students, gender) => pipe (

  Object .values,                                     /* <-- 1 */

  flat,                                               /* <-- 2 */ 

  filter (({isMan}) => isMan == (gender == 'man')),   /* <-- 3 */

  groupBy (x => `${x.school}|${x.grade}`),            /* <-- 4 */

  Object .values,                                     /* <-- 5 */

  map (groupStudentMessages)                          /* <-- 6 */

) (students)


const students = {SCHOOL_1: [/* girls */ {id: '1', school: 'SCHOOL_1', grade: 'A', message: 'Congratulations!', isMan: false}, {id: '2', school: 'SCHOOL_1', grade: 'A', message: 'Good work!', isMan: false}, {id: '3', school: 'SCHOOL_1', grade: 'A', message: 'Ok', isMan: false}, /* boys */ {id: '4', school: 'SCHOOL_1', grade: 'A', message: 'Congratulations!', isMan: true}, {id: '5', school: 'SCHOOL_1', grade: 'B', message: 'Good work!', isMan: true}, {id: '6', school: 'SCHOOL_1', grade: 'B', message: 'Good work!', isMan: true}, {id: '7', school: 'SCHOOL_1', grade: 'A', message: 'Congratulations!', isMan: true}, {id: '8', school: 'SCHOOL_1', grade: 'B', message: 'Good work!', isMan: true}], SCHOOL_2: [/* girls */ {id: '9', school: 'SCHOOL_2', grade: 'A', message: 'Congratulations!', isMan: false}, {id: '10', school: 'SCHOOL_2', grade: 'A', message: 'Congratulations!', isMan: false}, {id: '11', school: 'SCHOOL_2', grade: 'A', message: 'Congratulations!', isMan: false}, {id: '12', school: 'SCHOOL_2', grade: 'B', message: 'Good work!', isMan: false}, {id: '13', school: 'SCHOOL_2', grade: 'B', message: 'Nice!', isMan: false}, /* boys */ {id: '14', school: 'SCHOOL_2', grade: 'A', message: 'Congratulations!', isMan: true}, {id: '15', school: 'SCHOOL_2', grade: 'A', message: 'Congratulations!', isMan: true}, {id: '16', school: 'SCHOOL_2', grade: 'A', message: 'Congratulations!', isMan: true}, {id: '17', school: 'SCHOOL_2', grade: 'B', message: 'Congratulations!', isMan: true}]}


console .log (groupMessages (students, 'woman'))

.as-console-wrapper {max-height: 100% !important; top: 0}

我希望即使没有注释,主函数中的操作顺序也足够清晰。为了使这一点更加清晰,我们提取了groupStudentMessages辅助函数,并使每个管道步骤成为单行代码。

请注意,前六个函数是非常有用的、可重用的函数。


查看完整回答
反对 回复 2023-08-18
?
牛魔王的故事

TA贡献1830条经验 获得超3个赞

对我来说,你必须删除第二个 for 循环似乎很奇怪。

但这仍然是关系数据库要解决的问题。


查看完整回答
反对 回复 2023-08-18
  • 3 回答
  • 0 关注
  • 96 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信