javascript映射中的javascript数组切片功能

时间:2019-03-12 09:55:42

标签: javascript typescript

在javascript数组中,我们有一种称为slice的方法,该方法返回数组一部分的浅表副本。是否可以在js映射中执行相同操作。例如,我的应用程序中有一个映射,其中包含键值对形式的以下数据。

let dataSet = new Map<number, string>();

{1, 'Andy'},
{2, 'Jack'}, 
{3, 'Smith'},
{4, 'Dave'},
...
{99, 'Sam'}

是否有任何方法可以根据开始索引和结束索引对地图进行切片,并从原始地图返回切片的地图(或数组)的副本。我对map.forEach((value, key) => {})有所了解,但据我所知,它将始终从零开始并遍历每个索引。处理大型数据集时效率不高。我想要一种类似的方法

getSlicedDataFromMap(startIndex, endIndex){
   // Logic 
}

getSlicedDataFromMap(10, 20);

// returns {10, 'Carl'}, {11, 'Jerry'}, {12, 'Steve'}, ... , {20, 'Robert'}

3 个答案:

答案 0 :(得分:0)

我认为这是不可能的,如Mozilla JavaScript reference所述:

  

Map对象按插入顺序迭代其元素 – for ... of循环为每次迭代返回[key,value]数组。

也就是说,如果您的数据采用以下数组形式:

const myMap = new Map([
  [1, 'one'],
  [2, 'two'],
  [3, 'three'],
]);

您可以执行以下操作:

function getSlicedDataFromMap(theMap, startIndex, endIndex) {
  const sliced = [];
  let i;
  for (i = startIndex; i <= endIndex; i++) {
    sliced.push([ i, theMap.get(i) ]);
  }

  return sliced;
}

const result = getSlicedDataFromMap(myMap, 1, 2);

答案 1 :(得分:0)

我真的很喜欢@ nina-scholz的答案,我认为这是该问题的公认答案。

我还想为那些遇到此问题的人提供一些东西,以寻找一种基于任意键(而不只是整数序列)的Map切片方法。

这个答案有点冗长,因为我想表达这段旅程。

我们想要什么样的界面?

让我们首先定义我们如何使用函数:

const result = between('one-string', 'another-string', themap);

这似乎很好。非字符串键呢?函数之类的东西呢?

const randomFunction = () => 'I am very random';
const result = between('one-string', randomFunction, themap);

我认为那很好。

实施

考虑到我们可能从函数式编程中获得的一些想法,让我们构建此接口的实现。

如果我们有能力foldreduceMap呢?这似乎在这里很有用。

让我们实现fold(您的实现可能会有所不同,并且性能会更高。我将以一种令人愉悦的方式将其写出来):

  const fold = (fn, identity, [head, ...tail]) => {
    const res = fn(identity, head);

    return !tail.length ? res : fold(fn, res, tail);
  };

这使我们能够递归地遍历地图中的值(您可以实现迭代版本,这太好了!)。

好的,所以我们有fold函数,我们的计划是使用它来实现我们的between函数。让我们讨论一下。

between()

这里是between的实现,它从上方利用了我们的fold函数。我们一直遍历每个键/值对,直到找到开始的匹配为止,并且继续在结果中添加项,直到找到我们的结束匹配为止。


const between = (start, end, data) => {
  const result = fold(
    (agg, [key, value]) =>
      !agg.taking && key !== start
        ? agg
        : {
            taking: key !== end,
            taken: [...agg.taken, [key, value]]
          },
    { taking: false, taken: [] },
    data
  );

  return new Map([...result.taken]);
};

太好了!我们已经实现了与上面定义的原始API匹配的解决方案:

const result = between('one-string', 'another-string', themap);

还有一点可以提高效率

由于我们选择使用递归算法来实现fold,而我们的between利用了fold,因此我们的效率很低,因为我们的“循环”将继续即使我们不会在结果中添加任何其他项目,我们也找到了最终项目。

有没有一种方法可以缩短递归过程?有!它通过引入一种新的类型来工作

已减少

Reduced是一种新类型,我们可以将其添加到组合中,这将使我们能够快捷地进行搜索,并在找到结果中需要的最后一项后结束操作。

让我们实现一个简单的Reduced类型并将其添加到混合中,以提高性能。

const reduced = x => ({ x, reduced: true });

我们去了,应该去做。现在让我们使用它:

首先,我们需要告诉fold来考虑某个值被完全减小的可能性,也就是说,该值属于我们新的Reduced类型:

const fold = (fn, identity, [head, ...tail]) => {
    const res = fn(identity, head);

    if (res.reduced) { // <--- We've added this 
      return res.x;
    }

    return !tail.length ? res : fold(fn, res, tail);
  };

太棒了!现在我们可以从折叠中返回一个Reduced值,而fold函数将尊重它,并早日退出。

让我们的between函数将其传达给fold

const between = start => end => data => {
    const result = fold(
      (agg, [key, value]) => {

       // v-- We've added a finished property and we return a Reduced type from fold
        if (agg.finished) { 
          return reduced(agg);
        }

        return !agg.taking && key !== start
          ? agg
          : {
              finished: key === end,
              taking: key !== end,
              taken: [...agg.taken, [key, value]]
            };
      },
      { finished: false, taking: false, taken: [] },
      data
    ).taken;

    return new Map([...result]);
  };

就在那里。现在,我们可以获取Map中任何键之间的值,无论它们是整数,字符串,函数还是其他任何值。

以下是一些基于我们最终解决方案的代码框示例

https://codesandbox.io/s/xopkm9jlzq

答案 2 :(得分:0)

根据先前的答案,以下是TypeScript中的一些辅助函数,用于filtersliceMap,它们以更实用的方式构建:

// Map helper functions
function filterMap<K, V>(map: Map<K, V>, predicate: (item: [K, V], index: number) => boolean) {
    return createMapFrom(map, items => items.filter(predicate));
}

function sliceMap<K, V>(map: Map<K, V>, startIndex?: number, endIndex?: number) {
    return createMapFrom(map, items => items.slice(startIndex, endIndex));
}

function createMapFrom<K, V>(map: Map<K, V>, reducer: (items: [K, V][]) => [K, V][]) {
    return new Map(reducer(Array.from(map)));
}


// Usage examples
const letterMap = new Map(
    Array.from('abcdefghijklmopqrstuvwxyz')
         .map((c, i) => [i + 1, c] as [number, string])); // Map(25) {1 => "a", 2 => "b", 3 => "c", 4 => "d", 5 => "e", …}

// Slicing
const twoFirstletterMap = sliceMap(letterMap, 0, 2); // Map(2) {1 => "a", 2 => "b"}

// Filtering
const vowels = 'aeiou';

const vowelMap     = filterMap(letterMap, x => vowels.includes(x[1]));  // Map(5) {1 => "a", 5 => "e", 9 => "i", 14 => "o", 20 => "u"}
const consonantMap = filterMap(letterMap, x => !vowels.includes(x[1])); // Map(20) {2 => "b", 3 => "c", 4 => "d", 6 => "f", 7 => "g", …}

希望有一天在JavaScript中使用管道|>运算符,以获取createMapFrom的更简洁的语法:

map
|> Array.from
|> reducer
|> Map.from