This is loosely related to an earlier question I posted, though with a different image editing library (CamanJS instead of Pixastic) and the code refactored somewhat.
I'm trying to write a function changify that reverts the HTML5 image canvas to its original state, and then runs a number of preset changes to it. This code, which loads the right canvas element imageClipName from a list called camanCache, works:
changify = (imageClipName) ->
camanCache[imageClipName].revert ->
camanCache[imageClipName]
.brightness(10)
.contrast(10)
.noise(10)
.render()
console.log(camanCache[imageClipName])
But what I want is to find the saved preset changes from another list called imageValues and dynamically create the string of image function changes from the key value pairs of that list. Here's what I've tried to do, that fails:
imageClipName = "image_1"
imageValues = {image_1:{brightness: 10, contrast: 10, noise: 10}, image_2:{...}...}
changify = (imageClipName) ->
camanCache[imageClipName].revert ->
camanCache[imageClipName]
for o in imageValues when o.id == imageClipName
for key, val of o
.#{key}(val)
.render()
This gives me a coffeescript syntax error for the third line of the changify function (Unexpected 'INDENT'), but I suspect that .#{key}(val) won't work for appending multiple functions to camanCache[imageClipName]. How to dynamically string function calls like this in coffeescript?
I think you're trying to do this:
changify = (imageClipName) ->
camanCache[imageClipName].revert ->
clip = camanCache[imageClipName]
for id, methods of imageValues when id == imageClipName
for name, arg of methods
clip = clip[name](arg)
clip.render()
Note the switch to for ... of in the outer loop since you're iterating over an object, for ... in is for arrays in CoffeeScript.
So given this:
{brightness: 10, contrast: 10, noise: 10}
in methods, this loop:
clip = camanCache[imageClipName]
for name, arg of methods
clip = clip[name](arg)
clip.render()
would have the same effect as:
camanCache[imageClipName].brightness(10).contrast(10).noise(10).render()
The order of the key, val pairs when iterating over the object o is not defined so there is no guarantee about the order of the brightness, contrast, and noise calls. If you need them in a specific order then you'd have to use an array for methods:
imageValues = {image_1: [['brightness', 10], ['contrast', 10], ['noise', 10]], ... }
changify = (imageClipName) ->
camanCache[imageClipName].revert ->
clip = camanCache[imageClipName]
for id, methods of imageValues when id == imageClipName
for method in methods
clip = clip[method[0]](method[1])
clip.render()
or a bit nicer:
imageValues = {image_1: [ {name: 'brightness', arg: 10}, {name: 'contrast', arg: 10}, {name: 'noise', arg: 10}], ... }
changify = (imageClipName) ->
camanCache[imageClipName].revert ->
clip = camanCache[imageClipName]
for id, methods of imageValues when id == imageClipName
for method in methods
clip = clip[method.name](method.arg)
clip.render()
Related
I have data in the following format:
data = {
car1: {
starting_position: 1,
...
},
car5: {
starting_position: 2,
...
}
}
I want to create an object where starting_position becomes the key and the key in the original data becomes the value. I can do it like this:
byStartingPosition = {}
for k, properties of data
byStartingPosition[properties.starting_position] = k
But I can't imagine there is no one liner to do the same...
If you are using lodash 4.1.0 or later you could do it with this function https://lodash.com/docs#invertBy
_.invertBy data, (v) -> v.starting_position
https://jsfiddle.net/7kf9wn71/2/
You cannot reduce it semantically but you can make it more concise
byStartingPosition = {}
byStartingPosition[v.starting_position] = k for k,v of data
Rayon's comment was aaalmost there. You want to use reduce:
byStartPos = Object.keys(data).reduce(((obj, k) -> start = data[k].starting_position; obj[start] = k; obj), {})
Although that's obnoxiously long, not very idiomatic coffeescript, and frankly less readable than your original, it is a one-liner.
I have function defined like this
casper.executeRemote = (fn,params)->
->
try
fn.apply(this,params)
catch e
console.log(e)
getLinks = ->
elems = document.querySelectorAll('#pagination-flickr a')
and i am calling the function like below
casper.then ->
all_links = #evaluate #executeRemote getLinks
casper.log all_links,'debug'
However I get the below error when i try to run this in casperjs
ReferenceError: Can't find variable: fn
The same code works fine, if i try in a browser console(compiled js). What is that I am doing wrong?
Of course they work, but not at the border to the page context:
Note: The arguments and the return value to the evaluate function must be a simple primitive object. The rule of thumb: if it can be serialized via JSON, then it is fine.
Closures, functions, DOM nodes, etc. will not work!
Thankfully there is the toString function on Functions. So, you can change your code a little to account for passing the function as a string and evaling inside of the page context.
(Untested) example:
casper.executeRemote = (fn,params)->
fn = eval('('+fn+')')
try
fn.apply(this,params)
catch e
console.log(e)
getLinks = ->
elems = document.querySelectorAll('#pagination-flickr a')
casper.then ->
all_links = #evaluate(#executeRemote, getLinks.toString())
casper.log all_links,'debug'
Based on #Artjom's answer. I modified the code as shown below, then it works
executeRemote = (fn,params)->
->
try
fn.apply(this,params)
catch e
console.log(e)
getLinks = ->
elems = document.querySelectorAll('#pagination-flickr a')
links = (link.href for link in elems)
remoteWrapper = (excRm,gL)->
excRm=eval('('+excRm+')')
gL=eval('('+gL+')')
fn=excRm(gL)
fn()
casper.on 'remote.message',(message)->
#echo message
casper.then ->
all_links = #evaluate remoteWrapper, executeRemote.toString(), getLinks.toString()
casper.log all_links,'debug'
AnormCypher doc provides an example how to retriev data using the Stream API:
http://anormcypher.org/
"The first way to access the results of a return query is to use the Stream API.
When you call apply() on any Cypher statement, you will receive a lazy Stream of CypherRow instances, where each row can be seen as a dictionary:
// Create Cypher query
val allCountries = Cypher("start n=node(*) where n.type = 'Country' return n.code as code, n.name as name")
// Transform the resulting Stream[CypherRow] to a List[(String,String)]
val countries = allCountries.apply().map(row =>
row[String]("code") -> row[String]("name")
).toList
I am trying to use the same aproach to get path with the following Cypher query:
MATCH p = (n {id: 'n5'})-[*]-(m) RETURN p;
Yet, when running this code:
Cypher("MATCH p = (n {id: 'n5'})-[*]-(m) RETURN p;")().map {row =>
println(row[Option[org.anormcypher.NeoRelationship]]("p"))
}
I get exception (see below). How to get path info from CypherRow in this case?
Exception in thread "main" java.lang.RuntimeException: TypeDoesNotMatch(Unexpected type while building a relationship)
at org.anormcypher.MayErr$$anonfun$get$1.apply(Utils.scala:21)
at org.anormcypher.MayErr$$anonfun$get$1.apply(Utils.scala:21)
at scala.util.Either.fold(Either.scala:97)
at org.anormcypher.MayErr.get(Utils.scala:21)
at org.anormcypher.CypherRow$class.apply(AnormCypher.scala:303)
at org.anormcypher.CypherResultRow.apply(AnormCypher.scala:309)
at bigdata.test.n4j.Simple$$anonfun$main$1.apply(Simple.scala:31)
at bigdata.test.n4j.Simple$$anonfun$main$1.apply(Simple.scala:29)
at scala.collection.immutable.Stream.map(Stream.scala:376)
at bigdata.test.n4j.Simple$.main(Simple.scala:29)
Paths in Cypher were changed as of 2.0, so you can't work with them easily directly, as they're not collections. There probably should be a new Path type of some sort in AnormCypher, but for now you can use paths along with relationships() or nodes().
For example, you could do this to extract the relationships:
Cypher("MATCH p = (n {id: 'n5'})-[*]-(m) RETURN relationships(p);")().map {row =>
println(row[Seq[NeoRelationship]]("relationships(p)"))
}
I'm using the D3 function each, which accepts a callback function and calls it passing this as argument, but I need to access both this and _this. This is the coffeescript code:
#x = d3.scale.ordinal().domain(d3.range(#model.geneExpressions[0].length)).rangeBands([0, width])
getRow = (row) =>
cell = d3.select(this).selectAll(".cell")
.data(row)
.enter().append("rect")
.attr("x", (d,i) => #x(i))
rows = #heatmap.selectAll(".row")
.data(#model.geneExpressions)
.enter().append("g")
.each(getRow)
and the javascript that it generates:
var _this = this;
this.x = d3.scale.ordinal().domain(d3.range(this.model.geneExpressions[0].length)).rangeBands([0, width]);
getRow = function(row) {
var cell;
return cell = d3.select(_this).selectAll(".cell").data(row).enter().append("rect").attr("x", function(d, i) {
return _this.x(i);
})
};
rows = this.heatmap.selectAll(".row").data(this.model.geneExpressions).enter().append("g").attr("class", "row").each(getRow);
How can I get coffeescript to use this instead in this line and leave everything the same?:
return cell = d3.select(this) ...
The problem is that I can't pass #x as an argument to each and use the thin arrow instead of the fat arrow (because then I couldn't access #x), unless I rewrite the D3 function, which seems overkill.
So you have this structure:
#x = ...
getRow = (row) =>
d3.select(#)...attr('x', (d, i) => #x(i))
rows = ...each(getRow)
But you need getRow to be a normal -> function so that it gets the DOM element as # and you need the attr callback to be a bound => function so #x works, right?
Two possibilities immediately come to mind:
Use the CoffeeScript form of the usual JavaScript var that = this; trick.
Use a named bound function for the attr callback.
The first one looks something like this:
that = #
getRow = (row) ->
cell = d3.select(#)
.selectAll(".cell")
.data(row)
.enter().append("rect")
.attr("x", (d,i) -> that.x(i))
The second goes like this:
x_at_i = (d, i) => #x(i)
getRow = (row) ->
cell = d3.select(#)
.selectAll(".cell")
.data(row)
.enter().append("rect")
.attr("x", x_at_i)
is there a way to return an object from a comprehension in coffeescript? something so that i could express this:
form_values = () ->
ret = {}
ret[f.name] = f.value for f in $('input, textarea, select')
return ret
like this:
form_values = () -> f.name, f.value for f in $('input, textarea, select')
i'd like to construct a single object (not an array of objects). so if the markup looks something like this:
<form name=blah>
<input type=text name=blah1 value=111 />
<textarea name=blah2>222</textarea>
<select name=blah3>
<option value=333a>
<option value=333b>
</select>
</form>
the returned object would be something like this:
{
blah1: '111',
blah2: '222',
blah3: ''
}
form_values = new ->
#[f.name] = f.value for f in $ 'input, textarea, select'
this
or
form_values = new class then constructor: ->
#[f.name] = f.value for f in $ 'input, textarea, select'
Nope. Comprehensions only return arrays in CoffeeScript. Search the issue tracker for object comprehensions, and you'll find several proposals, but none were found suitable.
Check the functional library underscore and the extension _.mash from this mixin:
form_values = ->
_($('input, textarea, select')).mash f -> [f.name, f.value]
Using underscore's object function, you can do this:
form_values = _.object([f.name, f.value] for f in $('input, textarea, select'))
This has already been answered but probably lack of some explanations as this idiom is rather cryptic at first sight:
form_values = (new -> #[f.name] = f.value for f in $ 'input, textarea, select'; #)
// ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^
// create with |
// a new that |
// empty anonymous |
// object constructor |
// don't forget -/
// to return the
// newly created object
The key idea is to create an empty object (new) with an anonymous constructor (-> ...) that will create the various fields.
CoffeeScript's creator suggests using a helper function to convert an array of pairs into an object:
form_values = toObject([f.name, f.value] for f in $('input, textarea, select'))
This is arguably the most readable way of doing it, within the current language syntax. It's also very similar to how Python and other languages do it, except for the missing syntactic sugar.
The helper function can be easily written once, using for example the technique from #matyr's and #Sylvain's answers:
// Create a new object from an array of [key, value] pairs.
toObject = (pairs) ->
new -> #[key] = value for [key, value] in pairs; #
I believe you can do this with no added libraries right in CoffeeScript.
It should be something to the effect of:
$('input, textarea, select').each (item) => #form_values || #form_values = {}; #form_values[$(item).name] = $(item).value
You could simplify the syntax of that by pre-creating the form_values:
form_values = {}
$('input, textarea, select').each (item) -> form_values[$(item).name] = $(item).value
Here is a lengthier response with canned examples:
Take a very simple example where you wanted to map the obj to name value:
items = [ { a: 1 }, { b: 2 }, { c: 3 } ]
items.map((item) -> {name: Object.keys(item)[0], value: item[Object.keys(item)[0]]})
[ { name: 'a', value: 1 },
{ name: 'b', value: 2 },
{ name: 'c', value: 3 } ]
Note that the above is not really an Object comprehension, just demonstrating an example.
Now let's say there is a bit more structure and you just want to map a known unique key:
items = [{key: "abc", someVar: 1}, {key: "def", someVar: 2}]
In Python you'd do something simple like this: {x['key']:x for x in items}
In CoffeeScript you can get all of this down to one single line though with a caveat:
items.forEach (item) => #x || #x = {}; #x[item['key']] = item
{ abc: { key: 'abc', someVar: 1 },
def: { key: 'def', someVar: 2 } }
In the above code x was not previously defined in the scope, so using => and # allowed us to bind x with the #x || #x = {} if not previously found, then set the key.
If you don't want to use => and # you have to define x beforehand:
x = {}
items.forEach (item) => x || x = {}; x[item['key']] = item
{ abc: { key: 'abc', someVar: 1 },
def: { key: 'def', someVar: 2 } }
Not to beat a dead horse, but I personally thing this is readable and satisfies the 'one line' requirement without needing extra modules:
form_values = {}; form_values[f.name] = f.value for f in $('input, textarea, select')
Don't forget you can still use a semi-colon to combine lines in Coffeescript!