如何用lodash重构这个double for Loop?

How to refactor this double forLoop with lodash?

我有 selectedTags,最多可容纳 3 个标签。 vm.tags 可能包含数千个,很可能只有数百个我也需要比较的标签。

如果 3 个标签的 ID 与 vm.tags 中的一个标签的 ID 匹配,我需要打开它们的边框。还有 3 个边框:border1border2border3.

const tagsColorCheck = () => {
    let name, selected, its_ticker;
    let selectedTags = TagsFactory.retrieveTickerTags('onlyTags');

    if (selectedTags.length > 0) {
        for (let i=0; i<vm.tags.length; i++) {
            for (let j=0; j<selectedTags.length; j++) {
                if (selectedTags[j].term_id == vm.tags[i].term_id) {
                    name       = 'border'+ ( j + 1 );
                    selected   = 'selected';
                    its_ticker = 'its_ticker';

                    vm.tags[i][name]     = true;
                    vm.tags[i][selected] = true;
                    vm.tags[i][its_ticker] = selectedTags[j].its_ticker;
                }
            }
        }
    }
};

到目前为止,这是我正在处理的内容 (_.each):

const tagsColorCheck = () => {
    let name, selected, its_ticker, vmTerm, term_1, term_2, term_3, ticker_1, ticker_2, ticker_3;
    let selectedTags = TagsFactory.retrieveTickerTags('onlyTags');

    if (!_.isEmpty(selectedTags)) {
        vmTerm = R.findIndex(R.propEq('term_id', selectedTags[0].term_id))(vm.tags);
    }

    if (selectedTags[0]) { term_1 = parseInt(selectedTags[0].term_id); ticker_1 = selectedTags[0].its_ticker; }
    if (selectedTags[1]) { term_2 = parseInt(selectedTags[1].term_id); ticker_2 = selectedTags[1].its_ticker; }
    if (selectedTags[2]) { term_3 = parseInt(selectedTags[2].term_id); ticker_3 = selectedTags[2].its_ticker; }

    _.each(vm.tags, (tag) => {
        if (tag.term_id === term_1) {
            tag.selected = true;
            tag.border1  = true;
            tag.its_ticker = ticker_1;
        }

        if (tag.term_id === term_2) {
            tag.selected = true;
            tag.border2  = true;
            tag.its_ticker = ticker_2;
        }

        if (tag.term_id === term_3) {
            tag.selected = true;
            tag.border3  = true;
            tag.its_ticker = ticker_3;
        }
    })
};

还有这个 (for of loop):

const tagsColorCheck = () => {
    let name, selected, its_ticker, vmTerm, term_1, term_2, term_3, ticker_1, ticker_2, ticker_3;
    let selectedTags = TagsFactory.retrieveTickerTags('onlyTags');

    const borderRizeTag = (tag) => {
        if (tag.term_id === term_1) {
            tag.selected = true;
            tag.border1  = true;
            tag.its_ticker = ticker_1;
        }

        if (tag.term_id === term_2) {
            tag.selected = true;
            tag.border2  = true;
            tag.its_ticker = ticker_2;
        }

        if (tag.term_id === term_3) {
            tag.selected = true;
            tag.border3  = true;
            tag.its_ticker = ticker_3;
        }
        return tag;
    }

    if (!_.isEmpty(selectedTags)) {
        vmTerm = R.findIndex(R.propEq('term_id', selectedTags[0].term_id))(vm.tags);
    }

    if (selectedTags[0]) { term_1 = parseInt(selectedTags[0].term_id); ticker_1 = selectedTags[0].its_ticker; }
    if (selectedTags[1]) { term_2 = parseInt(selectedTags[1].term_id); ticker_2 = selectedTags[1].its_ticker; }
    if (selectedTags[2]) { term_3 = parseInt(selectedTags[2].term_id); ticker_3 = selectedTags[2].its_ticker; }

    for (let tag of vm.tags) {
        console.log(tag);
        tag = borderRizeTag(tag);
    }

    console.log('vmTerm',vmTerm);
};

想出了一个很棒的解决方案! :D 同时使用 _lodash and ramda.

所以在下面,立即 each 可以更快地推理,然后使用 R.equals 来比较 term_ids 是否匹配。然后在正确的 tag 对象上设置键的值。

if (!_.isEmpty(selectedTags)) {
    _.each(vm.tags, tag => {

        _.each(selectedTags, (selectedTag, index) => {
            let areTermIDsSame = R.equals;

            if (areTermIDsSame(parseInt(selectedTag.term_id), parseInt(tag.term_id))) {
                name       = 'border'+ ( index + 1 );
                selected   = 'selected';
                its_ticker = 'its_ticker';

                tag[name]       = true;
                tag[selected]   = true;
                tag[its_ticker] = selectedTag.its_ticker;
            }
        });
    })
}

ES6 fiddle 到 运行: http://www.es6fiddle.net/is0prsq9/ (注意,复制整个文本,并将其粘贴到浏览器控制台或节点 REPL 中,然后检查 tags查看结果)

它不是 lodash,在 ES6 结构中你真的不需要它。相关代码:

const tagsColorCheck = () => {

  let tags = TagsFactory.retrieveTickerTags('onlyTags')

  sel.forEach( (s,i) =>
    tags.filter(t => t.term_id === s.term_id).forEach( t => {
      t['border' + (i+1)] = true
      t.selected = true
      t.its_ticker = s.its_ticker
    })
  )

  return tags
}

如果您使用函数式语言编写此代码,您可以访问 list comprehension and it would be a bit cleaner. Essentially, this is a pretty clear case of (for every x in a and y in b) so a list comprehension is what you need, but you don't have it in javascript (mozilla has it,但在该领域之外没有用处)。

结果是一种有点实用的方法——但是,它永远不可能在纯粹的 javascript 中真正起作用。功能范式最重要的好处可能是 immutable data structures 您可以在其中编写新列表。相反,您只需在此处修改它们,这实际上根本不是很实用。尽管如此,如果您更喜欢 each 方法而不是文字增量方法,就像您在上面所做的以及我在 post 中所做的那样,那么它是一种(虽然速度较慢但可以说更干净)更好的方法。

想法很简单 - 通过 term_id 创建所有标签的索引。迭代选定的标签。如果通过标签索引中的 id 找到标签,则通过分配具有新属性的对象来改变它。

顺便说一句 - 唯一需要 lodash 的是 _.keyBy(),如果你不想使用 lodash,你可以使用 Array.prototype.reduce 轻松做到这一点。

/** mocked vm **/

const vm = {
 tags: [{ term_id: 1 }, { term_id: 2 }, { term_id: 3 }, { term_id: 4 }, { term_id: 5 }, { term_id: 6 }]
}

/** mocked TagsFactory **/

const TagsFactory = {
 retrieveTickerTags: () => [{ term_id: 1, its_ticker: 'ticker 1' }, { term_id: 4, its_ticker: 'ticker 4' }, { term_id: 5, its_ticker: 'ticker 5' }]
};

const tagsColorCheck = () => {
  const selectedTags = TagsFactory.retrieveTickerTags('onlyTags');

  if (selectedTags.length === 0) { // if selectedTags is empty exit
    return;
  }

  const vmTagsIndex = _.keyBy(vm.tags, (tag) => tag.term_id); // create an index of tags by term_id

  selectedTags.forEach(({
    term_id, its_ticker
  }, index) => { // loop through selectd tags and retreive term_id and its_ticker from the current selected tag
    const tag = vmTagsIndex[term_id]; // find the tag in the vmTagsIndex

    if (!tag) { // if the id doesn't exist in vmTagsIndex exit
      return;
    }

    Object.assign(tag, { // mutate the tag by assigining it an object with the available properties
      selected: true,
      [`border${index + 1}`]: true,
      its_ticker
    });
  });
};

tagsColorCheck();

console.log(vm.tags);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.15.0/lodash.min.js"></script>