翻阅《数据结构与算法javascript描述》--数组篇

导读:

这篇文章比较长,介绍了数组常见的操作方法以及一些注意事项,最后还有几道经典的练习题(面试题)。

数组的定义:

JavaScript 中的数组是一种特殊的对象,用来表示偏移量的索引是该对象的属性,索引可能是整数。然而,这些数字索引在内部被转换为字符串类型,这是因为 JavaScript 对象中的属性名必须是字符串。在内部被归类为数组。由于 Array 在 JavaScript 中被当作对象,因此它有许多属性和方法可以在编程时使用。

使用数组:

1.创建数组

  • 使用 [] 操作符 ,var arr=[] ,该方法效率最高。
  • 调用 Array 的构造函数创建数组,var myArr=new Arrery()

2.读写数组

3.由字符串生成数组,调用字符串对象的split()方法。

split() 方法通过把字符串分割成子字符串来把一个 String 对象分割成一个字符串数组。

str.split([separator][, limit])

separator指定用来分割字符串的字符(串)。separator 为一个字符串或正则表达式。

  • 当忽略 separator,返回整个字符串的数组。
  • 当 separator 是空字符串, str 将会把原字符串中每个字符的数组形式返回。

limit【可选】一个整数,限定返回的分割片段数量,返回的数组截取最多 limit 个元素。

/*
 * 定义了一个函数:使用指定的分隔符将一个字符串分割成一个字符串数组。分隔字符串后,
 * 该函数依次输出原始字符串信息,被使用的分隔符,返回数组元素的个数,以及返回数组中所有的元素。
 */
function splits(str, separator) {
    var afterString = str.split(separator);
    console.log('分割前的字符串 : "' + str + '"');
    console.log('separator : "' + separator + '"');
    console.log('分割后得到:');
    for (var i = 0; i < afterString.length; i++)
        console.log(afterString[i] + " , ");
}

var eg1 = "hello world";
var eg2 = "a,b,c,d";
var eg3 = "";//将str定义为一个空字符串,
var eg1separator = " "; //separator为空字符串时,str 将被转换为由字符串中字符组成的一个数组。var eg2separator = ",";
var eg2separator = ",";
splits(eg1, eg1separator);
splits(eg1, eg2separator);
splits(eg1);//忽略 separator,则返回整个字符串的数组形式。

splits(eg2, eg2separator);
splits(eg3);//返回一个包含一个空字符串的数组,而不是一个空数组。

4.对数组的整体性操作:(面试题之一)

浅复制:将数组a赋值给数组b,此时的数组b只是对数组a的引用,当数组a发生改变时,数组b也随着发生改变。

var a=[];
for(var i=0;i< 5;i++){
    a[i]=i;
}

var b=a;
console.log(a[1]);
console.log(b[1]);//赋值引用前 1

a[1]=999;
console.log(a[1]);
console.log(b[1]);//赋值引用后 999

深复制:可以封装一个copy()方法。

存取数组:

1.查找元素:indexOf() lastIndexOf()

indexOf() 方法返回指定值在字符串对象中首次出现的位置。从 fromIndex 位置开始查找,如果不存在,则返回 -1。

str.indexOf(searchValue[, fromIndex])

searchValue 表示被查找的值。

fromIndex 【可选】表示调用该方法的字符串中开始查找的位置。可以是任意整数。默认值为 0。

  • fromIndex < 0 ,查找整个字符串。
  • fromIndex >= str.length,该方法返回 -1,但当被查找的字符串是一个空字符串,此时返回 str.length。
/*
 * 从左向右索引。首字符索引(index)为 0,最后一个字符索引是 stringName.length - 1。
 */
"hello world".indexOf("hello");        // returns  0
"hello world hello".indexOf("hello");  // returns  0  因为是返回首次出现的位置
"hello world".indexOf("world", 0);     // returns  6
"hello world".indexOf("world", 999);   // returns  -1
"hello world".indexOf("", 10);         // returns  10
"hello world".indexOf("", 999);        // returns  11


/*
 * indexOf 方法区分大小写。例如,下面的表达式返回 -1:
 */
"hello world".indexOf("Hello");    // returns -1


/*
 * 检测是否存在某字符串
 */
"hello world".indexOf("hello") !== -1;   // true
"hello world".indexOf("hel") !== -1;     // false

2.数组的字符串表示:将数组转化为字符串:join() toString()

join() 方法将数组中的所有元素连接成一个字符串。(如果元素是undefined 或者null, 则会转化成空字符串。)

str = arr.join([separator = ','])

separator【可选】,指定连接每个数组元素的分隔符。分隔符会被转成字符串类型;

  • seprator省略时,默认为一个逗号。
  • seprator为一个空字符串时,直接连接数组中的所有元素。
/*
 * 使用四种不同的分隔符连接数组元素。
 * 首先创建了一个数组 arr,包含有三个元素,然后用四种不同的分隔符连接所有数组元素。
 * 首先是默认的分隔符逗号,然后是一个逗号加空格,接下来是一个加号前后加空格,最后是一个空字符串。
 */
var arr = ['Apple', 'Banner', 'Orange'];
var eg1 = arr.join();        // eg1的值变为"Apple,Banner,Orange"
var eg2 = arr.join(', ');    // eg2的值变为"Apple, Banner, Orange"
var eg3 = arr.join(' + ');   // eg3的值变为"Apple + Banner + Orange"
var eg4 = arr.join('');      // eg4的值变为"AppleBannerOrange"

toString() 方法返回一个表示当前函数源代码的字符串,也就是把一个值转换为字符串。

function.toString()

Function 对象覆盖了从 Object 继承来的 Object.prototype.toString 方法,包括 function关键字,形参列表,大括号,以及函数体中的内容。

在函数需要转换为字符串时,一般会自动调用函数的 toString 方法。

看到这里的toString(),我想起了toString() 与valueof() 的隐式调用。不严谨的说,当需要计算时,会隐式调用valueof()。当处于字符串环境时需要显示数据或者结果时会调用toString()。有兴趣的同学可以google一下。

3.由已有的数组创建新数组:

concat()方法合并已有的多个数组,创建新数组

splice()方法截取出一个数组的子集创建新数组

concat() 方法将传入的数组或非数组值与原数组合并,组成一个新的数组并返回.

var new_array = old_array.concat(value1[, value2[, ...[, valueN]]])

valueN 需要与原数组合并的数组或非数组值。

concat 方法创建一个新的数组,不修改调用它的对象和参数中的各个数组的值,而是将他们的每个元素拷贝一份放在组合成的新数组中。有两种拷贝的方式:

  • 对象引用(非对象直接量):复制对象引用放到组合的新数组里,原数组和新数组都引用同一个实际的对象,当实际的对象被修改时,两个数组都会被修改。
  • 字符串和数字: 复制字符串和数字的值放到新数组里.

concat 方法连接一个或多个数组(值)不会改变原本的数组/值。

/*
 * 连接两个数组,两个数组合并为一个新数组:
 */
var arr1 = ["a", "b", "c"];
var arr2 = [1, 2, 3];
console.log(arr1.concat(arr2)); // ["a", "b", "c", 1, 2, 3]


/*
 * 三个数组合并为一个新数组
 */
var num1 = [1, 2, 3];
var num2 = [4, 5, 6];
var num3 = [7, 8, 9];
console.log(num1.concat(num2, num3)); // [1, 2, 3, 4, 5, 6, 7, 8, 9]


/*
 * 将非数组值合并到数组里,多个数组和多个非数组值合并为一个新数组
 */
var myArr = ['a', 'b', 'c'];
console.log(myArr.concat(1, [2, 3]));  // ["a", "b", "c", 1, 2, 3]

当然,除上面之外,对concat方法,ES6跟ES7都有支持,有兴趣的同学自行google

splice() 方法用新元素替换旧元素,以此修改数组的内容。

array.splice(start, deleteCount[, item1[, item2[, ...]]])

start​ 从数组开始修改的位置。

  • 超出数组的长度,从数组末尾开始添加;
  • 负值,表示从数组末位开始的第几位。

deleteCount整数,表示要移除的数组元素的个数。

  • deleteCount 是 0,不移除元素。表示应该至少应添加一个新元素。
  • deleteCount 大于start 之后的元素的总数,则从 start 后面的元素都被删除(含第 start 位)。

itemN 要添加进数组的元素。不指定时, splice() 只删除数组元素。

注意,splice()与 slice()是不同的,splice() 方法会直接对数组进行修改。

var num = ["one", "tow", "three", "four"];
//从第2位开始删除0个元素,插入"insert",相当于增加的方法
num.splice(2, 0, "insert");  // ["one", "tow", "insert", "three", "four"]

//从第3位开始删除1个元素
num.splice(3, 1);   // ["one", "tow", "insert", "four"]

可变函数:

1.为数组增添元素:push()方法将元素增添至末尾, unshift()将元素增添至开头

push() 方法添加一个或多个元素到数组的末尾,并返回数组新的长度(length 属性值)。

arr.push(element1, ..., elementN)

var arr = ["a", "b"];
arr.push("c", "d");
console.log(arr);   // ["a", "b", "c", "d"]

unshift() 方法在数组的开头添加一个或者多个元素,并返回数组新的 length 值。

arr.unshift(element1, ..., elementN)

var arr = [1, 2];

arr.unshift(0);        // [0, 1, 2]
arr.unshift(-2, -1);   // [-2, -1, 0, 1, 2]
arr.unshift([-3]);     // [[-3], -2, -1, 0, 1, 2]

2.从数组中删除元素:pop() 方法可以删除数组末尾的元素, Shift() 方法可以删除数组的第一个元素

pop() 方法删除一个数组中的最后的一个元素,并且返回这个元素。

array.pop()

var arr = ["a", "b", "c"];
console.log(arr.pop());  // 删除了最后一个 c

shift() 方法删除数组的 第一个 元素,并返回这个元素。该方法会改变数组的长度。

arr.shift()

如果 length 属性的值为 0 (长度为 0),则返回 undefined。

var arr = ["a", "b", "c"];
console.log(arr.shift());  // 删除了第一个 a

3.从数组中间位置添加和删除元素:splice()

上面提过了。

4.数组排序:正序(字典顺序):sort(), 倒序;reverse()

sort() 方法对数组的元素做原地的排序,并返回该数组。 默认按照字符串的Unicode码位点(code point)排序。

arr.sort([compareFunction])

compareFunction【可选】用来指定按某种顺序进行排列的函数。

/**
 * compareFunction省略时,元素被转换为字符串并按照万国码位点顺序排序。
 */
var words = ['one', 'three', 'four'];
console.log(words.sort()); // [ 'four', 'one', 'three' ]

var num = [100, 10, 3, 21];
console.log(num.sort()); // 按字典正排序得到:[10, 100, 21, 3]

/**
 * 指明compareFunction时,数组会按照调用该函数的返回值排序。记 a 和 b 是两个将要被比较的元素
 * 当compareFunction(a, b) < 0 , a 被排列到 b 之前;
 * 当compareFunction(a, b) = 0 , a 和 b 的相对位置不变;
 * 当compareFunction(a, b) > 0 , b 被排列到 a 之前;
 * 当compareFunction(a, b) 必须总是对相同的输入返回相同的比较结果,否则排序的结果将是不确定的。
 */
var nums = [4, 2, 5, 1, 3];
nums.sort(function (a, b) {
    return b - a;
});
console.log(nums);// [ 5, 4, 3, 2, 1 ]


/**
 * 下面创建四个数组
 * 展示原数组,对数组进行排序,对比数字数组指定与不指定 compareFunction 情况下的结果。
 */
var eg1 = ["cat", "dog", "bear"];
var eg2 = ["80", "9", "700"];
var eg3 = [40, 1, 5, 200];
var eg4 = ["80", "9", "700", 40, 1, 5, 200];// 当使用比较函数后,数字数组会按照数字大小排序。
function compare(a, b) {
    return a - b;
}

console.log('eg1:', eg1.join());
console.log('排序后:', eg1.sort());

console.log('eg2:', eg2.join());
console.log('没有使用比较函数:', eg2.sort());
console.log('使用比较函数:', eg2.sort(compare));

console.log('eg3:', eg3.join());
console.log('没有使用比较函数:', eg3.sort());
console.log('使用比较函数:', eg3.sort(compare));

console.log('eg4:', eg4.join());
console.log('没有使用比较函数:', eg4.sort());
console.log('使用比较函数:', eg4.sort(compare));

映射优化:

当元素较多的时候,compareFunction 可能会有很高的负载,使用 map 辅助排序。首先将数组中的每个元素比较的实际值取出来,排序后再将数组恢复。

迭代器:

对数组的每个元素运用一个函数,可以返回一个值,一组值或一个新数组

  • 不生成新数组的迭代方法; forEach() every() some() reduce()
  • 生成新数组的迭代方法;:map() 和 filter()。

map() 方法返回一个由原数组中的每个元素调用一个指定方法后的返回值组成的新数组。

array.map(callback[, thisArg])

callback函数返回3个参数

  • currentValue 第一个参数,数组中当前被传递的元素。
  • index 第二个参数,数组中当前被传递的元素的索引。
  • array 第三个参数,调用 map 方法的数组,一般指原数组本身。

thisArg 【可选】 callback 函数时 this 指向的对象,省略或者赋值为 null 或 undefined,则 this 指向全局对象 。

map 方法会给原数组中的每个元素都按顺序调用一次 callback 函数。callback 每次执行后的返回值组合起来形成一个新数组。callback 函数只会在有值的索引上被调用;那些从来没被赋过值或者使用 delete 删除的索引则不会被调用。

/*
 * 求数组中每个元素的平方根
 */
var numbers = [1, 4, 9];
numbers.map(Math.sqrt);   // [1, 2, 3]


/*
 * 一般情况下,map 方法中的 callback 函数只接受一个参数,就是正在被遍历的数组元素本身。
 * 但某些情况下传入不止一个参数。这让我们很容易犯错误。
 */
["1", "2", "3"].map(parseInt);   // [1, NaN, NaN]
// 或许很多人一开始认为返回[1, 2, 3],原因自己去google。

filter() 方法使用指定的函数测试所有元素,并创建一个包含所有通过测试的元素的新数组。

arr.filter(callback[, thisArg])

callback 用来测试数组的每个元素的函数。

传入三个参数:

  1. 元素的值
  2. 元素的索引
  3. 被遍历的数组

thisArg 【可选】执行 callback 时的用于 this 的值。

/*
 * 筛选大于某一指定值
 */
function isBig(element) {
    return element <= 100;
}

[12, 5, 8, 130, 44].filter(isBig);  // [ 12, 5, 8, 44 ]

二维和多维数组:

1.创建二维数组

JavaScript 只支持一维数组,但是通过在数组里保存数组元素的方式,可以创建多维数组。二维数组类似由行和列构成的数据表格。在 JavaScript 中创建二维数组,先创建一个数组,然后让数组的每个元素也是一个数组。

这里 通过扩展数组对象增加一个新方法,该方法设定数组的行数、列数和初始值。下面是这个方法的定义(引用JavaScript: The Good Parts(O’Reilly)一书的一段代码):

Array.matrix = function (numrows, numcols, initial) {
    var arr = [];
    for (var i = 0; i < numrows; ++i) {
        var columns = [];
        for (var j = 0; j < numcols; ++j) {
            columns[j] = initial;
        }
        arr[i] = columns;
    }

2.处理方式i:使用嵌入式的for循环

对象数组:

数组除了包含基本数据类型元素(数字,字符串)还包含对象,数组的方法和属性对对象依然适用。在对象中,可以使用数组存储复杂的数据。

小练习

1. 筛选数组中最大值与最小值

/*
 * 秉承着尽量自己封装方法的思想,
 * 利用快速排序得出正序排列的数组后;
 * 最小值为最左边的数,最大值为最右边的数。
 */
function quickSort(arr, left, right) {
    function swap(arr, a, b) {
        var temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }

    //原地分区算法
    function partition(arr, left, right) {
        var pivot = arr[right];
        var storeIndex = left;
        for (var i = left; i < right; i++) {
            if (arr[i] < pivot) {
                swap(arr, storeIndex, i);
                storeIndex++;
            }
        }

        swap(arr, right, storeIndex);
        return storeIndex;
    }

    function sort(arr, left, right) {
        if (left > right) return;

        var storeIndex = partition(arr, left, right);
        sort(arr, left, storeIndex - 1);
        sort(arr, storeIndex + 1, right);
    }

    sort(arr, 0, arr.length - 1);
    return arr;
}

var arry = [1, 20, 2, 8];
quickSort(arry);
console.log(quickSort(arry));   // [ 1, 2, 8, 20 ]

var max = arry[arry.length - 1];
var min = arry[0];
console.log(max);       // 20
console.log(min);       // 1

2. 对一个数组,指定某一元素,删除它并返回新数组

/*
 *  这里简单用到一个splice()方法
 */
function deletes(arr, target) {
    for (var i = 0; i < arr.length; i++) {
        if (arr[i] == target) {
            arr.splice(i, 1)
        }
    }
    return arr;
}

var arr = ['as',88, '0uou','88'];
console.log(deletes(arr, 88));         // [ 'as',  '0uou' ]

// 我删除数字 88 ,它把我的字符串‘88’也删除了。
// 这里就是上文提到过的隐式地调用了toString方法
// 解决方法就是加入一个类型判断,这里我就不再提供代码了。

3. 判断某一数组中是否存在相同的元素

/*
 * 我这里提供一个参考,当然更好的方法是利用哈希表
 */
function isSame(arr) {
    var store = arr;
    for (var i = 0; i < arr.length; i++) {
        for (var k = i + 1; k < store.length; k++) {
            if (store[k] == arr[i]) {
                return true
            }
        }
    }
    return false
}

var a = isSame([1, 7, 7, 9]);
console.log(a);    // true

4. 数组去重(最优解)

function unique(arr) {
    var hash = {},
        result = [],
        type = '',
        item;

    for (var i = 0; i < arr.length; i++) {
        item = arr[i];
// 判断类型 type = Object.prototype.toString.apply(item); if (!hash[item + type]) { hash[item + type] = true; result.push(item); } } return result; } var testArr = [1, 'null', null, null, undefined, undefined, 2, 1, '2', 3, NaN, NaN, {"name": 1}, {"name": 2}, {"name": 1}]; console.log(unique(testArr)); // [ 1, 'null', null, undefined, 2, '2', 3, NaN, { name: 1 } ]

很遗憾,这还不是最完美的答案,因为不能正确区分对象。

文章已经很长了,这里我将会单独整理出来一篇博客:

面试整理之数组去重

到时大家可以去看看。

小结:

上面介绍了那么多种方法,大家可能都看混乱了吧,下面简单总结下这些方法:

  • 栈和队列的实现:.pop, .push, .shift,和 .unshift
  • 判断:.some和.every
  • 分割字符串:.split
  • 增加与删除(最强大).splice
  • 查找:.indexOf .lastIndexOf
  • 字符串拼接.join和.concat
  • 模型映射:.map
  • 过滤查询:.filter
  • 正序:.sort
  • 计算:.reduce和.reduceRight
  • 复制:.slice
  • 倒序.reverse
  • 循环:.forEach

注意:本篇是我翻阅《数据结构与算法javascript描述》后,自己所写的小结,需要对javascript深入学习的同学,还需看原书。初学者可以以此作为学习的大纲。至于好厉害的人,忽略本文,当然可以提建议,我定当修改。

本系列文章会持续更新。