💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
# 4数组 ### 检查变量的类型是否为数组 ### 问题 你希望检查一个变量是否为一个数组。 ~~~ myArray = [] console.log typeof myArray // outputs 'object' ~~~ “typeof” 运算符为数组输出了一个错误的结果。 ### 解决方案 使用下面的代码: ~~~ typeIsArray = Array.isArray || ( value ) -> return {}.toString.call( value ) is '[object Array]' ~~~ 为了使用这个,像下面这样调用 typeIsArray 就可以了。 ~~~ myArray = [] typeIsArray myArray // outputs true ~~~ ### 讨论 上面方法取自 "the Miller Device"。另外一个方式是使用 Douglas Crockford 的片段。 ~~~ typeIsArray = ( value ) -> value and typeof value is 'object' and value instanceof Array and typeof value.length is 'number' and typeof value.splice is 'function' and not ( value.propertyIsEnumerable 'length' ) ~~~ ### 将数组连接 ### 问题 你希望将两个数组连接到一起。 ### 解决方案 在 JavaScript 中,有两个标准方法可以用来连接数组。 第一种是使用 JavaScript 的数组方法 concat(): ~~~ array1 = [1, 2, 3] array2 = [4, 5, 6] array3 = array1.concat array2 # => [1, 2, 3, 4, 5, 6] ~~~ 需要指出的是 array1 没有被运算修改。连接后形成的新数组的返回值是一个新的对象。 如果你希望在连接两个数组后不产生新的对象,那么你可以使用下面的技术: ~~~ array1 = [1, 2, 3] array2 = [4, 5, 6] Array::push.apply array1, array2 array1 # => [1, 2, 3, 4, 5, 6] ~~~ 在上面的例子中,Array.prototype.push.apply(a, b) 方法修改了 array1 而没有产生一个新的数组对象。 在 CoffeeScript 中,我们可以简化上面的方式,通过给数组创建一个新方法 merge(): ~~~ Array::merge = (other) -> Array::push.apply @, other   array1 = [1, 2, 3] array2 = [4, 5, 6] array1.merge array2 array1 # => [1, 2, 3, 4, 5, 6] ~~~ 另一种方法,我可以直接将一个 CoffeeScript splat(array2) 放入 push() 中,避免了使用数组原型。 ~~~ array1 = [1, 2, 3] array2 = [4, 5, 6] array1.push array2... array1 # => [1, 2, 3, 4, 5, 6] ~~~ 一个更加符合语言习惯的方法是在一个数组语言中直接使用 splat 运算符(...)。这可以用来连接任意数量的数组。 ~~~ array1 = [1, 2, 3] array2 = [4, 5, 6] array3 = [array1..., array2...] array3 # => [1, 2, 3, 4, 5, 6] ~~~ ### 讨论 CoffeeScript 缺少了一种用来连接数组的特殊语法,但是 concat() 和 push() 是标准的 JavaScript 方法。 ### 由数组创建一个对象词典 ### 问题 你有一组对象,例如: ~~~ cats = [ { name: "Bubbles" age: 1 }, { name: "Sparkle" favoriteFood: "tuna" } ] ~~~ 但是你想让它像词典一样,可以通过关键字访问它,就像使用 cats["Bubbles"]。 ### 解决方案 你需要将你的数组转换为一个对象。通过这样使用 reduce: ~~~ # key = The key by which to index the dictionary   Array::toDict = (key) -> @reduce ((dict, obj) -> dict[ obj[key] ] = obj if obj[key]?; return dict), {} ~~~ 使用它时像下面这样: ~~~ catsDict = cats.toDict('name') catsDict["Bubbles"] # => { age: 1, name: "Bubbles" } ~~~ ### 讨论 另一种方法是使用数组推导: ~~~ Array::toDict = (key) -> dict = {} dict[obj[key]] = obj for obj in this when obj[key]? dict ~~~ 如果你使用 Underscore.js,你可以创建一个 mixin: ~~~ _.mixin toDict: (arr, key) -> throw new Error('_.toDict takes an Array') unless _.isArray arr _.reduce arr, ((dict, obj) -> dict[ obj[key] ] = obj if obj[key]?; return dict), {} catsDict = _.toDict(cats, 'name') catsDict["Sparkle"] # => { favoriteFood: "tuna", name: "Sparkle" } ~~~ ### 由数组创建一个字符串 ### 问题 你想由数组创建一个字符串。 ### 解决方案 使用 JavaScript 的数组方法 toString(): ~~~ ["one", "two", "three"].toString() # => 'one,two,three' ~~~ ### 讨论 toString() 是一个标准的 JavaScript 方法。不要忘记圆括号。 ### 定义数组范围 ### 问题 你想定义一个数组的范围。 ### 解决方案 在 CoffeeScript 中,有两种方式定义数组元素的范围。 ~~~ myArray = [1..10] # => [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] ~~~ ~~~ myArray = [1...10] # => [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ] ~~~ 要想反转元素的范围,则可以写成下面这样。 ~~~ myLargeArray = [10..1] # => [ 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 ] ~~~ ~~~ myLargeArray = [10...1] # => [ 10, 9, 8, 7, 6, 5, 4, 3, 2 ] ~~~ ### 讨论 包含范围以 “..” 运算符定义,包含最后一个值。 排除范围以 “...” 运算符定义,并且通常忽略最后一个值。 ### 筛选数组 ### 问题 你想要根据布尔条件来筛选数组。 ### 解决方案 使用 `Array.filter (ECMAScript 5): array = [1..10]` ~~~ array.filter (x) -> x > 5 # => [6,7,8,9,10] ~~~ 在 EC5 之前的实现中,可以通过添加一个筛选函数扩展 Array 的原型,该函数接受一个回调并对自身进行过滤,将回调函数返回 true 的元素收集起来。 ~~~ # 扩展 Array 的原型   Array::filter = (callback) -> element for element in this when callback(element)   array = [1..10]   # 筛选偶数   filtered_array = array.filter (x) -> x % 2 == 0 # => [2,4,6,8,10]     # 过滤掉小于或等于5的元素   gt_five = (x) -> x > 5 filtered_array = array.filter gt_five # => [6,7,8,9,10] ~~~ ### 讨论 这个方法与 Ruby 的 Array 的 #select 方法类似。 ### 列表推导 ### 问题 你有一个对象数组,想将它们映射到另一个数组,类似于 Python 的列表推导。 ### 解决方案 使用列表推导,但不要忘记还有 [mapping-arrays](http://coffeescript-cookbook.github.io/chapters/arrays/mapping-arrays) 。 ~~~ electric_mayhem = [ { name: "Doctor Teeth", instrument: "piano" }, { name: "Janice", instrument: "lead guitar" }, { name: "Sgt. Floyd Pepper", instrument: "bass" }, { name: "Zoot", instrument: "sax" }, { name: "Lips", instrument: "trumpet" }, { name: "Animal", instrument: "drums" } ]   names = (muppet.name for muppet in electric_mayhem) # => [ 'Doctor Teeth', 'Janice', 'Sgt. Floyd Pepper', 'Zoot', 'Lips', 'Animal' ] ~~~ ### 讨论 因为 CoffeeScript 直接支持列表推导,在你使用一个 Python 的语句时,他们会很好地起到作用。对于简单的映射,列表推导具有更好的可读性。但是对于复杂的转换或链式映射,映射数组可能更合适。 ### 映射数组 ### 问题 你有一个对象数组,想把这些对象映射到另一个数组中,就像 Ruby 的映射一样。 ### 解决方案 使用 map() 和匿名函数,但不要忘了还有列表推导。 ~~~ electric_mayhem = [ { name: "Doctor Teeth", instrument: "piano" }, { name: "Janice", instrument: "lead guitar" }, { name: "Sgt. Floyd Pepper", instrument: "bass" }, { name: "Zoot", instrument: "sax" }, { name: "Lips", instrument: "trumpet" }, { name: "Animal", instrument: "drums" } ]   names = electric_mayhem.map (muppet) -> muppet.name # => [ 'Doctor Teeth', 'Janice', 'Sgt. Floyd Pepper', 'Zoot', 'Lips', 'Animal' ] ~~~ ### 讨论 因为 CoffeeScript 支持匿名函数,所以在 CoffeeScript 中映射数组就像在 Ruby 中一样简单。映射在 CoffeeScript 中是处理复杂转换和连缀映射的好方法。如果你的转换如同上例中那么简单,那可能将它当成[列表推导](http://coffeescript-cookbook.github.io/chapters/arrays/list-comprehensions) 看起来会清楚一些。 ### 数组最大值 ### 问题 你需要找出数组中包含的最大的值。 ### 解决方案 你可以使用 JavaScript 实现,在列表推导基础上使用 Math.max(): ~~~ Math.max [12, 32, 11, 67, 1, 3]... # => 67 ~~~ 另一种方法,在 ECMAScript 5 中,可以使用 Array 的 reduce 方法,它与旧的 JavaScript 实现兼容。 ~~~ # ECMAScript 5   [12,32,11,67,1,3].reduce (a,b) -> Math.max a, b # => 67 ~~~ ### 讨论 Math.max 在这里比较两个数值,返回其中较大的一个。省略号 (...) 将每个数组价值转化为给函数的参数。你还可以使用它与其他带可变数量的参数进行讨论,如执行 console.log 。 ### 归纳数组 ### 问题 你有一个对象数组,想要把它们归纳为一个值,类似于 Ruby 中的 reduce() 和 reduceRight() 。 ### 解决方案 可以使用一个匿名函数包含 Array 的 reduce() 和 reduceRight() 方法,保持代码清晰易懂。这里归纳可能会像对数值和字符串应用 + 运算符那么简单。 ~~~ [1,2,3,4].reduce (x,y) -> x + y # => 10     ["words", "of", "bunch", "A"].reduceRight (x, y) -> x + " " + y # => 'A bunch of words' ~~~ 或者,也可能更复杂一些,例如把列表中的元素聚集到一个组合对象中。 ~~~ people = { name: 'alec', age: 10 } { name: 'bert', age: 16 } { name: 'chad', age: 17 }   people.reduce (x, y) -> x[y.name]= y.age x , {} # => { alec: 10, bert: 16, chad: 17 } ~~~ #### 讨论 Javascript 1.8 中引入了 reduce 和 reduceRight ,而 Coffeescript 为匿名函数提供了简单自然的表达语法。二者配合使用,可以把集合的项合并为组合的结果。 ### 删除数组中的相同元素 ### 问题 你想从数组中删除相同元素。 ### 解决方案 ~~~ Array::unique = -> output = {} output[@[key]] = @[key] for key in [0...@length] value for key, value of output   [1,1,2,2,2,3,4,5,6,6,6,"a","a","b","d","b","c"].unique() # => [ 1, 2, 3, 4, 5, 6, 'a', 'b', 'd', 'c' ] ~~~ ### 讨论 在 JavaScript 中有很多的独特方法来实现这一功能。这一次是基于“最快速的方法来查找数组的唯一元素”,出自[这里](http://www.shamasis.net/2009/09/fast-algorithm-to-find-unique-items-in-javascript-array/) 。 > 注意: 延长本机对象通常被认为是在 JavaScript 不好的做法,即便它在 Ruby 语言中相当普遍, (参考:[Maintainable JavaScript: Don’t modify objects you don’t own](http://www.nczonline.net/blog/2010/03/02/maintainable-javascript-dont-modify-objects-you-down-own/) ### 反转数组 ### 问题 你想要反转数组元素。 ### 解决方案 使用 JavaScript Array 的 reverse() 方法: ~~~ ["one", "two", "three"].reverse() # => ["three", "two", "one"] ~~~ #### 讨论 reverse() 是标准的 JavaScript 方法,别忘了带圆括号。 ### 打乱数组中的元素 ### 问题 你想打乱数组中的元素。 ### 解决方案 [ Fisher-Yates shuffle ](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle) 是一种高效、公正的方式来让数组中的元素随机化。这是一个相当简单的方法:在列表的结尾处开始,用一个随机元素交换最后一个元素列表中的最后一个元素。继续下一个并重复操作,直到你到达列表的起始端,最终列表中所有的元素都已打乱。这 [ Fisher-Yates shuffle Visualization ](http://bost.ocks.org/mike/shuffle/) 可以帮助你理解算法。 ~~~ shuffle = (source) -> # Arrays with < 2 elements do not shuffle well. Instead make it a noop. return source unless source.length >= 2 # From the end of the list to the beginning, pick element `index`. for index in [source.length-1..1] # Choose random element `randomIndex` to the front of `index` to swap with. randomIndex = Math.floor Math.random() * (index + 1) # Swap `randomIndex` with `index`, using destructured assignment [source[index], source[randomIndex]] = [source[randomIndex], source[index]] source   shuffle([1..9]) # => [ 3, 1, 5, 6, 4, 8, 2, 9, 7 ] ~~~ ### 讨论 #### 一种错误的方式 有一个很常见但是错误的打乱数组的方式:通过随机数。 ~~~ shuffle = (a) -> a.sort -> 0.5 - Math.random() ~~~ 如果你做了一个随机的排序,你应该得到一个序列随机的顺序,对吧?甚至[微软也用这种随机排序算法](http://www.robweir.com/blog/2010/02/microsoft-random-browser-ballot.html) 。原来,[这种随机排序算法产生有偏差的结果](http://blog.codinghorror.com/the-danger-of-naivete/) ,因为它存在一种洗牌的错觉。随机排序不会导致一个工整的洗牌,它会导致序列排序质量的参差不齐。 #### 速度和空间的优化 以上的解决方案处理速度是不一样的。该列表,当转换成 JavaScript 时,比它要复杂得多,变性分配比处理裸变量的速度要慢得多。以下代码并不完善,并且需要更多的源代码空间 … 但会编译量更小,运行更快: ~~~ shuffle = (a) -> i = a.length while --i > 0 j = ~~(Math.random() * (i + 1)) # ~~ is a common optimization for Math.floor t = a[j] a[j] = a[i] a[i] = t a ~~~ #### 扩展 Javascript 来包含乱序数组 下面的代码将乱序功能添加到数组原型中,这意味着你可以在任何希望的数组中运行它,并以更直接的方式来运行它。 ~~~ Array::shuffle ?= -> if @length > 1 then for i in [@length-1..1] j = Math.floor Math.random() * (i + 1) [@[i], @[j]] = [@[j], @[i]] this   [1..9].shuffle() # => [ 3, 1, 5, 6, 4, 8, 2, 9, 7 ] ~~~ > 注意: 虽然它像在 Ruby 语言中相当普遍,但是在 JavaScript 中扩展本地对象通常被认为是不太好的做法 ( 参考: [Maintainable JavaScript: Don’t modify objects you don’t own](http://www.nczonline.net/blog/2010/03/02/maintainable-javascript-dont-modify-objects-you-down-own/) 正如提到的,以上的代码的添加是十分安全的。它仅仅需要添 Array :: shuffle 如果它不存在,就要添加赋值运算符 (? =) 。这样,我们就不会重写到别人的代码,或是本地浏览器的方式。 > 同时,如果你认为你会使用很多的实用功能,可以考虑使用一个工具库,像 [ Lo-dash](https://lodash.com/) 。他们有很多功能,像跨浏览器的简洁高效的地图。 [Underscore](http://underscorejs.org/) 也是一个不错的选择。 ### 检测每个元素 ### 问题 你希望能够在特定的情况下检测出在数组中的每个元素。 ### 解决方案 使用 Array.every (ECMAScript 5): ~~~ evens = (x for x in [0..10] by 2)   evens.every (x)-> x % 2 == 0 # => true ~~~ Array.every 被加入到 Mozilla 的 Javascript 1.6 ,ECMAScript 5 标准。如果你的浏览器支持,但仍无法实施 EC5 ,那么请检查 [ _.all from underscore.js](http://documentcloud.github.io/underscore/) 。 对于一个真实例子,假设你有一个多选择列表,如下: ~~~ <select multiple id="my-select-list"> <option>1</option> <option>2</option> <option>Red Car</option> <option>Blue Car</option> </select> ~~~ 现在你要验证用户只选择了数字。让我们利用 array.every : ~~~ validateNumeric = (item)-> parseFloat(item) == parseInt(item) && !isNaN(item)   values = $("#my-select-list").val()   values.every validateNumeric ~~~ ### 讨论 这与 Ruby 中的 Array #all? 的方法很相似。 ### 使用数组来交换变量 ### 问题 你想通过数组来交换变量。 ### 解决方案 使用 CoffeeScript 的解构赋值语法: ~~~ a = 1 b = 3   [a, b] = [b, a]   a # => 3     b # => 1 ~~~ ### 讨论 解构赋值可以不依赖临时变量实现变量值的交换。 这种语法特别适合在遍历数组的时候只想迭代最短数组的情况: ~~~ ray1 = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ] ray2 = [ 5, 9, 14, 20 ]   intersection = (a, b) -> [a, b] = [b, a] if a.length > b.length value for value in a when value in b   intersection ray1, ray2 # => [ 5, 9 ]     intersection ray2, ray1 # => [ 5, 9 ] ~~~ ### 对象数组 ### 问题 你想要得到一个与你的某些属性匹配的数组对象。 你有一系列的对象,如: ~~~ cats = [ { name: "Bubbles" favoriteFood: "mice" age: 1 }, { name: "Sparkle" favoriteFood: "tuna" }, { name: "flyingCat" favoriteFood: "mice" age: 1 } ] ~~~ 你想用某些特征来滤出想要的对象。例如:猫的位置 ({ 年龄: 1 }) 或者猫的位置 ({ 年龄: 1 , 最爱的食物: "老鼠" }) ### 解决方案 你可以像这样来扩展数组: ~~~ Array::where = (query) -> return [] if typeof query isnt "object" hit = Object.keys(query).length @filter (item) -> match = 0 for key, val of query match += 1 if item[key] is val if match is hit then true else false   cats.where age:1 # => [ { name: 'Bubbles', favoriteFood: 'mice', age: 1 },{ name: 'flyingCat', favoriteFood: 'mice', age: 1 } ]     cats.where age:1, name: "Bubbles" # => [ { name: 'Bubbles', favoriteFood: 'mice', age: 1 } ]     cats.where age:1, favoriteFood:"tuna" # => [] ~~~ ### 讨论 这是一个确定的匹配。我们能够让匹配函数更加灵活: ~~~ Array::where = (query, matcher = (a,b) -> a is b) -> return [] if typeof query isnt "object" hit = Object.keys(query).length @filter (item) -> match = 0 for key, val of query match += 1 if matcher(item[key], val) if match is hit then true else false   cats.where name:"bubbles" # => []   # it's case sensitive     cats.where name:"bubbles", (a, b) -> "#{ a }".toLowerCase() is "#{ b }".toLowerCase() # => [ { name: 'Bubbles', favoriteFood: 'mice', age: 1 } ]   # now it's case insensitive ~~~ 处理收集的一种方式可以被叫做 “find” ,但是像 underscore 或者 lodash 这些库把它叫做 “where” 。 ### 类似 Python 的 zip 函数 ### 问题 你想把多个数组连在一起,生成一个数组的数组。换句话说,你需要实现与 Python 中的 zip 函数类似的功能。 Python 的 zip 函数返回的是元组的数组,其中每个元组中包含着作为参数的数组中的第 i 个元素。 ### 解决方案 使用下面的 CoffeeScript 代码: ~~~ # Usage: zip(arr1, arr2, arr3, ...)   zip = () -> lengthArray = (arr.length for arr in arguments) length = Math.max.apply(Math, lengthArray) argumentLength = arguments.length results = [] for i in [0...length] semiResult = [] for arr in arguments semiResult.push arr[i] results.push semiResult return results   zip([0, 1, 2, 3], [0, -1, -2, -3]) # => [[0, 0], [1, -1], [2, -2], [3, -3]] ~~~