【问题标题】:Best way to search for repeated values in a Multidimensional array in javascript在javascript中搜索多维数组中重复值的最佳方法
【发布时间】:2021-03-27 17:11:24
【问题描述】:

在 Javascript 中解决以下问题的最佳方法是什么? 我试图解决它,但我遇到了太多麻烦,甚至不值得发布我当前的代码,这是一个丑陋的烂摊子。

我有一个多维数组“Teams”,其中包含多个数组,其中包含 TeamName(字符串)、WinRecord(数字)、LossRecord(数字)和 Sort_Key(数字),格式如下:[TeamName, W, L, sort_key ]

我的数据如下所示:

Teams = [
   ['Team A', 25, 2, 88],
   ['Team B', 20, 7, 84],
   ['Team C', 20, 7, 76],
   ['Team D', 20, 7, 54],
   ['Team E', 20, 7, 65],
   ['Team F', 20, 6, 34],
   ['Team G', 19, 8, 67],
   ['Team H', 20, 7, 11],
   ...
   ['Team N', 3, 24, 33]
]

由于某种原因,前面的数据是任意顺序的。

我想要做的是查看多维数组“团队”内部,其中 W-L 记录在相邻数组中是相同的。从之前的数据来看,B、C、D、E 队的 W-L 战绩相同,而且都是相邻的。 F 队不一样,因为虽然它匹配 W,但它不匹配 L。请注意,H 队有相同的 20-7 记录,但由于它不相邻,我们不考虑它来进行比较!

现在我想通过 sort_key 按 ASC 顺序重新组织相邻的团队 B、C、D 和 E。 与另一个数组没有匹配 W-L 的数组必须保持在相同的位置。 因此解决方案如下所示:

Teams = [
   ['Team A', 25, 2, 88], //stays in the same place because it has no W-L match adjacent
   ['Team D', 20, 7, 54],    //sort_key is the lowest of this "chunk" so this goes first
   ['Team E', 20, 7, 65],    //sort_key second in ASC order  
   ['Team C', 20, 7, 76],    //then this one
   ['Team B', 20, 7, 84],    //finally this is the highest sort_key of this "chunk"
   ['Team F', 20, 6, 34], //stays in the same place because it has no W-L match adjacent
   ['Team G', 19, 8, 67], //stays in the same place because it has no W-L match adjacent
   ['Team H', 20, 7, 11], //stays in the same place because its NOT ADJACENT to the last 20-7 team
   ...
   ['Team N', 3, 24, 33] //stays in the same place because it has no W-L match adjacent
]

注意:前面的例子有相同 WL 为 20-7 的相邻数组,但可能有更多“块”与匹配的 WL 数组彼此相邻,这可能会发生多次,直到我们到达数组位置'n'。

我的想法是:

  1. 取第一个数组并与下一个数组比较,如果有W-L匹配则检查下一个,n次直到没有匹配。如果没有匹配,我们继续下一个数组并再次执行此步骤。

  2. 如果存在相邻的 W-L,我们需要继续检查下一个数组,直到没有完全匹配并停止。所有相邻的匹配数组都可以复制到一个临时数组中。我们需要“从” original_position 保存,因为如果我们从 Teams[2] 一直到 Teams[5] 获取数据,我们需要稍后从位置 2 重新插入排序值。也许将这个 original_position 保存到变量中?

  3. 我们现在通过 sort_key ASC 对临时数组进行排序。我们现在将 temp 数组“从”original_position 复制到原始 Teams 数组中。

  4. 我们继续寻找更多这样的匹配,直到我们到达位置“n”,我们可以退出并返回最终正确排序的 Teams 数组。

它看起来并不复杂,但我不知道如何将多维数组与两个匹配值 (W-L) 进行比较...请帮助 :)

【问题讨论】:

    标签: javascript function recursion multidimensional-array compare


    【解决方案1】:

    您可以编写一个通用的groupAdjacent 函数。它有两个参数 -

    1. t - 输入数组
    2. equal - 一个测试元素是否被认为是相邻的函数
    function groupAdjacent(t, equal)
    { function* run (r, q, i)
      { if (i >= t.length)
          return yield [...r, q]
        if (equal(q, t[i]))
          return yield* run([...r, q], t[i], i + 1)
        yield [...r, q]
        yield* run([], t[i], i + 1)
      }
      return t.length
        ? Array.from(run([], t[0], 1))
        : [] 
    }
    

    接下来,我们定义两个特定于您的特定 teams 数据的函数 -

    1. teamAdjacent - 确定团队邻接的原因
    2. teamSort - 比较两个团队的排序
    const teamAdjacent = (a, b) =>
      a[1] == b[1] && a[2] == b[2] // w/l is equal
    
    const teamSort = (a, b) =>
      a[3] - b[3]                  // sort by sortKey, ascending
    

    最后,我们将所有内容联系在一起。 groupAdjacent 创建一个组数组,然后我们 .sort 每个组,最后调用 .flat 返回一个未分组的 result -

    const result =
      groupAdjacent(teams, teamAdjacent)
        .map(_ => _.sort(teamSort))
        .flat()
    
    console.log(result)
    
    ['Team A', 25, 2, 88]
    ['Team D', 20, 7, 54]
    ['Team E', 20, 7, 65]
    ['Team C', 20, 7, 76]
    ['Team B', 20, 7, 84]
    ['Team F', 20, 6, 34]
    ['Team G', 19, 8, 67]
    ['Team H', 20, 7, 11]
    ['Team N', 3, 24, 33]
    

    展开sn-p在你自己的浏览器中验证结果-

    function groupAdjacent(t, equal)
    { function* run (r, q, i)
      { if (i >= t.length)
          return yield [...r, q]
        if (equal(q, t[i]))
          return yield* run([...r, q], t[i], i + 1)
        yield [...r, q]
        yield* run([], t[i], i + 1)
      }
      return t.length
        ? Array.from(run([], t[0], 1))
        : [] 
    }
    
    const teamAdjacent = (a, b) =>
      a[1] == b[1] && a[2] == b[2] // w/l is equal
    
    const teamSort = (a, b) =>
      a[3] - b[3]                  // sort by sortKey, ascending
    
    const teams =
      [ ['Team A', 25, 2, 88]
      , ['Team B', 20, 7, 84]
      , ['Team C', 20, 7, 76]
      , ['Team D', 20, 7, 54]
      , ['Team E', 20, 7, 65]
      , ['Team F', 20, 6, 34]
      , ['Team G', 19, 8, 67]
      , ['Team H', 20, 7, 11]
      , ['Team N', 3, 24, 33]
      ]
    
    const result =
      groupAdjacent(teams, teamAdjacent)
        .map(_ => _.sort(teamSort))
        .flat()
    
    console.log(result)

    如果你愿意,你也可以不用递归写groupAdjacent -

    function* groupAdjacent(t, equal)
    { if (t.length === 0) return
      let g = [t[0]]
      for (const _ of t.slice(1))
        if (equal(_, g[0]))
          g.push(_)
        else
          (yield g, g = [_])
      yield g
    }
    

    与此版本的唯一区别是您必须将 groupAdjacent 调用包装在 Array.from 中 -

    const result =
      Array.from(groupAdjacent(teams, teamAdjacent))
        .map(_ => _.sort(teamSort))
        .flat()
    
    // => (same output)
    

    相关阅读


    等价

    Scott 指出.map(fn).flat() 的使用与.flatMap(fn) 相同1。我应该注意到这一点,因为我认为我从未使用过 .flat,这是另一种没有必要的场景 -

    const result =
      groupAdjacent(teams, teamAdjacent)
        .map(_ => _.sort(teamSort))
        .flat()
    

    相当于-

    const result =
      groupAdjacent(teams, teamAdjacent)
        .flatMap(_ => _.sort(teamSort))
    

    1. 仅在展平 1 深度时等效,即.flat().flat(1)

    【讨论】:

    • 您的代码完美地解决了问题,满足了问题的所有要求。由于我不是 Javascript 专家,所以代码对我来说理解起来有点复杂,但我会逐行进行一些研究。您为我提供了一个优雅的解决方案,这将是一次很棒的 JS 多维数组排序学习体验。很棒的工作!
    • 我很高兴能帮上忙。特定要求使这种程序难以解决,但很有趣。我更新了帖子以包含groupAdjacent 的迭代版本,并包含了另外两个我认为您会发现有用的问答。有任何问题请告诉我:D
    • 一如既往的好。我即将发布我自己的解决方案,它使用相同的细分,具有不同的编码风格。一点:foo.map(...).flat() 最好写成foo.flatMap(...)。我特别注意到这一点,因为我在写我的时候做了完全相同的事情。只有当我将它与我编写的 Ramda 版本进行比较时,我才注意到它的简化。
    • @Scott 感谢您的评论。我用建议更新了我的答案。期待阅读您的帖子:D
    【解决方案2】:

    您正在请求组内的三级排序。使用array.sort(comparator-function)

    .map用于调整数据结构,进行4级排序; 计算组、胜利、失败、密钥。第二个.map 恢复为原始数据结构。

    例子:

    var Teams = [
          ['Team A', 25, 2, 88],
          ['Team B', 20, 7, 84],
          ['Team C', 20, 7, 76],
          ['Team D', 20, 7, 54],
          ['Team E', 20, 7, 65],
          ['Team F', 20, 6, 34],
          ['Team G', 19, 8, 67],
          ['Team H', 20, 7, 11],
          ['Team N',  3, 24, 33]
        ];
    
        var group = 0;
        
        Teams =
          Teams
          .map((item,index) => {
          
            if ( (index > 0)  
                 &&
                 ( item[1] != Teams[index-1][1]  // wins different
                   ||
                   item[2] != Teams[index-1][2]  // losses different
                 )
               ) group++;  // adjacency group
            
            return { item: item, group: group }  
            })
          .sort((a,b) => {
            const d0 = a.group - b.group // computed group ascending
            // const d1 = b.item[1] - a.item[1];  // wins   (accounted for in group)
            // const d2 = b.item[2] - a.item[2];  // losses (accounted for in group)
            const d3 = a.item[3] - b.item[3];  // key ascending
    
            // return (d0 != 0) ? d0 : (d1 != 0) ? d1 : (d2 != 0) ? d2 : d3;
            return (d0 != 0) ? d0 : d3;
            })
          .map(item => item.item)
        ;
    
        document.write ('<PRE>' + 
          Teams
          .reduce ( 
            (html, item) => html + JSON.stringify(item) + "\n"
            , ""
          ) + '</PRE>'
        );

    【讨论】:

    • 这不尊重原始数组的分区。 H 不合适。完成这部分计划更具挑战性。
    • 对分组所做的更改
    • 您的最新程序返回A-&gt;B-&gt;C-&gt;D-&gt;E-&gt;F-&gt;G-&gt;H-&gt;N 的订单。你如何让代码粘贴可执行,这样你的帖子就不会显示不正确的输出?
    • 已修复。忘记将排序结果分配回 Teams。也更改为可运行的 sn-p(对我来说是新功能,pow("Thank you",2)
    • 伟大的眼睛斯科特,谢谢。更新每个建议的答案,以根据相同的胜负计算组,然后仅使用键在组内排序。
    【解决方案3】:

    我倾向于通过Ramda的思路来思考这些问题。 (免责声明:我是它的主要作者之一。)Ramda 有一个函数groupWith,它完成了这项基本工作,基于一个测试两个连续值是否应该分组的函数,将连续匹配元素分组到子数组中一起。一次性版本使用两个同样受 Ramda 启发的简单辅助函数,last,获取数组的最后一个元素,init,获取所有最后一个元素。

    使用groupWith,编写我们的主函数非常简单,只需调用groupWith,使用一个测试输赢是否匹配的函数,然后按sort_key对各个组进行排序并将结果展平回单个数组。

    它可能看起来像这样:

    // utility functions
    const last = (xs = []) =>
      xs [xs.length - 1]
    
    const init = (xs = []) =>
      xs .slice (0, -1)
    
    const groupWith = (match) => (xs) => 
      xs.reduce (
        (xss, x, i) => i > 0 && match (x, last (last (xss)))
          ? [...init (xss), [...last (xss), x]]
          : [...xss, [x]],
        []
      )
      
      
    // main function
    const process = (xs) => 
      groupWith ((a, b) => a [1] == b [1] && a [2] == b [2]) (xs) 
        .flatMap (x => x .sort ((a, b) => a [3] - b [3])) 
    
    
    // sample data
    const teams = [['Team A', 25, 2, 88], ['Team B', 20, 7, 84], ['Team C', 20, 7, 76], ['Team D', 20, 7, 54], ['Team E', 20, 7, 65], ['Team F', 20, 6, 34], ['Team G', 19, 8, 67], ['Team H', 20, 7, 11], ['Team N', 3, 24, 33]]
    
    
    // demo
    console .log (process (teams))
    .as-console-wrapper {max-height: 100% !important; top: 0}

    这在结构上与Thankyou 的回答相同。但代码不同,可以单独呈现。

    拉出内联的辅助函数可能会更简洁。看起来像:

    const groupWL = (a, b) => 
      a [1] == b [1] && a [2] == b [2]
    
    const sortByIdx3 = (a, b) => 
      a [3] - b [3]
    
    const process = (xs) => 
      groupWith (groupWL) (xs) 
        .flatMap (x => x .sort (sortByIdx3)) 
    

    或者我们可以把这些助手写成

    const groupWL  = ([, w1, l1, ], [, w2, l2, ]) =>
      w1 == w2 && l1 == l2
    
    const sortByIdx3 = ([, , , sk1], [, , , sk2]) => 
      sk1 - sk2 
    

    最后,为了比较,这是我使用 Ramda 的方法(但请参阅下面的 注 1):

    const process = pipe (
      groupWith (both (eqProps (1), eqProps (2))),
      chain (sortBy (nth (3)))
    )
    

    其中pipegroupWithbotheqPropschainsortBynth 都是 Ramda 函数。


    note 1 这最初使用and 而不是both。我目前不确定为什么这会奏效,并将进行调查。感谢用户感谢您的关注!

    【讨论】:

    • 我很嫉妒你能够将这些组合成一个条件,i &gt; 0 &amp;&amp; match (x, last (last (xss)))。你不知道我盯着我的生成器花了多少时间试图折叠包含yield [...r, q] 的两个分支?
    • 这个特殊的问题让 Ramda 像金子一样闪耀。我的一个问题是您将两个函数传递给R.andgroupWith 的第一个参数不是期待一个函数吗?帮我看看路!
    • 我想像groupWith((a,b) =&gt; and(eqProps(1,a,b), eqProps(2,a,b))) 这样的东西是必需的吗?或者也许使用R.juxt
    • eqProps 视为prop =&gt; (a, b) =&gt; a[prop] == b[prop]。从技术上讲,它更复杂,使用 Ramda 的 equals 而不是 == 并且 Ramda 有点神奇的柯里化。但这是我通常使用它的方式。不幸的是,该名称类似于propEq,就像(prop, val) =&gt; obj =&gt; obj[prop] == val,再次真正使用equals。实际上那里有一个错误,适用于这个例子。我现在正在修复它。应该是both,而不是and
    • 我从不喜欢将 and 的名称从相当于 (f, g) =&gt; (x) =&gt; f(x) &amp;&amp; g(x) 的名称改为 (x, y) =&gt;x &amp;&amp; y。 Ramda 这样做是有道理的,但我仍然犯了使用and 的错误,我的意思是both。通常我可以在一个测试中看到它,我不确定它为什么在这里真正起作用。我今晚可能会调查一下。
    【解决方案4】:

    试试这个。

    逻辑。

    1. Pop Front 直到 W/L 与枢轴元素不同
    2. 对子数组进行排序并合并到结果数组中
    3. 执行 1 和 2 直到原始数组为空

    您可以添加复制登录以防止改变原始数组,推荐

    let teams = [
       ['Team A', 25, 2, 88], //stays in the same place because it has no W-L match adjacent
       ['Team D', 20, 7, 54],    //sort_key is the lowest of this "chunk" so this goes first
       ['Team E', 20, 7, 65],    //sort_key second in ASC order  
       ['Team C', 20, 7, 76],    //then this one
       ['Team B', 20, 7, 84],    //finally this is the highest sort_key of this "chunk"
       ['Team F', 20, 6, 34], //stays in the same place because it has no W-L match adjacent
       ['Team G', 19, 8, 67], //stays in the same place because it has no W-L match adjacent
       ['Team H', 20, 7, 11]
    ]
    
    const groupByWL = (teams) =>{
       const pivot = teams[0]
       const ret = [ pivot ]
    
       for(i in teams){
          if(i == 0) continue
    
          const [ name, W, L ] = teams[i]
          
          if(W != pivot[1] || L != pivot[2] ) break
    
          ret.push(teams[i])
       }
       
       ret.sort(function(a, b) {//put your sort logic in here
          return a[3] - b[3]
       })
        
       return ret
    }
    
    const search = teams =>{
        let ret = []
        
        while(teams.length){
           const chunk = groupByWL(teams)
           teams = teams.slice(chunk.length, teams.length)
           ret = [ ...ret, ...chunk ]
           console.log(teams)
           console.log(ret)
        }
        
        return ret
    }
    
    const result = search(teams)
    console.log(teams)
    
    
    

    【讨论】:

    • 我无法让它按预期工作,但我喜欢你所做的。我将进一步研究其他类型问题的支点。谢谢!
    • @toolnin:我不知道为什么它不适合你。它对我有用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多