开发者

enumerate and zip within Coffeescript?

Coming from Python, I like many of the features that Coffeescript borrows from Python and Perl (ranges/slices, comprehensions, destructuring assignments). Is there any syntactic sugar in Coffeescript to mimic Python's enumerate开发者_运维问答 or zip (itertools.izip) functions?

Here are the patterns that I don't care much for:

# an enumerate call would be helpful here
i = 0
for x in arr
  ... use x and i ...
  i++

and

# a zip would be useful here
n = Math.min(arr1.length,arr2.length)
for i in 0...n
  x = arr1[i]; y = arr2[i]
  ... use x and y ...


forEach is effectively built in:

a = ['a','b','c']
for el,i in a
    alert "Element #{el} is at index #{i}"


Enumerate:

arr.forEach (x, i) ->
    ... use x and i ...

zip / zipWith (I learned these from Haskell; I assume they mean the same thing in Python?):

zip = (arr1, arr2) ->
  basic_zip = (el1, el2) -> [el1, el2]
  zipWith basic_zip, arr1, arr2

zipWith = (func, arr1, arr2) ->
  min = Math.min arr1.length, arr2.length
  ret = []

  for i in [0...min]
    ret.push func(arr1[i], arr2[i])

  ret

Some examples (tested):

zip([1, 2, 3], [4, 5, 6])          # => [[1, 4], [2, 5], [3, 6]]

add = (el1, el2) -> el1 + el2
zipWith(add, [1, 2, 3], [4, 5, 6]) # => [5, 7, 9]

Update: Reimplemented Haskell-style, just for fun. Not as cool without the pattern matching, but oh well..

zipWith = (func, arr1, arr2) ->
  return [] if arr1.length is 0 or arr2.length is 0

  el1 = arr1.shift()
  el2 = arr2.shift()

  ret_arr = zipWith func, arr1, arr2
  ret_arr.unshift func(el1, el2)

  ret_arr

Oh, man, this was fun. SO needs more questions like this :D

Gist for zip and zipWith


For zipping and other such utility functions, Underscore.js is pretty much the standard library—and it happens to have been created by Jeremy Ashkenas, the man behind CoffeeScript. With it, you could write your zip example as

for elems in _.zip(arr1, arr2)
  x = elems[0]; y = elems[1]
  ...

or better yet

for [x, y] in _.zip(arr1, arr2)
  ...

using pattern-matching. Note, however, that _.zip uses the max length of arr1 and arr2, not the min; so if you don't want to handle undefined values, you should truncate the longer array first.

There's also a CoffeeScript implementation of Underscore, Underscore.coffee, which is a great place to look if you're wondering how to implement a particular loop in CoffeeScript.


The CoffeeScript Cookbook lists a nice implementation of a zip function:

# Usage: zip(arr1, arr2, arr3, ...)
zip = () ->
  lengthArray = (arr.length for arr in arguments)
  length = Math.min(lengthArray...)
  for i in [0...length]
    arr[i] for arr in arguments

zip([0, 1, 2, 3], [0, -1, -2, -3])
# => [[0, 0], [1, -1], [2, -2], [3, -3]]

http://coffeescriptcookbook.com/chapters/arrays/zip-function


Don't forget that CoffeeScript is just an alternate syntax for ECMAScript. At least for your first example, there is a perfectly good ECMAscript function (Array.prototype.forEach), which already does what you want:

arr = ["a", "b", "c"]

arr.forEach (el, i) ->
  alert "Element #{el} is at index #{i}"

Unfortunately, there is no Array.prototype.zip or Array.prototype.zipWith. That seems to be a pretty big omission, especially considering that there is both reduce and reduceRight, the latter of which many other languages don't have. My guess is that it is a simple oversight, and we are going to see zip in some future version of the language.


For zip, try this one:

zip = (x...) ->
  (y[i] for y in x for i in [0...Math.min (y.length for y in x)...])

If you prefer to zip all the way to the end, use Math.max().

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜