修剪数组的左侧和右侧

时间:2015-07-10 19:16:44

标签: javascript lodash

如何仅从数组的左侧和右侧修剪连续的虚假成员?

我想编写一个类似这样的方法

getLookupKey('prefix', 'prefix', 'middle', 'suffix', 'suffix');
// => 'prefix.prefix.middle.suffix.suffix'

getLookupKey(null, 'prefix', 'middle', 'suffix', null);
// => 'prefix.middle.suffix'

getLookupKey('name');
// => 'name'

getLookupKey();
// => ''

getLookupKey('prefix', null, 'suffix');
// => throws error

其中getLookupKey获取可变数量的参数并生成查找键。

我希望这个方法忽略数组左边和右边的空值,但如果中间有任何空值则抛出。

以下是一些行为规则:

  • 应该删除从数组开头开始计算为false的数组成员,直到成员计算结果为true。
  • 应该删除从数组末尾开始计算为false的数组成员,直到成员计算结果为true。
  • 在评估为true的成员之间评估为false的成员应该导致该方法抛出错误。

4 个答案:

答案 0 :(得分:1)

不使用任何库,只使用纯Javascript,因此拥有的代码行数多于库的含糖语法。应该具有比使用多个循环更好的性能,因为它只是一个循环,使用更多的参数会更加明显。

通过查看类似数组的arguments对象的每一端来工作,标记元素未评估为start的{​​{1}}和end位置。标记后,其他元素将连接到false字符串。如果我们有内容,那么我们会连接resultstart元素(如果存在)以提供完整内容,最后end return



result

function getLookupKey() {
    var length = arguments.length,
        result = '',
        index,
        start,
        end,
        stop,
        value;

    for (index = 0; index < length; index += 1) {
        if (arguments[index]) {
            start = index;
            break;
        }
    }

    for (index = length; index > start; index -= 1) {
        if (arguments[index]) {
            end = index;
            break;
        }
    }

    stop = end - 1;
    for (index = start + 1; index < end; index += 1) {
        value = arguments[index];
        if (!value) {
            throw new SyntaxError('middle evaluates false');
        }
        
        result += value;
        if (index !== stop) {
            result += '.';
        }
    }

    if (typeof start === 'number') {
        if (result) {
            result = '.' + result;
        }

        result = arguments[start] + result;
    }

    if (typeof end === 'number') {
        result += '.' + arguments[end];
    }

    return result;
}

function log(str) {
    document.getElementById('out').textContent += str + '\n';
}

var tests = [
    [
        [null, 'middle'], 'middle'
    ],
    [
        [null, null, null, 'middle', null, null], 'middle'
    ],
    [
        [null, null, 'middle', null, null, null], 'middle'
    ],
    [
        ['prefix1', 'prefix2', 'middle', 'suffix1', 'suffix2'], 'prefix1.prefix2.middle.suffix1.suffix2'
    ],
    [
        [null, 'prefix', 'middle', 'suffix', null], 'prefix.middle.suffix'
    ],
    [
        ['name'], 'name'
    ],
    [
        [null], ''
    ],
    [
        [null, null, null, null], ''
    ],
    [
        ['a', null, null, null], 'a'
    ],
    [
        [null, null, null, 'b'], 'b'
    ],
    [
        [], ''
    ],
    [
        ['prefix', null, 'suffix'], Error
    ],
    [
        [null, null, 'a', 'b', 'c', null, null], 'a.b.c'
    ],
    [
        ['a', null, 'b'], Error
    ],
    [
        [null, 'middle', null], 'middle'
    ],
    [
        [null, 'middle1', null, 'middle2', 'middle3', null], Error
    ],
    [
        [null, 'middle1', 'middle2', 'middle3', null, 'middle4', 'middle5', null], Error
    ],
    [
        [null, 'middle1', null, null, null, null, 'middle2', null], Error
    ]
];

function test(fn) {
    var length = tests.length,
        index,
        expected,
        args,
        actual;

    for (index = 0; index < length; index += 1) {
        args = tests[index][0];
        expected = tests[index][1];
        if (typeof expected === 'string') {
            try {
                actual = fn.apply(null, args);
            } catch (e) {
                actual = e.message;
            }
          
            log('Test ' + index + ': Expected: "' + expected + '" Actual: "' + actual + '"');
        } else if (expected === Error) {
            expected = 'middle evaluates false';
            try {
                actual = fn.apply(null, args);
            } catch (e) {
                actual = e.message;
            }
          
            log('Test ' + index + ': Expected: "' + expected + '" Actual: "' + actual + '"');
        } else {
            log('Test ' + index + ': coder error');
        }
    }
}

test(getLookupKey);
&#13;
&#13;
&#13;

简单但效率低于上述,在ES5中

&#13;
&#13;
<pre id="out"></pre>
&#13;
function getLookupKey() {
    var arr = Array.prototype.reduce.call(arguments, function (acc, arg) {
        if (arg || acc.length) {
            acc.push(arg);
        }
        
        return acc;
    }, []).reduceRight(function (acc, arg) {
        if (arg || acc.length) {
            acc.unshift(arg);
        }
        
        return acc;
    }, []);
    
    if (!arr.every(Boolean)) {
        throw new SyntaxError('middle evaluates false');
    }
    
    return arr.join('.');
}

function log(str) {
    document.getElementById('out').textContent += str + '\n';
}

var tests = [
    [
        [null, 'middle'], 'middle'
    ],
    [
        [null, null, null, 'middle', null, null], 'middle'
    ],
    [
        [null, null, 'middle', null, null, null], 'middle'
    ],
    [
        ['prefix1', 'prefix2', 'middle', 'suffix1', 'suffix2'], 'prefix1.prefix2.middle.suffix1.suffix2'
    ],
    [
        [null, 'prefix', 'middle', 'suffix', null], 'prefix.middle.suffix'
    ],
    [
        ['name'], 'name'
    ],
    [
        [null], ''
    ],
    [
        [null, null, null, null], ''
    ],
    [
        ['a', null, null, null], 'a'
    ],
    [
        [null, null, null, 'b'], 'b'
    ],
    [
        [], ''
    ],
    [
        ['prefix', null, 'suffix'], Error
    ],
    [
        [null, null, 'a', 'b', 'c', null, null], 'a.b.c'
    ],
    [
        ['a', null, 'b'], Error
    ],
    [
        [null, 'middle', null], 'middle'
    ],
    [
        [null, 'middle1', null, 'middle2', 'middle3', null], Error
    ],
    [
        [null, 'middle1', 'middle2', 'middle3', null, 'middle4', 'middle5', null], Error
    ],
    [
        [null, 'middle1', null, null, null, null, 'middle2', null], Error
    ]
];

function test(fn) {
    var length = tests.length,
        index,
        expected,
        args,
        actual;

    for (index = 0; index < length; index += 1) {
        args = tests[index][0];
        expected = tests[index][1];
        if (typeof expected === 'string') {
            try {
                actual = fn.apply(null, args);
            } catch (e) {
                actual = e.message;
            }
          
            log('Test ' + index + ': Expected: "' + expected + '" Actual: "' + actual + '"');
        } else if (expected === Error) {
            expected = 'middle evaluates false';
            try {
                actual = fn.apply(null, args);
            } catch (e) {
                actual = e.message;
            }
          
            log('Test ' + index + ': Expected: "' + expected + '" Actual: "' + actual + '"');
        } else {
            log('Test ' + index + ': coder error');
        }
    }
}

test(getLookupKey);
&#13;
&#13;
&#13;

简单但效率较低,在ES6中

&#13;
&#13;
<script src="https://cdnjs.cloudflare.com/ajax/libs/es5-shim/4.1.7/es5-shim.min.js"></script>
<pre id="out"></pre>
&#13;
function fVal(test, alt) {
    return test !== -1 ? test : alt;
}

function getLookupKey() {
    var arr = Array.prototype.slice.call(arguments),
        length = arr.length,
        begin = fVal(arr.findIndex(Boolean), length),
        end = length - fVal(arr.slice().reverse().findIndex(Boolean), 0);

    arr = arr.slice(begin, end);
    if (!arr.every(Boolean)) {
        throw new SyntaxError('middle evaluates false');
    }

    return arr.join('.');
}

function log(str) {
    document.getElementById('out').textContent += str + '\n';
}

var tests = [
    [
        [null, 'middle'], 'middle'],
    [
        [null, null, null, 'middle', null, null], 'middle'],
    [
        [null, null, 'middle', null, null, null], 'middle'],
    [
        ['prefix1', 'prefix2', 'middle', 'suffix1', 'suffix2'], 'prefix1.prefix2.middle.suffix1.suffix2'],
    [
        [null, 'prefix', 'middle', 'suffix', null], 'prefix.middle.suffix'],
    [
        ['name'], 'name'],
    [
        [null], ''],
    [
        [null, null, null, null], ''],
    [
        ['a', null, null, null], 'a'],
    [
        [null, null, null, 'b'], 'b'],
    [
        [], ''],
    [
        ['prefix', null, 'suffix'], Error],
    [
        [null, null, 'a', 'b', 'c', null, null], 'a.b.c'],
    [
        ['a', null, 'b'], Error],
    [
        [null, 'middle', null], 'middle'],
    [
        [null, 'middle1', null, 'middle2', 'middle3', null], Error],
    [
        [null, 'middle1', 'middle2', 'middle3', null, 'middle4', 'middle5', null], Error],
    [
        [null, 'middle1', null, null, null, null, 'middle2', null], Error]
];

function test(fn) {
    var length = tests.length,
        index,
        expected,
        args,
        actual;

    for (index = 0; index < length; index += 1) {
        args = tests[index][0];
        expected = tests[index][1];
        if (typeof expected === 'string') {
            try {
                actual = fn.apply(null, args);
            } catch (e) {
                actual = e.message;
            }

            log('Test ' + index + ': Expected: "' + expected + '" Actual: "' + actual + '"');
        } else if (expected === Error) {
            expected = 'middle evaluates false';
            try {
                actual = fn.apply(null, args);
            } catch (e) {
                actual = e.message;
            }

            log('Test ' + index + ': Expected: "' + expected + '" Actual: "' + actual + '"');
        } else {
            log('Test ' + index + ': coder error');
        }
    }
}

test(getLookupKey);
&#13;
&#13;
&#13;

简单但效率较低,在lodash中(可能比ES5更有效,并且最有可能优于ES6,目前没有jsPerf可用于测试假设)。

&#13;
&#13;
<script src="https://cdnjs.cloudflare.com/ajax/libs/es6-shim/0.32.2/es6-shim.min.js"></script>
<pre id="out"></pre>
&#13;
function getLookupKey() {
    var slice =_(arguments).dropWhile(_.isEmpty).dropRightWhile(_.isEmpty);
    
    if (!slice.all(Boolean)) {
        throw new SyntaxError('middle evaluates false');
    }
    
    return slice.join('.');
}

function log(str) {
    document.getElementById('out').textContent += str + '\n';
}

var tests = [
    [
        [null, 'middle'], 'middle'
    ],
    [
        [null, null, null, 'middle', null, null], 'middle'
    ],
    [
        [null, null, 'middle', null, null, null], 'middle'
    ],
    [
        ['prefix1', 'prefix2', 'middle', 'suffix1', 'suffix2'], 'prefix1.prefix2.middle.suffix1.suffix2'
    ],
    [
        [null, 'prefix', 'middle', 'suffix', null], 'prefix.middle.suffix'
    ],
    [
        ['name'], 'name'
    ],
    [
        [null], ''
    ],
    [
        [null, null, null, null], ''
    ],
    [
        ['a', null, null, null], 'a'
    ],
    [
        [null, null, null, 'b'], 'b'
    ],
    [
        [], ''
    ],
    [
        ['prefix', null, 'suffix'], Error
    ],
    [
        [null, null, 'a', 'b', 'c', null, null], 'a.b.c'
    ],
    [
        ['a', null, 'b'], Error
    ],
    [
        [null, 'middle', null], 'middle'
    ],
    [
        [null, 'middle1', null, 'middle2', 'middle3', null], Error
    ],
    [
        [null, 'middle1', 'middle2', 'middle3', null, 'middle4', 'middle5', null], Error
    ],
    [
        [null, 'middle1', null, null, null, null, 'middle2', null], Error
    ]
];

function test(fn) {
    var length = tests.length,
        index,
        expected,
        args,
        actual;

    for (index = 0; index < length; index += 1) {
        args = tests[index][0];
        expected = tests[index][1];
        if (typeof expected === 'string') {
            try {
                actual = fn.apply(null, args);
            } catch (e) {
                actual = e.message;
            }
          
            log('Test ' + index + ': Expected: "' + expected + '" Actual: "' + actual + '"');
        } else if (expected === Error) {
            expected = 'middle evaluates false';
            try {
                actual = fn.apply(null, args);
            } catch (e) {
                actual = e.message;
            }
          
            log('Test ' + index + ': Expected: "' + expected + '" Actual: "' + actual + '"');
        } else {
            log('Test ' + index + ': coder error');
        }
    }
}

test(getLookupKey);
&#13;
&#13;
&#13;

答案 1 :(得分:1)

有趣的问题,这是我对解决方案的看法。我使用dropWhiledropRightWhile来完成大部分繁重工作。

dropWhile将从左侧删除所有false值,直到找到真值。 dropRightWhile将从右侧删除所有错误的值,直到找到真值。 any方法用于测试成员中是否存在任何错误值。

isFalse方法只是检查falsey值的方法。它只是一个js技巧,可以将任何值转换为布尔值。

&#13;
&#13;
function log(value) {
    document.getElementById("output").innerHTML += JSON.stringify(value, null, 2) + "\n"
}

function getLookupKey() {
    var keyParts = trimArrayFalsey(_.toArray(arguments));
    if (_.any(keyParts, isFalse)) {
        throw new Error('Center null');
    }
    return keyParts.join('.');
}

function trimArrayFalsey(arr) {
    return _.chain(arr)
            .dropWhile(isFalse)
            .dropRightWhile(isFalse)
            .value();
}
        
function isFalse(value) {
    return !!!value;
}

log(getLookupKey('prefix', 'prefix', 'middle', 'suffix', 'suffix'));
// => 'prefix.prefix.middle.suffix.suffix'

log(getLookupKey(null, 'prefix', 'middle', 'suffix', null));
// => 'prefix.middle.suffix'

log(getLookupKey('name'));
// => 'name'

log(getLookupKey());
// => ''

log(getLookupKey('prefix', null, 'suffix'));
&#13;
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.5.0/lodash.min.js"></script>
<pre id="output"></pre>
&#13;
&#13;
&#13;

答案 2 :(得分:1)

dropWhile()dropRightWhile()是您的朋友:

function getLookupKey() {
    var falsey = _.negate(_.identity);

    var result = _(arguments)
        .dropWhile(falsey)
        .dropRightWhile(falsey)
        .join('.');

    if (_.includes(result, '..')) {
        throw new Error('invalid');
    } else if (_.isUndefined(result)) {
        result = '';
    }

    return result;
}

答案 3 :(得分:0)

实现:

function getLookupKey(){
  var keyParts = trimArrayFalsey(_.toArray(arguments));
  if (! _.isArray(keyParts) || ! _.all(keyParts)) {
    throw new Error('Center null');
  }

  return keyParts.join('.');
}

依赖方法:

var _ = require("lodash");

/**
 *  getArrayRightFalsyCount(array, [predicate=_.identity])
 *  Get a count of the contiguous falsey members on the right of the array.
 */
function getArrayRightFalsyCount(arr, predicate){
  if (! _.isArray(arr)) return 0;
  predicate = predicate || _.identity;
  return _.reduce(arr, function(result, value, index){
    return predicate(value) ? 0 : result + 1;
  }, 0);
}

/**
 *  getArrayLeftFalsyCount(array, [predicate=_.identity])
 *  Get a count of the contiguous falsey members on the left of the array.
 */
function getArrayLeftFalsyCount(arr, predicate){
  if (! _.isArray(arr)) return 0;
  return _.reduceRight(arr, function(result, value, index){
    return value ? 0 : result + 1;
  }, 0);
}

/**
 *  trimArrayFalsey(array, [predicate=_.identity])
 *  Trims the contiguous falsey members from the left and
 *  right edge of the array, but not from the middle.
 */
function trimArrayFalsey(arr, predicate){
  if (! _.isArray(arr)) return null;
  return _.slice(
    arr,
    getArrayLeftFalsyCount(arr, predicate),
    arr.length - getArrayRightFalsyCount(arr, predicate)
  );
}

测试它:

getArrayRightFalsyCount(["a", false, "b", false, null, "", 0]);
// => 4

getArrayLeftFalsyCount([0, "", null, false, "b", false, "a"]);
// => 4

trimArrayFalsey([]);
// => []

trimArrayFalsey(["a", "b", "c"]);
// => ["a", "b", "c"]

trimArrayFalsey([null, null, false, "a", null, "b", "c", 0, ""]);
// => ["a", null, "b", "c"]

getLookupKey(null, null, "a", "b", "c", null, null);
// => "a.b.c"

getLookupKey("a", null, "b");
// => Uncaught Error: Center null