Trim 一个数组的左边和右边

Trim the left and right of an array

如何 trim 仅从数组的左侧和右侧获取连续的假成员?

我想写一个方法来做这样的事情

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 采用可变数量的参数并生成查找键。

我希望此方法忽略数组左侧和右侧的空值,但如果中间有空值则抛出。

以下是一些行为规则:

实施:

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

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

通过查看类似数组的 arguments 对象的每一端来工作,标记元素未计算为 falsestartend 位置。标记后,其他元素将连接到 result 字符串。如果我们有内容,那么我们连接 startend 元素(如果它们存在)以提供完整内容,最后 returning 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);
<pre id="out"></pre>

在 ES5 中比上面的简单但效率较低

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);
<script src="https://cdnjs.cloudflare.com/ajax/libs/es5-shim/4.1.7/es5-shim.min.js"></script>
<pre id="out"></pre>

在 ES6 中再次简单但效率较低

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);
<script src="https://cdnjs.cloudflare.com/ajax/libs/es6-shim/0.32.2/es6-shim.min.js"></script>
<pre id="out"></pre>

在 lodash 中再次简单但效率较低(可能比 ES5 更高效,很可能比 ES6 更好,目前没有 jsPerf 可用于测试假设)。

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);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.0/lodash.min.js"></script>
<pre id="out"></pre>

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

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

isFalse 方法只是一种检查错误值的方法。将任何值转换为布尔值只是一个 js 技巧。

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'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.5.0/lodash.min.js"></script>
<pre id="output"></pre>

dropWhile() and 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;
}