从for()移动到map() - 无法理解它

时间:2018-02-17 21:22:01

标签: javascript arrays functional-programming

想知道是否有人可以提供帮助 - 我想要使用Array.mapArray.filter但我仍然坚持我的for循环,认为尽管阅读教程等我不能好像让我解决这个问题。

在这段代码中,我有Array个对象,我想:

  1. 将数组中的每个项目与其他项目进行比较,并确保obj[i] != obj[i]
  2. 对当前项执行操作:检查item.target是否为null,在distanceitem之间进行比较item+1,以及item& ; item+1 distance小于item& item.target然后我想将item.target替换为item
  3. 代码:

    for (var i = 0; i < 111; i++) {
      var itm = {x:Math.random()*w, y:Math.random()*h, tgt:null};
      dotArr.push(itm);
    }
    function findTarget(itemA, itemB){ 
      var x1 = itemA.x; 
      var y1 = itemA.y; 
      var x2 = itemB.x; 
      var y2 = itemB.y; 
      var distance = Math.sqrt( (x2-=x1)*x2 + (y2-=y1)*y2 ); 
      return distance; 
    }
    
    for (var i = 0; i < dotArr.length; i++) {
      let itm = dotArr[i];
      for (var j = 0; j < dotArr.length; j++) {
        if(itm != dotArr[j]){
          let itm2 = this.dotArr[j];
          if(itm.tgt==null){
            itm.tgt = itm2;
          }else{
            let newDist = findTarget(itm, itm2);
            let curDist = findTarget(itm, itm.tgt);
            if(newDist<curDist){
              itm.tgt = itm2;
            }
          }
        }
      }
    }
    

    我阅读的教程中的所有'将每个值乘以2'的示例都有意义,但无法将其推断为我一直使用的方法。

    预期结果:我有一堆粒子,它们循环遍历requestAnimationFrame()循环,检查每个循环的距离。每个粒子找到最接近的粒子并将其设置为'tgt'(然后在其他代码中向它移动),但它会更新每个循环。

3 个答案:

答案 0 :(得分:2)

摘要

const distance = (a, b) => 
  Math.sqrt(Math.pow(b.x - a.x, 2) + Math.pow(b.y - a.y, 2))

const findClosest = (test, particles) => particles.reduce(
  ({val, dist}, particle) => {
    const d = distance(test, particle)
    return d < dist && d != 0 ? {val: particle, dist: d} : {val, dist}
  },
  {val: null, dist: Infinity}
).val

const addTargets = particles => particles.map(particle => {
  particle.tgt = findClosest(particle, particles)
  return particle
})

(由于数据结构具有循环性,因此很难在代码段中执行.JSON字符串化不适用于循环。)

为正确的原因更改样式

您说您想要从for - 循环更改为mapfilter等。 al。,但你不能说为什么。请确保您出于正当理由这样做。我是函数式编程的坚定拥护者,我通常会推动初级开发人员负责进行此类更改。但我解释原因。

以下是我的解释:

  

&#34;当您进行循环播放时,您需要为原因执行此操作。如果您希望将值列表逐个转换为另一个值列表,那么有一个名为map的内置函数可以使您的代码更清晰,更简单。当您尝试检查应保留的内容时,您会filter,这会使您的代码更清晰,更简单。当您想要查找具有特定属性的列表中的第一个项目时,您有find,这再次更清晰,更简单。如果你试图将这些元素组合起来,直到你将它们减少到单个值,你就可以使用reduce,这是惊喜,惊喜,更简洁。

     

&#34;使用这些内容的原因是为了更好地表达代码的 intent 。你的意图很可能永远不会从一些值开始不断增加某个计数器的值,并在满足某些条件时结束,在每次迭代时执行一些例程。&#39;如果您可以使用更好地表达目标的工具,那么您的代码更容易理解。因此,请在代码中查找mapfilterfindreduce的含义。

     

&#34;不是每个for - 循环都适合这些模式之一,但它们中的一大部分会。替换那些合适的代码将使代码更容易理解,因此更易于维护。&#34;

我将从那里继续解释从不担心fencepost错误的优点,以及这些函数中的一些如何与更通用的类型一起工作,从而更容易重用这些代码。但这是我与我的团队一起使用的基本要点。

您需要决定为什么您正在改变,以及它是否适用于您的情况。根据您的要求,确实有可能它没有。

功能mapfindfilter仅适用于列表中的各个项目。 reduce适用于一个项目和当前累计的值。看起来您的要求就是在所有值上成对进行单词。这可能意味着这些功能都不适合。

或许他们也这样做。继续阅读我将如何解决这个问题。

名称很重要

您包含一个名为findTarget的函数。我会假设这样的函数不知何故找到了一个目标。事实上,它只是计算两个项目之间的距离。

想象一下,来到其他人的代码并阅读使用findTarget的代码。在您阅读该功能之前,您将不知道它只是计算距离。代码看起来很奇怪。如果你只是将它命名为distance,那将更难理解。

此外,使用item或缩短版本itm并不能告诉读者任何有关这些内容的信息。 (更新:对帖子的更改指出这些是&#39;粒子&#39;,因此我将在代码中使用而不是itm。)

避免棘手

findTarget / distance函数做了一些奇怪的事情,有点难以理解。它在计算过程中修改计算变量:(x2-=x1)*x2(y2-=y1)*y2。虽然我可以看到它的效果相同,但是在没有这种棘手的情况下编写非常清晰的距离函数很容易:

const distance = (a, b) => 
    Math.sqrt((b.x - a.x) * (b.x - a.x) + (b.y - a.y) * (b.y - a.y))

有许多变体同样明确。

const distance = (a, b) =>
    Math.sqrt(Math.pow(b.x - a.x, 2) + Math.pow(b.y - a.y, 2))

有一天我们能够做到

const distance = (a, b) => Math.sqrt((b.x - a.x) ** 2 + (b.y - a.y) ** 2)

任何这些都可以使代码更加清晰。您还可以使用dx / dydeltaX / deltaY等中间变量,如果这样可以让您更清楚。

仔细查看您的要求

我花了太长时间查看你的代码,以确定你究竟想要做什么。

如果你可以将你需要的部分拆分成命名函数,那么它通常会更容易编写,并且对于其他人来说通常更容易理解(或者甚至在几个星期之后为自己更容易理解) 。)

所以,如果我现在正确地理解了这个问题,你就会得到一个已定位对象的列表,并且每个对象都希望用目标更新它们,这是最接近它们的对象。这听起来非常像map

鉴于此,我认为代码应该类似于:

const addTargets = particles => particles.map(item => ({
    x: item.x,
    y: item.y,
    tgt: findClosest(item, particles)
}))

现在我还不知道findClosest将如何运作,但如果我能写出来,我希望这符合目标。

请注意,此版本非常重视我对不可变性的函数式编程概念的信念。但它不会做你想做的事情,因为粒子的目标将是旧列表中的目标,而不是自己列表中的目标。我个人可能会考虑改变数据结构来解决这个问题。但相反,让我们放宽限制,而不是返回新项目,我们可以更新项目。

const addTargets = particles => particles.map(particle => {
    particle.tgt = findClosest(particle, particles)
    return particle
})

请注意我们在此处执行的操作:我们将没有目标(或使用null个)的项目列表转换为带有它们的项目列表。但我们将其分为两部分:一部分将没有目标的元素转换为含有它们的元素;第二个找到给定元素的适当目标。这更清楚地体现了要求。

我们仍然需要弄清楚如何为元素找到合适的目标。在摘要中,我们正在做的是获取元素列表并将其转换为单个元素。那是reduce。 (这不是find操作,因为它必须检查列表中的所有内容。)

让我们写下来,然后:

const findClosest = (test, particles) => particles.reduce(
  ({val, dist}, particle) => {
    const d = distance(test, particle)
    return d < dist && d != 0 ? {val: particle, dist: d} : {val, dist}
  },
  {val: null, dist: Infinity}
).val

我们在这里使用距离用于双重目的。首先,当然,我们正在研究两个粒子之间的距离。但其次,我们假设在同一个确切位置的另一个粒子是同一个粒子。如果这不准确,你必须稍微改变一下。

在每次迭代中,我们都有一个具有valdist属性的新对象。这总是代表我们迄今为止发现的最接近的粒子以及它与我们当前粒子的距离。最后,我们只返回val属性。 (Infinity的原因是每个粒子都比这更接近,所以我们不需要特定的逻辑来测试第一个。)

结论

最后,我们可以使用mapreduce。请注意,在此示例中,我们有两个可重用的辅助函数,但每个函数只使用一次。如果您不需要重复使用它们,则可以将它们折叠到调用它们的函数中。但我不推荐它。这段代码非常易读。折叠起来,这些将不那么具有表现力。

答案 1 :(得分:0)

dotArr.map(itemI => {
  const closestTarget = dotArr.reduce((currentMax, itemJ) => {
    if(currentMax === null){
       const targetDistance = findTarget(itemI, itemJ)}
       if(targetDistance !== null){
         return {item:itemJ, distance:targetDistance};
       }
       return null;
    }
    const newDistance = findTarget(itemI, itemJ);
    if((currentMax.distance - newDistance) < 0){ //No need to check if it is the same item, because distance is 0
      return {item:itemJ, distance: newDistance};
    }
    return sum;
  }, null);
  itemI.tgt = closestTarget.item;
  return itemI;
}

构建此示例后,我发现您正在使用一个非常复杂的示例来确定地图的工作原理。

Array.map通常用于一个值,因此我们可以将其用于[i],然后我们需要使用[j]迭代数组中的所有其他值,但我们可以' t使用map执行此操作,因为我们只关心最接近的[j],因此我们可以使用Array.reduce,它也是Array.map之类的累加器,但最终结果是您想要的任何内容是的,虽然Array.map的最终结果总是一个长度相同的数组。

我的reduce函数的作用是它遍历整个列表,类似于[j]。我将currentMax初始化为null,因此当j==0然后currentMax===null时,我会找出与[j]进行比较的状态[i]。 return语句是currentMax

[j+1]等于的结果

当我最终找到最接近的目标时,我可以添加它itemI.tgt并且我必须返回它,以便新地图知道当前{{1} item的样子}}

不看index这就是我想象的实现方式

Array.map

所以这就是你总是需要写回程

的原因

答案 2 :(得分:-1)

我认为在这种情况下,您希望使用com.bumptech.glide.load.engine.GlideException: Failed to load resource There was 1 cause: java.lang.IllegalStateException(Failed to find any load path from class android.net.Uri$StringUri to class android.graphics.drawable.PictureDrawable) call GlideException#logRootCauses(String) for more detail 而不是reduce

map可以让您将一系列项目“缩减”为单个项目。

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce

例如

reduce

let largestThing = arrayOfThings.reduce(function (largest, nextItem) { if (largest == null) { return nextItem; } if (largest.prop > nextItem.prop){ return largest; } return nextItem; }, null); 作为回调的参数是回调中的起始“最大”。